From 3ab1afe2ced150d81a47a986dede4ce45a416a18 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Wed, 31 Jan 2024 09:13:32 -0800 Subject: [PATCH 01/81] Revert nested transactions (#3866) --- packages/server/src/fhir/repo.ts | 104 +++---------------- packages/server/src/fhir/transaction.test.ts | 2 +- 2 files changed, 16 insertions(+), 90 deletions(-) diff --git a/packages/server/src/fhir/repo.ts b/packages/server/src/fhir/repo.ts index dc379a2116..fb23d221ea 100644 --- a/packages/server/src/fhir/repo.ts +++ b/packages/server/src/fhir/repo.ts @@ -56,6 +56,7 @@ import validator from 'validator'; import { getConfig } from '../config'; import { getRequestContext } from '../context'; import { getDatabasePool } from '../database'; +import { globalLogger } from '../logger'; import { getRedis } from '../redis'; import { r4ProjectId } from '../seed'; import { @@ -181,8 +182,6 @@ const lookupTables: LookupTable[] = [ */ export class Repository extends BaseRepository implements FhirRepository { private readonly context: RepositoryContext; - private conn?: PoolClient; - private transactionDepth = 0; private closed = false; constructor(context: RepositoryContext) { @@ -1783,105 +1782,32 @@ export class Repository extends BaseRepository implements FhirRepository { - this.assertNotClosed(); - if (!this.conn) { - this.conn = await getDatabasePool().connect(); - } - return this.conn; - } - - /** - * Releases the database connection. - * Include an error to remove the connection from the pool. - * See: https://github.com/brianc/node-postgres/blob/master/packages/pg-pool/index.js#L333 - * @param err - Optional error to remove the connection from the pool. - */ - private releaseConnection(err?: boolean | Error): void { - if (this.conn) { - this.conn.release(err); - this.conn = undefined; - } + return getDatabasePool(); } async withTransaction(callback: (client: PoolClient) => Promise): Promise { + const conn = await getDatabasePool().connect(); try { - const client = await this.beginTransaction(); - const result = await callback(client); - await this.commitTransaction(); + await conn.query('BEGIN'); + const result = await callback(conn); + await conn.query('COMMIT'); + conn.release(); return result; - } catch (err) { + } catch (err: any) { + globalLogger.error('Transaction error', err); const operationOutcomeError = new OperationOutcomeError(normalizeOperationOutcome(err), err); - await this.rollbackTransaction(operationOutcomeError); + try { + await conn.query('ROLLBACK'); + } catch (err2: any) { + globalLogger.error('Rollback error', err2); + } + conn.release(err); throw operationOutcomeError; - } finally { - this.endTransaction(); - } - } - - private async beginTransaction(): Promise { - this.assertNotClosed(); - this.transactionDepth++; - const conn = await this.getConnection(); - if (this.transactionDepth === 1) { - await conn.query('BEGIN'); - } else { - await conn.query('SAVEPOINT sp' + this.transactionDepth); - } - return conn; - } - - private async commitTransaction(): Promise { - this.assertInTransaction(); - const conn = await this.getConnection(); - if (this.transactionDepth === 1) { - await conn.query('COMMIT'); - } else { - await conn.query('RELEASE SAVEPOINT sp' + this.transactionDepth); - } - } - - private async rollbackTransaction(error: Error): Promise { - this.assertInTransaction(); - const conn = await this.getConnection(); - if (this.transactionDepth === 1) { - await conn.query('ROLLBACK'); - this.releaseConnection(error); - } else { - await conn.query('ROLLBACK TO SAVEPOINT sp' + this.transactionDepth); - } - } - - private endTransaction(): void { - this.assertInTransaction(); - this.transactionDepth--; - if (this.transactionDepth === 0) { - this.releaseConnection(); - } - } - - private assertInTransaction(): void { - if (this.transactionDepth <= 0) { - throw new Error('Not in transaction'); } } close(): void { - if (this.transactionDepth > 0) { - throw new Error('Closing with active transaction'); - } this.assertNotClosed(); - this.releaseConnection(); this.closed = true; } diff --git a/packages/server/src/fhir/transaction.test.ts b/packages/server/src/fhir/transaction.test.ts index 3532f1575f..9fc5fc7606 100644 --- a/packages/server/src/fhir/transaction.test.ts +++ b/packages/server/src/fhir/transaction.test.ts @@ -6,7 +6,7 @@ import { loadTestConfig } from '../config'; import { withTestContext } from '../test.setup'; import { Repository } from './repo'; -describe('FHIR Repo', () => { +describe.skip('FHIR Repo Transactions', () => { let repo: Repository; beforeAll(async () => { From bb4f7d423988a8e2129a9e327629c045b8e9b969 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Wed, 31 Jan 2024 19:23:44 -0800 Subject: [PATCH 02/81] Fix token search with pipe in value (#3867) * Fix token search with pipe in value * Tests and cleanup * Handle trailing delimiter in splitN * Comment cleanup --- packages/core/src/utils.test.ts | 1 + packages/core/src/utils.ts | 22 ++++++++++----- .../server/src/fhir/lookups/token.test.ts | 27 +++++++++++++++++++ packages/server/src/fhir/lookups/token.ts | 5 ++-- 4 files changed, 46 insertions(+), 9 deletions(-) diff --git a/packages/core/src/utils.test.ts b/packages/core/src/utils.test.ts index 594abc8d76..b92973e430 100644 --- a/packages/core/src/utils.test.ts +++ b/packages/core/src/utils.test.ts @@ -1088,6 +1088,7 @@ describe('Core Utils', () => { splitN('_has:Observation:subject:encounter:Encounter._has:DiagnosticReport:encounter:result.status', ':', 3) ).toEqual(['_has', 'Observation', 'subject:encounter:Encounter._has:DiagnosticReport:encounter:result.status']); expect(splitN('organization', ':', 2)).toEqual(['organization']); + expect(splitN('system|', '|', 2)).toEqual(['system', '']); }); test('lazy', () => { diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index a4d6ebfea4..67f76709a6 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -930,19 +930,27 @@ export const sleep = (ms: number): Promise => setTimeout(resolve, ms); }); +/** + * Splits a string into an array of strings using the specified delimiter. + * Unlike the built-in split function, this function will split the string into a maximum of exactly n parts. + * Trailing empty strings are included in the result. + * @param str - The string to split. + * @param delim - The delimiter. + * @param n - The maximum number of parts to split the string into. + * @returns The resulting array of strings. + */ export function splitN(str: string, delim: string, n: number): string[] { const result: string[] = []; for (let i = 0; i < n - 1; i++) { - let delimIndex = str.indexOf(delim); + const delimIndex = str.indexOf(delim); if (delimIndex < 0) { - delimIndex = str.length; + break; + } else { + result.push(str.slice(0, delimIndex)); + str = str.slice(delimIndex + delim.length); } - result.push(str.slice(0, delimIndex)); - str = str.slice(delimIndex + delim.length); - } - if (str) { - result.push(str); } + result.push(str); return result; } diff --git a/packages/server/src/fhir/lookups/token.test.ts b/packages/server/src/fhir/lookups/token.test.ts index 747fe8ab12..d8ca43bb70 100644 --- a/packages/server/src/fhir/lookups/token.test.ts +++ b/packages/server/src/fhir/lookups/token.test.ts @@ -472,4 +472,31 @@ describe('Identifier Lookup Table', () => { expect(bundleContains(searchResult1, sr1)).toBe(true); expect(bundleContains(searchResult1, sr2)).toBe(false); })); + + test('Identifier value with pipe', () => + withTestContext(async () => { + const system = randomUUID(); + const base = randomUUID(); + const id1 = base + '|1'; + const id2 = base + '|2'; + + const p1 = await systemRepo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family: 'Smith' }], + identifier: [{ system, value: id1 }], + }); + + await systemRepo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family: 'Smith' }], + identifier: [{ system, value: id2 }], + }); + + const r1 = await systemRepo.search({ + resourceType: 'Patient', + filters: [{ code: 'identifier', operator: Operator.EQUALS, value: `${system}|${id1}` }], + }); + expect(r1.entry?.length).toEqual(1); + expect(r1.entry?.[0]?.resource?.id).toEqual(p1.id); + })); }); diff --git a/packages/server/src/fhir/lookups/token.ts b/packages/server/src/fhir/lookups/token.ts index b5a055e31a..1360622de7 100644 --- a/packages/server/src/fhir/lookups/token.ts +++ b/packages/server/src/fhir/lookups/token.ts @@ -8,6 +8,7 @@ import { OperationOutcomeError, PropertyType, SortRule, + splitN, toTypedValue, TypedValue, } from '@medplum/core'; @@ -21,10 +22,10 @@ import { SearchParameter, } from '@medplum/fhirtypes'; import { PoolClient } from 'pg'; +import { getRequestContext } from '../../context'; import { Column, Condition, Conjunction, Disjunction, Exists, Expression, Negation, SelectQuery } from '../sql'; import { LookupTable } from './lookuptable'; import { compareArrays, deriveIdentifierSearchParameter } from './util'; -import { getRequestContext } from '../../context'; interface Token { readonly code: string; @@ -458,7 +459,7 @@ function buildWhereExpression(tableName: string, filter: Filter): Expression | u * @returns A WHERE Condition on the token table, if applicable, else undefined */ function buildWhereCondition(tableName: string, operator: FhirOperator, query: string): Expression | undefined { - const parts = query.split('|'); + const parts = splitN(query, '|', 2); // Handle the case where the query value is a system|value pair (e.g. token or identifier search) if (parts.length === 2) { const systemCondition = new Condition(new Column(tableName, 'system'), '=', parts[0]); From 48fffecfe71241b79baad6f594494ea43b98278d Mon Sep 17 00:00:00 2001 From: Derrick Farris Date: Thu, 1 Feb 2024 08:18:29 -0800 Subject: [PATCH 03/81] feat(agent): add `ping` via `Agent/$push` (#3846) * feat(agent): add `ping` via `Agent/$push` * fix(agent/test): remove unnecessary agent channel * cleanup(agent): remove noisy comments * test(agent): add failure case for ping * feat(agent): add `ping` to `Agent/tools` UI * test(agent): add `ToolsPage.test.tsx` * fix(mock): remove duplicate import * test(agent): fix `ToolsPage` tests, add test for `$push` timeout * test(mock): tests for `MockClient.pushToAgent()` --- packages/agent/src/app.ts | 53 +++++++- packages/agent/src/net-utils.test.ts | 128 ++++++++++++++++++ packages/app/src/AppRoutes.tsx | 10 +- packages/app/src/resource/ResourcePage.tsx | 4 + packages/app/src/resource/ToolsPage.test.tsx | 96 +++++++++++++ packages/app/src/resource/ToolsPage.tsx | 82 +++++++++++ packages/core/src/client.ts | 4 +- packages/core/src/contenttype.ts | 1 + packages/mock/src/client.test.ts | 42 +++++- packages/mock/src/client.ts | 52 ++++++- packages/server/src/agent/websockets.test.ts | 56 +++++++- .../server/src/fhir/operations/agentpush.ts | 10 +- 12 files changed, 523 insertions(+), 15 deletions(-) create mode 100644 packages/agent/src/net-utils.test.ts create mode 100644 packages/app/src/resource/ToolsPage.test.tsx create mode 100644 packages/app/src/resource/ToolsPage.tsx diff --git a/packages/agent/src/app.ts b/packages/agent/src/app.ts index 95bf9b9a2d..512a08b934 100644 --- a/packages/agent/src/app.ts +++ b/packages/agent/src/app.ts @@ -1,6 +1,8 @@ import { + AgentError, AgentMessage, AgentTransmitRequest, + AgentTransmitResponse, ContentType, Hl7Message, LogLevel, @@ -10,11 +12,17 @@ import { } from '@medplum/core'; import { Endpoint, Reference } from '@medplum/fhirtypes'; import { Hl7Client } from '@medplum/hl7'; +import { exec as _exec } from 'node:child_process'; +import { isIPv4, isIPv6 } from 'node:net'; +import { promisify } from 'node:util'; +import { platform } from 'os'; import WebSocket from 'ws'; import { Channel } from './channel'; import { AgentDicomChannel } from './dicom'; import { AgentHl7Channel } from './hl7'; +const exec = promisify(_exec); + export class App { static instance: App; readonly log: Logger; @@ -137,7 +145,11 @@ export class App { // @ts-expect-error - Deprecated message type case 'push': case 'agent:transmit:request': - this.pushMessage(command); + if (command.contentType === ContentType.PING) { + await this.tryPingIp(command); + } else { + this.pushMessage(command); + } break; case 'agent:error': this.log.error(command.body); @@ -253,6 +265,45 @@ export class App { } } + private async tryPingIp(message: AgentTransmitRequest): Promise { + try { + if (message.body && message.body !== 'PING') { + const warnMsg = 'Message body present but unused. Body should be empty for a ping request.'; + this.log.warn(warnMsg); + } + if (!isIPv4(message.remote)) { + let errMsg = `Attempted to ping invalid IP: ${message.remote}`; + if (isIPv6(message.remote)) { + errMsg = `Attempted to ping an IPv6 address: ${message.remote}\n\nIPv6 is currently unsupported.`; + } + this.log.error(errMsg); + throw new Error(errMsg); + } + // This covers Windows, Linux, and Mac + const { stderr, stdout } = await exec( + platform() === 'win32' ? `ping ${message.remote}` : `ping -c 4 ${message.remote}` + ); + if (stderr) { + throw new Error(`Received on stderr:\n\n${stderr}`); + } + this.log.info(`Ping result for ${message.remote}:\n\n${stdout}`); + this.addToWebSocketQueue({ + type: 'agent:transmit:response', + channel: message.channel, + contentType: ContentType.PING, + remote: message.remote, + callback: message.callback, + body: stdout, + } satisfies AgentTransmitResponse); + } catch (err) { + this.log.error(`Error during ping attempt to ${message.remote ?? 'NO_IP_GIVEN'}: ${normalizeErrorString(err)}`); + this.addToWebSocketQueue({ + type: 'agent:error', + body: (err as Error).message, + } satisfies AgentError); + } + } + private async sendToWebSocket(message: AgentMessage): Promise { if (!this.webSocket) { throw new Error('WebSocket not connected'); diff --git a/packages/agent/src/net-utils.test.ts b/packages/agent/src/net-utils.test.ts new file mode 100644 index 0000000000..5c156e6ef8 --- /dev/null +++ b/packages/agent/src/net-utils.test.ts @@ -0,0 +1,128 @@ +import { AgentMessage, allOk, ContentType, LogLevel, sleep } from '@medplum/core'; +import { Agent, Resource } from '@medplum/fhirtypes'; +import { MockClient } from '@medplum/mock'; +import { Client, Server } from 'mock-socket'; +import { App } from './app'; + +jest.mock('node-windows'); + +const medplum = new MockClient(); + +describe('Agent Net Utils', () => { + let mockServer: Server; + let mySocket: Client | undefined = undefined; + let wsClient: Client; + let app: App; + let onMessage: (command: AgentMessage) => void; + + beforeAll(async () => { + console.log = jest.fn(); + + medplum.router.router.add('POST', ':resourceType/:id/$execute', async () => { + return [allOk, {} as Resource]; + }); + + mockServer = new Server('wss://example.com/ws/agent'); + + mockServer.on('connection', (socket) => { + mySocket = socket; + socket.on('message', (data) => { + const command = JSON.parse((data as Buffer).toString('utf8')) as AgentMessage; + if (command.type === 'agent:connect:request') { + socket.send( + Buffer.from( + JSON.stringify({ + type: 'agent:connect:response', + }) + ) + ); + } else { + onMessage(command); + } + }); + }); + + const agent = await medplum.createResource({ + resourceType: 'Agent', + } as Agent); + + // Start the app + app = new App(medplum, agent.id as string, LogLevel.INFO); + await app.start(); + + // Wait for the WebSocket to connect + // eslint-disable-next-line no-unmodified-loop-condition + while (!mySocket) { + await sleep(100); + } + + wsClient = mySocket as unknown as Client; + }); + + afterAll(() => { + app.stop(); + mockServer.stop(); + }); + + test('Ping -- valid', async () => { + let resolve: (value: AgentMessage) => void; + let reject: (error: Error) => void; + + const messageReceived = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + + onMessage = (command) => resolve(command); + + expect(wsClient).toBeDefined(); + wsClient.send( + Buffer.from( + JSON.stringify({ + type: 'agent:transmit:request', + contentType: ContentType.PING, + remote: '127.0.0.1', + body: 'PING', + }) + ) + ); + + const timer = setTimeout(() => { + reject(new Error('Timeout')); + }, 3500); + + await expect(messageReceived).resolves.toMatchObject({ type: 'agent:transmit:response', body: expect.any(String) }); + clearTimeout(timer); + }); + + test('Ping -- non-IP remote', async () => { + let resolve: (value: AgentMessage) => void; + let reject: (error: Error) => void; + + const messageReceived = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + + onMessage = (command) => resolve(command); + + expect(wsClient).toBeDefined(); + wsClient.send( + Buffer.from( + JSON.stringify({ + type: 'agent:transmit:request', + contentType: ContentType.PING, + remote: 'https://localhost:3001', + body: 'PING', + }) + ) + ); + + const timer = setTimeout(() => { + reject(new Error('Timeout')); + }, 3500); + + await expect(messageReceived).resolves.toMatchObject({ type: 'agent:error', body: expect.any(String) }); + clearTimeout(timer); + }); +}); diff --git a/packages/app/src/AppRoutes.tsx b/packages/app/src/AppRoutes.tsx index d597386668..7f571d2184 100644 --- a/packages/app/src/AppRoutes.tsx +++ b/packages/app/src/AppRoutes.tsx @@ -21,12 +21,12 @@ import { CreateClientPage } from './admin/CreateClientPage'; import { EditMembershipPage } from './admin/EditMembershipPage'; import { InvitePage } from './admin/InvitePage'; import { PatientsPage } from './admin/PatientsPage'; +import { ProjectAdminConfigPage } from './admin/ProjectAdminConfigPage'; import { ProjectDetailsPage } from './admin/ProjectDetailsPage'; import { ProjectPage } from './admin/ProjectPage'; import { SecretsPage } from './admin/SecretsPage'; import { SitesPage } from './admin/SitesPage'; import { SuperAdminPage } from './admin/SuperAdminPage'; -import { ProjectAdminConfigPage } from './admin/ProjectAdminConfigPage'; import { UsersPage } from './admin/UsersPage'; import { AssaysPage } from './lab/AssaysPage'; import { PanelsPage } from './lab/PanelsPage'; @@ -40,9 +40,12 @@ import { ChecklistPage } from './resource/ChecklistPage'; import { DeletePage } from './resource/DeletePage'; import { DetailsPage } from './resource/DetailsPage'; import { EditPage } from './resource/EditPage'; +import { FormCreatePage } from './resource/FormCreatePage'; import { HistoryPage } from './resource/HistoryPage'; +import { JsonCreatePage } from './resource/JsonCreatePage'; import { JsonPage } from './resource/JsonPage'; import { PreviewPage } from './resource/PreviewPage'; +import { ProfilesPage } from './resource/ProfilesPage'; import { QuestionnaireBotsPage } from './resource/QuestionnaireBotsPage'; import { QuestionnaireResponsePage } from './resource/QuestionnaireResponsePage'; import { ReferenceRangesPage } from './resource/ReferenceRangesPage'; @@ -51,9 +54,7 @@ import { ResourcePage } from './resource/ResourcePage'; import { ResourceVersionPage } from './resource/ResourceVersionPage'; import { SubscriptionsPage } from './resource/SubscriptionsPage'; import { TimelinePage } from './resource/TimelinePage'; -import { FormCreatePage } from './resource/FormCreatePage'; -import { JsonCreatePage } from './resource/JsonCreatePage'; -import { ProfilesPage } from './resource/ProfilesPage'; +import { ToolsPage } from './resource/ToolsPage'; export function AppRoutes(): JSX.Element { return ( @@ -96,6 +97,7 @@ export function AppRoutes(): JSX.Element { } /> } /> + } /> }> } /> } /> diff --git a/packages/app/src/resource/ResourcePage.tsx b/packages/app/src/resource/ResourcePage.tsx index cd179f576c..d0b020b402 100644 --- a/packages/app/src/resource/ResourcePage.tsx +++ b/packages/app/src/resource/ResourcePage.tsx @@ -40,6 +40,10 @@ function getTabs(resourceType: string): string[] { result.push('Ranges'); } + if (resourceType === 'Agent') { + result.push('Tools'); + } + result.push('Details', 'Edit', 'Event', 'History', 'Blame', 'JSON', 'Apps', 'Profiles'); return result; } diff --git a/packages/app/src/resource/ToolsPage.test.tsx b/packages/app/src/resource/ToolsPage.test.tsx new file mode 100644 index 0000000000..596fb5b637 --- /dev/null +++ b/packages/app/src/resource/ToolsPage.test.tsx @@ -0,0 +1,96 @@ +import { Notifications } from '@mantine/notifications'; +import { getReferenceString } from '@medplum/core'; +import { Agent } from '@medplum/fhirtypes'; +import { MockClient } from '@medplum/mock'; +import { MedplumProvider } from '@medplum/react'; +import { MemoryRouter } from 'react-router-dom'; +import { AppRoutes } from '../AppRoutes'; +import { act, fireEvent, render, screen } from '../test-utils/render'; + +const medplum = new MockClient(); + +describe('ToolsPage', () => { + let agent: Agent; + + function setup(url: string): void { + render( + + + + + + + ); + } + + beforeAll(async () => { + agent = await medplum.createResource({ + resourceType: 'Agent', + name: 'Agente', + } as Agent); + }); + + test('Renders last ping', async () => { + // load agent page + await act(async () => { + setup(`/${getReferenceString(agent)}`); + }); + + const toolsTab = screen.getByRole('tab', { name: 'Tools' }); + + // click on Tools tab + await act(async () => { + fireEvent.click(toolsTab); + }); + + expect(screen.getByText(agent.name)).toBeInTheDocument(); + expect(screen.getByLabelText('IP')).toBeInTheDocument(); + + await act(async () => { + fireEvent.change(screen.getByLabelText('IP'), { target: { value: '8.8.8.8' } }); + fireEvent.click(screen.getByText('Ping')); + }); + + await expect(screen.findByText('statistics', { exact: false })).resolves.toBeInTheDocument(); + }); + + test('Displays error notification whenever invalid IP entered', async () => { + // load agent tools page + await act(async () => { + setup(`/${getReferenceString(agent)}/tools`); + }); + + expect(screen.getByText(agent.name)).toBeInTheDocument(); + expect(screen.getByLabelText('IP')).toBeInTheDocument(); + + await act(async () => { + fireEvent.change(screen.getByLabelText('IP'), { target: { value: 'abc123' } }); + fireEvent.click(screen.getByText('Ping')); + }); + + await expect(screen.findByText('Invalid IP entered')).resolves.toBeInTheDocument(); + }); + + test('Displays error notification whenever agent unreachable', async () => { + medplum.setAgentAvailable(false); + + // load agent tools page + await act(async () => { + setup(`/${getReferenceString(agent)}/tools`); + }); + + expect(screen.getByText(agent.name)).toBeInTheDocument(); + expect(screen.getByLabelText('IP')).toBeInTheDocument(); + + await act(async () => { + fireEvent.change(screen.getByLabelText('IP'), { target: { value: '8.8.8.8' } }); + fireEvent.click(screen.getByText('Ping')); + }); + + await expect( + screen.findByText('"$push" operation timed out. Agent may be unreachable') + ).resolves.toBeInTheDocument(); + + medplum.setAgentAvailable(true); + }); +}); diff --git a/packages/app/src/resource/ToolsPage.tsx b/packages/app/src/resource/ToolsPage.tsx new file mode 100644 index 0000000000..ee3e3e98bb --- /dev/null +++ b/packages/app/src/resource/ToolsPage.tsx @@ -0,0 +1,82 @@ +import { Button, Input, Title } from '@mantine/core'; +import { showNotification } from '@mantine/notifications'; +import { ContentType } from '@medplum/core'; +import { Agent, Reference } from '@medplum/fhirtypes'; +import { Document, Form, ResourceName, useMedplum } from '@medplum/react'; +import { IconRouter } from '@tabler/icons-react'; +import { useCallback, useMemo, useRef, useState } from 'react'; +import { useParams } from 'react-router-dom'; + +export function ToolsPage(): JSX.Element | null { + const medplum = useMedplum(); + const { id } = useParams() as { id: string }; + const reference = useMemo(() => ({ reference: 'Agent/' + id }) as Reference, [id]); + const [ip, setIp] = useState(''); + const [lastPing, setLastPing] = useState(); + const [pinging, setPinging] = useState(false); + + const ipRef = useRef(ip); + ipRef.current = ip; + + const onSubmit = useCallback(() => { + if (ipRef.current === '') { + return; + } + setPinging(true); + medplum + .pushToAgent(reference, ipRef.current, 'PING', ContentType.PING, true) + .then((pingResult: string) => { + setLastPing(pingResult); + setPinging(false); + }) + .catch((err: unknown) => { + setPinging(false); + if ((err as Error).message === 'Destination device not found') { + // Report error + showNotification({ + color: 'red', + title: 'Error', + message: 'Invalid IP entered', + autoClose: false, + }); + } else if ((err as Error).message === 'Timeout') { + showNotification({ + color: 'red', + title: 'Error', + message: '"$push" operation timed out. Agent may be unreachable', + autoClose: false, + }); + } + }); + }, [medplum, reference]); + + return ( + + Ping from Agent +
+ Agent: +
+
+ + setIp(e.target.value)} value={ip} /> + +
+ + {!pinging && lastPing && ( + <> + Last Ping +
{lastPing}
+ + )} +
+ ); +} diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 7af60a484b..2ee8428d17 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -2397,7 +2397,7 @@ export class MedplumClient extends EventTarget { */ pushToAgent( agent: Agent | Reference, - destination: Device | Reference, + destination: Device | Reference | string, body: any, contentType?: string, waitForResponse?: boolean, @@ -2406,7 +2406,7 @@ export class MedplumClient extends EventTarget { return this.post( this.fhirUrl('Agent', resolveId(agent) as string, '$push'), { - destination: getReferenceString(destination), + destination: typeof destination === 'string' ? destination : getReferenceString(destination), body, contentType, waitForResponse, diff --git a/packages/core/src/contenttype.ts b/packages/core/src/contenttype.ts index e1456f54d4..30c8868e39 100644 --- a/packages/core/src/contenttype.ts +++ b/packages/core/src/contenttype.ts @@ -16,4 +16,5 @@ export const ContentType = { SVG: 'image/svg+xml', TEXT: 'text/plain', TYPESCRIPT: 'text/typescript', + PING: 'x-application/ping', } as const; diff --git a/packages/mock/src/client.test.ts b/packages/mock/src/client.test.ts index 353506e859..41b9282ba9 100644 --- a/packages/mock/src/client.test.ts +++ b/packages/mock/src/client.test.ts @@ -13,7 +13,7 @@ import { indexStructureDefinitionBundle, } from '@medplum/core'; import { readJson } from '@medplum/definitions'; -import { Bundle, CodeableConcept, Patient, SearchParameter, ServiceRequest } from '@medplum/fhirtypes'; +import { Agent, Bundle, CodeableConcept, Patient, SearchParameter, ServiceRequest } from '@medplum/fhirtypes'; import { randomUUID, webcrypto } from 'crypto'; import { TextEncoder } from 'util'; import { MockClient } from './client'; @@ -669,6 +669,46 @@ describe('MockClient', () => { expect(homer.name[0].given[0]).toEqual('Homer'); expect(homer.name[0].family).toEqual('Simpson'); }); + + test('pushToAgent() -- Valid IP', async () => { + const medplum = new MockClient(); + const agent = await medplum.createResource({ resourceType: 'Agent', status: 'active', name: 'Agente' }); + await expect(medplum.pushToAgent(agent, '8.8.8.8', 'PING', ContentType.PING, true)).resolves.toMatch( + /8.8.8.8 ping statistics/ + ); + }); + + test('pushToAgent() - Valid IP other than 8.8.8.8', async () => { + const medplum = new MockClient(); + const agent = await medplum.createResource({ resourceType: 'Agent', status: 'active', name: 'Agente' }); + const oldWarn = console.warn; + console.warn = jest.fn(); + await expect(medplum.pushToAgent(agent, '127.0.0.1', 'PING', ContentType.PING, true)).rejects.toThrow( + OperationOutcomeError + ); + expect(console.warn).toHaveBeenCalled(); + console.warn = oldWarn; + }); + + test('pushToAgent() -- Invalid IP', async () => { + const medplum = new MockClient(); + const agent = await medplum.createResource({ resourceType: 'Agent', status: 'active', name: 'Agente' }); + await expect(medplum.pushToAgent(agent, 'abc123', 'PING', ContentType.PING, true)).rejects.toThrow( + OperationOutcomeError + ); + }); + + test('pushToAgent() -- Agent Timeout', async () => { + const medplum = new MockClient(); + const agent = await medplum.createResource({ resourceType: 'Agent', status: 'active', name: 'Agente' }); + await expect(medplum.pushToAgent(agent, '8.8.8.8', 'PING', ContentType.PING, true)).resolves.toBeDefined(); + medplum.setAgentAvailable(false); + await expect(medplum.pushToAgent(agent, '8.8.8.8', 'PING', ContentType.PING, true)).rejects.toThrow( + OperationOutcomeError + ); + medplum.setAgentAvailable(true); + await expect(medplum.pushToAgent(agent, '8.8.8.8', 'PING', ContentType.PING, true)).resolves.toBeDefined(); + }); }); describe('MockAsyncClientStorage', () => { diff --git a/packages/mock/src/client.ts b/packages/mock/src/client.ts index 72f436e17f..97e74df543 100644 --- a/packages/mock/src/client.ts +++ b/packages/mock/src/client.ts @@ -8,10 +8,20 @@ import { LoginState, MedplumClient, MedplumClientOptions, + OperationOutcomeError, ProfileResource, } from '@medplum/core'; import { FhirRequest, FhirRouter, HttpMethod, MemoryRepository } from '@medplum/fhir-router'; -import { Binary, Resource, SearchParameter, StructureDefinition, UserConfiguration } from '@medplum/fhirtypes'; +import { + Agent, + Binary, + Device, + Reference, + Resource, + SearchParameter, + StructureDefinition, + UserConfiguration, +} from '@medplum/fhirtypes'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment /** @ts-ignore */ import type { CustomTableLayout, TDocumentDefinitions, TFontDictionary } from 'pdfmake/interfaces'; @@ -21,7 +31,6 @@ import { DrAliceSmith, DrAliceSmithPreviousVersion, DrAliceSmithSchedule, - makeDrAliceSmithSlots, ExampleBot, ExampleClient, ExampleQuestionnaire, @@ -44,6 +53,7 @@ import { HomerSimpson, HomerSimpsonPreviousVersion, HomerSimpsonSpecimen, + makeDrAliceSmithSlots, TestOrganization, } from './mocks'; import { ExampleAccessPolicy, ExampleStatusValueSet, ExampleUserConfiguration } from './mocks/accesspolicy'; @@ -78,6 +88,7 @@ export class MockClient extends MedplumClient { readonly client: MockFetchClient; readonly debug: boolean; activeLoginOverride?: LoginState; + private agentAvailable: boolean = true; private readonly profile: ReturnType; constructor(clientOptions?: MockClientOptions) { @@ -179,6 +190,43 @@ export class MockClient extends MedplumClient { url: 'https://example.com/binary/123', }; } + + async pushToAgent( + agent: Agent | Reference, + destination: Device | Reference | string, + body: any, + contentType?: string | undefined, + _waitForResponse?: boolean | undefined, + _options?: RequestInit | undefined + ): Promise { + if (contentType === ContentType.PING) { + if (!this.agentAvailable) { + throw new OperationOutcomeError(badRequest('Timeout')); + } + if (typeof destination === 'string' && destination !== '8.8.8.8') { + // Exception for test case + if (destination !== 'abc123') { + console.warn('IPs other than 8.8.8.8 will always throw an error in MockClient'); + } + throw new OperationOutcomeError(badRequest('Destination device not found')); + } + return `PING 8.8.8.8 (8.8.8.8): 56 data bytes +64 bytes from 8.8.8.8: icmp_seq=0 ttl=115 time=10.977 ms +64 bytes from 8.8.8.8: icmp_seq=1 ttl=115 time=13.037 ms +64 bytes from 8.8.8.8: icmp_seq=2 ttl=115 time=23.159 ms +64 bytes from 8.8.8.8: icmp_seq=3 ttl=115 time=12.725 ms + +--- 8.8.8.8 ping statistics --- +4 packets transmitted, 4 packets received, 0.0% packet loss +round-trip min/avg/max/stddev = 10.977/14.975/23.159/4.790 ms +`; + } + return undefined; + } + + setAgentAvailable(value: boolean): void { + this.agentAvailable = value; + } } export class MockFetchClient { diff --git a/packages/server/src/agent/websockets.test.ts b/packages/server/src/agent/websockets.test.ts index 3154291de6..b4db207492 100644 --- a/packages/server/src/agent/websockets.test.ts +++ b/packages/server/src/agent/websockets.test.ts @@ -371,7 +371,7 @@ describe('Agent WebSockets', () => { 'NK1|1|JONES^BARBARA^K|SPO|||||20011105\r' + 'PV1|1|I|2000^2012^01||||004777^LEBAUER^SIDNEY^J.|||SUR||-||1|A0-', }); - pushRequest.then(() => true); + pushRequest.then(() => undefined); }) .expectText((str) => { const message = JSON.parse(str); @@ -397,7 +397,7 @@ describe('Agent WebSockets', () => { .expectClosed(); }); - test('Ping', async () => { + test('Heartbeat', async () => { await request(server) .ws('/ws/agent') .sendText( @@ -417,6 +417,58 @@ describe('Agent WebSockets', () => { .expectClosed(); }); + test('Ping IP', async () => { + let pushRequest: any = undefined; + let pushResponse: any = undefined; + + await request(server) + .ws('/ws/agent') + .sendText( + JSON.stringify({ + type: 'agent:connect:request', + accessToken, + agentId: agent.id, + }) + ) + .expectText('{"type":"agent:connect:response"}') + .exec(async () => { + // Send the request but do not wait for the response + pushRequest = request(server) + .post(`/fhir/R4/Agent/${agent.id}/$push`) + .set('Content-Type', ContentType.JSON) + .set('Authorization', 'Bearer ' + accessToken) + .send({ + waitForResponse: true, + channel: 'test', + destination: '8.8.8.8', + contentType: ContentType.PING, + body: 'PING', + }); + pushRequest.then(() => undefined); + }) + .expectText((str) => { + const message = JSON.parse(str); + expect(message.type).toBe('agent:transmit:request'); + expect(message.remote).toBe('8.8.8.8'); + expect(message.callback).toBeDefined(); + pushResponse = JSON.stringify({ + type: 'agent:transmit:response', + callback: message.callback, + contentType: ContentType.PING, + body: 'PING', + }); + return true; + }) + .exec((ws) => ws.send(pushResponse)) + .exec(async () => { + const res = await pushRequest; + expect(res.status).toBe(200); + expect(res.headers['content-type']).toBe('x-application/ping; charset=utf-8'); + }) + .close() + .expectClosed(); + }); + test('Unknown message type', async () => { await request(server) .ws('/ws/agent') diff --git a/packages/server/src/fhir/operations/agentpush.ts b/packages/server/src/fhir/operations/agentpush.ts index 3bcb0a945b..e1e7dd2c01 100644 --- a/packages/server/src/fhir/operations/agentpush.ts +++ b/packages/server/src/fhir/operations/agentpush.ts @@ -10,6 +10,7 @@ import { import { Agent, Device } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { randomUUID } from 'node:crypto'; +import { isIPv4 } from 'node:net'; import { asyncWrap } from '../../async'; import { getAuthenticatedContext } from '../../context'; import { getRedis } from '../../redis'; @@ -113,8 +114,8 @@ export const agentPushHandler = asyncWrap(async (req: Request, res: Response) => /** * Returns the Agent for the execute request. - * If using "/Agent/:id/$execute", then the agent ID is read from the path parameter. - * If using "/Agent/$execute?identifier=...", then the agent is searched by identifier. + * If using "/Agent/:id/$push", then the agent ID is read from the path parameter. + * If using "/Agent/$push?identifier=...", then the agent is searched by identifier. * Otherwise, returns undefined. * @param req - The HTTP request. * @param repo - The repository. @@ -144,13 +145,16 @@ async function getDevice(repo: Repository, destination: string): Promise({ reference: destination }); - } catch (err) { + } catch (_err) { return undefined; } } if (destination.startsWith('Device?')) { return repo.searchOne(parseSearchDefinition(destination)); } + if (isIPv4(destination)) { + return { resourceType: 'Device', url: destination }; + } return undefined; } From 06c2974ca691f0823e0cbf26e4b0ec9e0ca2aaf0 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Thu, 1 Feb 2024 10:33:44 -0800 Subject: [PATCH 04/81] Fixes to ResourceDiffTable with arrays (#3870) --- .../ResourceDiffTable.test.tsx | 148 ++++++++++++++ .../ResourceDiffTable/ResourceDiffTable.tsx | 180 ++++++++++++------ 2 files changed, 267 insertions(+), 61 deletions(-) diff --git a/packages/react/src/ResourceDiffTable/ResourceDiffTable.test.tsx b/packages/react/src/ResourceDiffTable/ResourceDiffTable.test.tsx index daf46d3d71..9d5f7c345c 100644 --- a/packages/react/src/ResourceDiffTable/ResourceDiffTable.test.tsx +++ b/packages/react/src/ResourceDiffTable/ResourceDiffTable.test.tsx @@ -57,4 +57,152 @@ describe('ResourceDiffTable', () => { // Birth date did not change, and therefore should not be shown expect(screen.queryByText('Birth Date')).toBeNull(); }); + + test('Array index operations', async () => { + const original: Patient = { + resourceType: 'Patient', + id: '123', + meta: { versionId: '456' }, + name: [{ family: 'Smith', given: ['John'] }], + identifier: [ + { system: 'http://example.com/foo', value: '123' }, + { system: 'http://example.com/bar', value: '456' }, + ], + }; + + const revised: Patient = { + ...original, + meta: { versionId: '457' }, + identifier: [ + { system: 'http://example.com/foo', value: '123x' }, + { system: 'http://example.com/bar', value: '456x' }, + ], + }; + + await act(async () => { + setup({ original, revised }); + }); + + await waitFor(() => screen.getByText('Replace identifier[0].value')); + expect(screen.getByText('Replace identifier[0].value')).toBeInTheDocument(); + expect(screen.getByText('123')).toBeInTheDocument(); + expect(screen.getByText('123x')).toBeInTheDocument(); + expect(screen.getByText('Replace identifier[1].value')).toBeInTheDocument(); + expect(screen.getByText('456')).toBeInTheDocument(); + expect(screen.getByText('456x')).toBeInTheDocument(); + }); + + test('Single array add', async () => { + const original: Patient = { + resourceType: 'Patient', + id: '123', + meta: { versionId: '456' }, + name: [{ family: 'Smith', given: ['John'] }], + identifier: [{ system: 'http://example.com/foo', value: '123' }], + }; + + const revised: Patient = { + ...original, + meta: { versionId: '457' }, + identifier: [ + { system: 'http://example.com/foo', value: '123' }, + { system: 'http://example.com/bar', value: '456' }, + ], + }; + + await act(async () => { + setup({ original, revised }); + }); + + await waitFor(() => screen.getByText('Add identifier.last()')); + + const operations = screen.getAllByText('Add identifier.last()'); + expect(operations).toHaveLength(1); + }); + + test('Combine patch operations on array add', async () => { + const original: Patient = { + resourceType: 'Patient', + id: '123', + meta: { versionId: '456' }, + name: [{ family: 'Smith', given: ['John'] }], + identifier: [{ system: 'http://example.com/foo', value: '123' }], + }; + + const revised: Patient = { + ...original, + meta: { versionId: '457' }, + identifier: [ + { system: 'http://example.com/foo', value: '123' }, + { system: 'http://example.com/bar', value: '456' }, + { system: 'http://example.com/baz', value: '789' }, + ], + }; + + await act(async () => { + setup({ original, revised }); + }); + + await waitFor(() => screen.getByText('Replace identifier')); + + const operations = screen.getAllByText('Replace identifier'); + expect(operations).toHaveLength(1); + }); + + test('Single array remove', async () => { + const original: Patient = { + resourceType: 'Patient', + id: '123', + meta: { versionId: '456' }, + name: [{ family: 'Smith', given: ['John'] }], + identifier: [ + { system: 'http://example.com/foo', value: '123' }, + { system: 'http://example.com/bar', value: '456' }, + ], + }; + + const revised: Patient = { + ...original, + meta: { versionId: '457' }, + identifier: [{ system: 'http://example.com/foo', value: '123' }], + }; + + await act(async () => { + setup({ original, revised }); + }); + + await waitFor(() => screen.getByText('Remove identifier[1]')); + + const operations = screen.getAllByText('Remove identifier[1]'); + expect(operations).toHaveLength(1); + }); + + test('Combine patch operations on array remove', async () => { + const original: Patient = { + resourceType: 'Patient', + id: '123', + meta: { versionId: '456' }, + name: [{ family: 'Smith', given: ['John'] }], + identifier: [ + { system: 'http://example.com/foo', value: '123' }, + { system: 'http://example.com/bar', value: '456' }, + { system: 'http://example.com/baz', value: '789' }, + ], + }; + + const revised: Patient = { + ...original, + meta: { versionId: '457' }, + identifier: [{ system: 'http://example.com/foo', value: '123' }], + }; + + await act(async () => { + setup({ original, revised }); + }); + + await waitFor(() => screen.getByText('Replace identifier')); + + const operations = screen.getAllByText('Replace identifier'); + expect(operations).toHaveLength(1); + }); }); diff --git a/packages/react/src/ResourceDiffTable/ResourceDiffTable.tsx b/packages/react/src/ResourceDiffTable/ResourceDiffTable.tsx index 03f38da6c1..c2c97a5e86 100644 --- a/packages/react/src/ResourceDiffTable/ResourceDiffTable.tsx +++ b/packages/react/src/ResourceDiffTable/ResourceDiffTable.tsx @@ -1,9 +1,17 @@ import { Table } from '@mantine/core'; -import { capitalize, evalFhirPathTyped, getSearchParameterDetails, toTypedValue } from '@medplum/core'; +import { + InternalSchemaElement, + TypedValue, + arrayify, + capitalize, + evalFhirPathTyped, + getSearchParameterDetails, + toTypedValue, +} from '@medplum/core'; import { Resource, SearchParameter } from '@medplum/fhirtypes'; import { useMedplum } from '@medplum/react-hooks'; -import { useEffect, useState } from 'react'; -import { createPatch } from 'rfc6902'; +import { useEffect, useMemo, useState } from 'react'; +import { Operation, createPatch } from 'rfc6902'; import { ResourcePropertyDisplay } from '../ResourcePropertyDisplay/ResourcePropertyDisplay'; import classes from './ResourceDiffTable.module.css'; @@ -14,6 +22,7 @@ export interface ResourceDiffTableProps { export function ResourceDiffTable(props: ResourceDiffTableProps): JSX.Element | null { const medplum = useMedplum(); + const { original, revised } = props; const [schemaLoaded, setSchemaLoaded] = useState(false); useEffect(() => { @@ -23,14 +32,44 @@ export function ResourceDiffTable(props: ResourceDiffTableProps): JSX.Element | .catch(console.log); }, [medplum, props.original.resourceType]); - if (!schemaLoaded) { + const diffTable = useMemo(() => { + if (!schemaLoaded) { + return null; + } + + const typedOriginal = [toTypedValue(original)]; + const typedRevised = [toTypedValue(revised)]; + const result = []; + + // First, we filter and conslidate the patch operations + // We can do this because we do not use the "value" field in the patch operations + // Remove patch operations on meta elements such as "meta.lastUpdated" and "meta.versionId" + // Consolidate patch operations on arrays + const patch = mergePatchOperations(createPatch(original, revised)); + + // Next, convert the patch operations to a diff table + for (const op of patch) { + const path = op.path; + const fhirPath = jsonPathToFhirPath(path); + const property = tryGetElementDefinition(original.resourceType, fhirPath); + const originalValue = op.op === 'add' ? undefined : evalFhirPathTyped(fhirPath, typedOriginal); + const revisedValue = op.op === 'remove' ? undefined : evalFhirPathTyped(fhirPath, typedRevised); + result.push({ + key: `op-${op.op}-${op.path}`, + name: `${capitalize(op.op)} ${fhirPath}`, + property: property, + originalValue: touchUpValue(property, originalValue), + revisedValue: touchUpValue(property, revisedValue), + }); + } + + return result; + }, [schemaLoaded, original, revised]); + + if (!diffTable) { return null; } - const patch = createPatch(props.original, props.revised); - const typedOriginal = [toTypedValue(props.original)]; - const typedRevised = [toTypedValue(props.revised)]; - return ( @@ -41,57 +80,58 @@ export function ResourceDiffTable(props: ResourceDiffTableProps): JSX.Element | - {patch.map((op) => { - if (op.path.startsWith('/meta')) { - return null; - } - - const path = op.path; - const fhirPath = jsonPathToFhirPath(path); - const details = getSearchParameterDetails(props.original.resourceType, { - resourceType: 'SearchParameter', - base: [props.original.resourceType], - code: props.original.resourceType + '.' + fhirPath, - expression: props.original.resourceType + '.' + fhirPath, - } as SearchParameter); - const property = details?.elementDefinitions?.[0]; - const isArray = !!property?.isArray; - const originalValue = op.op === 'add' ? undefined : evalFhirPathTyped(fhirPath, typedOriginal)?.[0]; - const revisedValue = op.op === 'remove' ? undefined : evalFhirPathTyped(fhirPath, typedRevised)?.[0]; - - return ( - - - {capitalize(op.op)} {fhirPath} - - - {originalValue && ( - - )} - - - {revisedValue && ( - - )} - - - ); - })} + {diffTable.map((row) => ( + + {row.name} + + {row.originalValue && ( + + )} + + + {row.revisedValue && ( + + )} + + + ))}
); } +function mergePatchOperations(patch: Operation[]): Operation[] { + const result: Operation[] = []; + for (const patchOperation of patch) { + const { op, path } = patchOperation; + if (path.startsWith('/meta/lastUpdated') || path.startsWith('/meta/versionId')) { + continue; + } + const count = patch.filter((el) => el.op === op && el.path === path).length; + const resultOperation = { op, path } as Operation; + if (count > 1 && (op === 'add' || op === 'remove') && /\/[0-9-]+$/.test(path)) { + // Remove everything after the last slash + resultOperation.op = 'replace'; + resultOperation.path = path.replace(/\/[^/]+$/, ''); + } + if (!result.some((el) => el.op === resultOperation.op && el.path === resultOperation.path)) { + // Only add the operation if it doesn't already exist + result.push(resultOperation); + } + } + return result; +} + function jsonPathToFhirPath(path: string): string { const parts = path.split('/').filter(Boolean); let result = ''; @@ -111,12 +151,30 @@ function jsonPathToFhirPath(path: string): string { return result; } -function fixArray(inputValue: any, isArray: boolean): any { - if (Array.isArray(inputValue) && !isArray) { - return inputValue[0]; - } - if (!Array.isArray(inputValue) && isArray) { - return [inputValue]; +function tryGetElementDefinition(resourceType: string, fhirPath: string): InternalSchemaElement | undefined { + const details = getSearchParameterDetails(resourceType, { + resourceType: 'SearchParameter', + base: [resourceType], + code: resourceType + '.' + fhirPath, + expression: resourceType + '.' + fhirPath, + } as SearchParameter); + return details?.elementDefinitions?.[0]; +} + +function touchUpValue( + property: InternalSchemaElement | undefined, + input: TypedValue[] | TypedValue | undefined +): TypedValue | undefined { + if (!input) { + return input; } - return inputValue; + return { + type: Array.isArray(input) ? input[0].type : input.type, + value: fixArray(input, !!property?.isArray), + }; +} + +function fixArray(input: TypedValue[] | TypedValue, isArray: boolean): any { + const inputValue = (arrayify(input) as TypedValue[]).flatMap((v) => v.value); + return isArray ? inputValue : inputValue[0]; } From cd09c5c42a5d2a0524c111cd361eb1f7921b34f4 Mon Sep 17 00:00:00 2001 From: Matt Long Date: Thu, 1 Feb 2024 10:51:54 -0800 Subject: [PATCH 05/81] No longer need to build core for @medplum/react npm run dev (#3871) --- packages/react/.storybook/main.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/react/.storybook/main.ts b/packages/react/.storybook/main.ts index 20981071a1..b83b59e8ac 100644 --- a/packages/react/.storybook/main.ts +++ b/packages/react/.storybook/main.ts @@ -1,6 +1,7 @@ import turbosnap from 'vite-plugin-turbosnap'; import type { StorybookConfig } from '@storybook/react-vite'; import { mergeConfig } from 'vite'; +import path from 'path'; const config: StorybookConfig = { stories: [ @@ -28,16 +29,24 @@ const config: StorybookConfig = { docs: { autodocs: 'tag', }, - viteFinal(config, { configType }) { - let finalConfig = config; + async viteFinal(inputConfig, { configType }) { + let config = inputConfig; if (configType === 'PRODUCTION') { - finalConfig = mergeConfig(config, { + config = mergeConfig(config, { plugins: [turbosnap({ rootDir: config.root ?? process.cwd() })], }); + } else if (configType === 'DEVELOPMENT') { + config = mergeConfig(config, { + resolve: { + alias: { + '@medplum/core': path.resolve(__dirname, '../../core/src'), + }, + }, + }); } - return finalConfig; + return config; }, }; From 0ad5a694cac044591641d621a342d3131cf7f09c Mon Sep 17 00:00:00 2001 From: Medplum Bot <152649536+medplumbot@users.noreply.github.com> Date: Thu, 1 Feb 2024 13:14:35 -0800 Subject: [PATCH 06/81] Dependency upgrades (#3874) --- examples/foomedical/package.json | 18 +- examples/medplum-chart-demo/package.json | 12 +- examples/medplum-demo-bots/package.json | 2 +- examples/medplum-fhircast-demo/package.json | 10 +- examples/medplum-hello-world/package.json | 12 +- examples/medplum-live-chat-demo/package.json | 12 +- examples/medplum-nextjs-demo/package.json | 12 +- .../medplum-react-native-example/package.json | 2 +- examples/medplum-task-demo/package.json | 12 +- .../package.json | 10 +- package-lock.json | 3137 +++++++---------- package.json | 8 +- packages/app/package.json | 16 +- packages/cdk/package.json | 10 +- packages/cli/package.json | 16 +- packages/docs/package.json | 2 +- packages/eslint-config/package.json | 4 +- packages/expo-polyfills/package.json | 2 +- packages/generator/package.json | 4 +- packages/graphiql/package.json | 8 +- packages/react-hooks/package.json | 10 +- packages/react/package.json | 34 +- packages/server/package.json | 26 +- 23 files changed, 1465 insertions(+), 1914 deletions(-) diff --git a/examples/foomedical/package.json b/examples/foomedical/package.json index 8282f3a0c9..503253328f 100644 --- a/examples/foomedical/package.json +++ b/examples/foomedical/package.json @@ -25,20 +25,20 @@ "@babel/preset-env": "7.23.9", "@babel/preset-react": "7.23.3", "@babel/preset-typescript": "7.23.3", - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", + "@mantine/core": "7.5.1", + "@mantine/hooks": "7.5.1", + "@mantine/notifications": "7.5.1", "@medplum/core": "3.0.2", "@medplum/eslint-config": "3.0.2", "@medplum/fhirtypes": "3.0.2", "@medplum/mock": "3.0.2", "@medplum/react": "3.0.2", "@tabler/icons-react": "2.46.0", - "@testing-library/jest-dom": "6.3.0", - "@testing-library/react": "14.1.2", - "@types/jest": "29.5.11", - "@types/node": "20.11.8", - "@types/react": "18.2.48", + "@testing-library/jest-dom": "6.4.1", + "@testing-library/react": "14.2.0", + "@types/jest": "29.5.12", + "@types/node": "20.11.16", + "@types/react": "18.2.51", "@types/react-dom": "18.2.18", "@vitejs/plugin-react": "4.2.1", "babel-jest": "29.7.0", @@ -49,7 +49,7 @@ "jest-environment-jsdom": "29.7.0", "jest-transform-stub": "2.0.0", "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-chartjs-2": "5.2.0", "react-dom": "18.2.0", diff --git a/examples/medplum-chart-demo/package.json b/examples/medplum-chart-demo/package.json index f155473540..6e584605b6 100644 --- a/examples/medplum-chart-demo/package.json +++ b/examples/medplum-chart-demo/package.json @@ -19,20 +19,20 @@ ] }, "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", + "@mantine/core": "7.5.1", + "@mantine/hooks": "7.5.1", + "@mantine/notifications": "7.5.1", "@medplum/core": "3.0.2", "@medplum/eslint-config": "3.0.2", "@medplum/fhirtypes": "3.0.2", "@medplum/react": "3.0.2", "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.8", - "@types/react": "18.2.48", + "@types/node": "20.11.16", + "@types/react": "18.2.51", "@types/react-dom": "18.2.18", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.21.3", diff --git a/examples/medplum-demo-bots/package.json b/examples/medplum-demo-bots/package.json index 3e0ac86c35..7b6f6a89a4 100644 --- a/examples/medplum-demo-bots/package.json +++ b/examples/medplum-demo-bots/package.json @@ -30,7 +30,7 @@ "@medplum/eslint-config": "3.0.2", "@medplum/fhirtypes": "3.0.2", "@medplum/mock": "3.0.2", - "@types/node": "20.11.8", + "@types/node": "20.11.16", "@types/node-fetch": "2.6.11", "@types/ssh2-sftp-client": "9.0.3", "@vitest/coverage-v8": "1.2.2", diff --git a/examples/medplum-fhircast-demo/package.json b/examples/medplum-fhircast-demo/package.json index d423539171..eb95ef2069 100644 --- a/examples/medplum-fhircast-demo/package.json +++ b/examples/medplum-fhircast-demo/package.json @@ -15,19 +15,19 @@ ] }, "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", + "@mantine/core": "7.5.1", + "@mantine/hooks": "7.5.1", + "@mantine/notifications": "7.5.1", "@medplum/core": "3.0.2", "@medplum/eslint-config": "3.0.2", "@medplum/fhirtypes": "3.0.2", "@medplum/react": "3.0.2", "@tabler/icons-react": "2.46.0", - "@types/react": "18.2.48", + "@types/react": "18.2.51", "@types/react-dom": "18.2.18", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.21.3", diff --git a/examples/medplum-hello-world/package.json b/examples/medplum-hello-world/package.json index a4f4873eb1..c8c7396246 100644 --- a/examples/medplum-hello-world/package.json +++ b/examples/medplum-hello-world/package.json @@ -19,20 +19,20 @@ ] }, "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", + "@mantine/core": "7.5.1", + "@mantine/hooks": "7.5.1", + "@mantine/notifications": "7.5.1", "@medplum/core": "3.0.2", "@medplum/eslint-config": "3.0.2", "@medplum/fhirtypes": "3.0.2", "@medplum/react": "3.0.2", "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.8", - "@types/react": "18.2.48", + "@types/node": "20.11.16", + "@types/react": "18.2.51", "@types/react-dom": "18.2.18", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.21.3", diff --git a/examples/medplum-live-chat-demo/package.json b/examples/medplum-live-chat-demo/package.json index c7751e2e4c..e11975b307 100644 --- a/examples/medplum-live-chat-demo/package.json +++ b/examples/medplum-live-chat-demo/package.json @@ -19,20 +19,20 @@ ] }, "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", + "@mantine/core": "7.5.1", + "@mantine/hooks": "7.5.1", + "@mantine/notifications": "7.5.1", "@medplum/core": "3.0.2", "@medplum/eslint-config": "3.0.2", "@medplum/fhirtypes": "3.0.2", "@medplum/react": "3.0.2", "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.8", - "@types/react": "18.2.48", + "@types/node": "20.11.16", + "@types/react": "18.2.51", "@types/react-dom": "18.2.18", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.21.3", diff --git a/examples/medplum-nextjs-demo/package.json b/examples/medplum-nextjs-demo/package.json index 5aba00f179..28b5c37801 100644 --- a/examples/medplum-nextjs-demo/package.json +++ b/examples/medplum-nextjs-demo/package.json @@ -10,9 +10,9 @@ "start": "next start" }, "dependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", + "@mantine/core": "7.5.1", + "@mantine/hooks": "7.5.1", + "@mantine/notifications": "7.5.1", "@medplum/core": "3.0.2", "@medplum/react": "3.0.2", "next": "14.1.0", @@ -22,13 +22,13 @@ }, "devDependencies": { "@medplum/fhirtypes": "3.0.2", - "@types/node": "20.11.8", - "@types/react": "18.2.48", + "@types/node": "20.11.16", + "@types/react": "18.2.51", "@types/react-dom": "18.2.18", "eslint": "8.56.0", "eslint-config-next": "14.1.0", "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss-preset-mantine": "1.13.0", "typescript": "5.3.3" } } diff --git a/examples/medplum-react-native-example/package.json b/examples/medplum-react-native-example/package.json index d56b71442c..68109e0071 100644 --- a/examples/medplum-react-native-example/package.json +++ b/examples/medplum-react-native-example/package.json @@ -27,7 +27,7 @@ "expo-status-bar": "1.11.1", "react": "18.2.0", "react-dom": "18.2.0", - "react-native": "0.73.2", + "react-native": "0.73.3", "react-native-web": "0.19.10" }, "devDependencies": { diff --git a/examples/medplum-task-demo/package.json b/examples/medplum-task-demo/package.json index 4cd48951ce..f30b3ede7d 100644 --- a/examples/medplum-task-demo/package.json +++ b/examples/medplum-task-demo/package.json @@ -22,21 +22,21 @@ ] }, "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", + "@mantine/core": "7.5.1", + "@mantine/hooks": "7.5.1", + "@mantine/notifications": "7.5.1", "@medplum/core": "3.0.2", "@medplum/definitions": "3.0.2", "@medplum/eslint-config": "3.0.2", "@medplum/fhirtypes": "3.0.2", "@medplum/react": "3.0.2", "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.8", - "@types/react": "18.2.48", + "@types/node": "20.11.16", + "@types/react": "18.2.51", "@types/react-dom": "18.2.18", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.21.3", diff --git a/examples/medplum-websocket-subscriptions-demo/package.json b/examples/medplum-websocket-subscriptions-demo/package.json index ced1eec3df..4fe8cb8d88 100644 --- a/examples/medplum-websocket-subscriptions-demo/package.json +++ b/examples/medplum-websocket-subscriptions-demo/package.json @@ -20,9 +20,9 @@ }, "devDependencies": { "@emotion/react": "11.11.3", - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", + "@mantine/core": "7.5.1", + "@mantine/hooks": "7.5.1", + "@mantine/notifications": "7.5.1", "@medplum/core": "3.0.2", "@medplum/eslint-config": "3.0.2", "@medplum/fhir-router": "3.0.2", @@ -30,8 +30,8 @@ "@medplum/mock": "3.0.2", "@medplum/react": "3.0.2", "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.8", - "@types/react": "18.2.48", + "@types/node": "20.11.16", + "@types/react": "18.2.51", "@types/react-dom": "18.2.18", "@vitejs/plugin-react": "4.2.1", "react": "18.2.0", diff --git a/package-lock.json b/package-lock.json index 63456582a5..0bb12c585f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,8 +19,8 @@ "@cyclonedx/cyclonedx-npm": "1.16.1", "@microsoft/api-documenter": "7.23.20", "@microsoft/api-extractor": "7.39.4", - "@types/jest": "29.5.11", - "@types/node": "20.11.8", + "@types/jest": "29.5.12", + "@types/node": "20.11.16", "babel-jest": "29.7.0", "babel-preset-vite": "1.1.3", "cross-env": "7.0.3", @@ -33,11 +33,11 @@ "nyc": "15.1.0", "prettier": "3.2.4", "rimraf": "5.0.5", - "sort-package-json": "2.6.0", + "sort-package-json": "2.7.0", "source-map-explorer": "2.5.3", "ts-node": "10.9.2", "tslib": "2.6.2", - "turbo": "1.11.3", + "turbo": "1.12.2", "typescript": "5.3.3" }, "engines": { @@ -51,20 +51,20 @@ "@babel/preset-env": "7.23.9", "@babel/preset-react": "7.23.3", "@babel/preset-typescript": "7.23.3", - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", + "@mantine/core": "7.5.1", + "@mantine/hooks": "7.5.1", + "@mantine/notifications": "7.5.1", "@medplum/core": "3.0.2", "@medplum/eslint-config": "3.0.2", "@medplum/fhirtypes": "3.0.2", "@medplum/mock": "3.0.2", "@medplum/react": "3.0.2", "@tabler/icons-react": "2.46.0", - "@testing-library/jest-dom": "6.3.0", - "@testing-library/react": "14.1.2", - "@types/jest": "29.5.11", - "@types/node": "20.11.8", - "@types/react": "18.2.48", + "@testing-library/jest-dom": "6.4.1", + "@testing-library/react": "14.2.0", + "@types/jest": "29.5.12", + "@types/node": "20.11.16", + "@types/react": "18.2.51", "@types/react-dom": "18.2.18", "@vitejs/plugin-react": "4.2.1", "babel-jest": "29.7.0", @@ -75,7 +75,7 @@ "jest-environment-jsdom": "29.7.0", "jest-transform-stub": "2.0.0", "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-chartjs-2": "5.2.0", "react-dom": "18.2.0", @@ -174,20 +174,20 @@ "examples/medplum-chart-demo": { "version": "3.0.2", "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", + "@mantine/core": "7.5.1", + "@mantine/hooks": "7.5.1", + "@mantine/notifications": "7.5.1", "@medplum/core": "3.0.2", "@medplum/eslint-config": "3.0.2", "@medplum/fhirtypes": "3.0.2", "@medplum/react": "3.0.2", "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.8", - "@types/react": "18.2.48", + "@types/node": "20.11.16", + "@types/react": "18.2.51", "@types/react-dom": "18.2.18", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.21.3", @@ -291,7 +291,7 @@ "@medplum/eslint-config": "3.0.2", "@medplum/fhirtypes": "3.0.2", "@medplum/mock": "3.0.2", - "@types/node": "20.11.8", + "@types/node": "20.11.16", "@types/node-fetch": "2.6.11", "@types/ssh2-sftp-client": "9.0.3", "@vitest/coverage-v8": "1.2.2", @@ -310,19 +310,19 @@ "examples/medplum-fhircast-demo": { "version": "3.0.2", "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", + "@mantine/core": "7.5.1", + "@mantine/hooks": "7.5.1", + "@mantine/notifications": "7.5.1", "@medplum/core": "3.0.2", "@medplum/eslint-config": "3.0.2", "@medplum/fhirtypes": "3.0.2", "@medplum/react": "3.0.2", "@tabler/icons-react": "2.46.0", - "@types/react": "18.2.48", + "@types/react": "18.2.51", "@types/react-dom": "18.2.18", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.21.3", @@ -420,20 +420,20 @@ "examples/medplum-hello-world": { "version": "3.0.2", "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", + "@mantine/core": "7.5.1", + "@mantine/hooks": "7.5.1", + "@mantine/notifications": "7.5.1", "@medplum/core": "3.0.2", "@medplum/eslint-config": "3.0.2", "@medplum/fhirtypes": "3.0.2", "@medplum/react": "3.0.2", "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.8", - "@types/react": "18.2.48", + "@types/node": "20.11.16", + "@types/react": "18.2.51", "@types/react-dom": "18.2.18", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.21.3", @@ -531,20 +531,20 @@ "examples/medplum-live-chat-demo": { "version": "3.0.2", "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", + "@mantine/core": "7.5.1", + "@mantine/hooks": "7.5.1", + "@mantine/notifications": "7.5.1", "@medplum/core": "3.0.2", "@medplum/eslint-config": "3.0.2", "@medplum/fhirtypes": "3.0.2", "@medplum/react": "3.0.2", "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.8", - "@types/react": "18.2.48", + "@types/node": "20.11.16", + "@types/react": "18.2.51", "@types/react-dom": "18.2.18", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.21.3", @@ -642,9 +642,9 @@ "examples/medplum-nextjs-demo": { "version": "3.0.2", "dependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", + "@mantine/core": "7.5.1", + "@mantine/hooks": "7.5.1", + "@mantine/notifications": "7.5.1", "@medplum/core": "3.0.2", "@medplum/react": "3.0.2", "next": "14.1.0", @@ -654,13 +654,13 @@ }, "devDependencies": { "@medplum/fhirtypes": "3.0.2", - "@types/node": "20.11.8", - "@types/react": "18.2.48", + "@types/node": "20.11.16", + "@types/react": "18.2.51", "@types/react-dom": "18.2.18", "eslint": "8.56.0", "eslint-config-next": "14.1.0", "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss-preset-mantine": "1.13.0", "typescript": "5.3.3" } }, @@ -675,7 +675,7 @@ "expo-status-bar": "1.11.1", "react": "18.2.0", "react-dom": "18.2.0", - "react-native": "0.73.2", + "react-native": "0.73.3", "react-native-web": "0.19.10" }, "devDependencies": { @@ -686,21 +686,21 @@ "examples/medplum-task-demo": { "version": "3.0.2", "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", + "@mantine/core": "7.5.1", + "@mantine/hooks": "7.5.1", + "@mantine/notifications": "7.5.1", "@medplum/core": "3.0.2", "@medplum/definitions": "3.0.2", "@medplum/eslint-config": "3.0.2", "@medplum/fhirtypes": "3.0.2", "@medplum/react": "3.0.2", "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.8", - "@types/react": "18.2.48", + "@types/node": "20.11.16", + "@types/react": "18.2.51", "@types/react-dom": "18.2.18", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.21.3", @@ -799,9 +799,9 @@ "version": "3.0.2", "devDependencies": { "@emotion/react": "11.11.3", - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", + "@mantine/core": "7.5.1", + "@mantine/hooks": "7.5.1", + "@mantine/notifications": "7.5.1", "@medplum/core": "3.0.2", "@medplum/eslint-config": "3.0.2", "@medplum/fhir-router": "3.0.2", @@ -809,8 +809,8 @@ "@medplum/mock": "3.0.2", "@medplum/react": "3.0.2", "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.8", - "@types/react": "18.2.48", + "@types/node": "20.11.16", + "@types/react": "18.2.51", "@types/react-dom": "18.2.18", "@vitejs/plugin-react": "4.2.1", "react": "18.2.0", @@ -1267,25 +1267,25 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@aws-sdk/client-acm": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-acm/-/client-acm-3.501.0.tgz", - "integrity": "sha512-2F5hD6mdEOSIfMm/YJfqc/0hLx2i6OyF4rEP77RZPZjFdxFsQskISQ/w8JK3TljyNJSYiXkH5Oovjv0BlU7SHA==", + "version": "3.504.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-acm/-/client-acm-3.504.0.tgz", + "integrity": "sha512-5Yybn8RreBrPSWgQETxg/9twEx9Ho5xCmAxxmyTAuzudBlxN/D4oJtZnGAARXTpjM27NqJo5tpEfizoePzJvcw==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.501.0", + "@aws-sdk/client-sts": "3.504.0", "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.501.0", - "@aws-sdk/middleware-host-header": "3.496.0", - "@aws-sdk/middleware-logger": "3.496.0", - "@aws-sdk/middleware-recursion-detection": "3.496.0", - "@aws-sdk/middleware-signing": "3.496.0", - "@aws-sdk/middleware-user-agent": "3.496.0", - "@aws-sdk/region-config-resolver": "3.496.0", - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", - "@aws-sdk/util-user-agent-browser": "3.496.0", - "@aws-sdk/util-user-agent-node": "3.496.0", + "@aws-sdk/credential-provider-node": "3.504.0", + "@aws-sdk/middleware-host-header": "3.502.0", + "@aws-sdk/middleware-logger": "3.502.0", + "@aws-sdk/middleware-recursion-detection": "3.502.0", + "@aws-sdk/middleware-signing": "3.502.0", + "@aws-sdk/middleware-user-agent": "3.502.0", + "@aws-sdk/region-config-resolver": "3.502.0", + "@aws-sdk/types": "3.502.0", + "@aws-sdk/util-endpoints": "3.502.0", + "@aws-sdk/util-user-agent-browser": "3.502.0", + "@aws-sdk/util-user-agent-node": "3.502.0", "@smithy/config-resolver": "^2.1.1", "@smithy/core": "^1.3.1", "@smithy/fetch-http-handler": "^2.4.1", @@ -1318,25 +1318,25 @@ } }, "node_modules/@aws-sdk/client-cloudformation": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudformation/-/client-cloudformation-3.501.0.tgz", - "integrity": "sha512-aDYafR4iJorrmftwVbNjUIKH0AM6RwNyyxBGN34R0xfLpP2fq6IPASulP56Eah2Fv14KVbYYFFDirKw4b8WPeA==", + "version": "3.504.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudformation/-/client-cloudformation-3.504.0.tgz", + "integrity": "sha512-qmejnayn5p9MX5WRO/304hymAFhKeV+kmi1U90qE1tVURMKGf45QC5ndIXwsu39G65qKekhWwyKwtJfGOaTGtg==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.501.0", + "@aws-sdk/client-sts": "3.504.0", "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.501.0", - "@aws-sdk/middleware-host-header": "3.496.0", - "@aws-sdk/middleware-logger": "3.496.0", - "@aws-sdk/middleware-recursion-detection": "3.496.0", - "@aws-sdk/middleware-signing": "3.496.0", - "@aws-sdk/middleware-user-agent": "3.496.0", - "@aws-sdk/region-config-resolver": "3.496.0", - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", - "@aws-sdk/util-user-agent-browser": "3.496.0", - "@aws-sdk/util-user-agent-node": "3.496.0", + "@aws-sdk/credential-provider-node": "3.504.0", + "@aws-sdk/middleware-host-header": "3.502.0", + "@aws-sdk/middleware-logger": "3.502.0", + "@aws-sdk/middleware-recursion-detection": "3.502.0", + "@aws-sdk/middleware-signing": "3.502.0", + "@aws-sdk/middleware-user-agent": "3.502.0", + "@aws-sdk/region-config-resolver": "3.502.0", + "@aws-sdk/types": "3.502.0", + "@aws-sdk/util-endpoints": "3.502.0", + "@aws-sdk/util-user-agent-browser": "3.502.0", + "@aws-sdk/util-user-agent-node": "3.502.0", "@smithy/config-resolver": "^2.1.1", "@smithy/core": "^1.3.1", "@smithy/fetch-http-handler": "^2.4.1", @@ -1371,25 +1371,25 @@ } }, "node_modules/@aws-sdk/client-cloudfront": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudfront/-/client-cloudfront-3.501.0.tgz", - "integrity": "sha512-VewcZ9hmzxAHiZZ44mg1hGNeDkGo+5F4lNOguFgxp6IwRDQaR3zySNzrPMZkfjI0TQHJ0f1bx7Os3T4FNyy9lw==", + "version": "3.504.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudfront/-/client-cloudfront-3.504.0.tgz", + "integrity": "sha512-lBwHYHeNv+zD8oRBTB3lder6fgAe3ZMpfApopC/7Futt1jCmuv81cM6ots6VJ7+J5TaNeYQpc8+wA4DqFc1bsw==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.501.0", + "@aws-sdk/client-sts": "3.504.0", "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.501.0", - "@aws-sdk/middleware-host-header": "3.496.0", - "@aws-sdk/middleware-logger": "3.496.0", - "@aws-sdk/middleware-recursion-detection": "3.496.0", - "@aws-sdk/middleware-signing": "3.496.0", - "@aws-sdk/middleware-user-agent": "3.496.0", - "@aws-sdk/region-config-resolver": "3.496.0", - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", - "@aws-sdk/util-user-agent-browser": "3.496.0", - "@aws-sdk/util-user-agent-node": "3.496.0", + "@aws-sdk/credential-provider-node": "3.504.0", + "@aws-sdk/middleware-host-header": "3.502.0", + "@aws-sdk/middleware-logger": "3.502.0", + "@aws-sdk/middleware-recursion-detection": "3.502.0", + "@aws-sdk/middleware-signing": "3.502.0", + "@aws-sdk/middleware-user-agent": "3.502.0", + "@aws-sdk/region-config-resolver": "3.502.0", + "@aws-sdk/types": "3.502.0", + "@aws-sdk/util-endpoints": "3.502.0", + "@aws-sdk/util-user-agent-browser": "3.502.0", + "@aws-sdk/util-user-agent-node": "3.502.0", "@aws-sdk/xml-builder": "3.496.0", "@smithy/config-resolver": "^2.1.1", "@smithy/core": "^1.3.1", @@ -1425,25 +1425,25 @@ } }, "node_modules/@aws-sdk/client-cloudwatch-logs": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.501.0.tgz", - "integrity": "sha512-Lad4FHqTut9ZM8VKW48cnd8OBYekkVbYxWb6uWnf05NA9JMuy/zRBD4MGpv3MjhhRe4iX159G1406yK39N2GDg==", + "version": "3.504.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.504.0.tgz", + "integrity": "sha512-pXpRHHs5DZcmX2Q/CHN641+plvGhwN57FSgRcEU9wnsheAuadqhVCJkke2StPJ50U06UOQYW5YFGsv2F3FfNCQ==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.501.0", + "@aws-sdk/client-sts": "3.504.0", "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.501.0", - "@aws-sdk/middleware-host-header": "3.496.0", - "@aws-sdk/middleware-logger": "3.496.0", - "@aws-sdk/middleware-recursion-detection": "3.496.0", - "@aws-sdk/middleware-signing": "3.496.0", - "@aws-sdk/middleware-user-agent": "3.496.0", - "@aws-sdk/region-config-resolver": "3.496.0", - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", - "@aws-sdk/util-user-agent-browser": "3.496.0", - "@aws-sdk/util-user-agent-node": "3.496.0", + "@aws-sdk/credential-provider-node": "3.504.0", + "@aws-sdk/middleware-host-header": "3.502.0", + "@aws-sdk/middleware-logger": "3.502.0", + "@aws-sdk/middleware-recursion-detection": "3.502.0", + "@aws-sdk/middleware-signing": "3.502.0", + "@aws-sdk/middleware-user-agent": "3.502.0", + "@aws-sdk/region-config-resolver": "3.502.0", + "@aws-sdk/types": "3.502.0", + "@aws-sdk/util-endpoints": "3.502.0", + "@aws-sdk/util-user-agent-browser": "3.502.0", + "@aws-sdk/util-user-agent-node": "3.502.0", "@smithy/config-resolver": "^2.1.1", "@smithy/core": "^1.3.1", "@smithy/eventstream-serde-browser": "^2.1.1", @@ -1479,25 +1479,25 @@ } }, "node_modules/@aws-sdk/client-ecs": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ecs/-/client-ecs-3.501.0.tgz", - "integrity": "sha512-UAOd7GKWFY6puxMYcVeZAfdfbmY2CEEsmn7sB4UKIc5pSax7xVnOpdYxZyHsHV1Xq85Vkfm/65I6r8XBFu++0w==", + "version": "3.504.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ecs/-/client-ecs-3.504.0.tgz", + "integrity": "sha512-l7WQKOv2LnxOJrxrCBOg16L/bZRRwItEzUJV/qLpbe4j2dHHRksGJZU+Pw4jIzY25WIh8scp7Y5XqYG3r7tPRQ==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.501.0", + "@aws-sdk/client-sts": "3.504.0", "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.501.0", - "@aws-sdk/middleware-host-header": "3.496.0", - "@aws-sdk/middleware-logger": "3.496.0", - "@aws-sdk/middleware-recursion-detection": "3.496.0", - "@aws-sdk/middleware-signing": "3.496.0", - "@aws-sdk/middleware-user-agent": "3.496.0", - "@aws-sdk/region-config-resolver": "3.496.0", - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", - "@aws-sdk/util-user-agent-browser": "3.496.0", - "@aws-sdk/util-user-agent-node": "3.496.0", + "@aws-sdk/credential-provider-node": "3.504.0", + "@aws-sdk/middleware-host-header": "3.502.0", + "@aws-sdk/middleware-logger": "3.502.0", + "@aws-sdk/middleware-recursion-detection": "3.502.0", + "@aws-sdk/middleware-signing": "3.502.0", + "@aws-sdk/middleware-user-agent": "3.502.0", + "@aws-sdk/region-config-resolver": "3.502.0", + "@aws-sdk/types": "3.502.0", + "@aws-sdk/util-endpoints": "3.502.0", + "@aws-sdk/util-user-agent-browser": "3.502.0", + "@aws-sdk/util-user-agent-node": "3.502.0", "@smithy/config-resolver": "^2.1.1", "@smithy/core": "^1.3.1", "@smithy/fetch-http-handler": "^2.4.1", @@ -1531,25 +1531,25 @@ } }, "node_modules/@aws-sdk/client-lambda": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.501.0.tgz", - "integrity": "sha512-RkuNt5JalmPGxy3Nz73U9B0ArqVqkLLIP5BMvMlR9ghmrZWFaY/5AiIDUmz8xtfJjL9FHzdtt2/JcIYtAbrpeA==", + "version": "3.504.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.504.0.tgz", + "integrity": "sha512-YRhJ4tkNGTauLZZBUUUj63xlu3LAZTdqtRV0n2wYeI/ylT869YwOW41glJmMKy0kqG+u/p4YgVIyDjGZk5aLVQ==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.501.0", + "@aws-sdk/client-sts": "3.504.0", "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.501.0", - "@aws-sdk/middleware-host-header": "3.496.0", - "@aws-sdk/middleware-logger": "3.496.0", - "@aws-sdk/middleware-recursion-detection": "3.496.0", - "@aws-sdk/middleware-signing": "3.496.0", - "@aws-sdk/middleware-user-agent": "3.496.0", - "@aws-sdk/region-config-resolver": "3.496.0", - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", - "@aws-sdk/util-user-agent-browser": "3.496.0", - "@aws-sdk/util-user-agent-node": "3.496.0", + "@aws-sdk/credential-provider-node": "3.504.0", + "@aws-sdk/middleware-host-header": "3.502.0", + "@aws-sdk/middleware-logger": "3.502.0", + "@aws-sdk/middleware-recursion-detection": "3.502.0", + "@aws-sdk/middleware-signing": "3.502.0", + "@aws-sdk/middleware-user-agent": "3.502.0", + "@aws-sdk/region-config-resolver": "3.502.0", + "@aws-sdk/types": "3.502.0", + "@aws-sdk/util-endpoints": "3.502.0", + "@aws-sdk/util-user-agent-browser": "3.502.0", + "@aws-sdk/util-user-agent-node": "3.502.0", "@smithy/config-resolver": "^2.1.1", "@smithy/core": "^1.3.1", "@smithy/eventstream-serde-browser": "^2.1.1", @@ -1586,33 +1586,33 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.501.0.tgz", - "integrity": "sha512-ovxYSGdnEdr4UrNiT+9e3ov2XULFr0bcyoXJkYxnkXPDg9Y65nuZgZAIZQMS6wnJVmNrUprhqTSQB3KHXvaEuQ==", + "version": "3.504.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.504.0.tgz", + "integrity": "sha512-J8xPsnk7EDwalFSaDxPFNT2+x99nG2uQTpsLXAV3bWbT1nD/JZ+fase9GqxM11v6WngzqRvTQg26ljMn5hQSKA==", "dependencies": { "@aws-crypto/sha1-browser": "3.0.0", "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.501.0", + "@aws-sdk/client-sts": "3.504.0", "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.501.0", - "@aws-sdk/middleware-bucket-endpoint": "3.496.0", - "@aws-sdk/middleware-expect-continue": "3.496.0", - "@aws-sdk/middleware-flexible-checksums": "3.496.0", - "@aws-sdk/middleware-host-header": "3.496.0", - "@aws-sdk/middleware-location-constraint": "3.496.0", - "@aws-sdk/middleware-logger": "3.496.0", - "@aws-sdk/middleware-recursion-detection": "3.496.0", - "@aws-sdk/middleware-sdk-s3": "3.499.0", - "@aws-sdk/middleware-signing": "3.496.0", - "@aws-sdk/middleware-ssec": "3.498.0", - "@aws-sdk/middleware-user-agent": "3.496.0", - "@aws-sdk/region-config-resolver": "3.496.0", - "@aws-sdk/signature-v4-multi-region": "3.499.0", - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", - "@aws-sdk/util-user-agent-browser": "3.496.0", - "@aws-sdk/util-user-agent-node": "3.496.0", + "@aws-sdk/credential-provider-node": "3.504.0", + "@aws-sdk/middleware-bucket-endpoint": "3.502.0", + "@aws-sdk/middleware-expect-continue": "3.502.0", + "@aws-sdk/middleware-flexible-checksums": "3.502.0", + "@aws-sdk/middleware-host-header": "3.502.0", + "@aws-sdk/middleware-location-constraint": "3.502.0", + "@aws-sdk/middleware-logger": "3.502.0", + "@aws-sdk/middleware-recursion-detection": "3.502.0", + "@aws-sdk/middleware-sdk-s3": "3.502.0", + "@aws-sdk/middleware-signing": "3.502.0", + "@aws-sdk/middleware-ssec": "3.502.0", + "@aws-sdk/middleware-user-agent": "3.502.0", + "@aws-sdk/region-config-resolver": "3.502.0", + "@aws-sdk/signature-v4-multi-region": "3.502.0", + "@aws-sdk/types": "3.502.0", + "@aws-sdk/util-endpoints": "3.502.0", + "@aws-sdk/util-user-agent-browser": "3.502.0", + "@aws-sdk/util-user-agent-node": "3.502.0", "@aws-sdk/xml-builder": "3.496.0", "@smithy/config-resolver": "^2.1.1", "@smithy/core": "^1.3.1", @@ -1654,25 +1654,25 @@ } }, "node_modules/@aws-sdk/client-secrets-manager": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.501.0.tgz", - "integrity": "sha512-/8IMGC1kakHYvHH7kv1gSZaTAoNZdga8q4IUijNH9QHaufEf8j00Pp289n2g3ML5+ACVC3Y2CuWVrrYsKHfxXw==", + "version": "3.504.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.504.0.tgz", + "integrity": "sha512-JPwsYfQMjs5t74JmA4r1AjpiOG/LEw74d4a8vEdSy3pe2lhl/sSsxSdQtbI30wlJJramngtLNZjxn2+BGDphbg==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.501.0", + "@aws-sdk/client-sts": "3.504.0", "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.501.0", - "@aws-sdk/middleware-host-header": "3.496.0", - "@aws-sdk/middleware-logger": "3.496.0", - "@aws-sdk/middleware-recursion-detection": "3.496.0", - "@aws-sdk/middleware-signing": "3.496.0", - "@aws-sdk/middleware-user-agent": "3.496.0", - "@aws-sdk/region-config-resolver": "3.496.0", - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", - "@aws-sdk/util-user-agent-browser": "3.496.0", - "@aws-sdk/util-user-agent-node": "3.496.0", + "@aws-sdk/credential-provider-node": "3.504.0", + "@aws-sdk/middleware-host-header": "3.502.0", + "@aws-sdk/middleware-logger": "3.502.0", + "@aws-sdk/middleware-recursion-detection": "3.502.0", + "@aws-sdk/middleware-signing": "3.502.0", + "@aws-sdk/middleware-user-agent": "3.502.0", + "@aws-sdk/region-config-resolver": "3.502.0", + "@aws-sdk/types": "3.502.0", + "@aws-sdk/util-endpoints": "3.502.0", + "@aws-sdk/util-user-agent-browser": "3.502.0", + "@aws-sdk/util-user-agent-node": "3.502.0", "@smithy/config-resolver": "^2.1.1", "@smithy/core": "^1.3.1", "@smithy/fetch-http-handler": "^2.4.1", @@ -1705,25 +1705,25 @@ } }, "node_modules/@aws-sdk/client-sesv2": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.501.0.tgz", - "integrity": "sha512-QxhDSR/x+Vcehd9NORGm+wk7m6OtFiPH1ZyM3S4CuJd0PdLSfrGpUYWyR5YczTewEVmAZdXdUYd6q4ZaH6PO1Q==", + "version": "3.504.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.504.0.tgz", + "integrity": "sha512-vMFJAMMol5u2JueLbSVDhIz4ntBARJ8hvYDmpnJ7yZwLUqaZ1TnLPQQni/gRm8ujIyp557xraGaAVPFWDEb+XA==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.501.0", + "@aws-sdk/client-sts": "3.504.0", "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.501.0", - "@aws-sdk/middleware-host-header": "3.496.0", - "@aws-sdk/middleware-logger": "3.496.0", - "@aws-sdk/middleware-recursion-detection": "3.496.0", - "@aws-sdk/middleware-signing": "3.496.0", - "@aws-sdk/middleware-user-agent": "3.496.0", - "@aws-sdk/region-config-resolver": "3.496.0", - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", - "@aws-sdk/util-user-agent-browser": "3.496.0", - "@aws-sdk/util-user-agent-node": "3.496.0", + "@aws-sdk/credential-provider-node": "3.504.0", + "@aws-sdk/middleware-host-header": "3.502.0", + "@aws-sdk/middleware-logger": "3.502.0", + "@aws-sdk/middleware-recursion-detection": "3.502.0", + "@aws-sdk/middleware-signing": "3.502.0", + "@aws-sdk/middleware-user-agent": "3.502.0", + "@aws-sdk/region-config-resolver": "3.502.0", + "@aws-sdk/types": "3.502.0", + "@aws-sdk/util-endpoints": "3.502.0", + "@aws-sdk/util-user-agent-browser": "3.502.0", + "@aws-sdk/util-user-agent-node": "3.502.0", "@smithy/config-resolver": "^2.1.1", "@smithy/core": "^1.3.1", "@smithy/fetch-http-handler": "^2.4.1", @@ -1755,25 +1755,25 @@ } }, "node_modules/@aws-sdk/client-ssm": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ssm/-/client-ssm-3.501.0.tgz", - "integrity": "sha512-XmG9ZwAbInsUbn8dS5iPfDs/VO7U6jOcuERrlyWpKxs+iyHUCi3dwexCCkgMvbxE4NQ4isr6DR17mk8pXpbJ2w==", + "version": "3.504.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ssm/-/client-ssm-3.504.0.tgz", + "integrity": "sha512-HaqPsRds/1F8MSy0xM8iwsjSUlSoKsYsyi4DVwZ/C29t6iunAnxx+tcQ9U7pK3usMGWCcsAcJdJp2VksmWCmFQ==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.501.0", + "@aws-sdk/client-sts": "3.504.0", "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.501.0", - "@aws-sdk/middleware-host-header": "3.496.0", - "@aws-sdk/middleware-logger": "3.496.0", - "@aws-sdk/middleware-recursion-detection": "3.496.0", - "@aws-sdk/middleware-signing": "3.496.0", - "@aws-sdk/middleware-user-agent": "3.496.0", - "@aws-sdk/region-config-resolver": "3.496.0", - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", - "@aws-sdk/util-user-agent-browser": "3.496.0", - "@aws-sdk/util-user-agent-node": "3.496.0", + "@aws-sdk/credential-provider-node": "3.504.0", + "@aws-sdk/middleware-host-header": "3.502.0", + "@aws-sdk/middleware-logger": "3.502.0", + "@aws-sdk/middleware-recursion-detection": "3.502.0", + "@aws-sdk/middleware-signing": "3.502.0", + "@aws-sdk/middleware-user-agent": "3.502.0", + "@aws-sdk/region-config-resolver": "3.502.0", + "@aws-sdk/types": "3.502.0", + "@aws-sdk/util-endpoints": "3.502.0", + "@aws-sdk/util-user-agent-browser": "3.502.0", + "@aws-sdk/util-user-agent-node": "3.502.0", "@smithy/config-resolver": "^2.1.1", "@smithy/core": "^1.3.1", "@smithy/fetch-http-handler": "^2.4.1", @@ -1807,22 +1807,22 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.496.0.tgz", - "integrity": "sha512-fuaMuxKg7CMUsP9l3kxYWCOxFsBjdA0xj5nlikaDm1661/gB4KkAiGqRY8LsQkpNXvXU8Nj+f7oCFADFyGYzyw==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.502.0.tgz", + "integrity": "sha512-OZAYal1+PQgUUtWiHhRayDtX0OD+XpXHKAhjYgEIPbyhQaCMp3/Bq1xDX151piWXvXqXLJHFKb8DUEqzwGO9QA==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", "@aws-sdk/core": "3.496.0", - "@aws-sdk/middleware-host-header": "3.496.0", - "@aws-sdk/middleware-logger": "3.496.0", - "@aws-sdk/middleware-recursion-detection": "3.496.0", - "@aws-sdk/middleware-user-agent": "3.496.0", - "@aws-sdk/region-config-resolver": "3.496.0", - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", - "@aws-sdk/util-user-agent-browser": "3.496.0", - "@aws-sdk/util-user-agent-node": "3.496.0", + "@aws-sdk/middleware-host-header": "3.502.0", + "@aws-sdk/middleware-logger": "3.502.0", + "@aws-sdk/middleware-recursion-detection": "3.502.0", + "@aws-sdk/middleware-user-agent": "3.502.0", + "@aws-sdk/region-config-resolver": "3.502.0", + "@aws-sdk/types": "3.502.0", + "@aws-sdk/util-endpoints": "3.502.0", + "@aws-sdk/util-user-agent-browser": "3.502.0", + "@aws-sdk/util-user-agent-node": "3.502.0", "@smithy/config-resolver": "^2.1.1", "@smithy/core": "^1.3.1", "@smithy/fetch-http-handler": "^2.4.1", @@ -1853,24 +1853,75 @@ "node": ">=14.0.0" } }, + "node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.504.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.504.0.tgz", + "integrity": "sha512-ODA33/nm2srhV08EW0KZAP577UgV0qjyr7Xp2yEo8MXWL4ZqQZprk1c+QKBhjr4Djesrm0VPmSD/np0mtYP68A==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.504.0", + "@aws-sdk/core": "3.496.0", + "@aws-sdk/middleware-host-header": "3.502.0", + "@aws-sdk/middleware-logger": "3.502.0", + "@aws-sdk/middleware-recursion-detection": "3.502.0", + "@aws-sdk/middleware-signing": "3.502.0", + "@aws-sdk/middleware-user-agent": "3.502.0", + "@aws-sdk/region-config-resolver": "3.502.0", + "@aws-sdk/types": "3.502.0", + "@aws-sdk/util-endpoints": "3.502.0", + "@aws-sdk/util-user-agent-browser": "3.502.0", + "@aws-sdk/util-user-agent-node": "3.502.0", + "@smithy/config-resolver": "^2.1.1", + "@smithy/core": "^1.3.1", + "@smithy/fetch-http-handler": "^2.4.1", + "@smithy/hash-node": "^2.1.1", + "@smithy/invalid-dependency": "^2.1.1", + "@smithy/middleware-content-length": "^2.1.1", + "@smithy/middleware-endpoint": "^2.4.1", + "@smithy/middleware-retry": "^2.1.1", + "@smithy/middleware-serde": "^2.1.1", + "@smithy/middleware-stack": "^2.1.1", + "@smithy/node-config-provider": "^2.2.1", + "@smithy/node-http-handler": "^2.3.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/smithy-client": "^2.3.1", + "@smithy/types": "^2.9.1", + "@smithy/url-parser": "^2.1.1", + "@smithy/util-base64": "^2.1.1", + "@smithy/util-body-length-browser": "^2.1.1", + "@smithy/util-body-length-node": "^2.2.1", + "@smithy/util-defaults-mode-browser": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-retry": "^2.1.1", + "@smithy/util-utf8": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": "^3.504.0" + } + }, "node_modules/@aws-sdk/client-sts": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.501.0.tgz", - "integrity": "sha512-Uwc/xuxsA46dZS5s+4U703LBNDrGpWF7RB4XYEEMD21BLfGuqntxLLQux8xxKt3Pcur0CsXNja5jXt3uLnE5MA==", + "version": "3.504.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.504.0.tgz", + "integrity": "sha512-IESs8FkL7B/uY+ml4wgoRkrr6xYo4PizcNw6JX17eveq1gRBCPKeGMjE6HTDOcIYZZ8rqz/UeuH3JD4UhrMOnA==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.501.0", - "@aws-sdk/middleware-host-header": "3.496.0", - "@aws-sdk/middleware-logger": "3.496.0", - "@aws-sdk/middleware-recursion-detection": "3.496.0", - "@aws-sdk/middleware-user-agent": "3.496.0", - "@aws-sdk/region-config-resolver": "3.496.0", - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", - "@aws-sdk/util-user-agent-browser": "3.496.0", - "@aws-sdk/util-user-agent-node": "3.496.0", + "@aws-sdk/middleware-host-header": "3.502.0", + "@aws-sdk/middleware-logger": "3.502.0", + "@aws-sdk/middleware-recursion-detection": "3.502.0", + "@aws-sdk/middleware-user-agent": "3.502.0", + "@aws-sdk/region-config-resolver": "3.502.0", + "@aws-sdk/types": "3.502.0", + "@aws-sdk/util-endpoints": "3.502.0", + "@aws-sdk/util-user-agent-browser": "3.502.0", + "@aws-sdk/util-user-agent-node": "3.502.0", "@smithy/config-resolver": "^2.1.1", "@smithy/core": "^1.3.1", "@smithy/fetch-http-handler": "^2.4.1", @@ -1901,6 +1952,9 @@ }, "engines": { "node": ">=14.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": "^3.504.0" } }, "node_modules/@aws-sdk/cloudfront-signer": { @@ -1932,13 +1986,32 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.496.0.tgz", - "integrity": "sha512-lukQMJ8SWWP5RqkRNOHi/H+WMhRvSWa3Fc5Jf/VP6xHiPLfF1XafcvthtV91e0VwPCiseI+HqChrcGq8pvnxHw==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.502.0.tgz", + "integrity": "sha512-KIB8Ae1Z7domMU/jU4KiIgK4tmYgvuXlhR54ehwlVHxnEoFPoPuGHFZU7oFn79jhhSLUFQ1lRYMxP0cEwb7XeQ==", + "dependencies": { + "@aws-sdk/types": "3.502.0", + "@smithy/property-provider": "^2.1.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.503.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.503.1.tgz", + "integrity": "sha512-rTdlFFGoPPFMF2YjtlfRuSgKI+XsF49u7d98255hySwhsbwd3Xp+utTTPquxP+CwDxMHbDlI7NxDzFiFdsoZug==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.502.0", + "@smithy/fetch-http-handler": "^2.4.1", + "@smithy/node-http-handler": "^2.3.1", "@smithy/property-provider": "^2.1.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/smithy-client": "^2.3.1", "@smithy/types": "^2.9.1", + "@smithy/util-stream": "^2.1.1", "tslib": "^2.5.0" }, "engines": { @@ -1946,15 +2019,16 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.501.0.tgz", - "integrity": "sha512-6UXnwLtYIr298ljveumCVXsH+x7csGscK5ylY+veRFy514NqyloRdJt8JY26hhh5SF9MYnkW+JyWSJ2Ls3tOjQ==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.496.0", - "@aws-sdk/credential-provider-process": "3.496.0", - "@aws-sdk/credential-provider-sso": "3.501.0", - "@aws-sdk/credential-provider-web-identity": "3.496.0", - "@aws-sdk/types": "3.496.0", + "version": "3.504.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.504.0.tgz", + "integrity": "sha512-ODICLXfr8xTUd3wweprH32Ge41yuBa+u3j0JUcLdTUO1N9ldczSMdo8zOPlP0z4doqD3xbnqMkjNQWgN/Q+5oQ==", + "dependencies": { + "@aws-sdk/client-sts": "3.504.0", + "@aws-sdk/credential-provider-env": "3.502.0", + "@aws-sdk/credential-provider-process": "3.502.0", + "@aws-sdk/credential-provider-sso": "3.504.0", + "@aws-sdk/credential-provider-web-identity": "3.504.0", + "@aws-sdk/types": "3.502.0", "@smithy/credential-provider-imds": "^2.2.1", "@smithy/property-provider": "^2.1.1", "@smithy/shared-ini-file-loader": "^2.3.1", @@ -1966,16 +2040,17 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.501.0.tgz", - "integrity": "sha512-NM62D8gYrQ1nyLYwW4k48B2/lMHDzHDcQccS1wJakr6bg5sdtG06CumwlVcY+LAa0o1xRnhHmh/yiwj/nN4avw==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.496.0", - "@aws-sdk/credential-provider-ini": "3.501.0", - "@aws-sdk/credential-provider-process": "3.496.0", - "@aws-sdk/credential-provider-sso": "3.501.0", - "@aws-sdk/credential-provider-web-identity": "3.496.0", - "@aws-sdk/types": "3.496.0", + "version": "3.504.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.504.0.tgz", + "integrity": "sha512-6+V5hIh+tILmUjf2ZQWQINR3atxQVgH/bFrGdSR/sHSp/tEgw3m0xWL3IRslWU1e4/GtXrfg1iYnMknXy68Ikw==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.502.0", + "@aws-sdk/credential-provider-http": "3.503.1", + "@aws-sdk/credential-provider-ini": "3.504.0", + "@aws-sdk/credential-provider-process": "3.502.0", + "@aws-sdk/credential-provider-sso": "3.504.0", + "@aws-sdk/credential-provider-web-identity": "3.504.0", + "@aws-sdk/types": "3.502.0", "@smithy/credential-provider-imds": "^2.2.1", "@smithy/property-provider": "^2.1.1", "@smithy/shared-ini-file-loader": "^2.3.1", @@ -1987,11 +2062,11 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.496.0.tgz", - "integrity": "sha512-/YZscCTGOKVmGr916Th4XF8Sz6JDtZ/n2loHG9exok9iy/qIbACsTRNLP9zexPxhPoue/oZqecY5xbVljfY34A==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.502.0.tgz", + "integrity": "sha512-fJJowOjQ4infYQX0E1J3xFVlmuwEYJAFk0Mo1qwafWmEthsBJs+6BR2RiWDELHKrSK35u4Pf3fu3RkYuCtmQFw==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.502.0", "@smithy/property-provider": "^2.1.1", "@smithy/shared-ini-file-loader": "^2.3.1", "@smithy/types": "^2.9.1", @@ -2002,13 +2077,13 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.501.0.tgz", - "integrity": "sha512-y90dlvvZ55PwecODFdMx0NiNlJJfm7X6S61PKdLNCMRcu1YK+eWn0CmPHGHobBUQ4SEYhnFLcHSsf+VMim6BtQ==", + "version": "3.504.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.504.0.tgz", + "integrity": "sha512-4MgH2or2SjPzaxM08DCW+BjaX4DSsEGJlicHKmz6fh+w9JmLh750oXcTnbvgUeVz075jcs6qTKjvUcsdGM/t8Q==", "dependencies": { - "@aws-sdk/client-sso": "3.496.0", - "@aws-sdk/token-providers": "3.501.0", - "@aws-sdk/types": "3.496.0", + "@aws-sdk/client-sso": "3.502.0", + "@aws-sdk/token-providers": "3.504.0", + "@aws-sdk/types": "3.502.0", "@smithy/property-provider": "^2.1.1", "@smithy/shared-ini-file-loader": "^2.3.1", "@smithy/types": "^2.9.1", @@ -2019,11 +2094,12 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.496.0.tgz", - "integrity": "sha512-IbP+qLlvJSpNPj+zW6TtFuLRTK5Tf0hW+2pom4vFyi5YSH4pn8UOC136UdewX8vhXGS9BJQ5zBDMasIyl5VeGQ==", + "version": "3.504.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.504.0.tgz", + "integrity": "sha512-L1ljCvGpIEFdJk087ijf2ohg7HBclOeB1UgBxUBBzf4iPRZTQzd2chGaKj0hm2VVaXz7nglswJeURH5PFcS5oA==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/client-sts": "3.504.0", + "@aws-sdk/types": "3.502.0", "@smithy/property-provider": "^2.1.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -2033,9 +2109,9 @@ } }, "node_modules/@aws-sdk/lib-storage": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.501.0.tgz", - "integrity": "sha512-XZREd1O0S8AjM3RS85T2QCVJzXk+BSAGNOFvGP8t2al2Ti35O4+AvSHT75rmOGAZAsthtL2o9bt0h1VFnaIP+g==", + "version": "3.504.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.504.0.tgz", + "integrity": "sha512-A2h/yHy+2JFhqiCL1vfSlKxLRIZyyQte58O8s0yAV/TDt7ElzeXMTVtCUvhcOrnjtdHKfh4F36jeZSh1ja/9HA==", "dependencies": { "@smithy/abort-controller": "^2.1.1", "@smithy/middleware-endpoint": "^2.4.1", @@ -2053,11 +2129,11 @@ } }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.496.0.tgz", - "integrity": "sha512-B+ilBMSs3+LJuo2bl2KB8GFdu+8PPVtYEWtwhNkmnaU8iMisgMBp5uuM8sUDvJX7I4iSF0WbgnhguX4cJqfAew==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.502.0.tgz", + "integrity": "sha512-mUSP2DUcjhO5zM2b21CvZ9AqwI8DaAeZA6NYHOxWGTV9BUxHcdGWXEjDkcVj9CQ0gvNwTtw6B5L/q52rVAnZbw==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.502.0", "@aws-sdk/util-arn-parser": "3.495.0", "@smithy/node-config-provider": "^2.2.1", "@smithy/protocol-http": "^3.1.1", @@ -2070,11 +2146,11 @@ } }, "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.496.0.tgz", - "integrity": "sha512-+exo5DVc+BeDus2iI6Fz1thefHGDXxUhHZ+4VHQ6HkStMy3Y22HugyEGHSQZmtRL86Hjr7dFbEWFsC47a2ItGA==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.502.0.tgz", + "integrity": "sha512-DxfAuBVuPSt8as9xP57o8ks6ySVSjwO2NNNAdpLwk4KhEAPYEpHlf2yWYorYLrS+dDmwfYgOhRNoguuBdCu6ow==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.502.0", "@smithy/protocol-http": "^3.1.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -2084,13 +2160,13 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.496.0.tgz", - "integrity": "sha512-yQIWfjEMvgsAJ7ku224vXDjXPD+f9zfKZFialJva8VUlEr7hQp4CQ0rxV3YThSaixKEDDs5k6kOjWAd2BPGr2A==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.502.0.tgz", + "integrity": "sha512-kCt2zQDFumz/LnJJJOSd2GW4dr8oT8YMJKgxC/pph3aRXoSHXRwhrMbFnQ8swEE9vjywxtcED8sym0b0tNhhoA==", "dependencies": { "@aws-crypto/crc32": "3.0.0", "@aws-crypto/crc32c": "3.0.0", - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.502.0", "@smithy/is-array-buffer": "^2.1.1", "@smithy/protocol-http": "^3.1.1", "@smithy/types": "^2.9.1", @@ -2102,11 +2178,11 @@ } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.496.0.tgz", - "integrity": "sha512-jUdPpSJeqCYXf6hSjfwsfHway7peIV8Vz51w/BN91bF4vB/bYwAC5o9/iJiK/EoByp5asxA8fg9wFOyGjzdbLg==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.502.0.tgz", + "integrity": "sha512-EjnG0GTYXT/wJBmm5/mTjDcAkzU8L7wQjOzd3FTXuTCNNyvAvwrszbOj5FlarEw5XJBbQiZtBs+I5u9+zy560w==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.502.0", "@smithy/protocol-http": "^3.1.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -2116,11 +2192,11 @@ } }, "node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.496.0.tgz", - "integrity": "sha512-i4ocJ2Zs86OtPREbB18InFukhqg2qtBxb5gywv79IHDPVmpOYE4m/3v3yGUrkjfF2GTlUL0k5FskNNqw41yfng==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.502.0.tgz", + "integrity": "sha512-fLRwPuTZvEWQkPjys03m3D6tYN4kf7zU6+c8mJxwvEg+yfBuv2RBsbd+Vn2bTisUjXvIg1kyBzONlpHoIyFneg==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.502.0", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" }, @@ -2129,11 +2205,11 @@ } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.496.0.tgz", - "integrity": "sha512-EwMVSY6iBMeGbVnvwdaFl/ClMS/YWtxCAo+bcEtgk8ltRuo7qgbJem8Km/fvWC1vdWvIbe4ArdJ8iGzq62ffAw==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.502.0.tgz", + "integrity": "sha512-FDyv6K4nCoHxbjLGS2H8ex8I0KDIiu4FJgVRPs140ZJy6gE5Pwxzv6YTzZGLMrnqcIs9gh065Lf6DjwMelZqaw==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.502.0", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" }, @@ -2142,11 +2218,11 @@ } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.496.0.tgz", - "integrity": "sha512-+IuOcFsfqg2WAnaEzH6KhVbicqCxtOq9w3DH2jwTpddRlCx2Kqf6wCzg8luhHRGyjBZdsbIS+OXwyMevoppawA==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.502.0.tgz", + "integrity": "sha512-hvbyGJbxeuezxOu8VfFmcV4ql1hKXLxHTe5FNYfEBat2KaZXVhc1Hg+4TvB06/53p+E8J99Afmumkqbxs2esUA==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.502.0", "@smithy/protocol-http": "^3.1.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -2156,11 +2232,11 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.499.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.499.0.tgz", - "integrity": "sha512-thTb47U1hYHk5ei+yO0D0aehbgQXeAcgvyyxOID9/HDuRfWuTvKdclWh/goIeDfvSS87VBukEAjnCa5JYBwzug==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.502.0.tgz", + "integrity": "sha512-GbGugrfyL5bNA/zw8iQll92yXBONfWSC8Ns00DtkOU1saPXp4/7WHtyyZGYdvPa73T1IsuZy9egpoYRBmRcd5Q==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.502.0", "@aws-sdk/util-arn-parser": "3.495.0", "@smithy/node-config-provider": "^2.2.1", "@smithy/protocol-http": "^3.1.1", @@ -2175,11 +2251,11 @@ } }, "node_modules/@aws-sdk/middleware-signing": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.496.0.tgz", - "integrity": "sha512-Oq73Brs4IConvWnRlh8jM1V7LHoTw9SVQklu/QW2FPlNrB3B8fuTdWHHYIWv7ybw1bykXoCY99v865Mmq/Or/g==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.502.0.tgz", + "integrity": "sha512-4hF08vSzJ7L6sB+393gOFj3s2N6nLusYS0XrMW6wYNFU10IDdbf8Z3TZ7gysDJJHEGQPmTAesPEDBsasGWcMxg==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.502.0", "@smithy/property-provider": "^2.1.1", "@smithy/protocol-http": "^3.1.1", "@smithy/signature-v4": "^2.1.1", @@ -2192,11 +2268,11 @@ } }, "node_modules/@aws-sdk/middleware-ssec": { - "version": "3.498.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.498.0.tgz", - "integrity": "sha512-sWujXgzeTqMZzj/pRYEnnEbSzhBosqw9DXHOY1Mg2igI9NEfGlB7lPARp6aKmCaYlP3Bcj2X86vKCqF53mbyig==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.502.0.tgz", + "integrity": "sha512-1nidVTIba6/aVjjzD/WNqWdzSyTrXOHO3Ddz2MGD8S1yGSrYz4iYaq4Bm/uosfdr8B1L0Ws0pjdRXrNfzSw/DQ==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.502.0", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" }, @@ -2205,12 +2281,12 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.496.0.tgz", - "integrity": "sha512-+iMtRxFk0GmFWNUF4ilxylOQd9PZdR4ZC9jkcPIh1PZlvKtpCyFywKlk5RRZKklSoJ/CttcqwhMvOXTNbWm/0w==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.502.0.tgz", + "integrity": "sha512-TxbBZbRiXPH0AUxegqiNd9aM9zNSbfjtBs5MEfcBsweeT/B2O7K1EjP9+CkB8Xmk/5FLKhAKLr19b1TNoE27rw==", "dependencies": { - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", + "@aws-sdk/types": "3.502.0", + "@aws-sdk/util-endpoints": "3.502.0", "@smithy/protocol-http": "^3.1.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -2220,11 +2296,11 @@ } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.496.0.tgz", - "integrity": "sha512-URrNVOPHPgEDm6QFu6lDC2cUFs+Jx23mA3jEwCvoKlXiEY/ZoWjH8wlX3OMUlLrF1qoUTuD03jjrJzF6zoCgug==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.502.0.tgz", + "integrity": "sha512-mxmsX2AGgnSM+Sah7mcQCIneOsJQNiLX0COwEttuf8eO+6cLMAZvVudH3BnWTfea4/A9nuri9DLCqBvEmPrilg==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.502.0", "@smithy/node-config-provider": "^2.2.1", "@smithy/types": "^2.9.1", "@smithy/util-config-provider": "^2.2.1", @@ -2236,12 +2312,12 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.499.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.499.0.tgz", - "integrity": "sha512-8HSFnZErRm7lAfk+Epxrf4QNdQEamg1CnbLybtKQQEjmvxLuXYvj16KlpYEZIwEENOMEvnCqMc7syTPkmjVhJA==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.502.0.tgz", + "integrity": "sha512-NpOXtUXH0ZAgnyI3Y3s2fPrgwbsWoNMwdoXdFZvH0eDzzX80tim7Yuy6dzVA5zrxSzOYs1xjcOhM+4CmM0QZiw==", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.499.0", - "@aws-sdk/types": "3.496.0", + "@aws-sdk/middleware-sdk-s3": "3.502.0", + "@aws-sdk/types": "3.502.0", "@smithy/protocol-http": "^3.1.1", "@smithy/signature-v4": "^2.1.1", "@smithy/types": "^2.9.1", @@ -2252,46 +2328,15 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.501.0.tgz", - "integrity": "sha512-MvLPhNxlStmQqVm2crGLUqYWvK/AbMmI9j4FbEfJ15oG/I+730zjSJQEy2MvdiqbJRDPZ/tRCL89bUedOrmi0g==", + "version": "3.504.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.504.0.tgz", + "integrity": "sha512-YIJWWsZi2ClUiILS1uh5L6VjmCUSTI6KKMuL9DkGjYqJ0aI6M8bd8fT9Wm7QmXCyjcArTgr/Atkhia4T7oKvzQ==", "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/middleware-host-header": "3.496.0", - "@aws-sdk/middleware-logger": "3.496.0", - "@aws-sdk/middleware-recursion-detection": "3.496.0", - "@aws-sdk/middleware-user-agent": "3.496.0", - "@aws-sdk/region-config-resolver": "3.496.0", - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", - "@aws-sdk/util-user-agent-browser": "3.496.0", - "@aws-sdk/util-user-agent-node": "3.496.0", - "@smithy/config-resolver": "^2.1.1", - "@smithy/fetch-http-handler": "^2.4.1", - "@smithy/hash-node": "^2.1.1", - "@smithy/invalid-dependency": "^2.1.1", - "@smithy/middleware-content-length": "^2.1.1", - "@smithy/middleware-endpoint": "^2.4.1", - "@smithy/middleware-retry": "^2.1.1", - "@smithy/middleware-serde": "^2.1.1", - "@smithy/middleware-stack": "^2.1.1", - "@smithy/node-config-provider": "^2.2.1", - "@smithy/node-http-handler": "^2.3.1", + "@aws-sdk/client-sso-oidc": "3.504.0", + "@aws-sdk/types": "3.502.0", "@smithy/property-provider": "^2.1.1", - "@smithy/protocol-http": "^3.1.1", "@smithy/shared-ini-file-loader": "^2.3.1", - "@smithy/smithy-client": "^2.3.1", "@smithy/types": "^2.9.1", - "@smithy/url-parser": "^2.1.1", - "@smithy/util-base64": "^2.1.1", - "@smithy/util-body-length-browser": "^2.1.1", - "@smithy/util-body-length-node": "^2.2.1", - "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", - "@smithy/util-endpoints": "^1.1.1", - "@smithy/util-retry": "^2.1.1", - "@smithy/util-utf8": "^2.1.1", "tslib": "^2.5.0" }, "engines": { @@ -2299,9 +2344,9 @@ } }, "node_modules/@aws-sdk/types": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.496.0.tgz", - "integrity": "sha512-umkGadK4QuNQaMoDICMm7NKRI/mYSXiyPjcn3d53BhsuArYU/52CebGQKdt4At7SwwsiVJZw9RNBHyN5Mm0HVw==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.502.0.tgz", + "integrity": "sha512-M0DSPYe/gXhwD2QHgoukaZv5oDxhW3FfvYIrJptyqUq3OnPJBcDbihHjrE0PBtfh/9kgMZT60/fQ2NVFANfa2g==", "dependencies": { "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -2322,11 +2367,11 @@ } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.496.0.tgz", - "integrity": "sha512-1QzOiWHi383ZwqSi/R2KgKCd7M+6DxkxI5acqLPm8mvDRDP2jRjrnVaC0g9/tlttWousGEemDUWStwrD2mVYSw==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.502.0.tgz", + "integrity": "sha512-6LKFlJPp2J24r1Kpfoz5ESQn+1v5fEjDB3mtUKRdpwarhm3syu7HbKlHCF3KbcCOyahobvLvhoedT78rJFEeeg==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.502.0", "@smithy/types": "^2.9.1", "@smithy/util-endpoints": "^1.1.1", "tslib": "^2.5.0" @@ -2347,22 +2392,22 @@ } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.496.0.tgz", - "integrity": "sha512-4j2spN+h0I0qfSMsGvJXTfQBu1e18rPdekKvzsGJxhaAE1tNgUfUT4nbvc5uVn0sNjZmirskmJ3kfbzVOrqIFg==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.502.0.tgz", + "integrity": "sha512-v8gKyCs2obXoIkLETAeEQ3AM+QmhHhst9xbM1cJtKUGsRlVIak/XyyD+kVE6kmMm1cjfudHpHKABWk9apQcIZQ==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.502.0", "@smithy/types": "^2.9.1", "bowser": "^2.11.0", "tslib": "^2.5.0" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.496.0.tgz", - "integrity": "sha512-h0Ax0jlDc7UIo3KoSI4C4tVLBFoiAdx3+DhTVfgLS7x93d41dMlziPoBX2RgdcFn37qnzw6AQKTVTMwDbRCGpg==", + "version": "3.502.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.502.0.tgz", + "integrity": "sha512-9RjxpkGZKbTdl96tIJvAo+vZoz4P/cQh36SBUt9xfRfW0BtsaLyvSrvlR5wyUYhvRcC12Axqh/8JtnAPq//+Vw==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.502.0", "@smithy/node-config-provider": "^2.2.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -2500,9 +2545,9 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.9.tgz", - "integrity": "sha512-B2L9neXTIyPQoXDm+NtovPvG6VOLWnaXu3BIeVDWwdKFgG30oNa6CqVGiJPDWQwIAK49t9gnQI9c6K6RzabiKw==", + "version": "7.23.10", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.10.tgz", + "integrity": "sha512-2XpP2XhkXzgxecPNEEK8Vz8Asj9aRxt08oKOqtiZoqV2UGZ5T+EkyP9sXQ9nwMxBIG34a7jmasVqoMop7VdPUw==", "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.20", @@ -10022,9 +10067,9 @@ } }, "node_modules/@floating-ui/dom": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.0.tgz", - "integrity": "sha512-SZ0BEXzsaaS6THZfZJUcAobbZTD+MvfGM42bxgeg0Tnkp4/an/avqwAXiVLsFtIBZtfsx3Ymvwx0+KnnhdA/9g==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.1.tgz", + "integrity": "sha512-iA8qE43/H5iGozC3W0YSnVSW42Vh522yyM1gj+BqRwVsTNOyr231PsXDaV04yT39PsO0QL2QpbI/M0ZaLUQgRQ==", "dependencies": { "@floating-ui/core": "^1.6.0", "@floating-ui/utils": "^0.2.1" @@ -10045,11 +10090,11 @@ } }, "node_modules/@floating-ui/react-dom": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.7.tgz", - "integrity": "sha512-B5GJxKUyPcGsvE1vua+Abvw0t6zVMyTbtG+Jk7BoI4hfc5Ahv50dstRIAn0nS0274kR9gnKwxIXyGA8EzBZJrA==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", + "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", "dependencies": { - "@floating-ui/dom": "^1.6.0" + "@floating-ui/dom": "^1.6.1" }, "peerDependencies": { "react": ">=16.8.0", @@ -11423,9 +11468,9 @@ } }, "node_modules/@mantine/core": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.5.0.tgz", - "integrity": "sha512-0Qfn4oLCs6Qrli+JK6Q325xhNblVEPSKOB4sDMUkvKYUlCt/2lsIhwUXarVBgiIV3X+rKccf0/LcEWmpn/dYuw==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.5.1.tgz", + "integrity": "sha512-V7apuQuRubqxTRXb1uxOM43K7tkLRzpbb1ONJ/sj8QRp/26bShkdYp7EVuSKyrQ8DQ5EGYyBBGyzBOQARh41gA==", "dependencies": { "@floating-ui/react": "^0.24.8", "clsx": "2.0.0", @@ -11435,53 +11480,53 @@ "type-fest": "^3.13.1" }, "peerDependencies": { - "@mantine/hooks": "7.5.0", + "@mantine/hooks": "7.5.1", "react": "^18.2.0", "react-dom": "^18.2.0" } }, "node_modules/@mantine/dropzone": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@mantine/dropzone/-/dropzone-7.5.0.tgz", - "integrity": "sha512-vjYwVrdRiCK0TpPPYliUMNIeCrOhWiFwP6DlmO0Wnb8X+GpyW2+cuWCxdpcFQHZZGzbNUT3QB8ulIAuwFP9jKw==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@mantine/dropzone/-/dropzone-7.5.1.tgz", + "integrity": "sha512-pUQt7EwhDkTWbj4OLCbsvbkbp1vCXS6nGm8Y4pTrca1vR9S4+hilR6oULEY0UbQgHq+hfsWKDtnlhLkf45tJXA==", "dev": true, "dependencies": { "react-dropzone-esm": "15.0.1" }, "peerDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", + "@mantine/core": "7.5.1", + "@mantine/hooks": "7.5.1", "react": "^18.2.0", "react-dom": "^18.2.0" } }, "node_modules/@mantine/hooks": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.5.0.tgz", - "integrity": "sha512-KCL/RRMO+9HRIaNww3RIykifWL9XHovnANAyaCU2YUHOPyGCLSXs1UfFxsKNU71HaZ7cHwqSd7J0rR8JpVYLxw==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.5.1.tgz", + "integrity": "sha512-LfrEOkX8U2KbkYAU5BMA7FPbMva/TSd65c45W35wHSx3iqYMsoPN9+Ll1zc/HT0XNFp73jGet9cU7VREbAl0/A==", "peerDependencies": { "react": "^18.2.0" } }, "node_modules/@mantine/notifications": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-7.5.0.tgz", - "integrity": "sha512-43+PwX1WTTffmRjAAH/42Zk0ffXny5DMIry+WGfyNQdoN1JPL1w9eulpWhPFMUn2fm7wHyrKTRpiZdfp8ry2Ow==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-7.5.1.tgz", + "integrity": "sha512-IQDOAz+U9G6IkYXAXG9qL5EESmnhWV3JBJrxwBOPPdi1e9S/akQlsmABWS/voB9WFnOnbMbrkF067RVBA7W4dg==", "dependencies": { - "@mantine/store": "7.5.0", + "@mantine/store": "7.5.1", "react-transition-group": "4.4.5" }, "peerDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", + "@mantine/core": "7.5.1", + "@mantine/hooks": "7.5.1", "react": "^18.2.0", "react-dom": "^18.2.0" } }, "node_modules/@mantine/store": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@mantine/store/-/store-7.5.0.tgz", - "integrity": "sha512-NvPS6ERKxHGnkIEY8ip/v3ySYYPQlJq6KtSSXALlJ/BdyezBTsBrkEkEgJxJcYK5H4TYr9jDjLYNL3PQy4GHxg==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@mantine/store/-/store-7.5.1.tgz", + "integrity": "sha512-sDaPXB3v9JlJghNTnRTFT2hC3HN6pdBcCXj0CqO/QrJgtRA7A3FxW+mnY7YQOaBxHJ1MIRr+zsv0Qy1f/pu1dw==", "peerDependencies": { "react": "^18.2.0" } @@ -12700,9 +12745,9 @@ } }, "node_modules/@opentelemetry/api-logs": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.46.0.tgz", - "integrity": "sha512-+9BcqfiEDGPXEIo+o3tso/aqGM5dGbGwAkGVp3FPpZ8GlkK1YlaKRd9gMVyPaeRATwvO5wYGGnCsAc/sMMM9Qw==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.48.0.tgz", + "integrity": "sha512-1/aMiU4Eqo3Zzpfwu51uXssp5pzvHFObk8S9pKAiXb1ne8pvg1qxBQitYL1XUiAMEXFzgjaidYG2V6624DRhhw==", "dependencies": { "@opentelemetry/api": "^1.0.0" }, @@ -12711,54 +12756,54 @@ } }, "node_modules/@opentelemetry/auto-instrumentations-node": { - "version": "0.40.3", - "resolved": "https://registry.npmjs.org/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.40.3.tgz", - "integrity": "sha512-MgBCzpFU4FBQEsXPgt5driYzxErf2JGntQ8Amtc94sqrnvq+FKdEBa6vxOpZlPM+c4Xr6tPpAT1ecTBV8U87hw==", - "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", - "@opentelemetry/instrumentation-amqplib": "^0.33.5", - "@opentelemetry/instrumentation-aws-lambda": "^0.37.4", - "@opentelemetry/instrumentation-aws-sdk": "^0.37.2", - "@opentelemetry/instrumentation-bunyan": "^0.34.1", - "@opentelemetry/instrumentation-cassandra-driver": "^0.34.2", - "@opentelemetry/instrumentation-connect": "^0.32.4", - "@opentelemetry/instrumentation-cucumber": "^0.2.1", - "@opentelemetry/instrumentation-dataloader": "^0.5.4", - "@opentelemetry/instrumentation-dns": "^0.32.5", - "@opentelemetry/instrumentation-express": "^0.34.1", - "@opentelemetry/instrumentation-fastify": "^0.32.6", - "@opentelemetry/instrumentation-fs": "^0.8.4", - "@opentelemetry/instrumentation-generic-pool": "^0.32.5", - "@opentelemetry/instrumentation-graphql": "^0.36.1", - "@opentelemetry/instrumentation-grpc": "^0.46.0", - "@opentelemetry/instrumentation-hapi": "^0.33.3", - "@opentelemetry/instrumentation-http": "^0.46.0", - "@opentelemetry/instrumentation-ioredis": "^0.36.1", - "@opentelemetry/instrumentation-knex": "^0.32.4", - "@opentelemetry/instrumentation-koa": "^0.36.4", - "@opentelemetry/instrumentation-lru-memoizer": "^0.33.5", - "@opentelemetry/instrumentation-memcached": "^0.32.5", - "@opentelemetry/instrumentation-mongodb": "^0.38.1", - "@opentelemetry/instrumentation-mongoose": "^0.34.0", - "@opentelemetry/instrumentation-mysql": "^0.34.5", - "@opentelemetry/instrumentation-mysql2": "^0.34.5", - "@opentelemetry/instrumentation-nestjs-core": "^0.33.4", - "@opentelemetry/instrumentation-net": "^0.32.5", - "@opentelemetry/instrumentation-pg": "^0.37.2", - "@opentelemetry/instrumentation-pino": "^0.34.5", - "@opentelemetry/instrumentation-redis": "^0.35.5", - "@opentelemetry/instrumentation-redis-4": "^0.35.6", - "@opentelemetry/instrumentation-restify": "^0.34.3", - "@opentelemetry/instrumentation-router": "^0.33.4", - "@opentelemetry/instrumentation-socket.io": "^0.35.0", - "@opentelemetry/instrumentation-tedious": "^0.6.5", - "@opentelemetry/instrumentation-winston": "^0.33.1", - "@opentelemetry/resource-detector-alibaba-cloud": "^0.28.5", - "@opentelemetry/resource-detector-aws": "^1.3.5", - "@opentelemetry/resource-detector-container": "^0.3.5", - "@opentelemetry/resource-detector-gcp": "^0.29.5", + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.41.0.tgz", + "integrity": "sha512-CmVVzphp1eb6Aenrr4B6TDsCNuDvut8NQbosTSdJFfnEX4+3szVQuaJquaicGI5tLvVxrgVbZAv52C7bJgMHkg==", + "dependencies": { + "@opentelemetry/instrumentation": "^0.48.0", + "@opentelemetry/instrumentation-amqplib": "^0.34.0", + "@opentelemetry/instrumentation-aws-lambda": "^0.38.0", + "@opentelemetry/instrumentation-aws-sdk": "^0.38.0", + "@opentelemetry/instrumentation-bunyan": "^0.35.0", + "@opentelemetry/instrumentation-cassandra-driver": "^0.35.0", + "@opentelemetry/instrumentation-connect": "^0.33.0", + "@opentelemetry/instrumentation-cucumber": "^0.3.0", + "@opentelemetry/instrumentation-dataloader": "^0.6.0", + "@opentelemetry/instrumentation-dns": "^0.33.0", + "@opentelemetry/instrumentation-express": "^0.35.0", + "@opentelemetry/instrumentation-fastify": "^0.33.0", + "@opentelemetry/instrumentation-fs": "^0.9.0", + "@opentelemetry/instrumentation-generic-pool": "^0.33.0", + "@opentelemetry/instrumentation-graphql": "^0.37.0", + "@opentelemetry/instrumentation-grpc": "^0.48.0", + "@opentelemetry/instrumentation-hapi": "^0.34.0", + "@opentelemetry/instrumentation-http": "^0.48.0", + "@opentelemetry/instrumentation-ioredis": "^0.37.0", + "@opentelemetry/instrumentation-knex": "^0.33.0", + "@opentelemetry/instrumentation-koa": "^0.37.0", + "@opentelemetry/instrumentation-lru-memoizer": "^0.34.0", + "@opentelemetry/instrumentation-memcached": "^0.33.0", + "@opentelemetry/instrumentation-mongodb": "^0.39.0", + "@opentelemetry/instrumentation-mongoose": "^0.35.0", + "@opentelemetry/instrumentation-mysql": "^0.35.0", + "@opentelemetry/instrumentation-mysql2": "^0.35.0", + "@opentelemetry/instrumentation-nestjs-core": "^0.34.0", + "@opentelemetry/instrumentation-net": "^0.33.0", + "@opentelemetry/instrumentation-pg": "^0.38.0", + "@opentelemetry/instrumentation-pino": "^0.35.0", + "@opentelemetry/instrumentation-redis": "^0.36.0", + "@opentelemetry/instrumentation-redis-4": "^0.36.0", + "@opentelemetry/instrumentation-restify": "^0.35.0", + "@opentelemetry/instrumentation-router": "^0.34.0", + "@opentelemetry/instrumentation-socket.io": "^0.36.0", + "@opentelemetry/instrumentation-tedious": "^0.7.0", + "@opentelemetry/instrumentation-winston": "^0.34.0", + "@opentelemetry/resource-detector-alibaba-cloud": "^0.28.6", + "@opentelemetry/resource-detector-aws": "^1.3.6", + "@opentelemetry/resource-detector-container": "^0.3.6", + "@opentelemetry/resource-detector-gcp": "^0.29.6", "@opentelemetry/resources": "^1.12.0", - "@opentelemetry/sdk-node": "^0.46.0" + "@opentelemetry/sdk-node": "^0.48.0" }, "engines": { "node": ">=14" @@ -12767,344 +12812,6 @@ "@opentelemetry/api": "^1.4.1" } }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/context-async-hooks": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.19.0.tgz", - "integrity": "sha512-0i1ECOc9daKK3rjUgDDXf0GDD5XfCou5lXnt2DALIc2qKoruPPcesobNKE54laSVUWnC3jX26RzuOa31g0V32A==", - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.8.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/core": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.19.0.tgz", - "integrity": "sha512-w42AukJh3TP8R0IZZOVJVM/kMWu8g+lm4LzT70WtuKqhwq7KVhcDzZZuZinWZa6TtQCl7Smt2wolEYzpHabOgw==", - "dependencies": { - "@opentelemetry/semantic-conventions": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.8.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/exporter-trace-otlp-grpc": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.46.0.tgz", - "integrity": "sha512-kR4kehnfIhv7v/2MuNYfrlh9A/ZtQofwCzurTIplornUjdzhKDGgjui1NkNTqTfM1QkqfCiavGsf5hwocx29bA==", - "dependencies": { - "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.19.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.46.0", - "@opentelemetry/otlp-transformer": "0.46.0", - "@opentelemetry/resources": "1.19.0", - "@opentelemetry/sdk-trace-base": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/exporter-trace-otlp-http": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.46.0.tgz", - "integrity": "sha512-vZ2pYOB+qrQ+jnKPY6Gnd58y1k/Ti//Ny6/XsSX7/jED0X77crtSVgC6N5UA0JiGJOh6QB2KE9gaH99010XHzg==", - "dependencies": { - "@opentelemetry/core": "1.19.0", - "@opentelemetry/otlp-exporter-base": "0.46.0", - "@opentelemetry/otlp-transformer": "0.46.0", - "@opentelemetry/resources": "1.19.0", - "@opentelemetry/sdk-trace-base": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/exporter-trace-otlp-proto": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.46.0.tgz", - "integrity": "sha512-A7PftDM57w1TLiirrhi8ceAnCpYkpUBObELdn239IyYF67zwngImGfBLf5Yo3TTAOA2Oj1TL76L8zWVL8W+Suw==", - "dependencies": { - "@opentelemetry/core": "1.19.0", - "@opentelemetry/otlp-exporter-base": "0.46.0", - "@opentelemetry/otlp-proto-exporter-base": "0.46.0", - "@opentelemetry/otlp-transformer": "0.46.0", - "@opentelemetry/resources": "1.19.0", - "@opentelemetry/sdk-trace-base": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/exporter-zipkin": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-1.19.0.tgz", - "integrity": "sha512-TY1fy4JiOBN5a8T9fknqTMcz0DXIeFBr6sklaLCgwtj+G699a5R4CekNwpeM7DHSwC44UMX7gljO2I6dYsTS3A==", - "dependencies": { - "@opentelemetry/core": "1.19.0", - "@opentelemetry/resources": "1.19.0", - "@opentelemetry/sdk-trace-base": "1.19.0", - "@opentelemetry/semantic-conventions": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/otlp-exporter-base": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.46.0.tgz", - "integrity": "sha512-hfkh7cG17l77ZSLRAogz19SIJzr0KeC7xv5PDyTFbHFpwwoxV/bEViO49CqUFH6ckXB63NrltASP9R7po+ahTQ==", - "dependencies": { - "@opentelemetry/core": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/otlp-grpc-exporter-base": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.46.0.tgz", - "integrity": "sha512-/KB/xfZZiWIY2JknvCoT/e9paIzQO3QCBN5gR6RyxpXM/AGx3YTAOKvB/Ts9Va19jo5aE74gB7emhFaCNy4Rmw==", - "dependencies": { - "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.19.0", - "@opentelemetry/otlp-exporter-base": "0.46.0", - "protobufjs": "^7.2.3" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/otlp-proto-exporter-base": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-proto-exporter-base/-/otlp-proto-exporter-base-0.46.0.tgz", - "integrity": "sha512-rEJBA8U2AxfEzrdIUcyyjOweyVFkO6V1XAxwP161JkxpvNuVDdULHAfRVnGtoZhiVA1XsJKcpIIq2MEKAqq4cg==", - "dependencies": { - "@opentelemetry/core": "1.19.0", - "@opentelemetry/otlp-exporter-base": "0.46.0", - "protobufjs": "^7.2.3" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/otlp-transformer": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.46.0.tgz", - "integrity": "sha512-Fj9hZwr6xuqgsaERn667Uf6kuDG884puWhyrai2Jen2Fq+bGf4/5BzEJp/8xvty0VSU4EfXOto/ys3KpSz2UHg==", - "dependencies": { - "@opentelemetry/api-logs": "0.46.0", - "@opentelemetry/core": "1.19.0", - "@opentelemetry/resources": "1.19.0", - "@opentelemetry/sdk-logs": "0.46.0", - "@opentelemetry/sdk-metrics": "1.19.0", - "@opentelemetry/sdk-trace-base": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.8.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/propagator-b3": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-1.19.0.tgz", - "integrity": "sha512-v7y5IBOKBm0vP3yf0DHzlw4L2gL6tZ0KeeMTaxfO5IuomMffDbrGWcvYFp0Dt4LdZctTSK523rVLBB9FBHBciQ==", - "dependencies": { - "@opentelemetry/core": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.8.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/propagator-jaeger": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.19.0.tgz", - "integrity": "sha512-dedkOoTzKg+nYoLWCMp0Im+wo+XkTRW6aXhi8VQRtMW/9SNJGOllCJSu8llToLxMDF0+6zu7OCrKkevAof2tew==", - "dependencies": { - "@opentelemetry/core": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.8.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/resources": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.19.0.tgz", - "integrity": "sha512-RgxvKuuMOf7nctOeOvpDjt2BpZvZGr9Y0vf7eGtY5XYZPkh2p7e2qub1S2IArdBMf9kEbz0SfycqCviOu9isqg==", - "dependencies": { - "@opentelemetry/core": "1.19.0", - "@opentelemetry/semantic-conventions": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.8.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/sdk-logs": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.46.0.tgz", - "integrity": "sha512-Knlyk4+G72uEzNh6GRN1Fhmrj+/rkATI5/lOrevN7zRDLgp4kfyZBGGoWk7w+qQjlYvwhIIdPVxlIcipivdZIg==", - "dependencies": { - "@opentelemetry/core": "1.19.0", - "@opentelemetry/resources": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.4.0 <1.8.0", - "@opentelemetry/api-logs": ">=0.39.1" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/sdk-metrics": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.19.0.tgz", - "integrity": "sha512-FiMii40zr0Fmys4F1i8gmuCvbinBnBsDeGBr4FQemOf0iPCLytYQm5AZJ/nn4xSc71IgKBQwTFQRAGJI7JvZ4Q==", - "dependencies": { - "@opentelemetry/core": "1.19.0", - "@opentelemetry/resources": "1.19.0", - "lodash.merge": "^4.6.2" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.8.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/sdk-node": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.46.0.tgz", - "integrity": "sha512-BQhzdCRZXchhKjZaFkgxlgoowjOt/QXekJ1CZgfvFO9Yg5GV15LyJFUEyQkDyD8XbshGo3Cnj0WZMBnDWtWY1A==", - "dependencies": { - "@opentelemetry/api-logs": "0.46.0", - "@opentelemetry/core": "1.19.0", - "@opentelemetry/exporter-trace-otlp-grpc": "0.46.0", - "@opentelemetry/exporter-trace-otlp-http": "0.46.0", - "@opentelemetry/exporter-trace-otlp-proto": "0.46.0", - "@opentelemetry/exporter-zipkin": "1.19.0", - "@opentelemetry/instrumentation": "0.46.0", - "@opentelemetry/resources": "1.19.0", - "@opentelemetry/sdk-logs": "0.46.0", - "@opentelemetry/sdk-metrics": "1.19.0", - "@opentelemetry/sdk-trace-base": "1.19.0", - "@opentelemetry/sdk-trace-node": "1.19.0", - "@opentelemetry/semantic-conventions": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.8.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/sdk-trace-base": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.19.0.tgz", - "integrity": "sha512-+IRvUm+huJn2KqfFW3yW/cjvRwJ8Q7FzYHoUNx5Fr0Lws0LxjMJG1uVB8HDpLwm7mg5XXH2M5MF+0jj5cM8BpQ==", - "dependencies": { - "@opentelemetry/core": "1.19.0", - "@opentelemetry/resources": "1.19.0", - "@opentelemetry/semantic-conventions": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.8.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/sdk-trace-node": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.19.0.tgz", - "integrity": "sha512-TCiEq/cUjM15RFqBRwWomTVbOqzndWL4ILa7ZCu0zbjU1/XY6AgHkgrgAc7vGP6TjRqH4Xryuglol8tcIfbBUQ==", - "dependencies": { - "@opentelemetry/context-async-hooks": "1.19.0", - "@opentelemetry/core": "1.19.0", - "@opentelemetry/propagator-b3": "1.19.0", - "@opentelemetry/propagator-jaeger": "1.19.0", - "@opentelemetry/sdk-trace-base": "1.19.0", - "semver": "^7.5.2" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.8.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.19.0.tgz", - "integrity": "sha512-14jRpC8f5c0gPSwoZ7SbEJni1PqI+AhAE8m1bMz6v+RPM4OlP1PT2UHBJj5Qh/ALLPjhVU/aZUK3YyjTUqqQVg==", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/@opentelemetry/context-async-hooks": { "version": "1.21.0", "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.21.0.tgz", @@ -13242,9 +12949,9 @@ } }, "node_modules/@opentelemetry/instrumentation": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.46.0.tgz", - "integrity": "sha512-a9TijXZZbk0vI5TGLZl+0kxyFfrXHhX6Svtz7Pp2/VBlCSKrazuULEyoJQrOknJyFWNMEmbbJgOciHCCpQcisw==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.48.0.tgz", + "integrity": "sha512-sjtZQB5PStIdCw5ovVTDGwnmQC+GGYArJNgIcydrDSqUTdYBnMrN9P4pwQZgS3vTGIp+TU1L8vMXGe51NVmIKQ==", "dependencies": { "@types/shimmer": "^1.0.2", "import-in-the-middle": "1.7.1", @@ -13260,12 +12967,12 @@ } }, "node_modules/@opentelemetry/instrumentation-amqplib": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.33.5.tgz", - "integrity": "sha512-WQ/XPzNLOHL3fpsmgoQUkiKCkJ09hvPN8wGrGzzOHMiJ5/3LqvfvxsJ4Rcd6aWkA4il3hEfpl+V0VF0t/DP65A==", + "version": "0.34.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.34.0.tgz", + "integrity": "sha512-lVGRkyGnjFJv9O8oO/+uT40nrNj4UO+UN0k8708guy/toVgxsVpv4PtdWJTjbtu89UDk9gUxq62jpHxqrVaNnw==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13276,11 +12983,11 @@ } }, "node_modules/@opentelemetry/instrumentation-aws-lambda": { - "version": "0.37.4", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.37.4.tgz", - "integrity": "sha512-/wdZwUalIWAbxeycvmE+25c1xCMhe5EUuj8bN0MWWN3L8N2SYvfv6DmiRgwrTIPXRgIyFugh2udNiF4MezZN4Q==", + "version": "0.38.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.38.0.tgz", + "integrity": "sha512-MIPvM8S4LqGKE+IAnYVCRUnEjaWbPsbqL4p2BnGcox08e6+JQe+0d16DI0cKVSFZOzV5T/or3ewQ/bB0lPm8yg==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/propagator-aws-xray": "^1.3.1", "@opentelemetry/resources": "^1.8.0", "@opentelemetry/semantic-conventions": "^1.0.0", @@ -13294,13 +13001,13 @@ } }, "node_modules/@opentelemetry/instrumentation-aws-sdk": { - "version": "0.37.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.37.2.tgz", - "integrity": "sha512-eFHHOk0P9EFQ9Ho+2w7KH9R8cH7hut0/ePSsrk0nAM6Tiq2lBPeHn8ialCWESAA9jSjy+iuDelwmqAEXEe+jrg==", + "version": "0.38.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.38.0.tgz", + "integrity": "sha512-q1R+evR1j8sqd5LG+I8fKqP5TAPJEEOmJTvtEDYCVCdtzukI0ABYN8SHEIgDgYZmGBDM0yvC6jH0GmoEwvYuMw==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.46.0", - "@opentelemetry/propagation-utils": "^0.30.5", + "@opentelemetry/instrumentation": "^0.48.0", + "@opentelemetry/propagation-utils": "^0.30.6", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13311,12 +13018,12 @@ } }, "node_modules/@opentelemetry/instrumentation-bunyan": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-bunyan/-/instrumentation-bunyan-0.34.1.tgz", - "integrity": "sha512-+eshbCFr2dkUYO2jCpbYGFC5hs94UCOsQRK1XqNOjeiNvQRtqvKYqk8ARwJBYBX+aW4J02jOliAHQUh/d7gYPg==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-bunyan/-/instrumentation-bunyan-0.35.0.tgz", + "integrity": "sha512-bQ8OzV7nVTA+oGiTzLjUmRFAbnXi0U/Z4VJCpj+1DRsaAaMT17eRpAOh22LQR0JBnv2vBm8CvIQl4CcAnsB46g==", "dependencies": { - "@opentelemetry/api-logs": "^0.46.0", - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/api-logs": "^0.48.0", + "@opentelemetry/instrumentation": "^0.48.0", "@types/bunyan": "1.8.9" }, "engines": { @@ -13327,11 +13034,11 @@ } }, "node_modules/@opentelemetry/instrumentation-cassandra-driver": { - "version": "0.34.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cassandra-driver/-/instrumentation-cassandra-driver-0.34.2.tgz", - "integrity": "sha512-wuzq7QQ9o7PJnzseblNfBcURtM+9AwO6e1m644QYtAb/6YRR6qg6gAmAipVeQu01H5BuHBFC/92svaAkdIV2WQ==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cassandra-driver/-/instrumentation-cassandra-driver-0.35.0.tgz", + "integrity": "sha512-NlJkEiP37/WQvtSyYe4zxaBcaoweO/2+UtDssldk9NFmFutLHyMT/P5q5fe8i73ylmkPOAZnN8P48oHOhZHM1g==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13342,12 +13049,12 @@ } }, "node_modules/@opentelemetry/instrumentation-connect": { - "version": "0.32.4", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.32.4.tgz", - "integrity": "sha512-oUGph9idncdnHlQMwdIs6m6mii7Kdp9PpHkM9roOZy71h+2vvf6+cVn45bs2unBbE2Vxho2i/049QQbaYKDYlw==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.33.0.tgz", + "integrity": "sha512-EAMmUC2/KfeZl4qNgUsiVqT5Jti0jDl4GHi4TpDg41VBEJkRX/0+JcPBWgdFUgEfeiZr0GPVQud4i8jAwJ+ORw==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0", "@types/connect": "3.4.36" }, @@ -13359,11 +13066,11 @@ } }, "node_modules/@opentelemetry/instrumentation-cucumber": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cucumber/-/instrumentation-cucumber-0.2.1.tgz", - "integrity": "sha512-ydF0DpmE0D6wccAbxx1F+6kokzcSSRy3X78Bvgok/3fHUSSshGLErqNiQL1HV44OIcV6392P3tu/jtXtUq3UDQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cucumber/-/instrumentation-cucumber-0.3.0.tgz", + "integrity": "sha512-nM9BL0t2Nxwbb41MXxNXTDL0zq7FXhOX9F3OiAqYUJHqb7BHyzV9KoQ+Ao1BjqJR91hUm1OFNgHAk3y8uiuq4w==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13374,11 +13081,11 @@ } }, "node_modules/@opentelemetry/instrumentation-dataloader": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.5.4.tgz", - "integrity": "sha512-l1qQvvygxZJw+S+4hgYgzvT4GArqBrar42wzB5LVsOy04+gmbDw/4y7IqxZYepFyXKuBownGS8pR4huRL/Tj/A==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.6.0.tgz", + "integrity": "sha512-jkPdn83WV/TcnhQ5bOIoYcJGvMxXyYlCzbqfuB6HsMqf3CqpdgBQYlMuKi6qIfD4QWYt2R992yglNxPLuJ7xeg==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0" + "@opentelemetry/instrumentation": "^0.48.0" }, "engines": { "node": ">=14" @@ -13388,11 +13095,11 @@ } }, "node_modules/@opentelemetry/instrumentation-dns": { - "version": "0.32.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dns/-/instrumentation-dns-0.32.5.tgz", - "integrity": "sha512-fHJqrbezpZSipo4O9nF9yGq6R8oyr1W2gSlyk1foJNXBaqdCODTlzIa7BP50vGtLBN/m+qO8pMOWCJmYSBX35g==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dns/-/instrumentation-dns-0.33.0.tgz", + "integrity": "sha512-QDJadJOQg9CLqMC79r4T5ugN4C4lb6eJYLmHgnLg3fh1JUGfyjQHtD3T7lH0P8251Mnt5m8zjDDbPKcqK2aGcw==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0", "semver": "^7.5.4" }, @@ -13434,12 +13141,12 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/@opentelemetry/instrumentation-express": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.34.1.tgz", - "integrity": "sha512-pQBQxXpkH6imvzwCdPcw2FKjB1cphoRpmWTiGi6vtBdKXCP0hpne613ycpwhGG7C17S+mbarVmukbJKR4rmh6Q==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.35.0.tgz", + "integrity": "sha512-ZmSB4WMd88sSecOL7DlghzdBl56/8ymb02n+xEJ/6zUgONuw/1uoTh1TAaNPKfEWdNLoLKXQm+Gd2zBrUVOX0w==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13450,12 +13157,12 @@ } }, "node_modules/@opentelemetry/instrumentation-fastify": { - "version": "0.32.6", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.32.6.tgz", - "integrity": "sha512-UkBu8rAqeVC034jsRMiEAmYhFQ03pvmE/MnoPKE9gAbgVtPILdekHYqAKM0MdqnEjW7pO45t4wWsbtIcN0eiBw==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.33.0.tgz", + "integrity": "sha512-sl3q9Mt+yM6GlZJKhfLUIRrVEYqfmI0hqYLha5OFG5rLrgnZCCZVy8ra4+Pa40ecH1409cvwwBPf7k9AHEQBTw==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13466,12 +13173,12 @@ } }, "node_modules/@opentelemetry/instrumentation-fs": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.8.4.tgz", - "integrity": "sha512-g99963nK8TuisUM3KH+ee5hOCHdCHSKiAdmy0RMdiKT7ITh3rXUct7fghQibViQA7FVPkdwM9+uRKkigJSFS9w==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.9.0.tgz", + "integrity": "sha512-Xp31lb2Sj50ppsJ393650HxSi5IJIgddXxrUeVljmsabdmECPUj0YAt/Wwb1oIHFn04iL9Tq4VkF/otlLaI9ww==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13482,11 +13189,11 @@ } }, "node_modules/@opentelemetry/instrumentation-generic-pool": { - "version": "0.32.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.32.5.tgz", - "integrity": "sha512-MNFloXa3SDg/rHh397JnWADr7iYQ6HHRwT7ZId5Vt0L1Xx+Qp3XSIILsXk5qTXVUIytEIVgn8iMT+kZILHzSqg==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.33.0.tgz", + "integrity": "sha512-QMSSOfIqMJhXqFryLVbAMsgRktNHdhMEpsOgEiHurLfvAJhyEOBcTTUuo6Laqt50IIzIm3YuHL9ZtEl9fve2ag==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13497,11 +13204,11 @@ } }, "node_modules/@opentelemetry/instrumentation-graphql": { - "version": "0.36.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.36.1.tgz", - "integrity": "sha512-EOvaS+d2909TOJJjNTsb0vHaLg8WdTLoQS8bRKdy3lMgdd7I4OL9/LSC7dp4M8CvJKz9B464Ix9PnARvhMkNOw==", + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.37.0.tgz", + "integrity": "sha512-WL5Qn1aRudJDxVN0Ao73/yzXBGBJAH1Fd2tteuGXku/Qw9hYQ936CgoO66GWmSiq2lyjsojAk1t5f+HF9j3NXw==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0" + "@opentelemetry/instrumentation": "^0.48.0" }, "engines": { "node": ">=14" @@ -13511,12 +13218,12 @@ } }, "node_modules/@opentelemetry/instrumentation-grpc": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-grpc/-/instrumentation-grpc-0.46.0.tgz", - "integrity": "sha512-KemIpB4jmywQv/+MbVoUIMVp3vr+rzra37TYbN7kTsbrn213YlzdXVamf6nq/yChI6q+9JlUnCdSZf86D6NO6g==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-grpc/-/instrumentation-grpc-0.48.0.tgz", + "integrity": "sha512-MmJHkbqaulqfECjotRtco9AXOq+D1HLq53wI7UFeE8bl8kISP9iMkt+A7PrtPFpRGCglwFvSa0djId6EWsP0DQ==", "dependencies": { - "@opentelemetry/instrumentation": "0.46.0", - "@opentelemetry/semantic-conventions": "1.19.0" + "@opentelemetry/instrumentation": "0.48.0", + "@opentelemetry/semantic-conventions": "1.21.0" }, "engines": { "node": ">=14" @@ -13525,21 +13232,13 @@ "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@opentelemetry/instrumentation-grpc/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.19.0.tgz", - "integrity": "sha512-14jRpC8f5c0gPSwoZ7SbEJni1PqI+AhAE8m1bMz6v+RPM4OlP1PT2UHBJj5Qh/ALLPjhVU/aZUK3YyjTUqqQVg==", - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/instrumentation-hapi": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.33.3.tgz", - "integrity": "sha512-l14u1TFPXMUjfHqnrHtzuMyLq6V8BikwhYJO/hIgkbaPCxS38TloCtDLJvcs8S8AZlcQfkUqE/NFgXYETZRo+Q==", + "version": "0.34.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.34.0.tgz", + "integrity": "sha512-qUENVxwCYbRbJ8HBY54ZL1Z9q1guCEurW6tCFFJJKQFu/MKEw7GSFImy5DR2Mp8b5ggZO36lYFcx0QUxfy4GJg==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0", "@types/hapi__hapi": "20.0.13" }, @@ -13551,13 +13250,13 @@ } }, "node_modules/@opentelemetry/instrumentation-http": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.46.0.tgz", - "integrity": "sha512-t5cxgqfV9AcxVP00/OL1ggkOSZM57VXDpvlWaOidYyyfLKcUJ9e2fGbNwoVsGFboRDeH0iFo7gLA3EEvX13wCA==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.48.0.tgz", + "integrity": "sha512-uXqOsLhW9WC3ZlGm6+PSX0xjSDTCfy4CMjfYj6TPWusOO8dtdx040trOriF24y+sZmS3M+5UQc6/3/ZxBJh4Mw==", "dependencies": { - "@opentelemetry/core": "1.19.0", - "@opentelemetry/instrumentation": "0.46.0", - "@opentelemetry/semantic-conventions": "1.19.0", + "@opentelemetry/core": "1.21.0", + "@opentelemetry/instrumentation": "0.48.0", + "@opentelemetry/semantic-conventions": "1.21.0", "semver": "^7.5.2" }, "engines": { @@ -13567,28 +13266,6 @@ "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/core": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.19.0.tgz", - "integrity": "sha512-w42AukJh3TP8R0IZZOVJVM/kMWu8g+lm4LzT70WtuKqhwq7KVhcDzZZuZinWZa6TtQCl7Smt2wolEYzpHabOgw==", - "dependencies": { - "@opentelemetry/semantic-conventions": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.8.0" - } - }, - "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.19.0.tgz", - "integrity": "sha512-14jRpC8f5c0gPSwoZ7SbEJni1PqI+AhAE8m1bMz6v+RPM4OlP1PT2UHBJj5Qh/ALLPjhVU/aZUK3YyjTUqqQVg==", - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/instrumentation-http/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -13620,11 +13297,11 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/@opentelemetry/instrumentation-ioredis": { - "version": "0.36.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.36.1.tgz", - "integrity": "sha512-xxab7n4TD5igCFR5N0j5PJFYVeOXm54ZtCARVS32xavwGyGf+Sb6VtuVCLdl0re4JENCg18FO97Dyb1ql2EBUA==", + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.37.0.tgz", + "integrity": "sha512-xBPfu03IIG8x1pmt1Dx+XrBO4ZB4UjEcrouGbp6eV3dLQ7eJPYZgfNXmsqkpsxgNQuVCi2a3WEAwZ5Wl2hk7Vw==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/redis-common": "^0.36.1", "@opentelemetry/semantic-conventions": "^1.0.0", "@types/ioredis4": "npm:@types/ioredis@^4.28.10" @@ -13637,11 +13314,11 @@ } }, "node_modules/@opentelemetry/instrumentation-knex": { - "version": "0.32.4", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.32.4.tgz", - "integrity": "sha512-vqxK1wqhktQnBA7nGr6nqfhV5irSXK9v0R29hqlyTr/cB/86Hn3Z98zH58QvHo131FcE+d70QdiXu3P9S5vq4g==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.33.0.tgz", + "integrity": "sha512-7L3Q8Yy5vY4W4zpRrjKEc0OpVPYyERtDz5dAumKjkJsEVPANr7E8Cc+No6VjZGeN+HgFFwEo+jcQCTWJzdxvRw==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13652,14 +13329,14 @@ } }, "node_modules/@opentelemetry/instrumentation-koa": { - "version": "0.36.4", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.36.4.tgz", - "integrity": "sha512-Qt6IF0mo3FZZ+x4NQhv/pViDtPSw/h/QYDvyIqMCuqUibqta619WLUCwAcanKnZWvzMuYh6lT0TnZ8ktjXo6jQ==", + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.37.0.tgz", + "integrity": "sha512-EfuGv1RJCSZh77dDc3PtvZXGwcsTufn9tU6T9VOTFcxovpyJ6w0og73eD0D02syR8R+kzv6rg1TeS8+lj7pyrQ==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0", - "@types/koa": "2.13.9", + "@types/koa": "2.14.0", "@types/koa__router": "12.0.3" }, "engines": { @@ -13670,11 +13347,11 @@ } }, "node_modules/@opentelemetry/instrumentation-lru-memoizer": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.33.5.tgz", - "integrity": "sha512-vbm9QPEYD3FZNGSa+7xQ7uOkgbJtskIwp1KnsCpmdBBiulhBaqqgeNLFo7gd8UxMrI2Vu3LTlZil2D0dVt8g/A==", + "version": "0.34.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.34.0.tgz", + "integrity": "sha512-m1kXrc11XNj7cC6sfcsYqd+kuCcN2wI9LXpB2l2BZCogqxHCgjuVoiXvT6K9LfO4tLefcvhoK0N8XuVJ8xyyOw==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0" + "@opentelemetry/instrumentation": "^0.48.0" }, "engines": { "node": ">=14" @@ -13684,11 +13361,11 @@ } }, "node_modules/@opentelemetry/instrumentation-memcached": { - "version": "0.32.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-memcached/-/instrumentation-memcached-0.32.5.tgz", - "integrity": "sha512-1MS9LfgZruAsWOHVDTI+/Wy9mFFivUlpGUwYC4D+ho1I4NUyE33XHwL1BKcjMupkuRnE0JmbhdBfTG9Ai1vFkw==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-memcached/-/instrumentation-memcached-0.33.0.tgz", + "integrity": "sha512-TdGT5ytt8o7FTIsQvx010ykYbqu+IfGoOKA+IXLHdX1+l7vFWyv3ZOzQbRDmA4rxujJAAPf/n4/D7QECyedE/g==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0", "@types/memcached": "^2.2.6" }, @@ -13700,11 +13377,11 @@ } }, "node_modules/@opentelemetry/instrumentation-mongodb": { - "version": "0.38.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.38.1.tgz", - "integrity": "sha512-X6YjE8dOCf8lG8FGmoAvczZq7LtgYaRzZcLGthZSUJQ2rfp1JJRlJixc+COvhrn1HJj5ab+AsSdUQgTpfQgEHQ==", + "version": "0.39.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.39.0.tgz", + "integrity": "sha512-m9dMj39pcCshzlfCEn2lGrlNo7eV5fb9pGBnPyl/Am9Crh7Or8vOqvByCNd26Dgf5J978zTdLGF+6tM8j1WOew==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/sdk-metrics": "^1.9.1", "@opentelemetry/semantic-conventions": "^1.0.0" }, @@ -13716,12 +13393,12 @@ } }, "node_modules/@opentelemetry/instrumentation-mongoose": { - "version": "0.34.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.34.0.tgz", - "integrity": "sha512-/wGNK7qR/QXpcidXntKsnfACHETp8G/f2o/k8FPvJ2lukvdM9r7cHLys8QwkJkTN8Kt3qG1VIwarGKYtE/zOSw==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.35.0.tgz", + "integrity": "sha512-gReBMWD2Oa/wBGRWyg6B2dbPHhgkpOqDio31gE3DbC4JaqCsMByyeix75rZSzZ71RQmVh3d4jRLsqUtNVBzcyg==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13732,11 +13409,11 @@ } }, "node_modules/@opentelemetry/instrumentation-mysql": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.34.5.tgz", - "integrity": "sha512-cE8z1uJTeLcMj+R31t1pLkLqt3ryGMl1HApxsqqf8YCSHetrkVwGZOcyQ3phfgGSaNlC4/pdf3CQqfjhXbLWlA==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.35.0.tgz", + "integrity": "sha512-QKRHd3aFA2vKOPzIZ9Q3UIxYeNPweB62HGlX2l3shOKrUhrtTg2/BzaKpHQBy2f2nO2mxTF/mOFeVEDeANnhig==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0", "@types/mysql": "2.15.22" }, @@ -13748,11 +13425,11 @@ } }, "node_modules/@opentelemetry/instrumentation-mysql2": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.34.5.tgz", - "integrity": "sha512-Ai3+cJ83b11kLypIVEPLHuYCiIQjf828hHJPpllr78EhmHiq4S1yTW34aP3BzCBY+5Adr5PS5Nnv7NLBI/YfaQ==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.35.0.tgz", + "integrity": "sha512-DI9NXYJBbQ72rjz1KCKerQFQE+Z4xRDoyYek6JpITv5BlhPboA8zKkltxyQLL06Y2RTFYslw1gvg+x9CWlRzJw==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0", "@opentelemetry/sql-common": "^0.40.0" }, @@ -13764,11 +13441,11 @@ } }, "node_modules/@opentelemetry/instrumentation-nestjs-core": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.33.4.tgz", - "integrity": "sha512-AynD6TesE0bZg9UzS4ZLG//6TmU8GkRrjubhxs7jYZ3JRAlJAq1In0zSwM082JOIs7wr286nhs1FmqK91cG1cg==", + "version": "0.34.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.34.0.tgz", + "integrity": "sha512-HvbcCVAMZEIFrJ0Si9AfjxOr14KcH5h/lq5zLQ8AjZJpW0WaeO/ox5UgFi3J73Br91WbZHRgbXxMeodNycJJuA==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13779,11 +13456,11 @@ } }, "node_modules/@opentelemetry/instrumentation-net": { - "version": "0.32.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-net/-/instrumentation-net-0.32.5.tgz", - "integrity": "sha512-omBLTXyJeCtBy1MzVi+IzUcpXiup90k0z3AGhNKbzro4RhpsdMpN9O+3zZCXCIHMuyifhy7z8w99wmvvFO/FCQ==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-net/-/instrumentation-net-0.33.0.tgz", + "integrity": "sha512-x6awrqF0YfEhGGNE2JtEWvB+zEls7mFvLDii54DnWxpQU69+AqKCW/ZwC91EDefOMGSYBckL0uEn6XNOgkTTbw==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13794,12 +13471,11 @@ } }, "node_modules/@opentelemetry/instrumentation-pg": { - "version": "0.37.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.37.2.tgz", - "integrity": "sha512-MAiKqdtGItYjvD6rOCyGS27CdMaDnh2JuImIHXhrPjq/sb2JlBNm6m1e4BH4uik1VfcKt/I3pI3UkydSWIscCg==", + "version": "0.38.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.38.0.tgz", + "integrity": "sha512-Q7V/OJ1OZwaWYNOP/E9S6sfS03Z+PNU1SAjdAoXTj5j4u4iJSMSieLRWXFaHwsbefIOMkYvA00EBKF9IgbgbLA==", "dependencies": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0", "@opentelemetry/sql-common": "^0.40.0", "@types/pg": "8.6.1", @@ -13873,11 +13549,11 @@ } }, "node_modules/@opentelemetry/instrumentation-pino": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pino/-/instrumentation-pino-0.34.5.tgz", - "integrity": "sha512-auYDSeFhkPFXayT/060mQRL7O88Bt5NKcV0qOfquNa9J5/qs5dlJYdTOraxPrjiGPM3tHaAJ+AvAhR0CPYgZew==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pino/-/instrumentation-pino-0.35.0.tgz", + "integrity": "sha512-gMfJ5Qy793mbaAGnQE3yp1Cb0y4np74rBPu20Oy/v8TTgPQOEV5PyNI0GNGggmZQIJSkdtYa8Ndb3huH3iZE5g==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0" + "@opentelemetry/instrumentation": "^0.48.0" }, "engines": { "node": ">=14" @@ -13887,11 +13563,11 @@ } }, "node_modules/@opentelemetry/instrumentation-redis": { - "version": "0.35.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.35.5.tgz", - "integrity": "sha512-UPYUncDlLqDPtyU11UhyZOUxAyPQS6yQGT0b96KjpqMhmuRb3b0WxzZh3SoIaAyprL5f9fxyeV2HfSulR0aWFQ==", + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.36.0.tgz", + "integrity": "sha512-rKFylIacEBwLxKFrPvxpVi8hHY9qXfQSybYnYNyF/VxUWMGYDPMpbCnTQkiVR5u+tIhwSvhSDG2YQEq6syHUIQ==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/redis-common": "^0.36.1", "@opentelemetry/semantic-conventions": "^1.0.0" }, @@ -13903,11 +13579,11 @@ } }, "node_modules/@opentelemetry/instrumentation-redis-4": { - "version": "0.35.6", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.35.6.tgz", - "integrity": "sha512-OVSUJZAuy6OX18X2TKPdPlpwM5t4FooJU9QXiUxezhdMvfIAu00Agchw+gRbszkM7nvQ9dkXFOZO3nTmJNcLcA==", + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.36.0.tgz", + "integrity": "sha512-XO0EV2TxUsaRdcp79blyLGG5JWWl7NWVd/XNbU8vY7CuYUfRhWiTXYoM4PI+lwkAnUPvPtyiOzYs9px23GnibA==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/redis-common": "^0.36.1", "@opentelemetry/semantic-conventions": "^1.0.0" }, @@ -13919,12 +13595,12 @@ } }, "node_modules/@opentelemetry/instrumentation-restify": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-restify/-/instrumentation-restify-0.34.3.tgz", - "integrity": "sha512-nUqf4RTcQyc/YTWMT0e/ZqvuQqhRH04MOoSwaH6ocmyrEhKdPDq9AbvSMerQ/AxNC9yju/PytgzFFWH45hh3KA==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-restify/-/instrumentation-restify-0.35.0.tgz", + "integrity": "sha512-0ghtxsGJxHEwJfIzxDN3FCbNiTXqwv2jV6ip716jyjWN3f6MuRHm7NPWI/KNvu+IjqDj16KRGZG7nUAEB1ojog==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13935,11 +13611,11 @@ } }, "node_modules/@opentelemetry/instrumentation-router": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-router/-/instrumentation-router-0.33.4.tgz", - "integrity": "sha512-4140BgILsL0H8tA0BBN2tjzajgjxlDqd4xPatk2i5q9ofy8wlB0BlDJ4m36U7G1z0v+a+LshQSNqPP2VHZ9ORw==", + "version": "0.34.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-router/-/instrumentation-router-0.34.0.tgz", + "integrity": "sha512-7LsonkdnQi35eF7CWl8394QDgyd811gCawJ6QuS8GbWNIvZ3S2f9j+Zy0fWSmFgO28ruW1pUG51ql8xdXWa8TA==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13950,11 +13626,11 @@ } }, "node_modules/@opentelemetry/instrumentation-socket.io": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-socket.io/-/instrumentation-socket.io-0.35.0.tgz", - "integrity": "sha512-ED1/Wco05H9fepKqX7n2kONq9EXxkBZTKS29nUH9vVL7wSIT8sBIDqpHz94CnQ8xuicaQQ7c5h9TVuhjtzV43Q==", + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-socket.io/-/instrumentation-socket.io-0.36.0.tgz", + "integrity": "sha512-c9Zc6WKxTZtMaOj01kmJGLKabEj805YgTav4l9vgojHrf6MH1fTlw+SGvOKhfPfzmu2QhNORAfqPAB1poDwqEQ==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13965,11 +13641,11 @@ } }, "node_modules/@opentelemetry/instrumentation-tedious": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.6.5.tgz", - "integrity": "sha512-jjdrDegLUodz9np0yKCAa7sLxlAGTsxM7wU7l1mQ2NM6PHAGv4X3eSFClUk3fOipLx4+r5jTLnlsgu7g9CW+Qw==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.7.0.tgz", + "integrity": "sha512-o/5my8ZOuxACPSzMaXdPnQiMpmOPIJoTj+DRcs4vEJxk+KwlVNucoafSMpWQEyTNNuq2JI87Ru6Di2mp5T20EQ==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0", "@types/tedious": "^4.0.10" }, @@ -13981,11 +13657,11 @@ } }, "node_modules/@opentelemetry/instrumentation-winston": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-winston/-/instrumentation-winston-0.33.1.tgz", - "integrity": "sha512-4tSORoAY9f+yzqNVGcGr/3GydPMfgSiKK1OESc+qBwVTz0bmz4cOrhCruCngGzoqDCmPYpwqwR/8j4wRKgcUpw==", + "version": "0.34.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-winston/-/instrumentation-winston-0.34.0.tgz", + "integrity": "sha512-Ejssv6Uih7ipoNGYQLXd+cKZdEfTfTJ/vzpUSeYiJZ36mVYER1f8fs9Kb7jTKjHD55g2s6cUJj9lifbDFI4EEw==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0" + "@opentelemetry/instrumentation": "^0.48.0" }, "engines": { "node": ">=14" @@ -14090,21 +13766,10 @@ "@opentelemetry/api": ">=1.3.0 <1.8.0" } }, - "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/api-logs": { - "version": "0.48.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.48.0.tgz", - "integrity": "sha512-1/aMiU4Eqo3Zzpfwu51uXssp5pzvHFObk8S9pKAiXb1ne8pvg1qxBQitYL1XUiAMEXFzgjaidYG2V6624DRhhw==", - "dependencies": { - "@opentelemetry/api": "^1.0.0" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/propagation-utils": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagation-utils/-/propagation-utils-0.30.5.tgz", - "integrity": "sha512-9ENyx6ptmjyYzL7le3FXk/lJc3cFFTrh9Y/ubO9velQZ64BdjpF9kOMJN3Z8KLJFVt66HYoWy9xlWoSIfS/ICg==", + "version": "0.30.6", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagation-utils/-/propagation-utils-0.30.6.tgz", + "integrity": "sha512-qvnYee5I/xrBY5XClUlOASIOdoTbnFGNI6+ViKqdoErF2xKmhysXcmxlJNzQFNDK0muTfjvJMZcFyEielARk7g==", "engines": { "node": ">=14" }, @@ -14163,9 +13828,9 @@ } }, "node_modules/@opentelemetry/resource-detector-alibaba-cloud": { - "version": "0.28.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-alibaba-cloud/-/resource-detector-alibaba-cloud-0.28.5.tgz", - "integrity": "sha512-cobe2I0c5a66d98nCBprEB5erN5S0PTRrs49qSOnuTT2dC90nwSo2WDcSBfeDSKZH/7sB686P7FyKefWjQzhoA==", + "version": "0.28.6", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-alibaba-cloud/-/resource-detector-alibaba-cloud-0.28.6.tgz", + "integrity": "sha512-VuJXc+oDQ/SYRHBCQbEshl0WJtEMvgfSkTDBq+WSxj6y9sKe0HCt55Sxeb5nLmBdtCYWMQFniHe2K4GLSjDZVw==", "dependencies": { "@opentelemetry/resources": "^1.0.0", "@opentelemetry/semantic-conventions": "^1.0.0" @@ -14178,9 +13843,9 @@ } }, "node_modules/@opentelemetry/resource-detector-aws": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-aws/-/resource-detector-aws-1.3.5.tgz", - "integrity": "sha512-0GZJi8m6czksDJwpndSYJpnaPaFe83nEQVg4UnTTwB0cxKtrjpaarWDI46X0BuCX4bGp0M8pvI7f0rBt+LsIhA==", + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-aws/-/resource-detector-aws-1.3.6.tgz", + "integrity": "sha512-hFJ19yFwChqGCv1uMkXtjZU9BG8GcChe8cRCAkGWg1RZADse5S2ausf3D8pYw1cR3ktJtuAmRrGZniT6TDUPDw==", "dependencies": { "@opentelemetry/core": "^1.0.0", "@opentelemetry/resources": "^1.0.0", @@ -14194,9 +13859,9 @@ } }, "node_modules/@opentelemetry/resource-detector-container": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-container/-/resource-detector-container-0.3.5.tgz", - "integrity": "sha512-yLGLueH63DE9/WmDrizleqtT0uCOsslNqW+AcwzMHW7t8c2MtoJ7msgIsmi7tz5kxJgG8o54CnvXxobcwBhLCQ==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-container/-/resource-detector-container-0.3.6.tgz", + "integrity": "sha512-psxtzQakVuZKFl/ukn+nPW4Ixn+KPHGsWJMYKndmXrsgdFri78X+MHR0wLOO41Zn79sc35DiSk+GdJ24cCylbg==", "dependencies": { "@opentelemetry/resources": "^1.0.0", "@opentelemetry/semantic-conventions": "^1.0.0" @@ -14209,9 +13874,9 @@ } }, "node_modules/@opentelemetry/resource-detector-gcp": { - "version": "0.29.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-gcp/-/resource-detector-gcp-0.29.5.tgz", - "integrity": "sha512-cYckfRDDNX/nhiMF7fFooOCb/EObydNXWi0HwuPELHYa7heBCkWgTtNxs/PzuP46+FqDjuBcJL6beS2Ef7MyWg==", + "version": "0.29.6", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-gcp/-/resource-detector-gcp-0.29.6.tgz", + "integrity": "sha512-cx03fXPknmiVW0hpWAJr0Nr8xwkwRB8VNWPvNrmP7UzJ8eEztY9lHnVke4ZVFaVGvm/4EFxk2y5hPNggbIezoA==", "dependencies": { "@opentelemetry/core": "^1.0.0", "@opentelemetry/resources": "^1.0.0", @@ -14298,65 +13963,6 @@ "@opentelemetry/api": ">=1.3.0 <1.8.0" } }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/api-logs": { - "version": "0.48.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.48.0.tgz", - "integrity": "sha512-1/aMiU4Eqo3Zzpfwu51uXssp5pzvHFObk8S9pKAiXb1ne8pvg1qxBQitYL1XUiAMEXFzgjaidYG2V6624DRhhw==", - "dependencies": { - "@opentelemetry/api": "^1.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/instrumentation": { - "version": "0.48.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.48.0.tgz", - "integrity": "sha512-sjtZQB5PStIdCw5ovVTDGwnmQC+GGYArJNgIcydrDSqUTdYBnMrN9P4pwQZgS3vTGIp+TU1L8vMXGe51NVmIKQ==", - "dependencies": { - "@types/shimmer": "^1.0.2", - "import-in-the-middle": "1.7.1", - "require-in-the-middle": "^7.1.1", - "semver": "^7.5.2", - "shimmer": "^1.2.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/sdk-node/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@opentelemetry/sdk-node/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@opentelemetry/sdk-node/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/@opentelemetry/sdk-trace-base": { "version": "1.21.0", "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.21.0.tgz", @@ -15778,19 +15384,19 @@ } }, "node_modules/@react-native-community/cli": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-12.3.0.tgz", - "integrity": "sha512-XeQohi2E+S2+MMSz97QcEZ/bWpi8sfKiQg35XuYeJkc32Til2g0b97jRpn0/+fV0BInHoG1CQYWwHA7opMsrHg==", - "dependencies": { - "@react-native-community/cli-clean": "12.3.0", - "@react-native-community/cli-config": "12.3.0", - "@react-native-community/cli-debugger-ui": "12.3.0", - "@react-native-community/cli-doctor": "12.3.0", - "@react-native-community/cli-hermes": "12.3.0", - "@react-native-community/cli-plugin-metro": "12.3.0", - "@react-native-community/cli-server-api": "12.3.0", - "@react-native-community/cli-tools": "12.3.0", - "@react-native-community/cli-types": "12.3.0", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-12.3.2.tgz", + "integrity": "sha512-WgoUWwLDcf/G1Su2COUUVs3RzAwnV/vUTdISSpAUGgSc57mPabaAoUctKTnfYEhCnE3j02k3VtaVPwCAFRO3TQ==", + "dependencies": { + "@react-native-community/cli-clean": "12.3.2", + "@react-native-community/cli-config": "12.3.2", + "@react-native-community/cli-debugger-ui": "12.3.2", + "@react-native-community/cli-doctor": "12.3.2", + "@react-native-community/cli-hermes": "12.3.2", + "@react-native-community/cli-plugin-metro": "12.3.2", + "@react-native-community/cli-server-api": "12.3.2", + "@react-native-community/cli-tools": "12.3.2", + "@react-native-community/cli-types": "12.3.2", "chalk": "^4.1.2", "commander": "^9.4.1", "deepmerge": "^4.3.0", @@ -15809,11 +15415,11 @@ } }, "node_modules/@react-native-community/cli-clean": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-12.3.0.tgz", - "integrity": "sha512-iAgLCOWYRGh9ukr+eVQnhkV/OqN3V2EGd/in33Ggn/Mj4uO6+oUncXFwB+yjlyaUNz6FfjudhIz09yYGSF+9sg==", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-12.3.2.tgz", + "integrity": "sha512-90k2hCX0ddSFPT7EN7h5SZj0XZPXP0+y/++v262hssoey3nhurwF57NGWN0XAR0o9BSW7+mBfeInfabzDraO6A==", "dependencies": { - "@react-native-community/cli-tools": "12.3.0", + "@react-native-community/cli-tools": "12.3.2", "chalk": "^4.1.2", "execa": "^5.0.0" } @@ -15867,11 +15473,11 @@ } }, "node_modules/@react-native-community/cli-config": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-12.3.0.tgz", - "integrity": "sha512-BrTn5ndFD9uOxO8kxBQ32EpbtOvAsQExGPI7SokdI4Zlve70FziLtTq91LTlTUgMq1InVZn/jJb3VIDk6BTInQ==", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-12.3.2.tgz", + "integrity": "sha512-UUCzDjQgvAVL/57rL7eOuFUhd+d+6qfM7V8uOegQFeFEmSmvUUDLYoXpBa5vAK9JgQtSqMBJ1Shmwao+/oElxQ==", "dependencies": { - "@react-native-community/cli-tools": "12.3.0", + "@react-native-community/cli-tools": "12.3.2", "chalk": "^4.1.2", "cosmiconfig": "^5.1.0", "deepmerge": "^4.3.0", @@ -16013,22 +15619,22 @@ } }, "node_modules/@react-native-community/cli-debugger-ui": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-12.3.0.tgz", - "integrity": "sha512-w3b0iwjQlk47GhZWHaeTG8kKH09NCMUJO729xSdMBXE8rlbm4kHpKbxQY9qKb6NlfWSJN4noGY+FkNZS2rRwnQ==", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-12.3.2.tgz", + "integrity": "sha512-nSWQUL+51J682DlfcC1bjkUbQbGvHCC25jpqTwHIjmmVjYCX1uHuhPSqQKgPNdvtfOkrkACxczd7kVMmetxY2Q==", "dependencies": { "serve-static": "^1.13.1" } }, "node_modules/@react-native-community/cli-doctor": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-12.3.0.tgz", - "integrity": "sha512-BPCwNNesoQMkKsxB08Ayy6URgGQ8Kndv6mMhIvJSNdST3J1+x3ehBHXzG9B9Vfi+DrTKRb8lmEl/b/7VkDlPkA==", - "dependencies": { - "@react-native-community/cli-config": "12.3.0", - "@react-native-community/cli-platform-android": "12.3.0", - "@react-native-community/cli-platform-ios": "12.3.0", - "@react-native-community/cli-tools": "12.3.0", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-12.3.2.tgz", + "integrity": "sha512-GrAabdY4qtBX49knHFvEAdLtCjkmndjTeqhYO6BhsbAeKOtspcLT/0WRgdLIaKODRa61ADNB3K5Zm4dU0QrZOg==", + "dependencies": { + "@react-native-community/cli-config": "12.3.2", + "@react-native-community/cli-platform-android": "12.3.2", + "@react-native-community/cli-platform-ios": "12.3.2", + "@react-native-community/cli-tools": "12.3.2", "chalk": "^4.1.2", "command-exists": "^1.2.8", "deepmerge": "^4.3.0", @@ -16155,12 +15761,12 @@ } }, "node_modules/@react-native-community/cli-hermes": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-12.3.0.tgz", - "integrity": "sha512-G6FxpeZBO4AimKZwtWR3dpXRqTvsmEqlIkkxgwthdzn3LbVjDVIXKpVYU9PkR5cnT+KuAUxO0WwthrJ6Nmrrlg==", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-12.3.2.tgz", + "integrity": "sha512-SL6F9O8ghp4ESBFH2YAPLtIN39jdnvGBKnK4FGKpDCjtB3DnUmDsGFlH46S+GGt5M6VzfG2eeKEOKf3pZ6jUzA==", "dependencies": { - "@react-native-community/cli-platform-android": "12.3.0", - "@react-native-community/cli-tools": "12.3.0", + "@react-native-community/cli-platform-android": "12.3.2", + "@react-native-community/cli-tools": "12.3.2", "chalk": "^4.1.2", "hermes-profile-transformer": "^0.0.6", "ip": "^1.1.5" @@ -16220,11 +15826,11 @@ } }, "node_modules/@react-native-community/cli-platform-android": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-12.3.0.tgz", - "integrity": "sha512-VU1NZw63+GLU2TnyQ919bEMThpHQ/oMFju9MCfrd3pyPJz4Sn+vc3NfnTDUVA5Z5yfLijFOkHIHr4vo/C9bjnw==", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-12.3.2.tgz", + "integrity": "sha512-MZ5nO8yi/N+Fj2i9BJcJ9C/ez+9/Ir7lQt49DWRo9YDmzye66mYLr/P2l/qxsixllbbDi7BXrlLpxaEhMrDopg==", "dependencies": { - "@react-native-community/cli-tools": "12.3.0", + "@react-native-community/cli-tools": "12.3.2", "chalk": "^4.1.2", "execa": "^5.0.0", "fast-xml-parser": "^4.2.4", @@ -16320,11 +15926,11 @@ } }, "node_modules/@react-native-community/cli-platform-ios": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-12.3.0.tgz", - "integrity": "sha512-H95Sgt3wT7L8V75V0syFJDtv4YgqK5zbu69ko4yrXGv8dv2EBi6qZP0VMmkqXDamoPm9/U7tDTdbcf26ctnLfg==", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-12.3.2.tgz", + "integrity": "sha512-OcWEAbkev1IL6SUiQnM6DQdsvfsKZhRZtoBNSj9MfdmwotVZSOEZJ+IjZ1FR9ChvMWayO9ns/o8LgoQxr1ZXeg==", "dependencies": { - "@react-native-community/cli-tools": "12.3.0", + "@react-native-community/cli-tools": "12.3.2", "chalk": "^4.1.2", "execa": "^5.0.0", "fast-xml-parser": "^4.0.12", @@ -16420,17 +16026,17 @@ } }, "node_modules/@react-native-community/cli-plugin-metro": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-12.3.0.tgz", - "integrity": "sha512-tYNHIYnNmxrBcsqbE2dAnLMzlKI3Cp1p1xUgTrNaOMsGPDN1epzNfa34n6Nps3iwKElSL7Js91CzYNqgTalucA==" + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-12.3.2.tgz", + "integrity": "sha512-FpFBwu+d2E7KRhYPTkKvQsWb2/JKsJv+t1tcqgQkn+oByhp+qGyXBobFB8/R3yYvRRDCSDhS+atWTJzk9TjM8g==" }, "node_modules/@react-native-community/cli-server-api": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-12.3.0.tgz", - "integrity": "sha512-Rode8NrdyByC+lBKHHn+/W8Zu0c+DajJvLmOWbe2WY/ECvnwcd9MHHbu92hlT2EQaJ9LbLhGrSbQE3cQy9EOCw==", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-12.3.2.tgz", + "integrity": "sha512-iwa7EO9XFA/OjI5pPLLpI/6mFVqv8L73kNck3CNOJIUCCveGXBKK0VMyOkXaf/BYnihgQrXh+x5cxbDbggr7+Q==", "dependencies": { - "@react-native-community/cli-debugger-ui": "12.3.0", - "@react-native-community/cli-tools": "12.3.0", + "@react-native-community/cli-debugger-ui": "12.3.2", + "@react-native-community/cli-tools": "12.3.2", "compression": "^1.7.1", "connect": "^3.6.5", "errorhandler": "^1.5.1", @@ -16559,9 +16165,9 @@ } }, "node_modules/@react-native-community/cli-tools": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-12.3.0.tgz", - "integrity": "sha512-2GafnCr8D88VdClwnm9KZfkEb+lzVoFdr/7ybqhdeYM0Vnt/tr2N+fM1EQzwI1DpzXiBzTYemw8GjRq+Utcz2Q==", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-12.3.2.tgz", + "integrity": "sha512-nDH7vuEicHI2TI0jac/DjT3fr977iWXRdgVAqPZFFczlbs7A8GQvEdGnZ1G8dqRUmg+kptw0e4hwczAOG89JzQ==", "dependencies": { "appdirsjs": "^1.2.4", "chalk": "^4.1.2", @@ -16689,9 +16295,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/@react-native-community/cli-types": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-12.3.0.tgz", - "integrity": "sha512-MgOkmrXH4zsGxhte4YqKL7d+N8ZNEd3w1wo56MZlhu5WabwCJh87wYpU5T8vyfujFLYOFuFK5jjlcbs8F4/WDw==", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-12.3.2.tgz", + "integrity": "sha512-9D0UEFqLW8JmS16mjHJxUJWX8E+zJddrHILSH8AJHZ0NNHv4u2DXKdb0wFLMobFxGNxPT+VSOjc60fGvXzWHog==", "dependencies": { "joi": "^17.2.1" } @@ -17132,14 +16738,14 @@ } }, "node_modules/@react-native/community-cli-plugin": { - "version": "0.73.12", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.73.12.tgz", - "integrity": "sha512-xWU06OkC1cX++Duh/cD/Wv+oZ0oSY3yqbtxAqQA2H3Q+MQltNNJM6MqIHt1VOZSabRf/LVlR1JL6U9TXJirkaw==", + "version": "0.73.14", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.73.14.tgz", + "integrity": "sha512-KzIwsTvAJrXPtwhGOSm+OcJH1B8TpY8cS4xxzu/e2qv3a2n4VLePHTPAfco1tmvekV8OHWvvD9JSIX7i2fB1gg==", "dependencies": { - "@react-native-community/cli-server-api": "12.3.0", - "@react-native-community/cli-tools": "12.3.0", + "@react-native-community/cli-server-api": "12.3.2", + "@react-native-community/cli-tools": "12.3.2", "@react-native/dev-middleware": "0.73.7", - "@react-native/metro-babel-transformer": "0.73.13", + "@react-native/metro-babel-transformer": "0.73.14", "chalk": "^4.0.0", "execa": "^5.1.1", "metro": "^0.80.3", @@ -17273,12 +16879,12 @@ } }, "node_modules/@react-native/metro-babel-transformer": { - "version": "0.73.13", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.13.tgz", - "integrity": "sha512-k9AQifogQfgUXPlqQSoMtX2KUhniw4XvJl+nZ4hphCH7qiMDAwuP8OmkJbz5E/N+Ro9OFuLE7ax4GlwxaTsAWg==", + "version": "0.73.14", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.14.tgz", + "integrity": "sha512-5wLeYw/lormpSqYfI9H/geZ/EtPmi+x5qLkEit15Q/70hkzYo/M+aWztUtbOITfgTEOP8d6ybROzoGsqgyZLcw==", "dependencies": { "@babel/core": "^7.20.0", - "@react-native/babel-preset": "0.73.19", + "@react-native/babel-preset": "0.73.20", "hermes-parser": "0.15.0", "nullthrows": "^1.1.1" }, @@ -17289,72 +16895,6 @@ "@babel/core": "*" } }, - "node_modules/@react-native/metro-babel-transformer/node_modules/@react-native/babel-plugin-codegen": { - "version": "0.73.2", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.73.2.tgz", - "integrity": "sha512-PadyFZWVaWXIBP7Q5dgEL7eAd7tnsgsLjoHJB1hIRZZuVUg1Zqe3nULwC7RFAqOtr5Qx7KXChkFFcKQ3WnZzGw==", - "dependencies": { - "@react-native/codegen": "0.73.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/metro-babel-transformer/node_modules/@react-native/babel-preset": { - "version": "0.73.19", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.73.19.tgz", - "integrity": "sha512-ujon01uMOREZecIltQxPDmJ6xlVqAUFGI/JCSpeVYdxyXBoBH5dBb0ihj7h6LKH1q1jsnO9z4MxfddtypKkIbg==", - "dependencies": { - "@babel/core": "^7.20.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.18.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", - "@babel/plugin-proposal-numeric-separator": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.20.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.20.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-export-default-from": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.18.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-syntax-optional-chaining": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-async-to-generator": "^7.20.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.20.0", - "@babel/plugin-transform-flow-strip-types": "^7.20.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/plugin-transform-private-property-in-object": "^7.22.11", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-sticky-regex": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.5.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "@babel/template": "^7.0.0", - "@react-native/babel-plugin-codegen": "0.73.2", - "babel-plugin-transform-flow-enums": "^0.0.2", - "react-refresh": "^0.14.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, "node_modules/@react-native/normalize-color": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@react-native/normalize-color/-/normalize-color-2.1.0.tgz", @@ -17744,9 +17284,9 @@ } }, "node_modules/@sideway/address": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", - "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", "dependencies": { "@hapi/hoek": "^9.0.0" } @@ -18528,12 +18068,12 @@ } }, "node_modules/@storybook/addon-actions": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-7.6.10.tgz", - "integrity": "sha512-pcKmf0H/caGzKDy8cz1adNSjv+KOBWLJ11RzGExrWm+Ad5ACifwlsQPykJ3TQ/21sTd9IXVrE9uuq4LldEnPbg==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-7.6.12.tgz", + "integrity": "sha512-vK/H6K+AJ4ZSsCu/+MapYYI/xrynB6JoCOejt//flTigZOhwTWv7WXbmEeqGIIToXy0LA2IUZ1/kCjFXR0lEdQ==", "dev": true, "dependencies": { - "@storybook/core-events": "7.6.10", + "@storybook/core-events": "7.6.12", "@storybook/global": "^5.0.0", "@types/uuid": "^9.0.1", "dequal": "^2.0.2", @@ -18559,9 +18099,9 @@ } }, "node_modules/@storybook/addon-backgrounds": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-7.6.10.tgz", - "integrity": "sha512-kGzsN1QkfyI8Cz7TErEx9OCB3PMzpCFGLd/iy7FreXwbMbeAQ3/9fYgKUsNOYgOhuTz7S09koZUWjS/WJuZGFA==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-7.6.12.tgz", + "integrity": "sha512-G14uN5lDXUtXw+dmEPaB6lpDpR9K25ssYuWWn8yYR44B1WMuD4kDgw0QGb0g+xYQj9R1TsalKEJHA4AuSYkVGQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0", @@ -18574,12 +18114,12 @@ } }, "node_modules/@storybook/addon-controls": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-7.6.10.tgz", - "integrity": "sha512-LjwCQRMWq1apLtFwDi6U8MI6ITUr+KhxJucZ60tfc58RgB2v8ayozyDAonFEONsx9YSR1dNIJ2Z/e2rWTBJeYA==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-7.6.12.tgz", + "integrity": "sha512-NX4KajscOsuXyYE3hhniF+y0E59E6rM0FgIaZ48P9c0DD+wDo8bAISHjZvmKXtDVajLk4/JySvByx1eN6V3hmA==", "dev": true, "dependencies": { - "@storybook/blocks": "7.6.10", + "@storybook/blocks": "7.6.12", "lodash": "^4.17.21", "ts-dedent": "^2.0.0" }, @@ -18589,26 +18129,26 @@ } }, "node_modules/@storybook/addon-docs": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-7.6.10.tgz", - "integrity": "sha512-GtyQ9bMx1AOOtl6ZS9vwK104HFRK+tqzxddRRxhXkpyeKu3olm9aMgXp35atE/3fJSqyyDm2vFtxxH8mzBA20A==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-7.6.12.tgz", + "integrity": "sha512-AzMgnGYfEg+Z1ycJh8MEp44x1DfjRijKCVYNaPFT6o+TjN/9GBaAkV4ydxmQzMEMnnnh/0E9YeHO+ivBVSkNog==", "dev": true, "dependencies": { "@jest/transform": "^29.3.1", "@mdx-js/react": "^2.1.5", - "@storybook/blocks": "7.6.10", - "@storybook/client-logger": "7.6.10", - "@storybook/components": "7.6.10", - "@storybook/csf-plugin": "7.6.10", - "@storybook/csf-tools": "7.6.10", + "@storybook/blocks": "7.6.12", + "@storybook/client-logger": "7.6.12", + "@storybook/components": "7.6.12", + "@storybook/csf-plugin": "7.6.12", + "@storybook/csf-tools": "7.6.12", "@storybook/global": "^5.0.0", "@storybook/mdx2-csf": "^1.0.0", - "@storybook/node-logger": "7.6.10", - "@storybook/postinstall": "7.6.10", - "@storybook/preview-api": "7.6.10", - "@storybook/react-dom-shim": "7.6.10", - "@storybook/theming": "7.6.10", - "@storybook/types": "7.6.10", + "@storybook/node-logger": "7.6.12", + "@storybook/postinstall": "7.6.12", + "@storybook/preview-api": "7.6.12", + "@storybook/react-dom-shim": "7.6.12", + "@storybook/theming": "7.6.12", + "@storybook/types": "7.6.12", "fs-extra": "^11.1.0", "remark-external-links": "^8.0.0", "remark-slug": "^6.0.0", @@ -18676,24 +18216,24 @@ } }, "node_modules/@storybook/addon-essentials": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-7.6.10.tgz", - "integrity": "sha512-cjbuCCK/3dtUity0Uqi5LwbkgfxqCCE5x5mXZIk9lTMeDz5vB9q6M5nzncVDy8F8przF3NbDLLgxKlt8wjiICg==", - "dev": true, - "dependencies": { - "@storybook/addon-actions": "7.6.10", - "@storybook/addon-backgrounds": "7.6.10", - "@storybook/addon-controls": "7.6.10", - "@storybook/addon-docs": "7.6.10", - "@storybook/addon-highlight": "7.6.10", - "@storybook/addon-measure": "7.6.10", - "@storybook/addon-outline": "7.6.10", - "@storybook/addon-toolbars": "7.6.10", - "@storybook/addon-viewport": "7.6.10", - "@storybook/core-common": "7.6.10", - "@storybook/manager-api": "7.6.10", - "@storybook/node-logger": "7.6.10", - "@storybook/preview-api": "7.6.10", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-7.6.12.tgz", + "integrity": "sha512-Pl6n+19QC/T+cuU8DZjCwILXVxrdRTivNxPOiy8SEX+jjR4H0uAfXC9+RXCPjRFn64t4j1K7oIyoNokEn39cNw==", + "dev": true, + "dependencies": { + "@storybook/addon-actions": "7.6.12", + "@storybook/addon-backgrounds": "7.6.12", + "@storybook/addon-controls": "7.6.12", + "@storybook/addon-docs": "7.6.12", + "@storybook/addon-highlight": "7.6.12", + "@storybook/addon-measure": "7.6.12", + "@storybook/addon-outline": "7.6.12", + "@storybook/addon-toolbars": "7.6.12", + "@storybook/addon-viewport": "7.6.12", + "@storybook/core-common": "7.6.12", + "@storybook/manager-api": "7.6.12", + "@storybook/node-logger": "7.6.12", + "@storybook/preview-api": "7.6.12", "ts-dedent": "^2.0.0" }, "funding": { @@ -18706,9 +18246,9 @@ } }, "node_modules/@storybook/addon-highlight": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-7.6.10.tgz", - "integrity": "sha512-dIuS5QmoT1R+gFOcf6CoBa6D9UR5/wHCfPqPRH8dNNcCLtIGSHWQ4v964mS5OCq1Huj7CghmR15lOUk7SaYwUA==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-7.6.12.tgz", + "integrity": "sha512-rWNEyBhwncXEDd9z7l67BLBIPqn0SRI/CJpZvCSF5KLWrVaoSEDF8INavmbikd1JBMcajJ28Ur6NsGj+eJjJiw==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -18719,9 +18259,9 @@ } }, "node_modules/@storybook/addon-links": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-7.6.10.tgz", - "integrity": "sha512-s/WkSYHpr2pb9p57j6u/xDBg3TKJhBq55YMl0GB5gXgkRPIeuGbPhGJhm2yTGVFLvXgr/aHHnOxb/R/W8PiRhA==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-7.6.12.tgz", + "integrity": "sha512-rGwPYpZAANPrf2GaNi5t9zAjLF8PgzKizyBPltIXUtplxDg88ziXlDA1dhsuGDs4Kf0oXECyAHPw79JjkJQziA==", "dev": true, "dependencies": { "@storybook/csf": "^0.1.2", @@ -18742,9 +18282,9 @@ } }, "node_modules/@storybook/addon-measure": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-7.6.10.tgz", - "integrity": "sha512-OVfTI56+kc4hLWfZ/YPV3WKj/aA9e4iKXYxZyPdhfX4Z8TgZdD1wv9Z6e8DKS0H5kuybYrHKHaID5ki6t7qz3w==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-7.6.12.tgz", + "integrity": "sha512-K3aKErr84V0eVK7t+wco5cSYDdeotwoXi4e7VLSa2cdUz0wanOb4R7v3kf6vxucUyp05Lv+yHkz9zsbwuezepA==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0", @@ -18756,9 +18296,9 @@ } }, "node_modules/@storybook/addon-outline": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-7.6.10.tgz", - "integrity": "sha512-RVJrEoPArhI6zAIMNl1Gz0zrj84BTfEWYYz0yDWOTVgvN411ugsoIk1hw0671MOneXJ2RcQ9MFIeV/v6AVDQYg==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-7.6.12.tgz", + "integrity": "sha512-r6eO4EKh+zwGUNjxe8v/44BhyV+JD3Dl9GYMutsFqbwYsoWHJaZmzHuyqeFBXwx2MEoixdWdIzNMP71+srQqvw==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0", @@ -18770,12 +18310,12 @@ } }, "node_modules/@storybook/addon-storysource": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/addon-storysource/-/addon-storysource-7.6.10.tgz", - "integrity": "sha512-ZtMiO26Bqd2oEovEeJ5ulvIL/rsAuHHpjAgBRZd/Byw25DQKY3GTqGtV474Wjm5tzj7HWhfk69fqAv87HnveCw==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-storysource/-/addon-storysource-7.6.12.tgz", + "integrity": "sha512-30h9NrtdI4CWJO/7z14y6uvdAJWP/YLJi9j3GzxGF2pFVKI5qRymeQRsNvk3p0VgWvN5+E+OhoI1b1T9F3wvhw==", "dev": true, "dependencies": { - "@storybook/source-loader": "7.6.10", + "@storybook/source-loader": "7.6.12", "estraverse": "^5.2.0", "tiny-invariant": "^1.3.1" }, @@ -18785,9 +18325,9 @@ } }, "node_modules/@storybook/addon-toolbars": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-7.6.10.tgz", - "integrity": "sha512-PaXY/oj9yxF7/H0CNdQKcioincyCkfeHpISZriZbZqhyqsjn3vca7RFEmsB88Q+ou6rMeqyA9st+6e2cx/Ct6A==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-7.6.12.tgz", + "integrity": "sha512-TSwq8xO7fmS6GRTgJJa31OBzm+5zlgDYK2Q42jxFo/Vm10uMzCpjYJE6mIHpUDyjyBVQk6xxMMEcvo6no2eAWg==", "dev": true, "funding": { "type": "opencollective", @@ -18795,9 +18335,9 @@ } }, "node_modules/@storybook/addon-viewport": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-7.6.10.tgz", - "integrity": "sha512-+bA6juC/lH4vEhk+w0rXakaG8JgLG4MOYrIudk5vJKQaC6X58LIM9N4kzIS2KSExRhkExXBPrWsnMfCo7uxmKg==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-7.6.12.tgz", + "integrity": "sha512-51zsBeoaEzq699SKDCe+GG/2PDAJKKJtpjqxIc4lDskogaCJSb3Ie8LyookHAKYgbi2qealVgK8zaP27KUj3Pg==", "dev": true, "dependencies": { "memoizerific": "^1.11.3" @@ -18808,22 +18348,22 @@ } }, "node_modules/@storybook/blocks": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-7.6.10.tgz", - "integrity": "sha512-oSIukGC3yuF8pojABC/HLu5tv2axZvf60TaUs8eDg7+NiiKhzYSPoMQxs5uMrKngl+EJDB92ESgWT9vvsfvIPg==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-7.6.12.tgz", + "integrity": "sha512-T47KOAjgZmhV+Ov59A70inE5edInh1Jh5w/5J5cjpk9a2p4uhd337SnK4B8J5YLhcM2lbKRWJjzIJ0nDZQTdnQ==", "dev": true, "dependencies": { - "@storybook/channels": "7.6.10", - "@storybook/client-logger": "7.6.10", - "@storybook/components": "7.6.10", - "@storybook/core-events": "7.6.10", + "@storybook/channels": "7.6.12", + "@storybook/client-logger": "7.6.12", + "@storybook/components": "7.6.12", + "@storybook/core-events": "7.6.12", "@storybook/csf": "^0.1.2", - "@storybook/docs-tools": "7.6.10", + "@storybook/docs-tools": "7.6.12", "@storybook/global": "^5.0.0", - "@storybook/manager-api": "7.6.10", - "@storybook/preview-api": "7.6.10", - "@storybook/theming": "7.6.10", - "@storybook/types": "7.6.10", + "@storybook/manager-api": "7.6.12", + "@storybook/preview-api": "7.6.12", + "@storybook/theming": "7.6.12", + "@storybook/types": "7.6.12", "@types/lodash": "^4.14.167", "color-convert": "^2.0.1", "dequal": "^2.0.2", @@ -18847,15 +18387,15 @@ } }, "node_modules/@storybook/builder-manager": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/builder-manager/-/builder-manager-7.6.10.tgz", - "integrity": "sha512-f+YrjZwohGzvfDtH8BHzqM3xW0p4vjjg9u7uzRorqUiNIAAKHpfNrZ/WvwPlPYmrpAHt4xX/nXRJae4rFSygPw==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/builder-manager/-/builder-manager-7.6.12.tgz", + "integrity": "sha512-AJFrtBj0R11OFwwz+2j+ivRzttWXT6LesSGoLnxown24EV9uLQoHtGb7GOA2GyzY5wjUJS9gQBPGHXjvQEfLJA==", "dev": true, "dependencies": { "@fal-works/esbuild-plugin-global-externals": "^2.1.2", - "@storybook/core-common": "7.6.10", - "@storybook/manager": "7.6.10", - "@storybook/node-logger": "7.6.10", + "@storybook/core-common": "7.6.12", + "@storybook/manager": "7.6.12", + "@storybook/node-logger": "7.6.12", "@types/ejs": "^3.1.1", "@types/find-cache-dir": "^3.2.1", "@yarnpkg/esbuild-plugin-pnp": "^3.0.0-rc.10", @@ -18910,19 +18450,19 @@ } }, "node_modules/@storybook/builder-vite": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-7.6.10.tgz", - "integrity": "sha512-qxe19axiNJVdIKj943e1ucAmADwU42fTGgMSdBzzrvfH3pSOmx2057aIxRzd8YtBRnj327eeqpgCHYIDTunMYQ==", - "dev": true, - "dependencies": { - "@storybook/channels": "7.6.10", - "@storybook/client-logger": "7.6.10", - "@storybook/core-common": "7.6.10", - "@storybook/csf-plugin": "7.6.10", - "@storybook/node-logger": "7.6.10", - "@storybook/preview": "7.6.10", - "@storybook/preview-api": "7.6.10", - "@storybook/types": "7.6.10", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-7.6.12.tgz", + "integrity": "sha512-VJIn+XYVVhdJHHMEtYDnEyQQU4fRupugSFpP9XLYTRYgXPN9PSVey4vI/IyuHcHYINPba39UY2+8PW+5NgShxQ==", + "dev": true, + "dependencies": { + "@storybook/channels": "7.6.12", + "@storybook/client-logger": "7.6.12", + "@storybook/core-common": "7.6.12", + "@storybook/csf-plugin": "7.6.12", + "@storybook/node-logger": "7.6.12", + "@storybook/preview": "7.6.12", + "@storybook/preview-api": "7.6.12", + "@storybook/types": "7.6.12", "@types/find-cache-dir": "^3.2.1", "browser-assert": "^1.2.1", "es-module-lexer": "^0.9.3", @@ -18990,13 +18530,13 @@ } }, "node_modules/@storybook/channels": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.10.tgz", - "integrity": "sha512-ITCLhFuDBKgxetuKnWwYqMUWlU7zsfH3gEKZltTb+9/2OAWR7ez0iqU7H6bXP1ridm0DCKkt2UMWj2mmr9iQqg==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.12.tgz", + "integrity": "sha512-TaPl5Y3lOoVi5kTLgKNRX8xh2sUPekH0Id1l4Ymw+lpgriEY6r60bmkZLysLG1GhlskpQ/da2+S2ap2ht8P2TQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.10", - "@storybook/core-events": "7.6.10", + "@storybook/client-logger": "7.6.12", + "@storybook/core-events": "7.6.12", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -19008,23 +18548,23 @@ } }, "node_modules/@storybook/cli": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/cli/-/cli-7.6.10.tgz", - "integrity": "sha512-pK1MEseMm73OMO2OVoSz79QWX8ymxgIGM8IeZTCo9gImiVRChMNDFYcv8yPWkjuyesY8c15CoO48aR7pdA1OjQ==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/cli/-/cli-7.6.12.tgz", + "integrity": "sha512-x4sG1oIVERxp+WnWUexVlgaJCFmML0kGi7a5qfx7z4vHMxCV/WG7g1q7mPS/kqStCGEiQdTciCqOEFqlMh9MLw==", "dev": true, "dependencies": { "@babel/core": "^7.23.2", "@babel/preset-env": "^7.23.2", "@babel/types": "^7.23.0", "@ndelangen/get-tarball": "^3.0.7", - "@storybook/codemod": "7.6.10", - "@storybook/core-common": "7.6.10", - "@storybook/core-events": "7.6.10", - "@storybook/core-server": "7.6.10", - "@storybook/csf-tools": "7.6.10", - "@storybook/node-logger": "7.6.10", - "@storybook/telemetry": "7.6.10", - "@storybook/types": "7.6.10", + "@storybook/codemod": "7.6.12", + "@storybook/core-common": "7.6.12", + "@storybook/core-events": "7.6.12", + "@storybook/core-server": "7.6.12", + "@storybook/csf-tools": "7.6.12", + "@storybook/node-logger": "7.6.12", + "@storybook/telemetry": "7.6.12", + "@storybook/types": "7.6.12", "@types/semver": "^7.3.4", "@yarnpkg/fslib": "2.10.3", "@yarnpkg/libzip": "2.3.0", @@ -19217,9 +18757,9 @@ "dev": true }, "node_modules/@storybook/client-logger": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.10.tgz", - "integrity": "sha512-U7bbpu21ntgePMz/mKM18qvCSWCUGCUlYru8mgVlXLCKqFqfTeP887+CsPEQf29aoE3cLgDrxqbRJ1wxX9kL9A==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.12.tgz", + "integrity": "sha512-hiRv6dXsOttMPqm9SxEuFoAtDe9rs7TUS8XcO5rmJ9BgfwBJsYlHzAxXkazxmvlyZtKL7gMx6m8OYbCdZgUqtA==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -19230,18 +18770,18 @@ } }, "node_modules/@storybook/codemod": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-7.6.10.tgz", - "integrity": "sha512-pzFR0nocBb94vN9QCJLC3C3dP734ZigqyPmd0ZCDj9Xce2ytfHK3v1lKB6TZWzKAZT8zztauECYxrbo4LVuagw==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-7.6.12.tgz", + "integrity": "sha512-4EI4Ah1cvz6gFkXOS/LGf23oN8LO6ABGpWwPQoMHpIV3wUkFWBwrKFUe/UAQZGptnM0VZRYx4grS82Hluw4XJA==", "dev": true, "dependencies": { "@babel/core": "^7.23.2", "@babel/preset-env": "^7.23.2", "@babel/types": "^7.23.0", "@storybook/csf": "^0.1.2", - "@storybook/csf-tools": "7.6.10", - "@storybook/node-logger": "7.6.10", - "@storybook/types": "7.6.10", + "@storybook/csf-tools": "7.6.12", + "@storybook/node-logger": "7.6.12", + "@storybook/types": "7.6.12", "@types/cross-spawn": "^6.0.2", "cross-spawn": "^7.0.3", "globby": "^11.0.2", @@ -19271,18 +18811,18 @@ } }, "node_modules/@storybook/components": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/components/-/components-7.6.10.tgz", - "integrity": "sha512-H5hF8pxwtbt0LxV24KMMsPlbYG9Oiui3ObvAQkvGu6q62EYxRPeNSrq3GBI5XEbI33OJY9bT24cVaZx18dXqwQ==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/components/-/components-7.6.12.tgz", + "integrity": "sha512-PCijPqmlZd7qyTzNr+vD0Kf8sAI9vWJIaxbSjXwn/De3e63m4fsEcIf8FaUT8cMZ46AWZvaxaxX5km2u0UISJQ==", "dev": true, "dependencies": { "@radix-ui/react-select": "^1.2.2", "@radix-ui/react-toolbar": "^1.0.4", - "@storybook/client-logger": "7.6.10", + "@storybook/client-logger": "7.6.12", "@storybook/csf": "^0.1.2", "@storybook/global": "^5.0.0", - "@storybook/theming": "7.6.10", - "@storybook/types": "7.6.10", + "@storybook/theming": "7.6.12", + "@storybook/types": "7.6.12", "memoizerific": "^1.11.3", "use-resize-observer": "^9.1.0", "util-deprecate": "^1.0.2" @@ -19297,13 +18837,13 @@ } }, "node_modules/@storybook/core-client": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/core-client/-/core-client-7.6.10.tgz", - "integrity": "sha512-DjnzSzSNDmZyxyg6TxugzWQwOsW+n/iWVv6sHNEvEd5STr0mjuJjIEELmv58LIr5Lsre5+LEddqHsyuLyt8ubg==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/core-client/-/core-client-7.6.12.tgz", + "integrity": "sha512-VzVp32tMZsCzM4UIqfvCoJF7N9mBf6dsAxh1/ZgViy75Fht78pGo3JwZXW8osMbFSRpmWD7fxlUM5S7TQOYQug==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.10", - "@storybook/preview-api": "7.6.10" + "@storybook/client-logger": "7.6.12", + "@storybook/preview-api": "7.6.12" }, "funding": { "type": "opencollective", @@ -19311,14 +18851,14 @@ } }, "node_modules/@storybook/core-common": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.10.tgz", - "integrity": "sha512-K3YWqjCKMnpvYsWNjOciwTH6zWbuuZzmOiipziZaVJ+sB1XYmH52Y3WGEm07TZI8AYK9DRgwA13dR/7W0nw72Q==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.12.tgz", + "integrity": "sha512-kM9YiBBMM2x5v/oylL7gdO1PS4oehgJC21MivS9p5QZ8uuXKtCQ6UQvI3rzaV+1ZzUA4n+I8MyaMrNIQk8KDbw==", "dev": true, "dependencies": { - "@storybook/core-events": "7.6.10", - "@storybook/node-logger": "7.6.10", - "@storybook/types": "7.6.10", + "@storybook/core-events": "7.6.12", + "@storybook/node-logger": "7.6.12", + "@storybook/types": "7.6.12", "@types/find-cache-dir": "^3.2.1", "@types/node": "^18.0.0", "@types/node-fetch": "^2.6.4", @@ -19346,9 +18886,9 @@ } }, "node_modules/@storybook/core-common/node_modules/@types/node": { - "version": "18.19.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.10.tgz", - "integrity": "sha512-IZD8kAM02AW1HRDTPOlz3npFava678pr8Ie9Vp8uRhBROXAv8MXT2pCnGZZAKYdromsNQLHQcfWQ6EOatVLtqA==", + "version": "18.19.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.14.tgz", + "integrity": "sha512-EnQ4Us2rmOS64nHDWr0XqAD8DsO6f3XR6lf9UIIrZQpUzPVdN/oPuEzfDWNHSyXLvoGgjuEm/sPwFGSSs35Wtg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -19442,9 +18982,9 @@ } }, "node_modules/@storybook/core-events": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.10.tgz", - "integrity": "sha512-yccDH67KoROrdZbRKwxgTswFMAco5nlCyxszCDASCLygGSV2Q2e+YuywrhchQl3U6joiWi3Ps1qWu56NeNafag==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.12.tgz", + "integrity": "sha512-IO4cwk7bBCKH6lLnnIlHO9FwQXt/9CzLUAoZSY9msWsdPppCdKlw8ynJI5YarSNKDBUn8ArIfnRf0Mve0KQr9Q==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -19455,26 +18995,26 @@ } }, "node_modules/@storybook/core-server": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-7.6.10.tgz", - "integrity": "sha512-2icnqJkn3vwq0eJPP0rNaHd7IOvxYf5q4lSVl2AWTxo/Ae19KhokI6j/2vvS2XQJMGQszwshlIwrZUNsj5p0yw==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-7.6.12.tgz", + "integrity": "sha512-tjWifKsDnIc8pvbjVyQrOHef70Gcp93Bg3WwuysB8PGk7lcX2RD9zv44HNIyjxdOLSSv66IGKrOldEBL3hab4w==", "dev": true, "dependencies": { "@aw-web-design/x-default-browser": "1.4.126", "@discoveryjs/json-ext": "^0.5.3", - "@storybook/builder-manager": "7.6.10", - "@storybook/channels": "7.6.10", - "@storybook/core-common": "7.6.10", - "@storybook/core-events": "7.6.10", + "@storybook/builder-manager": "7.6.12", + "@storybook/channels": "7.6.12", + "@storybook/core-common": "7.6.12", + "@storybook/core-events": "7.6.12", "@storybook/csf": "^0.1.2", - "@storybook/csf-tools": "7.6.10", + "@storybook/csf-tools": "7.6.12", "@storybook/docs-mdx": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/manager": "7.6.10", - "@storybook/node-logger": "7.6.10", - "@storybook/preview-api": "7.6.10", - "@storybook/telemetry": "7.6.10", - "@storybook/types": "7.6.10", + "@storybook/manager": "7.6.12", + "@storybook/node-logger": "7.6.12", + "@storybook/preview-api": "7.6.12", + "@storybook/telemetry": "7.6.12", + "@storybook/types": "7.6.12", "@types/detect-port": "^1.3.0", "@types/node": "^18.0.0", "@types/pretty-hrtime": "^1.0.0", @@ -19508,9 +19048,9 @@ } }, "node_modules/@storybook/core-server/node_modules/@types/node": { - "version": "18.19.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.10.tgz", - "integrity": "sha512-IZD8kAM02AW1HRDTPOlz3npFava678pr8Ie9Vp8uRhBROXAv8MXT2pCnGZZAKYdromsNQLHQcfWQ6EOatVLtqA==", + "version": "18.19.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.14.tgz", + "integrity": "sha512-EnQ4Us2rmOS64nHDWr0XqAD8DsO6f3XR6lf9UIIrZQpUzPVdN/oPuEzfDWNHSyXLvoGgjuEm/sPwFGSSs35Wtg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -19646,12 +19186,12 @@ } }, "node_modules/@storybook/csf-plugin": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-7.6.10.tgz", - "integrity": "sha512-Sc+zZg/BnPH2X28tthNaQBnDiFfO0QmfjVoOx0fGYM9SvY3P5ehzWwp5hMRBim6a/twOTzePADtqYL+t6GMqqg==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-7.6.12.tgz", + "integrity": "sha512-fe/84AyctJcrpH1F/tTBxKrbjv0ilmG3ZTwVcufEiAzupZuYjQ/0P+Pxs8m8VxiGJZZ1pWofFFDbYi+wERjamQ==", "dev": true, "dependencies": { - "@storybook/csf-tools": "7.6.10", + "@storybook/csf-tools": "7.6.12", "unplugin": "^1.3.1" }, "funding": { @@ -19660,9 +19200,9 @@ } }, "node_modules/@storybook/csf-tools": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.10.tgz", - "integrity": "sha512-TnDNAwIALcN6SA4l00Cb67G02XMOrYU38bIpFJk5VMDX2dvgPjUtJNBuLmEbybGcOt7nPyyFIHzKcY5FCVGoWA==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.12.tgz", + "integrity": "sha512-MdhkYYxSW5I6Jpk34gTkAZsuj9sxe0xdyeUQpNa8CgJxG43F+ehZ6scW/IPjoSG9gCXBUJMekq26UrmbVfsLCQ==", "dev": true, "dependencies": { "@babel/generator": "^7.23.0", @@ -19670,7 +19210,7 @@ "@babel/traverse": "^7.23.2", "@babel/types": "^7.23.0", "@storybook/csf": "^0.1.2", - "@storybook/types": "7.6.10", + "@storybook/types": "7.6.12", "fs-extra": "^11.1.0", "recast": "^0.23.1", "ts-dedent": "^2.0.0" @@ -19734,14 +19274,14 @@ "dev": true }, "node_modules/@storybook/docs-tools": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/docs-tools/-/docs-tools-7.6.10.tgz", - "integrity": "sha512-UgbikducoXzqQHf2TozO0f2rshaeBNnShVbL5Ai4oW7pDymBmrfzdjGbF/milO7yxNKcoIByeoNmu384eBamgQ==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/docs-tools/-/docs-tools-7.6.12.tgz", + "integrity": "sha512-nY2lqEDTd/fR/D91ZLlIp+boSuJtkb8DqHW7pECy61rJqzGq4QpepRaWjQDKnGTgPItrsPfTPOu6iXvXNK07Ow==", "dev": true, "dependencies": { - "@storybook/core-common": "7.6.10", - "@storybook/preview-api": "7.6.10", - "@storybook/types": "7.6.10", + "@storybook/core-common": "7.6.12", + "@storybook/preview-api": "7.6.12", + "@storybook/types": "7.6.12", "@types/doctrine": "^0.0.3", "assert": "^2.1.0", "doctrine": "^3.0.0", @@ -19759,9 +19299,9 @@ "dev": true }, "node_modules/@storybook/manager": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/manager/-/manager-7.6.10.tgz", - "integrity": "sha512-Co3sLCbNYY6O4iH2ggmRDLCPWLj03JE5s/DOG8OVoXc6vBwTc/Qgiyrsxxp6BHQnPpM0mxL6aKAxE3UjsW/Nog==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/manager/-/manager-7.6.12.tgz", + "integrity": "sha512-WMWvswJHGiqJFJb98WQMQfZQhLuVtmci4y/VJGQ/Jnq1nJQs76BCtaeGiHcsYmRaAP1HMI4DbzuTSEgca146xw==", "dev": true, "funding": { "type": "opencollective", @@ -19769,19 +19309,19 @@ } }, "node_modules/@storybook/manager-api": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.6.10.tgz", - "integrity": "sha512-8eGVpRlpunuFScDtc7nxpPJf/4kJBAAZlNdlhmX09j8M3voX6GpcxabBamSEX5pXZqhwxQCshD4IbqBmjvadlw==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.6.12.tgz", + "integrity": "sha512-XA5KQpY44d6mlqt0AlesZ7fsPpm1PCpoV+nRGFBR0YtF6RdPFvrPyHhlGgLkJC4xSyb2YJmLKn8cERSluAcEgQ==", "dev": true, "dependencies": { - "@storybook/channels": "7.6.10", - "@storybook/client-logger": "7.6.10", - "@storybook/core-events": "7.6.10", + "@storybook/channels": "7.6.12", + "@storybook/client-logger": "7.6.12", + "@storybook/core-events": "7.6.12", "@storybook/csf": "^0.1.2", "@storybook/global": "^5.0.0", - "@storybook/router": "7.6.10", - "@storybook/theming": "7.6.10", - "@storybook/types": "7.6.10", + "@storybook/router": "7.6.12", + "@storybook/theming": "7.6.12", + "@storybook/types": "7.6.12", "dequal": "^2.0.2", "lodash": "^4.17.21", "memoizerific": "^1.11.3", @@ -19801,9 +19341,9 @@ "dev": true }, "node_modules/@storybook/node-logger": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.10.tgz", - "integrity": "sha512-ZBuqrv4bjJzKXyfRGFkVIi+z6ekn6rOPoQao4KmsfLNQAUUsEdR8Baw/zMnnU417zw5dSEaZdpuwx75SCQAeOA==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.12.tgz", + "integrity": "sha512-iS44/EjfF6hLecKzICmcpQoB9bmVi4tXx5gVXnbI5ZyziBibRQcg/U191Njl7wY2ScN/RCQOr8lh5k57rI3Prg==", "dev": true, "funding": { "type": "opencollective", @@ -19811,9 +19351,9 @@ } }, "node_modules/@storybook/postinstall": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/postinstall/-/postinstall-7.6.10.tgz", - "integrity": "sha512-SMdXtednPCy3+SRJ7oN1OPN1oVFhj3ih+ChOEX8/kZ5J3nfmV3wLPtsZvFGUCf0KWQEP1xL+1Urv48mzMKcV/w==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/postinstall/-/postinstall-7.6.12.tgz", + "integrity": "sha512-uR0mDPxLzPaouCNrLp8vID8lATVTOtG7HB6lfjjzMdE3sN6MLmK9n2z2nXjb5DRRxOFWMeE1/4Age1/Ml2tnmA==", "dev": true, "funding": { "type": "opencollective", @@ -19821,9 +19361,9 @@ } }, "node_modules/@storybook/preview": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/preview/-/preview-7.6.10.tgz", - "integrity": "sha512-F07BzVXTD3byq+KTWtvsw3pUu3fQbyiBNLFr2CnfU4XSdLKja5lDt8VqDQq70TayVQOf5qfUTzRd4M6pQkjw1w==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/preview/-/preview-7.6.12.tgz", + "integrity": "sha512-7vbeqQY3X+FCt/ccgCuBmj4rkbQebLHGEBAt8elcX0E2pr7SGW57lWhnasU3jeMaz7tNrkcs0gfl4hyVRWUHDg==", "dev": true, "funding": { "type": "opencollective", @@ -19831,17 +19371,17 @@ } }, "node_modules/@storybook/preview-api": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.6.10.tgz", - "integrity": "sha512-5A3etoIwZCx05yuv3KSTv1wynN4SR4rrzaIs/CTBp3BC4q1RBL+Or/tClk0IJPXQMlx/4Y134GtNIBbkiDofpw==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.6.12.tgz", + "integrity": "sha512-uSzeMSLnCRROjiofJP0F0niLWL+sboQ5ktHW6BAYoPwprumXduPxKBUVEZNxMbVYoAz9v/kEZmaLauh8LRP2Hg==", "dev": true, "dependencies": { - "@storybook/channels": "7.6.10", - "@storybook/client-logger": "7.6.10", - "@storybook/core-events": "7.6.10", + "@storybook/channels": "7.6.12", + "@storybook/client-logger": "7.6.12", + "@storybook/core-events": "7.6.12", "@storybook/csf": "^0.1.2", "@storybook/global": "^5.0.0", - "@storybook/types": "7.6.10", + "@storybook/types": "7.6.12", "@types/qs": "^6.9.5", "dequal": "^2.0.2", "lodash": "^4.17.21", @@ -19857,18 +19397,18 @@ } }, "node_modules/@storybook/react": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/react/-/react-7.6.10.tgz", - "integrity": "sha512-wwBn1cg2uZWW4peqqBjjU7XGmFq8HdkVUtWwh6dpfgmlY1Aopi+vPgZt7pY9KkWcTOq5+DerMdSfwxukpc3ajQ==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-7.6.12.tgz", + "integrity": "sha512-ITDRGi79Qg+z1kGYv+yyJESz/5AsJVdBTMO7tr1qV7gmHElkASt6UR8SBSqKgePOnYgy3k/1PLfbzOs6G4OgYQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.10", - "@storybook/core-client": "7.6.10", - "@storybook/docs-tools": "7.6.10", + "@storybook/client-logger": "7.6.12", + "@storybook/core-client": "7.6.12", + "@storybook/docs-tools": "7.6.12", "@storybook/global": "^5.0.0", - "@storybook/preview-api": "7.6.10", - "@storybook/react-dom-shim": "7.6.10", - "@storybook/types": "7.6.10", + "@storybook/preview-api": "7.6.12", + "@storybook/react-dom-shim": "7.6.12", + "@storybook/types": "7.6.12", "@types/escodegen": "^0.0.6", "@types/estree": "^0.0.51", "@types/node": "^18.0.0", @@ -19903,9 +19443,9 @@ } }, "node_modules/@storybook/react-dom-shim": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-7.6.10.tgz", - "integrity": "sha512-M+N/h6ximacaFdIDjMN2waNoWwApeVYTpFeoDppiFTvdBTXChyIuiPgYX9QSg7gDz92OaA52myGOot4wGvXVzg==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-7.6.12.tgz", + "integrity": "sha512-P8eu/s/RQlc/7Yvr260lqNa6rttxIYiPUuHQBu9oCacwkpB3Xep2R/PUY2CifRHqlDhaOINO/Z79oGZl4EBQRQ==", "dev": true, "funding": { "type": "opencollective", @@ -19917,15 +19457,15 @@ } }, "node_modules/@storybook/react-vite": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-7.6.10.tgz", - "integrity": "sha512-YE2+J1wy8nO+c6Nv/hBMu91Edew3K184L1KSnfoZV8vtq2074k1Me/8pfe0QNuq631AncpfCYNb37yBAXQ/80w==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-7.6.12.tgz", + "integrity": "sha512-kQjCWmTcHuZM1Mlt1QjpYNXP1TxfkSDFWC36fSEUC0q48wzyjUEZs6YraxZu0YE+zXK+X4tmaZhz8pUPgV3gLw==", "dev": true, "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "0.3.0", "@rollup/pluginutils": "^5.0.2", - "@storybook/builder-vite": "7.6.10", - "@storybook/react": "7.6.10", + "@storybook/builder-vite": "7.6.12", + "@storybook/react": "7.6.12", "@vitejs/plugin-react": "^3.0.1", "magic-string": "^0.30.0", "react-docgen": "^7.0.0" @@ -19981,9 +19521,9 @@ "dev": true }, "node_modules/@storybook/react/node_modules/@types/node": { - "version": "18.19.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.10.tgz", - "integrity": "sha512-IZD8kAM02AW1HRDTPOlz3npFava678pr8Ie9Vp8uRhBROXAv8MXT2pCnGZZAKYdromsNQLHQcfWQ6EOatVLtqA==", + "version": "18.19.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.14.tgz", + "integrity": "sha512-EnQ4Us2rmOS64nHDWr0XqAD8DsO6f3XR6lf9UIIrZQpUzPVdN/oPuEzfDWNHSyXLvoGgjuEm/sPwFGSSs35Wtg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -20002,12 +19542,12 @@ } }, "node_modules/@storybook/router": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.6.10.tgz", - "integrity": "sha512-G/H4Jn2+y8PDe8Zbq4DVxF/TPn0/goSItdILts39JENucHiuGBCjKjSWGBe1rkwKi1tUbB3yhxJVrLagxFEPpQ==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.6.12.tgz", + "integrity": "sha512-1fqscJbePFJXhapqiv7fAIIqAvouSsdPnqWjJGJrUMR6JBtRYMcrb3MnDeqi9OYnU73r65BrQBPtSzWM8nP0LQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.10", + "@storybook/client-logger": "7.6.12", "memoizerific": "^1.11.3", "qs": "^6.10.0" }, @@ -20017,13 +19557,13 @@ } }, "node_modules/@storybook/source-loader": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/source-loader/-/source-loader-7.6.10.tgz", - "integrity": "sha512-S3nOWyj+sdpsqJqKGIN3DKE1q+Q0KYxEyPlPCawMFazozUH7tOodTIqmHBqJZCSNqdC4M1S/qcL8vpP4PfXhuA==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/source-loader/-/source-loader-7.6.12.tgz", + "integrity": "sha512-fGmE9E4lJ+YCGNTnxQ/HtQnI21lufkfeUDBH4jKGf2TLPFBJWjpqaRwUZu5mVEvfMlNx9MnU+ZG4ISPFjlBHRg==", "dev": true, "dependencies": { "@storybook/csf": "^0.1.2", - "@storybook/types": "7.6.10", + "@storybook/types": "7.6.12", "estraverse": "^5.2.0", "lodash": "^4.17.21", "prettier": "^2.8.0" @@ -20049,14 +19589,14 @@ } }, "node_modules/@storybook/telemetry": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/telemetry/-/telemetry-7.6.10.tgz", - "integrity": "sha512-p3mOSUtIyy2tF1z6pQXxNh1JzYFcAm97nUgkwLzF07GfEdVAPM+ftRSLFbD93zVvLEkmLTlsTiiKaDvOY/lQWg==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/telemetry/-/telemetry-7.6.12.tgz", + "integrity": "sha512-eBG3sLb9CZ05pyK2JXBvnaAsxDzbZH57VyhtphhuZmx0DqF/78qIoHs9ebRJpJWV0sL5rtT9vIq8QXpQhDHLWg==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.10", - "@storybook/core-common": "7.6.10", - "@storybook/csf-tools": "7.6.10", + "@storybook/client-logger": "7.6.12", + "@storybook/core-common": "7.6.12", + "@storybook/csf-tools": "7.6.12", "chalk": "^4.1.0", "detect-package-manager": "^2.0.1", "fetch-retry": "^5.0.2", @@ -20156,13 +19696,13 @@ } }, "node_modules/@storybook/theming": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.6.10.tgz", - "integrity": "sha512-f5tuy7yV3TOP3fIboSqpgLHy0wKayAw/M8HxX0jVET4Z4fWlFK0BiHJabQ+XEdAfQM97XhPFHB2IPbwsqhCEcQ==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.6.12.tgz", + "integrity": "sha512-P4zoMKlSYbNrWJjQROuz+DZSDEpdf3TUvk203EqBRdElqw2EMHcqZ8+0HGPFfVHpqEj05+B9Mr6R/Z/BURj0lw==", "dev": true, "dependencies": { "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@storybook/client-logger": "7.6.10", + "@storybook/client-logger": "7.6.12", "@storybook/global": "^5.0.0", "memoizerific": "^1.11.3" }, @@ -20176,12 +19716,12 @@ } }, "node_modules/@storybook/types": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.10.tgz", - "integrity": "sha512-hcS2HloJblaMpCAj2axgGV+53kgSRYPT0a1PG1IHsZaYQILfHSMmBqM8XzXXYTsgf9250kz3dqFX1l0n3EqMlQ==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.12.tgz", + "integrity": "sha512-Wsbd+NS10/2yMHQ/26rXHflXam0hm2qufTFiHOX6VXZWxij3slRU88Fnwzp+1QSyjXb0qkEr8dOx7aG00+ItVw==", "dev": true, "dependencies": { - "@storybook/channels": "7.6.10", + "@storybook/channels": "7.6.12", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -20635,9 +20175,9 @@ } }, "node_modules/@testing-library/jest-dom": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.3.0.tgz", - "integrity": "sha512-hJVIrkFizEQxoWsGBlycTcQhrpoCH4DhXfrnHFFXgkx3Xdm15zycsq5Ep+vpw4W8S0NJa8cxDHcuJib+1tEbhg==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.4.1.tgz", + "integrity": "sha512-Z7qMM3J2Zw5H/nC2/5CYx5YcuaD56JmDFKNIozZ89VIo6o6Y9FMhssics4e2madEKYDNEpZz3+glPGz0yWMOag==", "dev": true, "dependencies": { "@adobe/css-tools": "^4.3.2", @@ -20735,9 +20275,9 @@ } }, "node_modules/@testing-library/react": { - "version": "14.1.2", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.1.2.tgz", - "integrity": "sha512-z4p7DVBTPjKM5qDZ0t5ZjzkpSNb+fZy1u6bzO7kk8oeGagpPCAtgh4cx1syrfp7a+QWkM021jGqjJaxJJnXAZg==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.2.0.tgz", + "integrity": "sha512-7uBnPHyOG6nDGCzv8SLeJbSa33ZoYw7swYpSLIgJvBALdq7l9zPNk33om4USrxy1lKTxXaVfufzLmq83WNfWIw==", "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", @@ -21135,9 +20675,9 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" }, "node_modules/@types/estree-jsx": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.3.tgz", - "integrity": "sha512-pvQ+TKeRHeiUGRhvYwRrQ/ISnohKkSJR14fT2yqyZ4e9K5vqc7hrtY2Y1Dw0ZwAzQ6DQsxsaCUuSIIi8v0Cq6w==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.4.tgz", + "integrity": "sha512-5idy3hvI9lAMqsyilBM+N+boaCf1MgoefbDxN6KEO5aK17TOHwFAYT9sjxzeKAiIWRUBgLxmZ9mPcnzZXtTcRQ==", "dev": true, "dependencies": { "@types/estree": "*" @@ -21247,9 +20787,9 @@ } }, "node_modules/@types/hast": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.3.tgz", - "integrity": "sha512-2fYGlaDy/qyLlhidX42wAH0KBi2TCjKMH8CHmBXgRlJ3Y+OXTiqsPQ6IWarZKwF1JoUcAJdPogv1d4b0COTpmQ==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", "dev": true, "dependencies": { "@types/unist": "*" @@ -21330,9 +20870,9 @@ } }, "node_modules/@types/jest": { - "version": "29.5.11", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.11.tgz", - "integrity": "sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ==", + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", "dev": true, "dependencies": { "expect": "^29.0.0", @@ -21376,9 +20916,9 @@ } }, "node_modules/@types/koa": { - "version": "2.13.9", - "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.13.9.tgz", - "integrity": "sha512-tPX3cN1dGrMn+sjCDEiQqXH2AqlPoPd594S/8zxwUm/ZbPsQXKqHPUypr2gjCPhHUc+nDJLduhh5lXI/1olnGQ==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.14.0.tgz", + "integrity": "sha512-DTDUyznHGNHAl+wd1n0z1jxNajduyTh8R53xoewuerdBzGo6Ogj6F2299BFtrexJw4NtgjsI5SMPCmV9gZwGXA==", "dependencies": { "@types/accepts": "*", "@types/content-disposition": "*", @@ -21432,9 +20972,9 @@ } }, "node_modules/@types/mdx": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.10.tgz", - "integrity": "sha512-Rllzc5KHk0Al5/WANwgSPl1/CwjqCy+AZrGd78zuK+jO9aDM6ffblZ+zIjgPNAaEBmlO0RYDvLNh7wD0zKVgEg==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.11.tgz", + "integrity": "sha512-HM5bwOaIQJIQbAYfax35HCKxx7a3KrK3nBtIqJgSOitivTD1y3oW9P3rxY9RkXYPUk7y/AjAohfHKmFpGE79zw==", "dev": true }, "node_modules/@types/memcached": { @@ -21487,9 +21027,9 @@ } }, "node_modules/@types/node": { - "version": "20.11.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.8.tgz", - "integrity": "sha512-i7omyekpPTNdv4Jb/Rgqg0RU8YqLcNsI12quKSDkRXNfx7Wxdm6HhK1awT3xTgEkgxPn3bvnSpiEAc7a7Lpyow==", + "version": "20.11.16", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz", + "integrity": "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==", "dependencies": { "undici-types": "~5.26.4" } @@ -21618,9 +21158,9 @@ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" }, "node_modules/@types/react": { - "version": "18.2.48", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.48.tgz", - "integrity": "sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==", + "version": "18.2.51", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.51.tgz", + "integrity": "sha512-XeoMaU4CzyjdRr3c4IQQtiH7Rpo18V07rYZUucEZQwOUEtGgTXv7e6igQiQ+xnV6MbMe1qjEmKdgMNnfppnXfg==", "devOptional": true, "dependencies": { "@types/prop-types": "*", @@ -21791,9 +21331,9 @@ } }, "node_modules/@types/ssh2/node_modules/@types/node": { - "version": "18.19.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.10.tgz", - "integrity": "sha512-IZD8kAM02AW1HRDTPOlz3npFava678pr8Ie9Vp8uRhBROXAv8MXT2pCnGZZAKYdromsNQLHQcfWQ6EOatVLtqA==", + "version": "18.19.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.14.tgz", + "integrity": "sha512-EnQ4Us2rmOS64nHDWr0XqAD8DsO6f3XR6lf9UIIrZQpUzPVdN/oPuEzfDWNHSyXLvoGgjuEm/sPwFGSSs35Wtg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -21936,16 +21476,16 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.19.1.tgz", - "integrity": "sha512-roQScUGFruWod9CEyoV5KlCYrubC/fvG8/1zXuT0WTcxX87GnMMmnksMwSg99lo1xiKrBzw2icsJPMAw1OtKxg==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.20.0.tgz", + "integrity": "sha512-fTwGQUnjhoYHeSF6m5pWNkzmDDdsKELYrOBxhjMrofPqCkoC2k3B2wvGHFxa1CTIqkEn88nlW1HVMztjo2K8Hg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.19.1", - "@typescript-eslint/type-utils": "6.19.1", - "@typescript-eslint/utils": "6.19.1", - "@typescript-eslint/visitor-keys": "6.19.1", + "@typescript-eslint/scope-manager": "6.20.0", + "@typescript-eslint/type-utils": "6.20.0", + "@typescript-eslint/utils": "6.20.0", + "@typescript-eslint/visitor-keys": "6.20.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -22004,15 +21544,15 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.19.1.tgz", - "integrity": "sha512-WEfX22ziAh6pRE9jnbkkLGp/4RhTpffr2ZK5bJ18M8mIfA8A+k97U9ZyaXCEJRlmMHh7R9MJZWXp/r73DzINVQ==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.20.0.tgz", + "integrity": "sha512-bYerPDF/H5v6V76MdMYhjwmwgMA+jlPVqjSDq2cRqMi8bP5sR3Z+RLOiOMad3nsnmDVmn2gAFCyNgh/dIrfP/w==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.19.1", - "@typescript-eslint/types": "6.19.1", - "@typescript-eslint/typescript-estree": "6.19.1", - "@typescript-eslint/visitor-keys": "6.19.1", + "@typescript-eslint/scope-manager": "6.20.0", + "@typescript-eslint/types": "6.20.0", + "@typescript-eslint/typescript-estree": "6.20.0", + "@typescript-eslint/visitor-keys": "6.20.0", "debug": "^4.3.4" }, "engines": { @@ -22032,13 +21572,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.19.1.tgz", - "integrity": "sha512-4CdXYjKf6/6aKNMSly/BP4iCSOpvMmqtDzRtqFyyAae3z5kkqEjKndR5vDHL8rSuMIIWP8u4Mw4VxLyxZW6D5w==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.20.0.tgz", + "integrity": "sha512-p4rvHQRDTI1tGGMDFQm+GtxP1ZHyAh64WANVoyEcNMpaTFn3ox/3CcgtIlELnRfKzSs/DwYlDccJEtr3O6qBvA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.19.1", - "@typescript-eslint/visitor-keys": "6.19.1" + "@typescript-eslint/types": "6.20.0", + "@typescript-eslint/visitor-keys": "6.20.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -22049,13 +21589,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.19.1.tgz", - "integrity": "sha512-0vdyld3ecfxJuddDjACUvlAeYNrHP/pDeQk2pWBR2ESeEzQhg52DF53AbI9QCBkYE23lgkhLCZNkHn2hEXXYIg==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.20.0.tgz", + "integrity": "sha512-qnSobiJQb1F5JjN0YDRPHruQTrX7ICsmltXhkV536mp4idGAYrIyr47zF/JmkJtEcAVnIz4gUYJ7gOZa6SmN4g==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.19.1", - "@typescript-eslint/utils": "6.19.1", + "@typescript-eslint/typescript-estree": "6.20.0", + "@typescript-eslint/utils": "6.20.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -22076,9 +21616,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.1.tgz", - "integrity": "sha512-6+bk6FEtBhvfYvpHsDgAL3uo4BfvnTnoge5LrrCj2eJN8g3IJdLTD4B/jK3Q6vo4Ql/Hoip9I8aB6fF+6RfDqg==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.20.0.tgz", + "integrity": "sha512-MM9mfZMAhiN4cOEcUOEx+0HmuaW3WBfukBZPCfwSqFnQy0grXYtngKCqpQN339X3RrwtzspWJrpbrupKYUSBXQ==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -22089,13 +21629,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.1.tgz", - "integrity": "sha512-aFdAxuhzBFRWhy+H20nYu19+Km+gFfwNO4TEqyszkMcgBDYQjmPJ61erHxuT2ESJXhlhrO7I5EFIlZ+qGR8oVA==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.20.0.tgz", + "integrity": "sha512-RnRya9q5m6YYSpBN7IzKu9FmLcYtErkDkc8/dKv81I9QiLLtVBHrjz+Ev/crAqgMNW2FCsoZF4g2QUylMnJz+g==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.19.1", - "@typescript-eslint/visitor-keys": "6.19.1", + "@typescript-eslint/types": "6.20.0", + "@typescript-eslint/visitor-keys": "6.20.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -22150,17 +21690,17 @@ "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.19.1.tgz", - "integrity": "sha512-JvjfEZuP5WoMqwh9SPAPDSHSg9FBHHGhjPugSRxu5jMfjvBpq5/sGTD+9M9aQ5sh6iJ8AY/Kk/oUYVEMAPwi7w==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.20.0.tgz", + "integrity": "sha512-/EKuw+kRu2vAqCoDwDCBtDRU6CTKbUmwwI7SH7AashZ+W+7o8eiyy6V2cdOqN49KsTcASWsC5QeghYuRDTyOOg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.19.1", - "@typescript-eslint/types": "6.19.1", - "@typescript-eslint/typescript-estree": "6.19.1", + "@typescript-eslint/scope-manager": "6.20.0", + "@typescript-eslint/types": "6.20.0", + "@typescript-eslint/typescript-estree": "6.20.0", "semver": "^7.5.4" }, "engines": { @@ -22208,12 +21748,12 @@ "dev": true }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.1.tgz", - "integrity": "sha512-gkdtIO+xSO/SmI0W68DBg4u1KElmIUo3vXzgHyGPs6cxgB0sa3TlptRAAE0hUY1hM6FcDKEv7aIwiTGm76cXfQ==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.20.0.tgz", + "integrity": "sha512-E8Cp98kRe4gKHjJD4NExXKz/zOJ1A2hhZc+IMVD6i7w4yjIvh6VyuRI0gRtxAsXtoC35uGMaQ9rjI2zJaXDEAw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/types": "6.20.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -23543,9 +23083,9 @@ } }, "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.6.tgz", + "integrity": "sha512-j1QzY8iPNPG4o4xmO3ptzpRxTciqD3MgEHtifP/YnJpIo58Xu+ne4BejlbkuaLfXn/nz6HFiw29bLpj2PNMdGg==", "engines": { "node": ">= 0.4" }, @@ -23554,9 +23094,9 @@ } }, "node_modules/aws-cdk": { - "version": "2.124.0", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.124.0.tgz", - "integrity": "sha512-kUOfqwIAaTEx4ZozojZEhWa8G+O9KU+P0tERtDVmTw9ip4QXNMwTTkjj/IPtoH8qfXGdeibTQ9MJwRvHOR8kXQ==", + "version": "2.125.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.125.0.tgz", + "integrity": "sha512-6qFtaDPzhddhwIbCpqBjMePzZS7bfthGFQYfcwF1OhqMv2f3VpHQQ0f7kz4UxXJXUIR5BbgCnlpawH3c0aNzKw==", "bin": { "cdk": "bin/cdk" }, @@ -23568,9 +23108,9 @@ } }, "node_modules/aws-cdk-lib": { - "version": "2.124.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.124.0.tgz", - "integrity": "sha512-K/Tey8TMw30GO6UD0qb19CPhBMZhleGshz520ZnbDUJwNfFtejwZOnpmRMOdUP9f4tHc5BrXl1VGsZtXtUaGhg==", + "version": "2.125.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.125.0.tgz", + "integrity": "sha512-yRcHuvpPYHuvffeJCnTSIqo6y+Qjeuf+BKmr/oyMcMhyfIzcGFFhh+ZQRCTYIJTfTyU6nh73TLhsZ4TmzFuBBA==", "bundleDependencies": [ "@balena/dockerignore", "case", @@ -25395,9 +24935,9 @@ "dev": true }, "node_modules/bullmq": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.1.5.tgz", - "integrity": "sha512-Rc9QGHrj/wJ8RMENKa839o1pJmdicg7KBTfmVU8YqYuEK2JcMSJaKMg2XrAi7sdYSawgOJgC/kiW9fCGYEj6Yg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.1.6.tgz", + "integrity": "sha512-VkLfig+xm4U3hc4QChzuuAy0NGQ9dfPB8o54hmcZHCX9ofp0Zn6bEY+W3Ytkk76eYwPAgXfywDBlAb2Unjl1Rg==", "dependencies": { "cron-parser": "^4.6.0", "glob": "^8.0.3", @@ -25728,9 +25268,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001580", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001580.tgz", - "integrity": "sha512-mtj5ur2FFPZcCEpXFy8ADXbDACuNFXg6mxVDqp7tqooX6l3zwm+d8EPoeOSIFRDvHs8qu7/SLFOGniULkcH2iA==", + "version": "1.0.30001582", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001582.tgz", + "integrity": "sha512-vsJG3V5vgfduaQGVxL53uSX/HUzxyr2eA8xCo36OLal7sRcSZbibJtLeh0qja4sFOr/QQGt4opB4tOy+eOgAxg==", "funding": [ { "type": "opencollective", @@ -25757,11 +25297,11 @@ } }, "node_modules/cdk": { - "version": "2.124.0", - "resolved": "https://registry.npmjs.org/cdk/-/cdk-2.124.0.tgz", - "integrity": "sha512-sX/6smDi41L+3Fl+KKhwfCDc5sso8OkctllOvMbmGRQ3uKgmSRTpiPIfV+BYGBHUPJ4u2mnSqacIq4sIccR+Mw==", + "version": "2.125.0", + "resolved": "https://registry.npmjs.org/cdk/-/cdk-2.125.0.tgz", + "integrity": "sha512-sSfchANTWXlztwSXJRV2j6dr/3ECwho4orFUmgmdr+4VZirOpREPPr3Uc+3Dao1U08C3O/SjvmBpYDDsIciVcw==", "dependencies": { - "aws-cdk": "2.124.0" + "aws-cdk": "2.125.0" }, "bin": { "cdk": "bin/cdk" @@ -25771,18 +25311,18 @@ } }, "node_modules/cdk-nag": { - "version": "2.28.22", - "resolved": "https://registry.npmjs.org/cdk-nag/-/cdk-nag-2.28.22.tgz", - "integrity": "sha512-xMdWVccpA0S5JAJ/I1KQg0Hrp7FxNxWP8DHj6ucVYpKy7e1R0XmZLBXO0bFGEgvZpFwOLO5GST741BauE4jaNg==", + "version": "2.28.27", + "resolved": "https://registry.npmjs.org/cdk-nag/-/cdk-nag-2.28.27.tgz", + "integrity": "sha512-b9QnWZ6KPoFZtowMxYPlsdTEoMrOfg0jRx09gr4525uHlO6gWf+eNJtXUmQRYcBgaWF05a9vGBnbq+QKoslbNw==", "peerDependencies": { "aws-cdk-lib": "^2.116.0", "constructs": "^10.0.5" } }, "node_modules/cdk-serverless-clamscan": { - "version": "2.6.84", - "resolved": "https://registry.npmjs.org/cdk-serverless-clamscan/-/cdk-serverless-clamscan-2.6.84.tgz", - "integrity": "sha512-UMlmPo+nGVc1rC4Mx4Y8f3K6GfN3bsgI2qNulpUbhspPaFd6NMTqOUrom6ydfXcY8S19RfLCzjBD9OsU79e2Lw==", + "version": "2.6.88", + "resolved": "https://registry.npmjs.org/cdk-serverless-clamscan/-/cdk-serverless-clamscan-2.6.88.tgz", + "integrity": "sha512-tX27dMwoUEsrJ/87HpfSBo2mwMaFXZMuGhKA5FCVnTcrv37TVARizp6S/zaLeGaNjboGMl/b2RUzF9rfxeUHVg==", "bin": { "0": "assets" }, @@ -27458,9 +26998,9 @@ } }, "node_modules/css-loader": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.9.1.tgz", - "integrity": "sha512-OzABOh0+26JKFdMzlK6PY1u5Zx8+Ck7CVRlcGNZoY9qwJjdfu2VWFuprTIpPW+Av5TZTVViYWcFQaEEQURLknQ==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.10.0.tgz", + "integrity": "sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==", "dependencies": { "icss-utils": "^5.1.0", "postcss": "^8.4.33", @@ -27479,7 +27019,16 @@ "url": "https://opencollective.com/webpack" }, "peerDependencies": { + "@rspack/core": "0.x || 1.x", "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, "node_modules/css-loader/node_modules/lru-cache": { @@ -29300,9 +28849,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.648", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.648.tgz", - "integrity": "sha512-EmFMarXeqJp9cUKu/QEciEApn0S/xRcpZWuAm32U7NgoZCimjsilKXHRO9saeEW55eHZagIDg6XTUOv32w9pjg==" + "version": "1.4.653", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.653.tgz", + "integrity": "sha512-wA2A2LQCqnEwQAvwADQq3KpMpNwgAUBnRmrFgRzHnPhbQUFArTR32Ab46f4p0MovDLcg4uqd4nCsN2hTltslpA==" }, "node_modules/elkjs": { "version": "0.9.1", @@ -31865,9 +31414,9 @@ "dev": true }, "node_modules/fhirpath": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/fhirpath/-/fhirpath-3.10.0.tgz", - "integrity": "sha512-/QMLMuzljlhsx6XatrnOCild5ZNAu5/y0Kr8NeibUSMprHIY4pVZpN11Ah4mNLEOs2UuGKLQxiXX5NUJ1EUn7w==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/fhirpath/-/fhirpath-3.10.1.tgz", + "integrity": "sha512-VaiivIB77CF1ECm/jQ2Q/YSqDY0jSXDZYP/kG//p5utLbyoEFMBKfG5e7oZ2qBMvy39S2bx7AXn8RL9PFLQ95A==", "dev": true, "dependencies": { "@lhncbc/ucum-lhc": "^5.0.0", @@ -32787,7 +32336,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -32801,7 +32349,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -33039,9 +32586,9 @@ } }, "node_modules/gaxios": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.1.1.tgz", - "integrity": "sha512-bw8smrX+XlAoo9o1JAksBwX+hi/RG15J+NTSxmNPIclKC3ZVK6C2afwY8OSdRvOK0+ZLecUJYtj2MmjOt3Dm0w==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.2.0.tgz", + "integrity": "sha512-H6+bHeoEAU5D6XNc6mPKeN5dLZqEDs9Gpk6I+SZBEzK5So58JVrHPmevNi35fRl1J9Y5TaeLW0kYx3pCJ1U2mQ==", "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", @@ -33703,12 +33250,9 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dependencies": { - "has-symbols": "^1.0.2" - }, + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.1.tgz", + "integrity": "sha512-6J4rC9ROz0UkOpjn0BRtSSqlewDTDYJNQvy8N8RSrPCduUWId1o9BQPEVII/KKBqRk/ZIQff1YbRkUDCH2N5Sg==", "engines": { "node": ">= 0.4" }, @@ -34470,9 +34014,9 @@ ] }, "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "engines": { "node": ">= 4" } @@ -37827,13 +37371,13 @@ "dev": true }, "node_modules/joi": { - "version": "17.12.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.0.tgz", - "integrity": "sha512-HSLsmSmXz+PV9PYoi3p7cgIbj06WnEBNT28n+bbBNcPZXZFqCzzvGqpTBPujx/Z0nh1+KNQPDrNgdmQ8dq0qYw==", + "version": "17.12.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.1.tgz", + "integrity": "sha512-vtxmq+Lsc5SlfqotnfVjlViWfOL9nt/avKNbKYizwf6gsCfq9NYY/ceYRMFD8XDdrjJ9abJyScWmhmIiy+XRtQ==", "dependencies": { "@hapi/hoek": "^9.3.0", "@hapi/topo": "^5.1.0", - "@sideway/address": "^4.1.4", + "@sideway/address": "^4.1.5", "@sideway/formula": "^3.0.1", "@sideway/pinpoint": "^2.0.0" } @@ -39356,9 +38900,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "version": "0.30.6", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.6.tgz", + "integrity": "sha512-n62qCLbPjNjyo+owKtveQxZFZTBm+Ms6YoGD23Wew6Vw337PElFNifQpknPruVRQV57kVShPnLGo9vWxVhpPvA==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -39379,9 +38923,9 @@ } }, "node_modules/mailparser": { - "version": "3.6.6", - "resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.6.6.tgz", - "integrity": "sha512-noCjBl3FToxmqTP2fp7z17hQsiCroWNntfTd8O+UejOAF59xeN5WGZK27ilexXV2e2X/cbUhG3L8sfEKaz0/sw==", + "version": "3.6.7", + "resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.6.7.tgz", + "integrity": "sha512-/3x8HW70DNehw+3vdOPKdlLuxOHoWcGB5jfx5vJ5XUbY9/2jUJbrrhda5Si8Dj/3w08U0y5uGAkqs5+SPTPKoA==", "dev": true, "dependencies": { "encoding-japanese": "2.0.0", @@ -39391,7 +38935,7 @@ "libmime": "5.2.1", "linkify-it": "5.0.0", "mailsplit": "5.4.0", - "nodemailer": "6.9.8", + "nodemailer": "6.9.9", "tlds": "1.248.0" } }, @@ -39565,9 +39109,9 @@ } }, "node_modules/markdown-to-jsx": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.4.0.tgz", - "integrity": "sha512-zilc+MIkVVXPyTb4iIUTIz9yyqfcWjszGXnwF9K/aiBWcHXFcmdEMTkG01/oQhwSCH7SY1BnG6+ev5BzWmbPrg==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.4.1.tgz", + "integrity": "sha512-GbrbkTnHp9u6+HqbPRFJbObi369AgJNXi/sGqq5HRsoZW063xR1XDCaConqq+whfEIAlzB1YPnOgsPc7B7bc/A==", "dev": true, "engines": { "node": ">= 10" @@ -39823,9 +39367,9 @@ } }, "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -40002,9 +39546,9 @@ } }, "node_modules/mdast-util-phrasing": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.0.0.tgz", - "integrity": "sha512-xadSsJayQIucJ9n053dfQwVu1kuXg7jCTdYsMK8rqzKZh52nLfSH/k0sAxE0u+pj/zKZX+o5wB+ML5mRayOxFA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", "dev": true, "dependencies": { "@types/mdast": "^4.0.0", @@ -41370,9 +40914,9 @@ } }, "node_modules/micromark-core-commonmark/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -41445,9 +40989,9 @@ } }, "node_modules/micromark-extension-directive/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -41497,9 +41041,9 @@ } }, "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -41569,9 +41113,9 @@ } }, "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -41645,9 +41189,9 @@ } }, "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -41752,9 +41296,9 @@ } }, "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -41838,9 +41382,9 @@ } }, "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -41920,9 +41464,9 @@ } }, "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -41998,9 +41542,9 @@ } }, "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -42088,9 +41632,9 @@ } }, "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -42157,9 +41701,9 @@ } }, "node_modules/micromark-factory-destination/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -42215,9 +41759,9 @@ } }, "node_modules/micromark-factory-label/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -42277,9 +41821,9 @@ } }, "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -42391,9 +41935,9 @@ } }, "node_modules/micromark-factory-title/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -42469,9 +42013,9 @@ } }, "node_modules/micromark-factory-whitespace/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -42597,9 +42141,9 @@ } }, "node_modules/micromark-util-classify-character/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -42710,9 +42254,9 @@ } }, "node_modules/micromark-util-decode-string/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -42895,9 +42439,9 @@ } }, "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -43021,9 +42565,9 @@ } }, "node_modules/micromark/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -43126,11 +42670,12 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.7.7", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.7.tgz", - "integrity": "sha512-+0n11YGyRavUR3IlaOzJ0/4Il1avMvJ1VJfhWfCn24ITQXhRr1gghbhhrda6tgtNcpZaWKdSuwKq20Jb7fnlyw==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.0.tgz", + "integrity": "sha512-CxmUYPFcTgET1zImteG/LZOy/4T5rTojesQXkSNBiquhydn78tfbCE9sjIjnJ/UcjNjOC1bphTCCW5rrS7cXAg==", "dependencies": { - "schema-utils": "^4.0.0" + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" }, "engines": { "node": ">= 12.13.0" @@ -43152,7 +42697,6 @@ "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -43810,9 +43354,9 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, "node_modules/nise": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.7.tgz", - "integrity": "sha512-wWtNUhkT7k58uvWTB/Gy26eA/EJKtPZFVAhEilN5UYVmmGRYOURbejRUyKm0Uu9XVEW7K5nBOZfR8VMB4QR2RQ==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", "dependencies": { "@sinonjs/commons": "^3.0.0", "@sinonjs/fake-timers": "^11.2.2", @@ -44577,9 +44121,9 @@ } }, "node_modules/nodemailer": { - "version": "6.9.8", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.8.tgz", - "integrity": "sha512-cfrYUk16e67Ks051i4CntM9kshRYei1/o/Gi8K1d+R34OIs21xdFnW7Pt7EucmVKA0LKtqUGNcjMZ7ehjl49mQ==", + "version": "6.9.9", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.9.tgz", + "integrity": "sha512-dexTll8zqQoVJEZPwQAKzxxtFn0qTnjdQTchoU6Re9BUUGBJiOy3YMn/0ShTW6J5M0dfQ1NeDeRTTl4oIWgQMA==", "engines": { "node": ">=6.0.0" } @@ -47881,9 +47425,9 @@ } }, "node_modules/postcss-preset-mantine": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/postcss-preset-mantine/-/postcss-preset-mantine-1.12.3.tgz", - "integrity": "sha512-cCwowf20mIyRXnV1cSVoMGfhYgy8ZqFJWsEJthdMZ3n7LijjucE9l/HO47gv5gAtr9nY1MkaEkpWS7ulhSTbSg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/postcss-preset-mantine/-/postcss-preset-mantine-1.13.0.tgz", + "integrity": "sha512-1bv/mQz2K+/FixIMxYd83BYH7PusDZaI7LpUtKbb1l/5N5w6t1p/V9ONHfRJeeAZyfa6Xc+AtR+95VKdFXRH1g==", "dev": true, "dependencies": { "postcss-mixins": "^9.0.4", @@ -48181,9 +47725,9 @@ } }, "node_modules/postgres-range": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.3.tgz", - "integrity": "sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.4.tgz", + "integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==" }, "node_modules/prebuild-install": { "version": "7.1.1", @@ -49296,12 +48840,18 @@ } }, "node_modules/react-intersection-observer": { - "version": "9.5.3", - "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.5.3.tgz", - "integrity": "sha512-NJzagSdUPS5rPhaLsHXYeJbsvdpbJwL6yCHtMk91hc0ufQ2BnXis+0QQ9NBh6n9n+Q3OyjR6OQLShYbaNBkThQ==", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.6.0.tgz", + "integrity": "sha512-ZJCi6aOY4kIulpUhWRNIFTyMUKOmSA25iw8sKH07fhlqWTaWD5CWvWarqH4N31EyjCFKsgetvr/amRpnuEWzRg==", "dev": true, "peerDependencies": { - "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } } }, "node_modules/react-is": { @@ -49352,17 +48902,17 @@ } }, "node_modules/react-native": { - "version": "0.73.2", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.73.2.tgz", - "integrity": "sha512-7zj9tcUYpJUBdOdXY6cM8RcXYWkyql4kMyGZflW99E5EuFPoC7Ti+ZQSl7LP9ZPzGD0vMfslwyDW0I4tPWUCFw==", + "version": "0.73.3", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.73.3.tgz", + "integrity": "sha512-RSQDtT2DNUcmB4IgmW9NhRb5wqvXFl6DI2NEJmt0ps2OrVHpoA8Tkq+lkFOA/fvPscJKtFKEHFBDSR5UHR3PUw==", "dependencies": { "@jest/create-cache-key-function": "^29.6.3", - "@react-native-community/cli": "12.3.0", - "@react-native-community/cli-platform-android": "12.3.0", - "@react-native-community/cli-platform-ios": "12.3.0", + "@react-native-community/cli": "12.3.2", + "@react-native-community/cli-platform-android": "12.3.2", + "@react-native-community/cli-platform-ios": "12.3.2", "@react-native/assets-registry": "0.73.1", "@react-native/codegen": "0.73.2", - "@react-native/community-cli-plugin": "0.73.12", + "@react-native/community-cli-plugin": "0.73.14", "@react-native/gradle-plugin": "0.73.4", "@react-native/js-polyfills": "0.73.1", "@react-native/normalize-colors": "0.73.2", @@ -49371,6 +48921,7 @@ "anser": "^1.4.9", "ansi-regex": "^5.0.0", "base64-js": "^1.5.1", + "chalk": "^4.0.0", "deprecated-react-native-prop-types": "^5.0.0", "event-target-shim": "^5.0.1", "flow-enums-runtime": "^0.0.6", @@ -52084,9 +51635,9 @@ "dev": true }, "node_modules/sort-package-json": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-2.6.0.tgz", - "integrity": "sha512-XSQ+lY9bAYA8ZsoChcEoPlgcSMaheziEp1beox1JVxy1SV4F2jSq9+h2rJ+3mC/Dhu9Ius1DLnInD5AWcsDXZw==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-2.7.0.tgz", + "integrity": "sha512-6AayF8bp6L+WROgpbhTMUtB9JSFmpGHjmW7DyaNPS1HwlTw2oSVlUUtlkHSEZmg5o89F3zvLBZNvMeZ1T4fjQg==", "dev": true, "dependencies": { "detect-indent": "^7.0.1", @@ -53044,12 +52595,12 @@ "dev": true }, "node_modules/storybook": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-7.6.10.tgz", - "integrity": "sha512-ypFeGhQTUBBfqSUVZYh7wS5ghn3O2wILCiQc4459SeUpvUn+skcqw/TlrwGSoF5EWjDA7gtRrWDxO3mnlPt5Cw==", + "version": "7.6.12", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-7.6.12.tgz", + "integrity": "sha512-zcH9CwIsE8N4PX3he5vaJ3mTTWGxu7cxJ/ag9oja/k3N5/IvQjRyIU1TLkRVb28BB8gaLyorpnc4C4aLVGy4WQ==", "dev": true, "dependencies": { - "@storybook/cli": "7.6.10" + "@storybook/cli": "7.6.12" }, "bin": { "sb": "index.js", @@ -55018,26 +54569,26 @@ } }, "node_modules/turbo": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/turbo/-/turbo-1.11.3.tgz", - "integrity": "sha512-RCJOUFcFMQNIGKSjC9YmA5yVP1qtDiBA0Lv9VIgrXraI5Da1liVvl3VJPsoDNIR9eFMyA/aagx1iyj6UWem5hA==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/turbo/-/turbo-1.12.2.tgz", + "integrity": "sha512-BcoQjBZ+LJCMdjzWhzQflOinUjek28rWXj07aaaAQ8T3Ehs0JFSjIsXOm4qIbo52G4xk3gFVcUtJhh/QRADl7g==", "dev": true, "bin": { "turbo": "bin/turbo" }, "optionalDependencies": { - "turbo-darwin-64": "1.11.3", - "turbo-darwin-arm64": "1.11.3", - "turbo-linux-64": "1.11.3", - "turbo-linux-arm64": "1.11.3", - "turbo-windows-64": "1.11.3", - "turbo-windows-arm64": "1.11.3" + "turbo-darwin-64": "1.12.2", + "turbo-darwin-arm64": "1.12.2", + "turbo-linux-64": "1.12.2", + "turbo-linux-arm64": "1.12.2", + "turbo-windows-64": "1.12.2", + "turbo-windows-arm64": "1.12.2" } }, "node_modules/turbo-darwin-64": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-1.11.3.tgz", - "integrity": "sha512-IsOOg2bVbIt3o/X8Ew9fbQp5t1hTHN3fGNQYrPQwMR2W1kIAC6RfbVD4A9OeibPGyEPUpwOH79hZ9ydFH5kifw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-1.12.2.tgz", + "integrity": "sha512-Aq/ePQ5KNx6XGwlZWTVTqpQYfysm1vkwkI6kAYgrX5DjMWn+tUXrSgNx4YNte0F+V4DQ7PtuWX+jRG0h0ZNg0A==", "cpu": [ "x64" ], @@ -55048,9 +54599,9 @@ ] }, "node_modules/turbo-darwin-arm64": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-1.11.3.tgz", - "integrity": "sha512-FsJL7k0SaPbJzI/KCnrf/fi3PgCDCjTliMc/kEFkuWVA6Httc3Q4lxyLIIinz69q6JTx8wzh6yznUMzJRI3+dg==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-wTr+dqkwJo/eXE+4SPTSeNBKyyfQJhI6I9sKVlCSBmtaNEqoGNgdVzgMUdqrg9AIFzLIiKO+zhfskNaSWpVFow==", "cpu": [ "arm64" ], @@ -55061,9 +54612,9 @@ ] }, "node_modules/turbo-linux-64": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-1.11.3.tgz", - "integrity": "sha512-SvW7pvTVRGsqtSkII5w+wriZXvxqkluw5FO/MNAdFw0qmoov+PZ237+37/NgArqE3zVn1GX9P6nUx9VO+xcQAg==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-1.12.2.tgz", + "integrity": "sha512-BggBKrLojGarDaa2zBo+kUR3fmjpd6bLA8Unm3Aa2oJw0UvEi3Brd+w9lNsPZHXXQYBUzNUY2gCdxf3RteWb0g==", "cpu": [ "x64" ], @@ -55074,9 +54625,9 @@ ] }, "node_modules/turbo-linux-arm64": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-1.11.3.tgz", - "integrity": "sha512-YhUfBi1deB3m+3M55X458J6B7RsIS7UtM3P1z13cUIhF+pOt65BgnaSnkHLwETidmhRh8Dl3GelaQGrB3RdCDw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-1.12.2.tgz", + "integrity": "sha512-v/apSRvVuwYjq1D9MJFsHv2EpGd1S4VoSdZvVfW6FaM06L8CFZa92urNR1svdGYN28YVKwK9Ikc9qudC6t/d5A==", "cpu": [ "arm64" ], @@ -55087,9 +54638,9 @@ ] }, "node_modules/turbo-windows-64": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-1.11.3.tgz", - "integrity": "sha512-s+vEnuM2TiZuAUUUpmBHDr6vnNbJgj+5JYfnYmVklYs16kXh+EppafYQOAkcRIMAh7GjV3pLq5/uGqc7seZeHA==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-1.12.2.tgz", + "integrity": "sha512-3uDdwXcRGkgopYFdPDpxQiuQjfQ12Fxq0fhj+iGymav0eWA4W4wzYwSdlUp6rT22qOBIzaEsrIspRwx1DsMkNg==", "cpu": [ "x64" ], @@ -55100,9 +54651,9 @@ ] }, "node_modules/turbo-windows-arm64": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-1.11.3.tgz", - "integrity": "sha512-ZR5z5Zpc7cASwfdRAV5yNScCZBsgGSbcwiA/u3farCacbPiXsfoWUkz28iyrx21/TRW0bi6dbsB2v17swa8bjw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-1.12.2.tgz", + "integrity": "sha512-zNIHnwtQfJSjFi7movwhPQh2rfrcKZ7Xv609EN1yX0gEp9GxooCUi2yNnBQ8wTqFjioA2M5hZtGJQ0RrKaEm/Q==", "cpu": [ "arm64" ], @@ -57165,9 +56716,9 @@ } }, "node_modules/webpack": { - "version": "5.90.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.0.tgz", - "integrity": "sha512-bdmyXRCXeeNIePv6R6tGPyy20aUobw4Zy8r0LUS2EWO+U+Ke/gYDgsCh7bl5rB6jPpr4r0SZa6dPxBxLooDT3w==", + "version": "5.90.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.1.tgz", + "integrity": "sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==", "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", @@ -57787,15 +57338,15 @@ "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" }, "node_modules/which-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", - "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz", + "integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.4", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.5", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "has-tostringtag": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -58457,23 +58008,23 @@ "version": "3.0.2", "license": "Apache-2.0", "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/dropzone": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", + "@mantine/core": "7.5.1", + "@mantine/dropzone": "7.5.1", + "@mantine/hooks": "7.5.1", + "@mantine/notifications": "7.5.1", "@medplum/core": "*", "@medplum/definitions": "*", "@medplum/fhirtypes": "*", "@medplum/mock": "*", "@medplum/react": "*", "@tabler/icons-react": "2.46.0", - "@testing-library/jest-dom": "6.3.0", - "@testing-library/react": "14.1.2", - "@types/react": "18.2.48", + "@testing-library/jest-dom": "6.4.1", + "@testing-library/react": "14.2.0", + "@types/react": "18.2.51", "@types/react-dom": "18.2.18", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.21.3", @@ -58604,12 +58155,12 @@ "version": "3.0.2", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.502.0", "@medplum/core": "*", - "aws-cdk-lib": "2.124.0", - "cdk": "2.124.0", - "cdk-nag": "2.28.22", - "cdk-serverless-clamscan": "2.6.84", + "aws-cdk-lib": "2.125.0", + "cdk": "2.125.0", + "cdk-nag": "2.28.27", + "cdk-serverless-clamscan": "2.6.88", "constructs": "10.3.0" }, "engines": { @@ -58621,14 +58172,14 @@ "version": "3.0.2", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-acm": "3.501.0", - "@aws-sdk/client-cloudformation": "3.501.0", - "@aws-sdk/client-cloudfront": "3.501.0", - "@aws-sdk/client-ecs": "3.501.0", - "@aws-sdk/client-s3": "3.501.0", - "@aws-sdk/client-ssm": "3.501.0", - "@aws-sdk/client-sts": "3.501.0", - "@aws-sdk/types": "3.496.0", + "@aws-sdk/client-acm": "3.504.0", + "@aws-sdk/client-cloudformation": "3.504.0", + "@aws-sdk/client-cloudfront": "3.504.0", + "@aws-sdk/client-ecs": "3.504.0", + "@aws-sdk/client-s3": "3.504.0", + "@aws-sdk/client-ssm": "3.504.0", + "@aws-sdk/client-sts": "3.504.0", + "@aws-sdk/types": "3.502.0", "@medplum/core": "*", "@medplum/hl7": "*", "aws-sdk-client-mock": "3.0.1", @@ -58710,7 +58261,7 @@ "raw-loader": "4.0.2", "react": "18.2.0", "react-dom": "18.2.0", - "react-intersection-observer": "9.5.3", + "react-intersection-observer": "9.6.0", "react-router-dom": "6.21.3", "typescript": "5.3.3", "url-loader": "4.1.1" @@ -58733,8 +58284,8 @@ "version": "3.0.2", "license": "Apache-2.0", "devDependencies": { - "@typescript-eslint/eslint-plugin": "6.19.1", - "@typescript-eslint/parser": "6.19.1", + "@typescript-eslint/eslint-plugin": "6.20.0", + "@typescript-eslint/parser": "6.20.0", "eslint": "8.56.0", "eslint-plugin-jsdoc": "48.0.4", "eslint-plugin-json-files": "4.1.0", @@ -58780,7 +58331,7 @@ "devDependencies": { "@medplum/core": "*", "@types/base-64": "1.0.2", - "@types/react": "18.2.48", + "@types/react": "18.2.51", "@types/text-encoding": "0.0.39", "esbuild": "0.20.0", "esbuild-node-externals": "1.12.0", @@ -58834,8 +58385,8 @@ "@types/json-schema": "7.0.15", "@types/pg": "8.11.0", "@types/unzipper": "0.10.9", - "fast-xml-parser": "4.3.3", - "fhirpath": "3.10.0", + "fast-xml-parser": "4.3.4", + "fhirpath": "3.10.1", "mkdirp": "3.0.1", "node-stream-zip": "1.15.0", "pg": "8.11.3", @@ -58847,9 +58398,9 @@ } }, "packages/generator/node_modules/fast-xml-parser": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.3.tgz", - "integrity": "sha512-coV/D1MhrShMvU6D0I+VAK3umz6hUaxxhL0yp/9RjfiYUfAv14rDhGQL+PLForhMdr0wq3PiV07WtkkNjJjNHg==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.4.tgz", + "integrity": "sha512-utnwm92SyozgA3hhH2I8qldf2lBqm6qHOICawRNRFu1qMe3+oqr+GcXjGqTmXTMGE5T4eC03kr/rlh5C1IRdZA==", "dev": true, "funding": [ { @@ -58875,18 +58426,18 @@ "devDependencies": { "@graphiql/react": "0.20.2", "@graphiql/toolkit": "0.9.1", - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", + "@mantine/core": "7.5.1", + "@mantine/hooks": "7.5.1", "@medplum/core": "*", "@medplum/fhirtypes": "*", "@medplum/react": "*", - "@types/react": "18.2.48", + "@types/react": "18.2.51", "@types/react-dom": "18.2.18", "graphiql": "3.1.0", "graphql": "16.8.1", "graphql-ws": "5.14.3", "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", "vite": "5.0.12" @@ -59035,42 +58586,42 @@ "version": "3.0.2", "license": "Apache-2.0", "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", + "@mantine/core": "7.5.1", + "@mantine/hooks": "7.5.1", + "@mantine/notifications": "7.5.1", "@medplum/core": "*", "@medplum/definitions": "*", "@medplum/fhirtypes": "*", "@medplum/mock": "*", "@medplum/react-hooks": "*", - "@storybook/addon-actions": "7.6.10", - "@storybook/addon-essentials": "7.6.10", - "@storybook/addon-links": "7.6.10", - "@storybook/addon-storysource": "7.6.10", - "@storybook/builder-vite": "7.6.10", - "@storybook/react": "7.6.10", - "@storybook/react-vite": "7.6.10", + "@storybook/addon-actions": "7.6.12", + "@storybook/addon-essentials": "7.6.12", + "@storybook/addon-links": "7.6.12", + "@storybook/addon-storysource": "7.6.12", + "@storybook/builder-vite": "7.6.12", + "@storybook/react": "7.6.12", + "@storybook/react-vite": "7.6.12", "@tabler/icons-react": "2.46.0", "@testing-library/dom": "9.3.4", - "@testing-library/jest-dom": "6.3.0", - "@testing-library/react": "14.1.2", + "@testing-library/jest-dom": "6.4.1", + "@testing-library/react": "14.2.0", "@testing-library/user-event": "14.5.2", - "@types/jest": "29.5.11", - "@types/node": "20.11.8", - "@types/react": "18.2.48", + "@types/jest": "29.5.12", + "@types/node": "20.11.16", + "@types/react": "18.2.51", "@types/react-dom": "18.2.18", "@vitejs/plugin-react": "4.2.1", "chromatic": "10.3.1", "jest": "29.7.0", "jest-each": "29.7.0", "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", "rfc6902": "5.1.1", "rimraf": "5.0.5", "sinon": "17.0.1", - "storybook": "7.6.10", + "storybook": "7.6.12", "typescript": "5.3.3", "vite-plugin-turbosnap": "^1.0.3" }, @@ -59111,11 +58662,11 @@ "@medplum/fhirtypes": "*", "@medplum/mock": "*", "@testing-library/dom": "9.3.4", - "@testing-library/jest-dom": "6.3.0", - "@testing-library/react": "14.1.2", - "@types/jest": "29.5.11", - "@types/node": "20.11.8", - "@types/react": "18.2.48", + "@testing-library/jest-dom": "6.4.1", + "@testing-library/react": "14.2.0", + "@types/jest": "29.5.12", + "@types/node": "20.11.16", + "@types/react": "18.2.51", "@types/react-dom": "18.2.18", "jest": "29.7.0", "jest-each": "29.7.0", @@ -59186,19 +58737,19 @@ "version": "3.0.2", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-cloudwatch-logs": "3.501.0", - "@aws-sdk/client-lambda": "3.501.0", - "@aws-sdk/client-s3": "3.501.0", - "@aws-sdk/client-secrets-manager": "3.501.0", - "@aws-sdk/client-sesv2": "3.501.0", - "@aws-sdk/client-ssm": "3.501.0", + "@aws-sdk/client-cloudwatch-logs": "3.504.0", + "@aws-sdk/client-lambda": "3.504.0", + "@aws-sdk/client-s3": "3.504.0", + "@aws-sdk/client-secrets-manager": "3.504.0", + "@aws-sdk/client-sesv2": "3.504.0", + "@aws-sdk/client-ssm": "3.504.0", "@aws-sdk/cloudfront-signer": "3.496.0", - "@aws-sdk/lib-storage": "3.501.0", - "@aws-sdk/types": "3.496.0", + "@aws-sdk/lib-storage": "3.504.0", + "@aws-sdk/types": "3.502.0", "@medplum/core": "*", "@medplum/definitions": "*", "@medplum/fhir-router": "*", - "@opentelemetry/auto-instrumentations-node": "0.40.3", + "@opentelemetry/auto-instrumentations-node": "0.41.0", "@opentelemetry/exporter-metrics-otlp-proto": "0.48.0", "@opentelemetry/exporter-trace-otlp-proto": "0.48.0", "@opentelemetry/sdk-metrics": "1.21.0", @@ -59207,7 +58758,7 @@ "@smithy/util-stream": "2.1.1", "bcryptjs": "2.4.3", "body-parser": "1.20.2", - "bullmq": "5.1.5", + "bullmq": "5.1.6", "bytes": "3.1.2", "compression": "1.7.4", "cookie-parser": "1.4.6", @@ -59225,7 +58776,7 @@ "jose": "5.2.0", "jszip": "3.10.1", "node-fetch": "2.7.0", - "nodemailer": "6.9.8", + "nodemailer": "6.9.9", "otplib": "12.0.1", "pg": "8.11.3", "pg-cursor": "2.10.3", @@ -59249,7 +58800,7 @@ "@types/ioredis": "4.28.10", "@types/json-schema": "7.0.15", "@types/mailparser": "3.4.4", - "@types/node": "20.11.8", + "@types/node": "20.11.16", "@types/node-fetch": "2.6.11", "@types/nodemailer": "6.4.14", "@types/pg": "8.11.0", @@ -59262,7 +58813,7 @@ "@types/ws": "8.5.10", "aws-sdk-client-mock": "3.0.1", "aws-sdk-client-mock-jest": "3.0.1", - "mailparser": "3.6.6", + "mailparser": "3.6.7", "openapi3-ts": "4.2.1", "set-cookie-parser": "2.6.0", "supertest": "6.3.4", diff --git a/package.json b/package.json index 1c5c3c729b..3dec839b2d 100644 --- a/package.json +++ b/package.json @@ -39,8 +39,8 @@ "@cyclonedx/cyclonedx-npm": "1.16.1", "@microsoft/api-documenter": "7.23.20", "@microsoft/api-extractor": "7.39.4", - "@types/jest": "29.5.11", - "@types/node": "20.11.8", + "@types/jest": "29.5.12", + "@types/node": "20.11.16", "babel-jest": "29.7.0", "babel-preset-vite": "1.1.3", "cross-env": "7.0.3", @@ -53,11 +53,11 @@ "nyc": "15.1.0", "prettier": "3.2.4", "rimraf": "5.0.5", - "sort-package-json": "2.6.0", + "sort-package-json": "2.7.0", "source-map-explorer": "2.5.3", "ts-node": "10.9.2", "tslib": "2.6.2", - "turbo": "1.11.3", + "turbo": "1.12.2", "typescript": "5.3.3" }, "packageManager": "npm@9.8.1", diff --git a/packages/app/package.json b/packages/app/package.json index 6a3a992f03..309a55b5b0 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -29,23 +29,23 @@ "last 1 Chrome versions" ], "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/dropzone": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", + "@mantine/core": "7.5.1", + "@mantine/dropzone": "7.5.1", + "@mantine/hooks": "7.5.1", + "@mantine/notifications": "7.5.1", "@medplum/core": "*", "@medplum/definitions": "*", "@medplum/fhirtypes": "*", "@medplum/mock": "*", "@medplum/react": "*", "@tabler/icons-react": "2.46.0", - "@testing-library/jest-dom": "6.3.0", - "@testing-library/react": "14.1.2", - "@types/react": "18.2.48", + "@testing-library/jest-dom": "6.4.1", + "@testing-library/react": "14.2.0", + "@types/react": "18.2.51", "@types/react-dom": "18.2.18", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.21.3", diff --git a/packages/cdk/package.json b/packages/cdk/package.json index 68dd81b1cd..89ad9131a6 100644 --- a/packages/cdk/package.json +++ b/packages/cdk/package.json @@ -23,12 +23,12 @@ "test": "jest --runInBand" }, "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.502.0", "@medplum/core": "*", - "aws-cdk-lib": "2.124.0", - "cdk": "2.124.0", - "cdk-nag": "2.28.22", - "cdk-serverless-clamscan": "2.6.84", + "aws-cdk-lib": "2.125.0", + "cdk": "2.125.0", + "cdk-nag": "2.28.27", + "cdk-serverless-clamscan": "2.6.88", "constructs": "10.3.0" }, "engines": { diff --git a/packages/cli/package.json b/packages/cli/package.json index f9a90d4e69..0b8ce6a536 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -41,14 +41,14 @@ "test": "jest" }, "dependencies": { - "@aws-sdk/client-acm": "3.501.0", - "@aws-sdk/client-cloudformation": "3.501.0", - "@aws-sdk/client-cloudfront": "3.501.0", - "@aws-sdk/client-ecs": "3.501.0", - "@aws-sdk/client-s3": "3.501.0", - "@aws-sdk/client-ssm": "3.501.0", - "@aws-sdk/client-sts": "3.501.0", - "@aws-sdk/types": "3.496.0", + "@aws-sdk/client-acm": "3.504.0", + "@aws-sdk/client-cloudformation": "3.504.0", + "@aws-sdk/client-cloudfront": "3.504.0", + "@aws-sdk/client-ecs": "3.504.0", + "@aws-sdk/client-s3": "3.504.0", + "@aws-sdk/client-ssm": "3.504.0", + "@aws-sdk/client-sts": "3.504.0", + "@aws-sdk/types": "3.502.0", "@medplum/core": "*", "@medplum/hl7": "*", "aws-sdk-client-mock": "3.0.1", diff --git a/packages/docs/package.json b/packages/docs/package.json index e4d6316e0f..3bfe3b8fa1 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -57,7 +57,7 @@ "raw-loader": "4.0.2", "react": "18.2.0", "react-dom": "18.2.0", - "react-intersection-observer": "9.5.3", + "react-intersection-observer": "9.6.0", "react-router-dom": "6.21.3", "typescript": "5.3.3", "url-loader": "4.1.1" diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 0bd17f63d8..a0544418c1 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -19,8 +19,8 @@ "author": "Medplum ", "main": "index.cjs", "devDependencies": { - "@typescript-eslint/eslint-plugin": "6.19.1", - "@typescript-eslint/parser": "6.19.1", + "@typescript-eslint/eslint-plugin": "6.20.0", + "@typescript-eslint/parser": "6.20.0", "eslint": "8.56.0", "eslint-plugin-jsdoc": "48.0.4", "eslint-plugin-json-files": "4.1.0", diff --git a/packages/expo-polyfills/package.json b/packages/expo-polyfills/package.json index 1111f19f2f..041085ee53 100644 --- a/packages/expo-polyfills/package.json +++ b/packages/expo-polyfills/package.json @@ -49,7 +49,7 @@ "devDependencies": { "@medplum/core": "*", "@types/base-64": "1.0.2", - "@types/react": "18.2.48", + "@types/react": "18.2.51", "@types/text-encoding": "0.0.39", "esbuild": "0.20.0", "esbuild-node-externals": "1.12.0", diff --git a/packages/generator/package.json b/packages/generator/package.json index e5e787d9e6..4586a326c9 100644 --- a/packages/generator/package.json +++ b/packages/generator/package.json @@ -27,8 +27,8 @@ "@types/json-schema": "7.0.15", "@types/pg": "8.11.0", "@types/unzipper": "0.10.9", - "fast-xml-parser": "4.3.3", - "fhirpath": "3.10.0", + "fast-xml-parser": "4.3.4", + "fhirpath": "3.10.1", "mkdirp": "3.0.1", "node-stream-zip": "1.15.0", "pg": "8.11.3", diff --git a/packages/graphiql/package.json b/packages/graphiql/package.json index bf8389c367..15483f5f3e 100644 --- a/packages/graphiql/package.json +++ b/packages/graphiql/package.json @@ -25,18 +25,18 @@ "devDependencies": { "@graphiql/react": "0.20.2", "@graphiql/toolkit": "0.9.1", - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", + "@mantine/core": "7.5.1", + "@mantine/hooks": "7.5.1", "@medplum/core": "*", "@medplum/fhirtypes": "*", "@medplum/react": "*", - "@types/react": "18.2.48", + "@types/react": "18.2.51", "@types/react-dom": "18.2.18", "graphiql": "3.1.0", "graphql": "16.8.1", "graphql-ws": "5.14.3", "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", "vite": "5.0.12" diff --git a/packages/react-hooks/package.json b/packages/react-hooks/package.json index b34aa16d16..2254047b30 100644 --- a/packages/react-hooks/package.json +++ b/packages/react-hooks/package.json @@ -62,11 +62,11 @@ "@medplum/fhirtypes": "*", "@medplum/mock": "*", "@testing-library/dom": "9.3.4", - "@testing-library/jest-dom": "6.3.0", - "@testing-library/react": "14.1.2", - "@types/jest": "29.5.11", - "@types/node": "20.11.8", - "@types/react": "18.2.48", + "@testing-library/jest-dom": "6.4.1", + "@testing-library/react": "14.2.0", + "@types/jest": "29.5.12", + "@types/node": "20.11.16", + "@types/react": "18.2.51", "@types/react-dom": "18.2.18", "jest": "29.7.0", "jest-each": "29.7.0", diff --git a/packages/react/package.json b/packages/react/package.json index eb82f3ad18..de8f3389c4 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -67,42 +67,42 @@ "test": "jest" }, "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", + "@mantine/core": "7.5.1", + "@mantine/hooks": "7.5.1", + "@mantine/notifications": "7.5.1", "@medplum/core": "*", "@medplum/definitions": "*", "@medplum/fhirtypes": "*", "@medplum/mock": "*", "@medplum/react-hooks": "*", - "@storybook/addon-actions": "7.6.10", - "@storybook/addon-essentials": "7.6.10", - "@storybook/addon-links": "7.6.10", - "@storybook/addon-storysource": "7.6.10", - "@storybook/builder-vite": "7.6.10", - "@storybook/react": "7.6.10", - "@storybook/react-vite": "7.6.10", + "@storybook/addon-actions": "7.6.12", + "@storybook/addon-essentials": "7.6.12", + "@storybook/addon-links": "7.6.12", + "@storybook/addon-storysource": "7.6.12", + "@storybook/builder-vite": "7.6.12", + "@storybook/react": "7.6.12", + "@storybook/react-vite": "7.6.12", "@tabler/icons-react": "2.46.0", "@testing-library/dom": "9.3.4", - "@testing-library/jest-dom": "6.3.0", - "@testing-library/react": "14.1.2", + "@testing-library/jest-dom": "6.4.1", + "@testing-library/react": "14.2.0", "@testing-library/user-event": "14.5.2", - "@types/jest": "29.5.11", - "@types/node": "20.11.8", - "@types/react": "18.2.48", + "@types/jest": "29.5.12", + "@types/node": "20.11.16", + "@types/react": "18.2.51", "@types/react-dom": "18.2.18", "@vitejs/plugin-react": "4.2.1", "chromatic": "10.3.1", "jest": "29.7.0", "jest-each": "29.7.0", "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", "rfc6902": "5.1.1", "rimraf": "5.0.5", "sinon": "17.0.1", - "storybook": "7.6.10", + "storybook": "7.6.12", "typescript": "5.3.3", "vite-plugin-turbosnap": "^1.0.3" }, diff --git a/packages/server/package.json b/packages/server/package.json index 1c14f50416..b46e808f3f 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -21,19 +21,19 @@ "test": "jest --runInBand" }, "dependencies": { - "@aws-sdk/client-cloudwatch-logs": "3.501.0", - "@aws-sdk/client-lambda": "3.501.0", - "@aws-sdk/client-s3": "3.501.0", - "@aws-sdk/client-secrets-manager": "3.501.0", - "@aws-sdk/client-sesv2": "3.501.0", - "@aws-sdk/client-ssm": "3.501.0", + "@aws-sdk/client-cloudwatch-logs": "3.504.0", + "@aws-sdk/client-lambda": "3.504.0", + "@aws-sdk/client-s3": "3.504.0", + "@aws-sdk/client-secrets-manager": "3.504.0", + "@aws-sdk/client-sesv2": "3.504.0", + "@aws-sdk/client-ssm": "3.504.0", "@aws-sdk/cloudfront-signer": "3.496.0", - "@aws-sdk/lib-storage": "3.501.0", - "@aws-sdk/types": "3.496.0", + "@aws-sdk/lib-storage": "3.504.0", + "@aws-sdk/types": "3.502.0", "@medplum/core": "*", "@medplum/definitions": "*", "@medplum/fhir-router": "*", - "@opentelemetry/auto-instrumentations-node": "0.40.3", + "@opentelemetry/auto-instrumentations-node": "0.41.0", "@opentelemetry/exporter-metrics-otlp-proto": "0.48.0", "@opentelemetry/exporter-trace-otlp-proto": "0.48.0", "@opentelemetry/sdk-metrics": "1.21.0", @@ -42,7 +42,7 @@ "@smithy/util-stream": "2.1.1", "bcryptjs": "2.4.3", "body-parser": "1.20.2", - "bullmq": "5.1.5", + "bullmq": "5.1.6", "bytes": "3.1.2", "compression": "1.7.4", "cookie-parser": "1.4.6", @@ -60,7 +60,7 @@ "jose": "5.2.0", "jszip": "3.10.1", "node-fetch": "2.7.0", - "nodemailer": "6.9.8", + "nodemailer": "6.9.9", "otplib": "12.0.1", "pg": "8.11.3", "pg-cursor": "2.10.3", @@ -84,7 +84,7 @@ "@types/ioredis": "4.28.10", "@types/json-schema": "7.0.15", "@types/mailparser": "3.4.4", - "@types/node": "20.11.8", + "@types/node": "20.11.16", "@types/node-fetch": "2.6.11", "@types/nodemailer": "6.4.14", "@types/pg": "8.11.0", @@ -97,7 +97,7 @@ "@types/ws": "8.5.10", "aws-sdk-client-mock": "3.0.1", "aws-sdk-client-mock-jest": "3.0.1", - "mailparser": "3.6.6", + "mailparser": "3.6.7", "openapi3-ts": "4.2.1", "set-cookie-parser": "2.6.0", "supertest": "6.3.4", From c9233246005cb9329230f75b4e46503c0cd25363 Mon Sep 17 00:00:00 2001 From: Matt Long Date: Fri, 2 Feb 2024 11:31:37 -0800 Subject: [PATCH 07/81] Refactor elements context and slicing logic (#3869) * Reorg files * s/BackboneElementContext/ElementsContext Since the logic in it needs to be shared with slices * Propagate absolute FHIR path through Input hierarchy * ElementsContext propagates parent contexts * Start cleaning up SliceInput * Walked types and extensions feeling good * Parse slice binding * ElementsContext merges in parent context * Improve CodeableConceptInput typing and tests passing * BackboneElementInput provides context again * Improved logic for assigning values to slices * Tests passing and build working * Don't attempt to set nested properties * PR feedback * thanks, sonar --- packages/app/src/admin/SecretsPage.tsx | 1 + packages/app/src/admin/SitesPage.tsx | 1 + packages/core/src/fhirpath/utils.test.ts | 7 +- packages/core/src/fhirpath/utils.ts | 21 +- packages/core/src/types.ts | 9 +- packages/core/src/typeschema/crawler.ts | 12 +- packages/core/src/typeschema/types.ts | 11 +- packages/generator/src/storybook.ts | 2 + packages/mock/src/mocks/uscore/index.ts | 4 +- .../uscore-v5.0.1-structuredefinitions.json | 2 +- .../BackboneElementInput.stories.tsx | 2 +- .../BackboneElementInput.test.tsx | 7 +- .../BackboneElementInput.tsx | 47 ++-- .../BackboneElementInput.utils.test.ts | 18 -- .../BackboneElementInput.utils.ts | 70 ----- ...eDefinition-us-core-medicationrequest.json | 1 - .../CheckboxFormSection.tsx | 4 +- .../CodeableConceptInput.stories.tsx | 10 +- .../CodeableConceptInput.test.tsx | 31 ++- .../CodeableConceptInput.tsx | 8 +- .../ContactPointInput/ContactPointInput.tsx | 4 +- .../react/src/ElementsInput/ElementsInput.tsx | 105 ++++---- .../ElementsInput/ElementsInput.utils.test.ts | 25 ++ .../src/ElementsInput/ElementsInput.utils.ts | 155 +++++++++++ .../ExtensionInput/ExtensionInput.test.tsx | 17 +- .../src/ExtensionInput/ExtensionInput.tsx | 40 +-- .../react/src/FormSection/FormSection.tsx | 4 +- .../src/IdentifierInput/IdentifierInput.tsx | 4 +- .../react/src/PatientSummary/Allergies.tsx | 2 + .../react/src/PatientSummary/Medications.tsx | 2 + .../PlanDefinitionBuilder.tsx | 1 + .../QuestionnaireBuilder.tsx | 1 + .../ResourceArrayInput.test.tsx | 8 +- .../ResourceArrayInput/ResourceArrayInput.tsx | 245 ++---------------- .../ResourceArrayInput.utils.test.ts | 221 ++++++++++++++++ .../ResourceArrayInput.utils.ts | 120 ++++++--- .../react/src/ResourceForm/ResourceForm.tsx | 1 + .../ResourcePropertyDisplay.utils.ts | 6 +- .../ResourcePropertyInput.stories.tsx | 42 ++- .../ResourcePropertyInput.test.tsx | 11 +- .../ResourcePropertyInput.tsx | 17 +- .../ResourcePropertyInput.utils.ts | 1 + packages/react/src/SliceInput/SliceInput.tsx | 130 ++++++++++ .../react/src/SliceInput/SliceInput.utils.ts | 10 + packages/react/src/buttons/ArrayAddButton.tsx | 30 +++ .../react/src/buttons/ArrayRemoveButton.tsx | 22 ++ 46 files changed, 973 insertions(+), 519 deletions(-) delete mode 100644 packages/react/src/BackboneElementInput/BackboneElementInput.utils.test.ts delete mode 100644 packages/react/src/BackboneElementInput/BackboneElementInput.utils.ts delete mode 100644 packages/react/src/BackboneElementInput/__test__/StructureDefinition-us-core-medicationrequest.json create mode 100644 packages/react/src/ElementsInput/ElementsInput.utils.test.ts create mode 100644 packages/react/src/ElementsInput/ElementsInput.utils.ts create mode 100644 packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.test.ts create mode 100644 packages/react/src/SliceInput/SliceInput.tsx create mode 100644 packages/react/src/SliceInput/SliceInput.utils.ts create mode 100644 packages/react/src/buttons/ArrayAddButton.tsx create mode 100644 packages/react/src/buttons/ArrayRemoveButton.tsx diff --git a/packages/app/src/admin/SecretsPage.tsx b/packages/app/src/admin/SecretsPage.tsx index 8dd7b65d1d..4535d3f27a 100644 --- a/packages/app/src/admin/SecretsPage.tsx +++ b/packages/app/src/admin/SecretsPage.tsx @@ -48,6 +48,7 @@ export function SecretsPage(): JSX.Element { { }); test('getTypedPropertyValueWithSchema', () => { - const value = { active: true }; + const typedValue: TypedValue = { type: 'Patient', value: { active: true } }; const path = 'active'; const goodElement: InternalSchemaElement = { description: '', @@ -239,8 +239,9 @@ describe('FHIRPath utils', () => { max: 0, type: [{ code: 'boolean' }], }; - expect(getTypedPropertyValueWithSchema(value, path, goodElement)).toEqual({ type: 'boolean', value: true }); + expect(getTypedPropertyValueWithSchema(typedValue, path, goodElement)).toEqual({ type: 'boolean', value: true }); + const choiceOfTypeTypedValue: TypedValue = { type: 'Extension', value: { valueBoolean: true } }; const extensionValueX: InternalSchemaElement = { description: '', path: 'Extension.value[x]', @@ -248,7 +249,7 @@ describe('FHIRPath utils', () => { max: 1, type: [{ code: 'boolean' }], }; - expect(getTypedPropertyValueWithSchema({ valueBoolean: true }, 'value[x]', extensionValueX)).toEqual({ + expect(getTypedPropertyValueWithSchema(choiceOfTypeTypedValue, 'value[x]', extensionValueX)).toEqual({ type: 'boolean', value: true, }); diff --git a/packages/core/src/fhirpath/utils.ts b/packages/core/src/fhirpath/utils.ts index 3e5cee831a..27eb6b9edb 100644 --- a/packages/core/src/fhirpath/utils.ts +++ b/packages/core/src/fhirpath/utils.ts @@ -59,6 +59,11 @@ export function singleton(collection: TypedValue[], type?: string): TypedValue | } } +export interface GetTypedPropertyValueOptions { + /** (optional) URL of a resource profile for type resolution */ + profileUrl?: string; +} + /** * Returns the value of the property and the property type. * Some property definitions support multiple types. @@ -67,16 +72,21 @@ export function singleton(collection: TypedValue[], type?: string): TypedValue | * This function returns the value and the type. * @param input - The base context (FHIR resource or backbone element). * @param path - The property path. + * @param options - (optional) Additional options * @returns The value of the property and the property type. */ -export function getTypedPropertyValue(input: TypedValue, path: string): TypedValue[] | TypedValue | undefined { +export function getTypedPropertyValue( + input: TypedValue, + path: string, + options?: GetTypedPropertyValueOptions +): TypedValue[] | TypedValue | undefined { if (!input.value) { return undefined; } - const elementDefinition = getElementDefinition(input.type, path); + const elementDefinition = getElementDefinition(input.type, path, options?.profileUrl); if (elementDefinition) { - return getTypedPropertyValueWithSchema(input.value, path, elementDefinition); + return getTypedPropertyValueWithSchema(input, path, elementDefinition); } return getTypedPropertyValueWithoutSchema(input, path); @@ -84,13 +94,13 @@ export function getTypedPropertyValue(input: TypedValue, path: string): TypedVal /** * Returns the value of the property and the property type using a type schema. - * @param value - The base context (FHIR resource or backbone element). + * @param typedValue - The base context (FHIR resource or backbone element). * @param path - The property path. * @param element - The property element definition. * @returns The value of the property and the property type. */ export function getTypedPropertyValueWithSchema( - value: TypedValue['value'], + typedValue: TypedValue, path: string, element: InternalSchemaElement ): TypedValue[] | TypedValue | undefined { @@ -115,6 +125,7 @@ export function getTypedPropertyValueWithSchema( // Therefore, cannot only check for endsWith('[x]') since FHIRPath uses this code path // with a path of 'value' and expects Choice of Types treatment + const value = typedValue.value; const types = element.type; if (!types || types.length === 0) { return undefined; diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 84dc72bbb8..ebdb98f9e5 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -349,10 +349,15 @@ function capitalizeDisplayWord(word: string): string { * Returns an element definition by type and property name. * @param typeName - The type name. * @param propertyName - The property name. + * @param profileUrl - (optional) The URL of the current resource profile * @returns The element definition if found. */ -export function getElementDefinition(typeName: string, propertyName: string): InternalSchemaElement | undefined { - const typeSchema = tryGetDataType(typeName); +export function getElementDefinition( + typeName: string, + propertyName: string, + profileUrl?: string +): InternalSchemaElement | undefined { + const typeSchema = tryGetDataType(typeName, profileUrl); if (!typeSchema) { return undefined; } diff --git a/packages/core/src/typeschema/crawler.ts b/packages/core/src/typeschema/crawler.ts index b433aa8f36..47293aceb9 100644 --- a/packages/core/src/typeschema/crawler.ts +++ b/packages/core/src/typeschema/crawler.ts @@ -102,21 +102,25 @@ class ResourceCrawler { } } -export function getNestedProperty(value: TypedValue, key: string): (TypedValue | TypedValue[] | undefined)[] { +export function getNestedProperty( + value: TypedValue, + key: string, + options?: { profileUrl?: string } +): (TypedValue | TypedValue[] | undefined)[] { if (key === '$this') { return [value]; } const [firstProp, ...nestedProps] = key.split('.'); - let propertyValues = [getTypedPropertyValue(value, firstProp)]; + let propertyValues = [getTypedPropertyValue(value, firstProp, options)]; for (const prop of nestedProps) { const next = []; for (const current of propertyValues) { if (Array.isArray(current)) { for (const element of current) { - next.push(getTypedPropertyValue(element, prop)); + next.push(getTypedPropertyValue(element, prop, options)); } } else if (current !== undefined) { - next.push(getTypedPropertyValue(current, prop)); + next.push(getTypedPropertyValue(current, prop, options)); } } propertyValues = next; diff --git a/packages/core/src/typeschema/types.ts b/packages/core/src/typeschema/types.ts index 1097739dfa..72501198ea 100644 --- a/packages/core/src/typeschema/types.ts +++ b/packages/core/src/typeschema/types.ts @@ -74,6 +74,7 @@ export interface SliceDefinition { elements: Record; min: number; max: number; + binding?: ElementDefinitionBinding; } export interface SliceDiscriminator { @@ -163,11 +164,16 @@ export function isDataTypeLoaded(type: string): boolean { } export function tryGetDataType(type: string, profileUrl?: string): InternalTypeSchema | undefined { - return getDataTypesMap(profileUrl)[type]; + let result: InternalTypeSchema | undefined = getDataTypesMap(profileUrl)[type]; + if (!result && profileUrl) { + // Fallback to base schema if no result found in profileUrl namespace + result = getDataTypesMap()[type]; + } + return result; } export function getDataType(type: string, profileUrl?: string): InternalTypeSchema { - const schema = getDataTypesMap(profileUrl)[type]; + const schema = tryGetDataType(type, profileUrl); if (!schema) { throw new OperationOutcomeError(badRequest('Unknown data type: ' + type)); } @@ -436,6 +442,7 @@ class StructureDefinitionParser { elements: {}, min: element.min ?? 0, max: element.max === '*' ? Number.POSITIVE_INFINITY : Number.parseInt(element.max as string, 10), + binding: element.binding, }; } diff --git a/packages/generator/src/storybook.ts b/packages/generator/src/storybook.ts index ecd6b869b0..a83cd44a09 100644 --- a/packages/generator/src/storybook.ts +++ b/packages/generator/src/storybook.ts @@ -83,6 +83,8 @@ const USCoreStructureDefinitionFiles = [ 'StructureDefinition-us-core-birthsex.json', 'StructureDefinition-us-core-genderIdentity.json', 'StructureDefinition-us-core-implantable-device.json', + 'StructureDefinition-us-core-blood-pressure.json', + 'StructureDefinition-us-core-medicationrequest.json', ]; const BUILD_USCORE = false; diff --git a/packages/mock/src/mocks/uscore/index.ts b/packages/mock/src/mocks/uscore/index.ts index a53c29d53b..3961fded92 100644 --- a/packages/mock/src/mocks/uscore/index.ts +++ b/packages/mock/src/mocks/uscore/index.ts @@ -1,9 +1,9 @@ -import { Device, Patient } from '@medplum/fhirtypes'; +import { Device, Patient, StructureDefinition } from '@medplum/fhirtypes'; import StructureDefinitionList from './uscore-v5.0.1-structuredefinitions.json'; import { HTTP_HL7_ORG, deepClone } from '@medplum/core'; import { HomerSimpson } from '../simpsons'; -export const USCoreStructureDefinitionList = StructureDefinitionList; +export const USCoreStructureDefinitionList = StructureDefinitionList as StructureDefinition[]; export const HomerSimpsonUSCorePatient: Patient = { ...deepClone(HomerSimpson), diff --git a/packages/mock/src/mocks/uscore/uscore-v5.0.1-structuredefinitions.json b/packages/mock/src/mocks/uscore/uscore-v5.0.1-structuredefinitions.json index a800f60960..41e4cba781 100644 --- a/packages/mock/src/mocks/uscore/uscore-v5.0.1-structuredefinitions.json +++ b/packages/mock/src/mocks/uscore/uscore-v5.0.1-structuredefinitions.json @@ -1 +1 @@ -[{"resourceType":"StructureDefinition","id":"us-core-patient","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient","version":"5.0.1","name":"USCorePatientProfile","title":"US Core Patient Profile","status":"active","experimental":false,"date":"2022-04-20T15:02:49-07:00","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"The US Core Patient Profile meets the U.S. Core Data for Interoperability (USCDI) v2 'Patient Demographics' requirements. This profile sets minimum expectations for the Patient resource to record, search, and fetch basic demographics and other administrative information about an individual patient. It identifies which core elements, extensions, vocabularies and value sets **SHALL** be present in the resource when using this profile to promote interoperability and adoption through common implementation. It identifies which core elements, extensions, vocabularies and value sets **SHALL** be present in the resource when using this profile. It provides the floor for standards development for specific uses cases.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"resource","abstract":false,"type":"Patient","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Patient","derivation":"constraint","snapshot":{"element":[{"id":"Patient","path":"Patient","short":"Information about an individual or animal receiving health care services","definition":"\\-","comment":"\\-","alias":["SubjectOfCare Client Resident"],"min":0,"max":"*","base":{"path":"Patient","min":0,"max":"*"},"constraint":[{"key":"dom-2","severity":"error","human":"If the resource is contained in another resource, it SHALL NOT contain nested Resources","expression":"contained.contained.empty()","xpath":"not(parent::f:contained and f:contained)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-3","severity":"error","human":"If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource","expression":"contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()","xpath":"not(exists(for $id in f:contained/*/f:id/@value return $contained[not(parent::*/descendant::f:reference/@value=concat('#', $contained/*/id/@value) or descendant::f:reference[@value='#'])]))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-4","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated","expression":"contained.meta.versionId.empty() and contained.meta.lastUpdated.empty()","xpath":"not(exists(f:contained/*/f:meta/f:versionId)) and not(exists(f:contained/*/f:meta/f:lastUpdated))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-5","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a security label","expression":"contained.meta.security.empty()","xpath":"not(exists(f:contained/*/f:meta/f:security))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice","valueBoolean":true},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice-explanation","valueMarkdown":"When a resource has no narrative, only systems that fully understand the data can display the resource to a human safely. Including a human readable representation in the resource makes for a much more robust eco-system and cheaper handling of resources by intermediary systems. Some ecosystems restrict distribution of resources to only those systems that do fully understand the resources, and as a consequence implementers may believe that the narrative is superfluous. However experience shows that such eco-systems often open up to new participants over time."}],"key":"dom-6","severity":"warning","human":"A resource should have narrative for robust management","expression":"text.`div`.exists()","xpath":"exists(f:text/h:div)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"us-core-6","severity":"error","human":"Either Patient.name.given and/or Patient.name.family SHALL be present or a Data Absent Reason Extension SHALL be present.","expression":"(name.family.exists() or name.given.exists()) xor extension.where(url='http://hl7.org/fhir/StructureDefinition/data-absent-reason').exists()","xpath":"(/f:name/f:extension/@url='http://hl7.org/fhir/StructureDefinition/data-absent-reason' and not(/f:name/f:family or /f:name/f:given)) or (not(/f:name/f:extension/@url='http://hl7.org/fhir/StructureDefinition/data-absent-reason') and (/f:name/f:family or /f:name/f:given))"}],"mustSupport":false,"isModifier":false,"isSummary":false},{"id":"Patient.id","path":"Patient.id","short":"Logical id of this artifact","definition":"The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.","comment":"The only time that a resource does not have an id is when it is being submitted to the server using a create operation.","min":0,"max":"1","base":{"path":"Resource.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":true},{"id":"Patient.meta","path":"Patient.meta","short":"Metadata about the resource","definition":"The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource.","min":0,"max":"1","base":{"path":"Resource.meta","min":0,"max":"1"},"type":[{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.implicitRules","path":"Patient.implicitRules","short":"A set of rules under which this content was created","definition":"A reference to a set of rules that were followed when the resource was constructed, and which must be understood when processing the content. Often, this is a reference to an implementation guide that defines the special rules along with other profiles etc.","comment":"Asserting this rule set restricts the content to be only understood by a limited set of trading partners. This inherently limits the usefulness of the data in the long term. However, the existing health eco-system is highly fractured, and not yet ready to define, collect, and exchange data in a generally computable sense. Wherever possible, implementers and/or specification writers should avoid using this element. Often, when used, the URL is a reference to an implementation guide that defines these special rules as part of it's narrative along with other profiles, value sets, etc.","min":0,"max":"1","base":{"path":"Resource.implicitRules","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because the implicit rules may provide additional knowledge about the resource that modifies it's meaning or interpretation","isSummary":true},{"id":"Patient.language","path":"Patient.language","short":"Language of the resource content","definition":"The base language in which the resource is written.","comment":"Language is provided to support indexing and accessibility (typically, services such as text to speech use the language tag). The html language tag in the narrative applies to the narrative. The language tag on the resource may be used to specify the language of other presentations generated from the data in the resource. Not all the content has to be in the base language. The Resource.language should not be assumed to apply to the narrative automatically. If a language is specified, it should it also be specified on the div element in the html (see rules in HTML5 for information about the relationship between xml:lang and the html lang attribute).","min":0,"max":"1","base":{"path":"Resource.language","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet","valueCanonical":"http://hl7.org/fhir/ValueSet/all-languages"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"Language"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"preferred","description":"A human language.","valueSet":"http://hl7.org/fhir/ValueSet/languages"}},{"id":"Patient.text","path":"Patient.text","short":"Text summary of the resource, for human interpretation","definition":"A human-readable narrative that contains a summary of the resource and can be used to represent the content of the resource to a human. The narrative need not encode all the structured data, but is required to contain sufficient detail to make it \"clinically safe\" for a human to just read the narrative. Resource definitions may define what content should be represented in the narrative to ensure clinical safety.","comment":"Contained resources do not have narrative. Resources that are not contained SHOULD have a narrative. In some cases, a resource may only have text with little or no additional discrete data (as long as all minOccurs=1 elements are satisfied). This may be necessary for data from legacy systems where information is captured as a \"text blob\" or where text is additionally entered raw or narrated and encoded information is added later.","alias":["narrative","html","xhtml","display"],"min":0,"max":"1","base":{"path":"DomainResource.text","min":0,"max":"1"},"type":[{"code":"Narrative"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contained","path":"Patient.contained","short":"Contained, inline Resources","definition":"These resources do not have an independent existence apart from the resource that contains them - they cannot be identified independently, and nor can they have their own independent transaction scope.","comment":"This should never be done when the content can be identified properly, as once identification is lost, it is extremely difficult (and context dependent) to restore it again. Contained resources may have profiles and tags In their meta elements, but SHALL NOT have security labels.","alias":["inline resources","anonymous resources","contained resources"],"min":0,"max":"*","base":{"path":"DomainResource.contained","min":0,"max":"*"},"type":[{"code":"Resource"}],"isModifier":false,"isSummary":false},{"id":"Patient.extension","path":"Patient.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"ordered":false,"rules":"open"},"short":"Extension","definition":"An Extension","min":0,"max":"*","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.extension:race","path":"Patient.extension","sliceName":"race","short":"US Core Race Extension","definition":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The race codes used to represent these concepts are based upon the [CDC Race and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 921 reference race. The race concepts are grouped by and pre-mapped to the 5 OMB race categories:\n\n - American Indian or Alaska Native\n - Asian\n - Black or African American\n - Native Hawaiian or Other Pacific Islander\n - White.","min":0,"max":"1","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension","profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-race"]}],"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":false,"isModifier":false},{"id":"Patient.extension:ethnicity","path":"Patient.extension","sliceName":"ethnicity","short":"US Core ethnicity Extension","definition":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The ethnicity codes used to represent these concepts are based upon the [CDC ethnicity and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 43 reference ethnicity. The ethnicity concepts are grouped by and pre-mapped to the 2 OMB ethnicity categories: - Hispanic or Latino - Not Hispanic or Latino.","min":0,"max":"1","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension","profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity"]}],"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":false,"isModifier":false},{"id":"Patient.extension:birthsex","path":"Patient.extension","sliceName":"birthsex","short":"Extension","definition":"A code classifying the person's sex assigned at birth as specified by the [Office of the National Coordinator for Health IT (ONC)](https://www.healthit.gov/newsroom/about-onc).","comment":"The codes required are intended to present birth sex (i.e., the sex recorded on the patient’s birth certificate) and not gender identity or reassigned sex.","min":0,"max":"1","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension","profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex"]}],"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":false,"isModifier":false},{"id":"Patient.extension:genderIdentity","path":"Patient.extension","sliceName":"genderIdentity","short":"Extension","definition":"An Extension","min":0,"max":"1","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension","profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity"]}],"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":false,"isModifier":false},{"id":"Patient.modifierExtension","path":"Patient.modifierExtension","short":"Extensions that cannot be ignored","definition":"May be used to represent additional information that is not part of the basic definition of the resource and that modifies the understanding of the element that contains it and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the resource that contains them","isSummary":false},{"id":"Patient.identifier","path":"Patient.identifier","short":"An identifier for this patient","definition":"An identifier for this patient.","requirements":"Patients are almost always assigned specific numerical identifiers.","min":1,"max":"*","base":{"path":"Patient.identifier","min":0,"max":"*"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.identifier.id","path":"Patient.identifier.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.identifier.extension","path":"Patient.identifier.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.identifier.use","path":"Patient.identifier.use","short":"usual | official | temp | secondary | old (If known)","definition":"The purpose of this identifier.","comment":"Applications can assume that an identifier is permanent unless it explicitly says that it is temporary.","requirements":"Allows the appropriate identifier for a particular context of use to be selected from among a set of identifiers.","min":0,"max":"1","base":{"path":"Identifier.use","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because applications should not mistake a temporary id for a permanent one.","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"IdentifierUse"}],"strength":"required","description":"Identifies the purpose for this identifier, if known .","valueSet":"http://hl7.org/fhir/ValueSet/identifier-use|4.0.1"}},{"id":"Patient.identifier.type","path":"Patient.identifier.type","short":"Description of identifier","definition":"A coded type for the identifier that can be used to determine which identifier to use for a specific purpose.","comment":"This element deals only with general categories of identifiers. It SHOULD not be used for codes that correspond 1..1 with the Identifier.system. Some identifiers may fall into multiple categories due to common usage. Where the system is known, a type is unnecessary because the type is always part of the system definition. However systems often need to handle identifiers where the system is not known. There is not a 1:1 relationship between type and system, since many different systems have the same type.","requirements":"Allows users to make use of identifiers when the identifier system is not known.","min":0,"max":"1","base":{"path":"Identifier.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"IdentifierType"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"extensible","description":"A coded type for an identifier that can be used to determine which identifier to use for a specific purpose.","valueSet":"http://hl7.org/fhir/ValueSet/identifier-type"}},{"id":"Patient.identifier.system","path":"Patient.identifier.system","short":"The namespace for the identifier value","definition":"Establishes the namespace for the value - that is, a URL that describes a set values that are unique.","comment":"Identifier.system is always case sensitive.","requirements":"There are many sets of identifiers. To perform matching of two identifiers, we need to know what set we're dealing with. The system identifies a particular set of unique identifiers.","min":1,"max":"1","base":{"path":"Identifier.system","min":0,"max":"1"},"type":[{"code":"uri"}],"example":[{"label":"General","valueUri":"http://www.acme.com/identifiers/patient"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.identifier.value","path":"Patient.identifier.value","short":"The value that is unique within the system.","definition":"The portion of the identifier typically relevant to the user and which is unique within the context of the system.","comment":"If the value is a full URI, then the system SHALL be urn:ietf:rfc:3986. The value's primary purpose is computational mapping. As a result, it may be normalized for comparison purposes (e.g. removing non-significant whitespace, dashes, etc.) A value formatted for human display can be conveyed using the [Rendered Value extension](http://hl7.org/fhir/R4/extension-rendered-value.html). Identifier.value is to be treated as case sensitive unless knowledge of the Identifier.system allows the processer to be confident that non-case-sensitive processing is safe.","min":1,"max":"1","base":{"path":"Identifier.value","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"123456"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.identifier.period","path":"Patient.identifier.period","short":"Time period when id is/was valid for use","definition":"Time period during which identifier is/was valid for use.","min":0,"max":"1","base":{"path":"Identifier.period","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.identifier.assigner","path":"Patient.identifier.assigner","short":"Organization that issued id (may be just text)","definition":"Organization that issued/manages the identifier.","comment":"The Identifier.assigner may omit the .reference element and only contain a .display element reflecting the name or other textual information about the assigning organization.","min":0,"max":"1","base":{"path":"Identifier.assigner","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.active","path":"Patient.active","short":"Whether this patient's record is in active use","definition":"Whether this patient record is in active use. \nMany systems use this property to mark as non-current patients, such as those that have not been seen for a period of time based on an organization's business rules.\n\nIt is often used to filter patient lists to exclude inactive patients\n\nDeceased patients may also be marked as inactive for the same reasons, but may be active for some time after death.","comment":"If a record is inactive, and linked to an active record, then future patient/record updates should occur on the other patient.","requirements":"Need to be able to mark a patient record as not to be used because it was created in error.","min":0,"max":"1","base":{"path":"Patient.active","min":0,"max":"1"},"type":[{"code":"boolean"}],"meaningWhenMissing":"This resource is generally assumed to be active if no value is provided for the active element","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labelled as a modifier because it is a status element that can indicate that a record should not be treated as valid","isSummary":true},{"id":"Patient.name","path":"Patient.name","short":"A name associated with the patient","definition":"A name associated with the individual.","comment":"A patient may have multiple names with different uses or applicable periods. For animals, the name is a \"HumanName\" in the sense that is assigned and used by humans and has the same patterns.","requirements":"Need to be able to track the patient by multiple names. Examples are your official name and a partner name.","min":1,"max":"*","base":{"path":"Patient.name","min":0,"max":"*"},"type":[{"code":"HumanName"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.name.id","path":"Patient.name.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.name.extension","path":"Patient.name.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.name.use","path":"Patient.name.use","short":"usual | official | temp | nickname | anonymous | old | maiden","definition":"Identifies the purpose for this name.","comment":"Applications can assume that a name is current unless it explicitly says that it is temporary or old.","requirements":"Allows the appropriate name for a particular context of use to be selected from among a set of names.","min":0,"max":"1","base":{"path":"HumanName.use","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because applications should not mistake a temporary or old name etc.for a current/permanent one","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"NameUse"}],"strength":"required","description":"The use of a human name.","valueSet":"http://hl7.org/fhir/ValueSet/name-use|4.0.1"}},{"id":"Patient.name.text","path":"Patient.name.text","short":"Text representation of the full name","definition":"Specifies the entire name as it should be displayed e.g. on an application UI. This may be provided instead of or as well as the specific parts.","comment":"Can provide both a text representation and parts. Applications updating a name SHALL ensure that when both text and parts are present, no content is included in the text that isn't found in a part.","requirements":"A renderable, unencoded form.","min":0,"max":"1","base":{"path":"HumanName.text","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.name.family","path":"Patient.name.family","short":"Family name (often called 'Surname')","definition":"The part of a name that links to the genealogy. In some cultures (e.g. Eritrea) the family name of a son is the first name of his father.","comment":"Family Name may be decomposed into specific parts using extensions (de, nl, es related cultures).","alias":["surname"],"min":0,"max":"1","base":{"path":"HumanName.family","min":0,"max":"1"},"type":[{"code":"string"}],"condition":["us-core-6"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.name.given","path":"Patient.name.given","short":"Given names (not always 'first'). Includes middle names","definition":"Given name.","comment":"If only initials are recorded, they may be used in place of the full name parts. Initials may be separated into multiple given names but often aren't due to paractical limitations. This element is not called \"first name\" since given names do not always come first.","alias":["first name","middle name"],"min":0,"max":"*","base":{"path":"HumanName.given","min":0,"max":"*"},"type":[{"code":"string"}],"orderMeaning":"Given Names appear in the correct order for presenting the name","condition":["us-core-6"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.name.prefix","path":"Patient.name.prefix","short":"Parts that come before the name","definition":"Part of the name that is acquired as a title due to academic, legal, employment or nobility status, etc. and that appears at the start of the name.","min":0,"max":"*","base":{"path":"HumanName.prefix","min":0,"max":"*"},"type":[{"code":"string"}],"orderMeaning":"Prefixes appear in the correct order for presenting the name","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.name.suffix","path":"Patient.name.suffix","short":"Parts that come after the name","definition":"Part of the name that is acquired as a title due to academic, legal, employment or nobility status, etc. and that appears at the end of the name.","min":0,"max":"*","base":{"path":"HumanName.suffix","min":0,"max":"*"},"type":[{"code":"string"}],"orderMeaning":"Suffixes appear in the correct order for presenting the name","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":false,"isModifier":false,"isSummary":true},{"id":"Patient.name.period","path":"Patient.name.period","short":"Time period when name was/is in use","definition":"Indicates the period of time when this name was valid for the named person.","requirements":"Allows names to be placed in historical context.","min":0,"max":"1","base":{"path":"HumanName.period","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":false,"isModifier":false,"isSummary":true},{"id":"Patient.telecom","path":"Patient.telecom","short":"A contact detail for the individual","definition":"A contact detail (e.g. a telephone number or an email address) by which the individual may be contacted.","comment":"A Patient may have multiple ways to be contacted with different uses or applicable periods. May need to have options for contacting the person urgently and also to help with identification. The address might not go directly to the individual, but may reach another party that is able to proxy for the patient (i.e. home phone, or pet owner's phone).","requirements":"People have (primary) ways to contact them in some way such as phone, email.","min":0,"max":"*","base":{"path":"Patient.telecom","min":0,"max":"*"},"type":[{"code":"ContactPoint"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":false,"isModifier":false,"isSummary":true},{"id":"Patient.telecom.id","path":"Patient.telecom.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.telecom.extension","path":"Patient.telecom.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.telecom.system","path":"Patient.telecom.system","short":"phone | fax | email | pager | url | sms | other","definition":"Telecommunications form for contact point - what communications system is required to make use of the contact.","min":1,"max":"1","base":{"path":"ContactPoint.system","min":0,"max":"1"},"type":[{"code":"code"}],"condition":["cpt-2"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"required","description":"Telecommunications form for contact point.","valueSet":"http://hl7.org/fhir/ValueSet/contact-point-system"}},{"id":"Patient.telecom.value","path":"Patient.telecom.value","short":"The actual contact point details","definition":"The actual contact point details, in a form that is meaningful to the designated communication system (i.e. phone number or email address).","comment":"Additional text data such as phone extension numbers, or notes about use of the contact are sometimes included in the value.","requirements":"Need to support legacy numbers that are not in a tightly controlled format.","min":1,"max":"1","base":{"path":"ContactPoint.value","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.telecom.use","path":"Patient.telecom.use","short":"home | work | temp | old | mobile - purpose of this contact point","definition":"Identifies the purpose for the contact point.","comment":"Applications can assume that a contact is current unless it explicitly says that it is temporary or old.","requirements":"Need to track the way a person uses this contact, so a user can choose which is appropriate for their purpose.","min":0,"max":"1","base":{"path":"ContactPoint.use","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because applications should not mistake a temporary or old contact etc.for a current/permanent one","isSummary":true,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/ValueSet/contact-point-use"}},{"id":"Patient.telecom.rank","path":"Patient.telecom.rank","short":"Specify preferred order of use (1 = highest)","definition":"Specifies a preferred order in which to use a set of contacts. ContactPoints with lower rank values are more preferred than those with higher rank values.","comment":"Note that rank does not necessarily follow the order in which the contacts are represented in the instance.","min":0,"max":"1","base":{"path":"ContactPoint.rank","min":0,"max":"1"},"type":[{"code":"positiveInt"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.telecom.period","path":"Patient.telecom.period","short":"Time period when the contact point was/is in use","definition":"Time period when the contact point was/is in use.","min":0,"max":"1","base":{"path":"ContactPoint.period","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.gender","path":"Patient.gender","short":"male | female | other | unknown","definition":"Administrative Gender - the gender that the patient is considered to have for administration and record keeping purposes.","comment":"The gender might not match the biological sex as determined by genetics or the individual's preferred identification. Note that for both humans and particularly animals, there are other legitimate possibilities than male and female, though the vast majority of systems and contexts only support male and female. Systems providing decision support or enforcing business rules should ideally do this on the basis of Observations dealing with the specific sex or gender aspect of interest (anatomical, chromosomal, social, etc.) However, because these observations are infrequently recorded, defaulting to the administrative gender is common practice. Where such defaulting occurs, rule enforcement should allow for the variation between administrative and biological, chromosomal and other gender aspects. For example, an alert about a hysterectomy on a male should be handled as a warning or overridable error, not a \"hard\" error. See the Patient Gender and Sex section for additional information about communicating patient gender and sex.","requirements":"Needed for identification of the individual, in combination with (at least) name and birth date.","min":1,"max":"1","base":{"path":"Patient.gender","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/ValueSet/administrative-gender"}},{"id":"Patient.birthDate","path":"Patient.birthDate","short":"The date of birth for the individual","definition":"The date of birth for the individual.","comment":"At least an estimated year should be provided as a guess if the real DOB is unknown There is a standard extension \"patient-birthTime\" available that should be used where Time is required (such as in maternity/infant care systems).","requirements":"Age of the individual drives many clinical processes.","min":0,"max":"1","base":{"path":"Patient.birthDate","min":0,"max":"1"},"type":[{"code":"date"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.deceased[x]","path":"Patient.deceased[x]","short":"Indicates if the individual is deceased or not","definition":"Indicates if the individual is deceased or not.","comment":"If there's no value in the instance, it means there is no statement on whether or not the individual is deceased. Most systems will interpret the absence of a value as a sign of the person being alive.","requirements":"The fact that a patient is deceased influences the clinical process. Also, in human communication and relation management it is necessary to know whether the person is alive.","min":0,"max":"1","base":{"path":"Patient.deceased[x]","min":0,"max":"1"},"type":[{"code":"boolean"},{"code":"dateTime"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because once a patient is marked as deceased, the actions that are appropriate to perform on the patient may be significantly different.","isSummary":true},{"id":"Patient.address","path":"Patient.address","short":"An address for the individual","definition":"An address for the individual.","comment":"Patient may have multiple addresses with different uses or applicable periods.","requirements":"May need to keep track of patient addresses for contacting, billing or reporting requirements and also to help with identification.","min":0,"max":"*","base":{"path":"Patient.address","min":0,"max":"*"},"type":[{"code":"Address"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.address.id","path":"Patient.address.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.address.extension","path":"Patient.address.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.address.use","path":"Patient.address.use","short":"home | work | temp | old | billing - purpose of this address","definition":"The purpose of this address.","comment":"Applications can assume that an address is current unless it explicitly says that it is temporary or old.","requirements":"Allows an appropriate address to be chosen from a list of many.","min":0,"max":"1","base":{"path":"Address.use","min":0,"max":"1"},"type":[{"code":"code"}],"example":[{"label":"General","valueCode":"home"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because applications should not mistake a temporary or old address etc.for a current/permanent one","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"AddressUse"}],"strength":"required","description":"The use of an address.","valueSet":"http://hl7.org/fhir/ValueSet/address-use|4.0.1"}},{"id":"Patient.address.type","path":"Patient.address.type","short":"postal | physical | both","definition":"Distinguishes between physical addresses (those you can visit) and mailing addresses (e.g. PO Boxes and care-of addresses). Most addresses are both.","comment":"The definition of Address states that \"address is intended to describe postal addresses, not physical locations\". However, many applications track whether an address has a dual purpose of being a location that can be visited as well as being a valid delivery destination, and Postal addresses are often used as proxies for physical locations (also see the [Location](http://hl7.org/fhir/R4/location.html#) resource).","min":0,"max":"1","base":{"path":"Address.type","min":0,"max":"1"},"type":[{"code":"code"}],"example":[{"label":"General","valueCode":"both"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"AddressType"}],"strength":"required","description":"The type of an address (physical / postal).","valueSet":"http://hl7.org/fhir/ValueSet/address-type|4.0.1"}},{"id":"Patient.address.text","path":"Patient.address.text","short":"Text representation of the address","definition":"Specifies the entire address as it should be displayed e.g. on a postal label. This may be provided instead of or as well as the specific parts.","comment":"Can provide both a text representation and parts. Applications updating an address SHALL ensure that when both text and parts are present, no content is included in the text that isn't found in a part.","requirements":"A renderable, unencoded form.","min":0,"max":"1","base":{"path":"Address.text","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"137 Nowhere Street, Erewhon 9132"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.address.line","path":"Patient.address.line","short":"Street name, number, direction & P.O. Box etc.","definition":"This component contains the house number, apartment number, street name, street direction, P.O. Box number, delivery hints, and similar address information.","min":0,"max":"*","base":{"path":"Address.line","min":0,"max":"*"},"type":[{"code":"string"}],"orderMeaning":"The order in which lines should appear in an address label","example":[{"label":"General","valueString":"137 Nowhere Street"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.address.city","path":"Patient.address.city","short":"Name of city, town etc.","definition":"The name of the city, town, suburb, village or other community or delivery center.","alias":["Municpality"],"min":0,"max":"1","base":{"path":"Address.city","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"Erewhon"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.address.district","path":"Patient.address.district","short":"District name (aka county)","definition":"The name of the administrative area (county).","comment":"District is sometimes known as county, but in some regions 'county' is used in place of city (municipality), so county name should be conveyed in city instead.","alias":["County"],"min":0,"max":"1","base":{"path":"Address.district","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"Madison"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.address.state","path":"Patient.address.state","short":"Sub-unit of country (abbreviations ok)","definition":"Sub-unit of a country with limited sovereignty in a federally organized country. A code may be used if codes are in common use (e.g. US 2 letter state codes).","alias":["Province","Territory"],"min":0,"max":"1","base":{"path":"Address.state","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"Two Letter USPS alphabetic codes.","valueSet":"http://hl7.org/fhir/us/core/ValueSet/us-core-usps-state"}},{"id":"Patient.address.postalCode","path":"Patient.address.postalCode","short":"US Zip Codes","definition":"A postal code designating a region defined by the postal service.","alias":["Zip","Zip Code"],"min":0,"max":"1","base":{"path":"Address.postalCode","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"9132"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.address.country","path":"Patient.address.country","short":"Country (e.g. can be ISO 3166 2 or 3 letter code)","definition":"Country - a nation as commonly understood or generally accepted.","comment":"ISO 3166 3 letter codes can be used in place of a human readable country name.","min":0,"max":"1","base":{"path":"Address.country","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.address.period","path":"Patient.address.period","short":"Time period when address was/is in use","definition":"Time period when address was/is in use.","requirements":"Allows addresses to be placed in historical context.","min":0,"max":"1","base":{"path":"Address.period","min":0,"max":"1"},"type":[{"code":"Period"}],"example":[{"label":"General","valuePeriod":{"start":"2010-03-23","end":"2010-07-01"}}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.maritalStatus","path":"Patient.maritalStatus","short":"Marital (civil) status of a patient","definition":"This field contains a patient's most recent marital (civil) status.","requirements":"Most, if not all systems capture it.","min":0,"max":"1","base":{"path":"Patient.maritalStatus","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MaritalStatus"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"extensible","description":"The domestic partnership status of a person.","valueSet":"http://hl7.org/fhir/ValueSet/marital-status"}},{"id":"Patient.multipleBirth[x]","path":"Patient.multipleBirth[x]","short":"Whether patient is part of a multiple birth","definition":"Indicates whether the patient is part of a multiple (boolean) or indicates the actual birth order (integer).","comment":"Where the valueInteger is provided, the number is the birth number in the sequence. E.g. The middle birth in triplets would be valueInteger=2 and the third born would have valueInteger=3 If a boolean value was provided for this triplets example, then all 3 patient records would have valueBoolean=true (the ordering is not indicated).","requirements":"For disambiguation of multiple-birth children, especially relevant where the care provider doesn't meet the patient, such as labs.","min":0,"max":"1","base":{"path":"Patient.multipleBirth[x]","min":0,"max":"1"},"type":[{"code":"boolean"},{"code":"integer"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.photo","path":"Patient.photo","short":"Image of the patient","definition":"Image of the patient.","comment":"Guidelines:\n* Use id photos, not clinical photos.\n* Limit dimensions to thumbnail.\n* Keep byte count low to ease resource updates.","requirements":"Many EHR systems have the capability to capture an image of the patient. Fits with newer social media usage too.","min":0,"max":"*","base":{"path":"Patient.photo","min":0,"max":"*"},"type":[{"code":"Attachment"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name","valueString":"Contact"}],"path":"Patient.contact","short":"A contact party (e.g. guardian, partner, friend) for the patient","definition":"A contact party (e.g. guardian, partner, friend) for the patient.","comment":"Contact covers all kinds of contact parties: family members, business contacts, guardians, caregivers. Not applicable to register pedigree and family ties beyond use of having contact.","requirements":"Need to track people you can contact about the patient.","min":0,"max":"*","base":{"path":"Patient.contact","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"pat-1","severity":"error","human":"SHALL at least contain a contact's details or a reference to an organization","expression":"name.exists() or telecom.exists() or address.exists() or organization.exists()","xpath":"exists(f:name) or exists(f:telecom) or exists(f:address) or exists(f:organization)","source":"http://hl7.org/fhir/StructureDefinition/Patient"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.id","path":"Patient.contact.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.extension","path":"Patient.contact.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.modifierExtension","path":"Patient.contact.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Patient.contact.relationship","path":"Patient.contact.relationship","short":"The kind of relationship","definition":"The nature of the relationship between the patient and the contact person.","requirements":"Used to determine which contact person is the most relevant to approach, depending on circumstances.","min":0,"max":"*","base":{"path":"Patient.contact.relationship","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ContactRelationship"}],"strength":"extensible","description":"The nature of the relationship between a patient and a contact person for that patient.","valueSet":"http://hl7.org/fhir/ValueSet/patient-contactrelationship"}},{"id":"Patient.contact.name","path":"Patient.contact.name","short":"A name associated with the contact person","definition":"A name associated with the contact person.","requirements":"Contact persons need to be identified by name, but it is uncommon to need details about multiple other names for that contact person.","min":0,"max":"1","base":{"path":"Patient.contact.name","min":0,"max":"1"},"type":[{"code":"HumanName"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.telecom","path":"Patient.contact.telecom","short":"A contact detail for the person","definition":"A contact detail for the person, e.g. a telephone number or an email address.","comment":"Contact may have multiple ways to be contacted with different uses or applicable periods. May need to have options for contacting the person urgently, and also to help with identification.","requirements":"People have (primary) ways to contact them in some way such as phone, email.","min":0,"max":"*","base":{"path":"Patient.contact.telecom","min":0,"max":"*"},"type":[{"code":"ContactPoint"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.address","path":"Patient.contact.address","short":"Address for the contact person","definition":"Address for the contact person.","requirements":"Need to keep track where the contact person can be contacted per postal mail or visited.","min":0,"max":"1","base":{"path":"Patient.contact.address","min":0,"max":"1"},"type":[{"code":"Address"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.gender","path":"Patient.contact.gender","short":"male | female | other | unknown","definition":"Administrative Gender - the gender that the contact person is considered to have for administration and record keeping purposes.","requirements":"Needed to address the person correctly.","min":0,"max":"1","base":{"path":"Patient.contact.gender","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"AdministrativeGender"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"required","description":"The gender of a person used for administrative purposes.","valueSet":"http://hl7.org/fhir/ValueSet/administrative-gender|4.0.1"}},{"id":"Patient.contact.organization","path":"Patient.contact.organization","short":"Organization that is associated with the contact","definition":"Organization on behalf of which the contact is acting or for which the contact is working.","requirements":"For guardians or business related contacts, the organization is relevant.","min":0,"max":"1","base":{"path":"Patient.contact.organization","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"condition":["pat-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.period","path":"Patient.contact.period","short":"The period during which this contact person or organization is valid to be contacted relating to this patient","definition":"The period during which this contact person or organization is valid to be contacted relating to this patient.","min":0,"max":"1","base":{"path":"Patient.contact.period","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.communication","path":"Patient.communication","short":"A language which may be used to communicate with the patient about his or her health","definition":"A language which may be used to communicate with the patient about his or her health.","comment":"If no language is specified, this *implies* that the default local language is spoken. If you need to convey proficiency for multiple modes, then you need multiple Patient.Communication associations. For animals, language is not a relevant field, and should be absent from the instance. If the Patient does not speak the default local language, then the Interpreter Required Standard can be used to explicitly declare that an interpreter is required.","requirements":"If a patient does not speak the local language, interpreters may be required, so languages spoken and proficiency are important things to keep track of both for patient and other persons of interest.","min":0,"max":"*","base":{"path":"Patient.communication","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":false,"isModifier":false,"isSummary":false},{"id":"Patient.communication.id","path":"Patient.communication.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.communication.extension","path":"Patient.communication.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.communication.modifierExtension","path":"Patient.communication.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Patient.communication.language","path":"Patient.communication.language","short":"The language which can be used to communicate with the patient about his or her health","definition":"The ISO-639-1 alpha 2 code in lower case for the language, optionally followed by a hyphen and the ISO-3166-1 alpha 2 code for the region in upper case; e.g. \"en\" for English, or \"en-US\" for American English versus \"en-EN\" for England English.","comment":"The structure aa-BB with this exact casing is one the most widely used notations for locale. However not all systems actually code this but instead have it as free text. Hence CodeableConcept instead of code as the data type.","requirements":"Most systems in multilingual countries will want to convey language. Not all systems actually need the regional dialect.","min":1,"max":"1","base":{"path":"Patient.communication.language","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"strength":"extensible","valueSet":"http://hl7.org/fhir/us/core/ValueSet/simple-language"}},{"id":"Patient.communication.preferred","path":"Patient.communication.preferred","short":"Language preference indicator","definition":"Indicates whether or not the patient prefers this language (over other languages he masters up a certain level).","comment":"This language is specifically identified for communicating healthcare information.","requirements":"People that master multiple languages up to certain level may prefer one or more, i.e. feel more confident in communicating in a particular language making other languages sort of a fall back method.","min":0,"max":"1","base":{"path":"Patient.communication.preferred","min":0,"max":"1"},"type":[{"code":"boolean"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.generalPractitioner","path":"Patient.generalPractitioner","short":"Patient's nominated primary care provider","definition":"Patient's nominated care provider.","comment":"This may be the primary care provider (in a GP context), or it may be a patient nominated care manager in a community/disability setting, or even organization that will provide people to perform the care provider roles. It is not to be used to record Care Teams, these should be in a CareTeam resource that may be linked to the CarePlan or EpisodeOfCare resources.\nMultiple GPs may be recorded against the patient for various reasons, such as a student that has his home GP listed along with the GP at university during the school semesters, or a \"fly-in/fly-out\" worker that has the onsite GP also included with his home GP to remain aware of medical issues.\n\nJurisdictions may decide that they can profile this down to 1 if desired, or 1 per type.","alias":["careProvider"],"min":0,"max":"*","base":{"path":"Patient.generalPractitioner","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization","http://hl7.org/fhir/StructureDefinition/Practitioner","http://hl7.org/fhir/StructureDefinition/PractitionerRole"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.managingOrganization","path":"Patient.managingOrganization","short":"Organization that is the custodian of the patient record","definition":"Organization that is the custodian of the patient record.","comment":"There is only one managing organization for a specific patient record. Other organizations will have their own Patient record, and may use the Link property to join the records together (or a Person resource which can include confidence ratings for the association).","requirements":"Need to know who recognizes this patient record, manages and updates it.","min":0,"max":"1","base":{"path":"Patient.managingOrganization","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.link","path":"Patient.link","short":"Link to another patient resource that concerns the same actual person","definition":"Link to another patient resource that concerns the same actual patient.","comment":"There is no assumption that linked patient records have mutual links.","requirements":"There are multiple use cases: \n\n* Duplicate patient records due to the clerical errors associated with the difficulties of identifying humans consistently, and \n* Distribution of patient information across multiple servers.","min":0,"max":"*","base":{"path":"Patient.link","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because it might not be the main Patient resource, and the referenced patient should be used instead of this Patient record. This is when the link.type value is 'replaced-by'","isSummary":true},{"id":"Patient.link.id","path":"Patient.link.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.link.extension","path":"Patient.link.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.link.modifierExtension","path":"Patient.link.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Patient.link.other","path":"Patient.link.other","short":"The other patient or related person resource that the link refers to","definition":"The other patient resource that the link refers to.","comment":"Referencing a RelatedPerson here removes the need to use a Person record to associate a Patient and RelatedPerson as the same individual.","min":1,"max":"1","base":{"path":"Patient.link.other","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-hierarchy","valueBoolean":false}],"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Patient","http://hl7.org/fhir/StructureDefinition/RelatedPerson"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.link.type","path":"Patient.link.type","short":"replaced-by | replaces | refer | seealso","definition":"The type of link between this patient resource and another patient resource.","min":1,"max":"1","base":{"path":"Patient.link.type","min":1,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"LinkType"}],"strength":"required","description":"The type of link between this patient resource and another patient resource.","valueSet":"http://hl7.org/fhir/ValueSet/link-type|4.0.1"}}]}},{"resourceType":"StructureDefinition","id":"us-core-race","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","version":"5.0.1","name":"USCoreRaceExtension","title":"US Core Race Extension","status":"active","date":"2019-05-21","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The race codes used to represent these concepts are based upon the [CDC Race and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 921 reference race. The race concepts are grouped by and pre-mapped to the 5 OMB race categories:\n\n - American Indian or Alaska Native\n - Asian\n - Black or African American\n - Native Hawaiian or Other Pacific Islander\n - White.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"purpose":"Complies with 2015 Edition Common Clinical Data Set for patient race.","copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"complex-type","abstract":false,"context":[{"type":"element","expression":"Patient"},{"type":"element","expression":"RelatedPerson"},{"type":"element","expression":"Person"},{"type":"element","expression":"Practitioner"},{"type":"element","expression":"FamilyMemberHistory"}],"type":"Extension","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Extension","derivation":"constraint","snapshot":{"element":[{"id":"Extension","path":"Extension","short":"US Core Race Extension","definition":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The race codes used to represent these concepts are based upon the [CDC Race and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 921 reference race. The race concepts are grouped by and pre-mapped to the 5 OMB race categories:\n\n - American Indian or Alaska Native\n - Asian\n - Black or African American\n - Native Hawaiian or Other Pacific Islander\n - White.","min":0,"max":"1","base":{"path":"Extension","min":0,"max":"*"},"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false},{"id":"Extension.id","path":"Extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension","path":"Extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory","path":"Extension.extension","sliceName":"ombCategory","short":"American Indian or Alaska Native|Asian|Black or African American|Native Hawaiian or Other Pacific Islander|White","definition":"The 5 race category codes according to the [OMB Standards for Maintaining, Collecting, and Presenting Federal Data on Race and Ethnicity, Statistical Policy Directive No. 15, as revised, October 30, 1997](https://www.govinfo.gov/content/pkg/FR-1997-10-30/pdf/97-28653.pdf).","min":0,"max":"5","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"ombCategory","isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","description":"The 5 race category codes according to the [OMB Standards for Maintaining, Collecting, and Presenting Federal Data on Race and Ethnicity, Statistical Policy Directive No. 15, as revised, October 30, 1997](https://www.govinfo.gov/content/pkg/FR-1997-10-30/pdf/97-28653.pdf).","valueSet":"http://hl7.org/fhir/us/core/ValueSet/omb-race-category"}},{"id":"Extension.extension:detailed","path":"Extension.extension","sliceName":"detailed","short":"Extended race codes","definition":"The 900+ CDC race codes that are grouped under one of the 5 OMB race category codes:.","min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"detailed","isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/us/core/ValueSet/detailed-race"}},{"id":"Extension.extension:text","path":"Extension.extension","sliceName":"text","short":"Race Text","definition":"Plain text representation of the race concept(s).","min":1,"max":"1","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"text","isModifier":false,"isSummary":false},{"id":"Extension.extension:text.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Extension.url","path":"Extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"uri"}],"code":"http://hl7.org/fhirpath/System.String"}],"fixedUri":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","isModifier":false,"isSummary":false},{"id":"Extension.value[x]","path":"Extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":0,"max":"0","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"base64Binary"},{"code":"boolean"},{"code":"canonical"},{"code":"code"},{"code":"date"},{"code":"dateTime"},{"code":"decimal"},{"code":"id"},{"code":"instant"},{"code":"integer"},{"code":"markdown"},{"code":"oid"},{"code":"positiveInt"},{"code":"string"},{"code":"time"},{"code":"unsignedInt"},{"code":"uri"},{"code":"url"},{"code":"uuid"},{"code":"Address"},{"code":"Age"},{"code":"Annotation"},{"code":"Attachment"},{"code":"CodeableConcept"},{"code":"Coding"},{"code":"ContactPoint"},{"code":"Count"},{"code":"Distance"},{"code":"Duration"},{"code":"HumanName"},{"code":"Identifier"},{"code":"Money"},{"code":"Period"},{"code":"Quantity"},{"code":"Range"},{"code":"Ratio"},{"code":"Reference"},{"code":"SampledData"},{"code":"Signature"},{"code":"Timing"},{"code":"ContactDetail"},{"code":"Contributor"},{"code":"DataRequirement"},{"code":"Expression"},{"code":"ParameterDefinition"},{"code":"RelatedArtifact"},{"code":"TriggerDefinition"},{"code":"UsageContext"},{"code":"Dosage"},{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false}]}},{"resourceType":"StructureDefinition","id":"us-core-ethnicity","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","version":"5.0.1","name":"USCoreEthnicityExtension","title":"US Core Ethnicity Extension","status":"active","date":"2019-05-21T00:00:00-04:00","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The ethnicity codes used to represent these concepts are based upon the [CDC ethnicity and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 43 reference ethnicity. The ethnicity concepts are grouped by and pre-mapped to the 2 OMB ethnicity categories: - Hispanic or Latino - Not Hispanic or Latino.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"purpose":"Complies with 2015 Edition Common Clinical Data Set for patient race.","copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"complex-type","abstract":false,"context":[{"type":"element","expression":"Patient"},{"type":"element","expression":"RelatedPerson"},{"type":"element","expression":"Person"},{"type":"element","expression":"Practitioner"},{"type":"element","expression":"FamilyMemberHistory"}],"type":"Extension","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Extension","derivation":"constraint","snapshot":{"element":[{"id":"Extension","path":"Extension","short":"US Core ethnicity Extension","definition":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The ethnicity codes used to represent these concepts are based upon the [CDC ethnicity and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 43 reference ethnicity. The ethnicity concepts are grouped by and pre-mapped to the 2 OMB ethnicity categories: - Hispanic or Latino - Not Hispanic or Latino.","min":0,"max":"1","base":{"path":"Extension","min":0,"max":"*"},"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false},{"id":"Extension.id","path":"Extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension","path":"Extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory","path":"Extension.extension","sliceName":"ombCategory","short":"Hispanic or Latino|Not Hispanic or Latino","definition":"The 2 ethnicity category codes according to the [OMB Standards for Maintaining, Collecting, and Presenting Federal Data on Race and Ethnicity, Statistical Policy Directive No. 15, as revised, October 30, 1997](https://www.govinfo.gov/content/pkg/FR-1997-10-30/pdf/97-28653.pdf).","min":0,"max":"1","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"ombCategory","isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/us/core/ValueSet/omb-ethnicity-category"}},{"id":"Extension.extension:detailed","path":"Extension.extension","sliceName":"detailed","short":"Extended ethnicity codes","definition":"The 41 CDC ethnicity codes that are grouped under one of the 2 OMB ethnicity category codes.","min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"detailed","isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/us/core/ValueSet/detailed-ethnicity"}},{"id":"Extension.extension:text","path":"Extension.extension","sliceName":"text","short":"ethnicity Text","definition":"Plain text representation of the ethnicity concept(s).","min":1,"max":"1","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"text","isModifier":false,"isSummary":false},{"id":"Extension.extension:text.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Extension.url","path":"Extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"uri"}],"code":"http://hl7.org/fhirpath/System.String"}],"fixedUri":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","isModifier":false,"isSummary":false},{"id":"Extension.value[x]","path":"Extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":0,"max":"0","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"base64Binary"},{"code":"boolean"},{"code":"canonical"},{"code":"code"},{"code":"date"},{"code":"dateTime"},{"code":"decimal"},{"code":"id"},{"code":"instant"},{"code":"integer"},{"code":"markdown"},{"code":"oid"},{"code":"positiveInt"},{"code":"string"},{"code":"time"},{"code":"unsignedInt"},{"code":"uri"},{"code":"url"},{"code":"uuid"},{"code":"Address"},{"code":"Age"},{"code":"Annotation"},{"code":"Attachment"},{"code":"CodeableConcept"},{"code":"Coding"},{"code":"ContactPoint"},{"code":"Count"},{"code":"Distance"},{"code":"Duration"},{"code":"HumanName"},{"code":"Identifier"},{"code":"Money"},{"code":"Period"},{"code":"Quantity"},{"code":"Range"},{"code":"Ratio"},{"code":"Reference"},{"code":"SampledData"},{"code":"Signature"},{"code":"Timing"},{"code":"ContactDetail"},{"code":"Contributor"},{"code":"DataRequirement"},{"code":"Expression"},{"code":"ParameterDefinition"},{"code":"RelatedArtifact"},{"code":"TriggerDefinition"},{"code":"UsageContext"},{"code":"Dosage"},{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false}]}},{"resourceType":"StructureDefinition","id":"us-core-birthsex","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","version":"5.0.1","name":"USCoreBirthSexExtension","title":"US Core Birth Sex Extension","status":"active","date":"2019-05-21","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"A code classifying the person's sex assigned at birth as specified by the [Office of the National Coordinator for Health IT (ONC)](https://www.healthit.gov/newsroom/about-onc). This extension aligns with the C-CDA Birth Sex Observation (LOINC 76689-9).","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"complex-type","abstract":false,"context":[{"type":"element","expression":"Patient"}],"type":"Extension","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Extension","derivation":"constraint","snapshot":{"element":[{"id":"Extension","path":"Extension","short":"Extension","definition":"A code classifying the person's sex assigned at birth as specified by the [Office of the National Coordinator for Health IT (ONC)](https://www.healthit.gov/newsroom/about-onc).","comment":"The codes required are intended to present birth sex (i.e., the sex recorded on the patient’s birth certificate) and not gender identity or reassigned sex.","min":0,"max":"1","base":{"path":"Extension","min":0,"max":"*"},"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false},{"id":"Extension.id","path":"Extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension","path":"Extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.url","path":"Extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"uri"}],"code":"http://hl7.org/fhirpath/System.String"}],"fixedUri":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","isModifier":false,"isSummary":false},{"id":"Extension.value[x]","path":"Extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","description":"Code for sex assigned at birth","valueSet":"http://hl7.org/fhir/us/core/ValueSet/birthsex"}}]}},{"resourceType":"StructureDefinition","id":"us-core-genderIdentity","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity","version":"5.0.1","name":"USCoreGenderIdentityExtension","title":"US Core Gender Identity Extension","status":"active","date":"2022-01-22","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"This extension provides concepts to describe the gender a person identifies as.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"purpose":"Complies with USCDI v2","copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"complex-type","abstract":false,"context":[{"type":"element","expression":"Patient"},{"type":"element","expression":"RelatedPerson"},{"type":"element","expression":"Person"},{"type":"element","expression":"Practitioner"}],"type":"Extension","baseDefinition":"http://hl7.org/fhir/StructureDefinition/patient-genderIdentity","derivation":"constraint","snapshot":{"element":[{"id":"Extension","path":"Extension","short":"Extension","definition":"An Extension","min":0,"max":"1","base":{"path":"Extension","min":0,"max":"*"},"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false},{"id":"Extension.id","path":"Extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension","path":"Extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Extension","definition":"An Extension","min":0,"max":"0","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.url","path":"Extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"uri"}],"code":"http://hl7.org/fhirpath/System.String"}],"fixedUri":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity","isModifier":false,"isSummary":false},{"id":"Extension.value[x]","path":"Extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"extensible","valueSet":"http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1021.32"}}]}},{"resourceType":"StructureDefinition","id":"us-core-implantable-device","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-implantable-device","version":"5.0.1","name":"USCoreImplantableDeviceProfile","title":"US Core Implantable Device Profile","status":"active","experimental":false,"date":"2022-04-20","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"The US Core Implantable Device Profile is based upon the core FHIR Device Resource and meets the U.S. Core Data for Interoperability (USCDI) v2 ‘Unique Device Identifier(s) for a Patient’s Implantable Device(s)’ requirements. To promote interoperability and adoption through common implementation, this profile sets minimum expectations for the Device resource to record, search, and fetch UDI information associated with a patient's implantable device(s). It identifies which core elements, extensions, vocabularies and value sets **SHALL** be present in the resource when using this profile. It provides the floor for standards development for specific uses cases.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"resource","abstract":false,"type":"Device","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Device","derivation":"constraint","snapshot":{"element":[{"id":"Device","path":"Device","short":"Item used in healthcare","definition":"\\-","comment":"\\-","min":0,"max":"*","base":{"path":"Device","min":0,"max":"*"},"constraint":[{"key":"dom-2","severity":"error","human":"If the resource is contained in another resource, it SHALL NOT contain nested Resources","expression":"contained.contained.empty()","xpath":"not(parent::f:contained and f:contained)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-3","severity":"error","human":"If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource","expression":"contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()","xpath":"not(exists(for $id in f:contained/*/f:id/@value return $contained[not(parent::*/descendant::f:reference/@value=concat('#', $contained/*/id/@value) or descendant::f:reference[@value='#'])]))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-4","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated","expression":"contained.meta.versionId.empty() and contained.meta.lastUpdated.empty()","xpath":"not(exists(f:contained/*/f:meta/f:versionId)) and not(exists(f:contained/*/f:meta/f:lastUpdated))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-5","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a security label","expression":"contained.meta.security.empty()","xpath":"not(exists(f:contained/*/f:meta/f:security))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice","valueBoolean":true},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice-explanation","valueMarkdown":"When a resource has no narrative, only systems that fully understand the data can display the resource to a human safely. Including a human readable representation in the resource makes for a much more robust eco-system and cheaper handling of resources by intermediary systems. Some ecosystems restrict distribution of resources to only those systems that do fully understand the resources, and as a consequence implementers may believe that the narrative is superfluous. However experience shows that such eco-systems often open up to new participants over time."}],"key":"dom-6","severity":"warning","human":"A resource should have narrative for robust management","expression":"text.`div`.exists()","xpath":"exists(f:text/h:div)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"}],"mustSupport":false,"isModifier":false,"isSummary":false},{"id":"Device.id","path":"Device.id","short":"Logical id of this artifact","definition":"The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.","comment":"The only time that a resource does not have an id is when it is being submitted to the server using a create operation.","min":0,"max":"1","base":{"path":"Resource.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":true},{"id":"Device.meta","path":"Device.meta","short":"Metadata about the resource","definition":"The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource.","min":0,"max":"1","base":{"path":"Resource.meta","min":0,"max":"1"},"type":[{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Device.implicitRules","path":"Device.implicitRules","short":"A set of rules under which this content was created","definition":"A reference to a set of rules that were followed when the resource was constructed, and which must be understood when processing the content. Often, this is a reference to an implementation guide that defines the special rules along with other profiles etc.","comment":"Asserting this rule set restricts the content to be only understood by a limited set of trading partners. This inherently limits the usefulness of the data in the long term. However, the existing health eco-system is highly fractured, and not yet ready to define, collect, and exchange data in a generally computable sense. Wherever possible, implementers and/or specification writers should avoid using this element. Often, when used, the URL is a reference to an implementation guide that defines these special rules as part of it's narrative along with other profiles, value sets, etc.","min":0,"max":"1","base":{"path":"Resource.implicitRules","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because the implicit rules may provide additional knowledge about the resource that modifies it's meaning or interpretation","isSummary":true},{"id":"Device.language","path":"Device.language","short":"Language of the resource content","definition":"The base language in which the resource is written.","comment":"Language is provided to support indexing and accessibility (typically, services such as text to speech use the language tag). The html language tag in the narrative applies to the narrative. The language tag on the resource may be used to specify the language of other presentations generated from the data in the resource. Not all the content has to be in the base language. The Resource.language should not be assumed to apply to the narrative automatically. If a language is specified, it should it also be specified on the div element in the html (see rules in HTML5 for information about the relationship between xml:lang and the html lang attribute).","min":0,"max":"1","base":{"path":"Resource.language","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet","valueCanonical":"http://hl7.org/fhir/ValueSet/all-languages"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"Language"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"preferred","description":"A human language.","valueSet":"http://hl7.org/fhir/ValueSet/languages"}},{"id":"Device.text","path":"Device.text","short":"Text summary of the resource, for human interpretation","definition":"A human-readable narrative that contains a summary of the resource and can be used to represent the content of the resource to a human. The narrative need not encode all the structured data, but is required to contain sufficient detail to make it \"clinically safe\" for a human to just read the narrative. Resource definitions may define what content should be represented in the narrative to ensure clinical safety.","comment":"Contained resources do not have narrative. Resources that are not contained SHOULD have a narrative. In some cases, a resource may only have text with little or no additional discrete data (as long as all minOccurs=1 elements are satisfied). This may be necessary for data from legacy systems where information is captured as a \"text blob\" or where text is additionally entered raw or narrated and encoded information is added later.","alias":["narrative","html","xhtml","display"],"min":0,"max":"1","base":{"path":"DomainResource.text","min":0,"max":"1"},"type":[{"code":"Narrative"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.contained","path":"Device.contained","short":"Contained, inline Resources","definition":"These resources do not have an independent existence apart from the resource that contains them - they cannot be identified independently, and nor can they have their own independent transaction scope.","comment":"This should never be done when the content can be identified properly, as once identification is lost, it is extremely difficult (and context dependent) to restore it again. Contained resources may have profiles and tags In their meta elements, but SHALL NOT have security labels.","alias":["inline resources","anonymous resources","contained resources"],"min":0,"max":"*","base":{"path":"DomainResource.contained","min":0,"max":"*"},"type":[{"code":"Resource"}],"isModifier":false,"isSummary":false},{"id":"Device.extension","path":"Device.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the resource. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.modifierExtension","path":"Device.modifierExtension","short":"Extensions that cannot be ignored","definition":"May be used to represent additional information that is not part of the basic definition of the resource and that modifies the understanding of the element that contains it and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the resource that contains them","isSummary":false},{"id":"Device.identifier","path":"Device.identifier","short":"Instance identifier","definition":"Unique instance identifiers assigned to a device by manufacturers other organizations or owners.","comment":"The barcode string from a barcode present on a device label or package may identify the instance, include names given to the device in local usage, or may identify the type of device. If the identifier identifies the type of device, Device.type element should be used.","min":0,"max":"*","base":{"path":"Device.identifier","min":0,"max":"*"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.definition","path":"Device.definition","short":"The reference to the definition for the device","definition":"The reference to the definition for the device.","min":0,"max":"1","base":{"path":"Device.definition","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/DeviceDefinition"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier","path":"Device.udiCarrier","short":"Unique Device Identifier (UDI) Barcode string","definition":"Unique device identifier (UDI) assigned to device label or package. Note that the Device may include multiple udiCarriers as it either may include just the udiCarrier for the jurisdiction it is sold, or for multiple jurisdictions it could have been sold.","comment":"Some devices may not have UDI information (for example. historical data or patient reported data).","min":0,"max":"1","base":{"path":"Device.udiCarrier","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Device.udiCarrier.id","path":"Device.udiCarrier.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier.extension","path":"Device.udiCarrier.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier.modifierExtension","path":"Device.udiCarrier.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.udiCarrier.deviceIdentifier","path":"Device.udiCarrier.deviceIdentifier","short":"Mandatory fixed portion of UDI","definition":"The device identifier (DI) is a mandatory, fixed portion of a UDI that identifies the labeler and the specific version or model of a device.","alias":["DI"],"min":1,"max":"1","base":{"path":"Device.udiCarrier.deviceIdentifier","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Device.udiCarrier.issuer","path":"Device.udiCarrier.issuer","short":"UDI Issuing Organization","definition":"Organization that is charged with issuing UDIs for devices. For example, the US FDA issuers include :\n1) GS1: \nhttp://hl7.org/fhir/NamingSystem/gs1-di, \n2) HIBCC:\nhttp://hl7.org/fhir/NamingSystem/hibcc-dI, \n3) ICCBBA for blood containers:\nhttp://hl7.org/fhir/NamingSystem/iccbba-blood-di, \n4) ICCBA for other devices:\nhttp://hl7.org/fhir/NamingSystem/iccbba-other-di.","alias":["Barcode System"],"min":0,"max":"1","base":{"path":"Device.udiCarrier.issuer","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier.jurisdiction","path":"Device.udiCarrier.jurisdiction","short":"Regional UDI authority","definition":"The identity of the authoritative source for UDI generation within a jurisdiction. All UDIs are globally unique within a single namespace with the appropriate repository uri as the system. For example, UDIs of devices managed in the U.S. by the FDA, the value is http://hl7.org/fhir/NamingSystem/fda-udi.","requirements":"Allows a recipient of a UDI to know which database will contain the UDI-associated metadata.","min":0,"max":"1","base":{"path":"Device.udiCarrier.jurisdiction","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier.carrierAIDC","path":"Device.udiCarrier.carrierAIDC","short":"UDI Machine Readable Barcode String","definition":"The full UDI carrier of the Automatic Identification and Data Capture (AIDC) technology representation of the barcode string as printed on the packaging of the device - e.g., a barcode or RFID. Because of limitations on character sets in XML and the need to round-trip JSON data through XML, AIDC Formats *SHALL* be base64 encoded.","comment":"The AIDC form of UDIs should be scanned or otherwise used for the identification of the device whenever possible to minimize errors in records resulting from manual transcriptions. If separate barcodes for DI and PI are present, concatenate the string with DI first and in order of human readable expression on label.","alias":["Automatic Identification and Data Capture"],"min":0,"max":"1","base":{"path":"Device.udiCarrier.carrierAIDC","min":0,"max":"1"},"type":[{"code":"base64Binary"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Device.udiCarrier.carrierHRF","path":"Device.udiCarrier.carrierHRF","short":"UDI Human Readable Barcode String","definition":"The full UDI carrier as the human readable form (HRF) representation of the barcode string as printed on the packaging of the device.","comment":"If separate barcodes for DI and PI are present, concatenate the string with DI first and in order of human readable expression on label.","alias":["Human Readable Form","UDI","Barcode String"],"min":0,"max":"1","base":{"path":"Device.udiCarrier.carrierHRF","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Device.udiCarrier.entryType","path":"Device.udiCarrier.entryType","short":"barcode | rfid | manual +","definition":"A coded entry to indicate how the data was entered.","requirements":"Supports a way to distinguish hand entered from machine read data.","min":0,"max":"1","base":{"path":"Device.udiCarrier.entryType","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"UDIEntryType"}],"strength":"required","description":"Codes to identify how UDI data was entered.","valueSet":"http://hl7.org/fhir/ValueSet/udi-entry-type|4.0.1"}},{"id":"Device.status","path":"Device.status","short":"active | inactive | entered-in-error | unknown","definition":"Status of the Device availability.","comment":"This element is labeled as a modifier because the status contains the codes inactive and entered-in-error that mark the device (record)as not currently valid.","min":0,"max":"1","base":{"path":"Device.status","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labelled as a modifier because it is a status element that contains status entered-in-error which means that the resource should not be treated as valid","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"FHIRDeviceStatus"}],"strength":"required","description":"The availability status of the device.","valueSet":"http://hl7.org/fhir/ValueSet/device-status|4.0.1"}},{"id":"Device.statusReason","path":"Device.statusReason","short":"online | paused | standby | offline | not-ready | transduc-discon | hw-discon | off","definition":"Reason for the dtatus of the Device availability.","min":0,"max":"*","base":{"path":"Device.statusReason","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"FHIRDeviceStatusReason"}],"strength":"extensible","description":"The availability status reason of the device.","valueSet":"http://hl7.org/fhir/ValueSet/device-status-reason"}},{"id":"Device.distinctIdentifier","path":"Device.distinctIdentifier","short":"The distinct identification string","definition":"The distinct identification string as required by regulation for a human cell, tissue, or cellular and tissue-based product.","comment":"For example, this applies to devices in the United States regulated under *Code of Federal Regulation 21CFR§1271.290(c)*.","alias":["Distinct Identification Code (DIC)"],"min":0,"max":"1","base":{"path":"Device.distinctIdentifier","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.manufacturer","path":"Device.manufacturer","short":"Name of device manufacturer","definition":"A name of the manufacturer.","min":0,"max":"1","base":{"path":"Device.manufacturer","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.manufactureDate","path":"Device.manufactureDate","short":"Date when the device was made","definition":"The date and time when the device was manufactured.","min":0,"max":"1","base":{"path":"Device.manufactureDate","min":0,"max":"1"},"type":[{"code":"dateTime"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.expirationDate","path":"Device.expirationDate","short":"Date and time of expiry of this device (if applicable)","definition":"The date and time beyond which this device is no longer valid or should not be used (if applicable).","min":0,"max":"1","base":{"path":"Device.expirationDate","min":0,"max":"1"},"type":[{"code":"dateTime"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.lotNumber","path":"Device.lotNumber","short":"Lot number of manufacture","definition":"Lot number assigned by the manufacturer.","min":0,"max":"1","base":{"path":"Device.lotNumber","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.serialNumber","path":"Device.serialNumber","short":"Serial number assigned by the manufacturer","definition":"The serial number assigned by the organization when the device was manufactured.","comment":"Alphanumeric Maximum 20.","min":0,"max":"1","base":{"path":"Device.serialNumber","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.deviceName","path":"Device.deviceName","short":"The name of the device as given by the manufacturer","definition":"This represents the manufacturer's name of the device as provided by the device, from a UDI label, or by a person describing the Device. This typically would be used when a person provides the name(s) or when the device represents one of the names available from DeviceDefinition.","min":0,"max":"*","base":{"path":"Device.deviceName","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.deviceName.id","path":"Device.deviceName.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.deviceName.extension","path":"Device.deviceName.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.deviceName.modifierExtension","path":"Device.deviceName.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.deviceName.name","path":"Device.deviceName.name","short":"The name of the device","definition":"The name of the device.","alias":["Σ"],"min":1,"max":"1","base":{"path":"Device.deviceName.name","min":1,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.deviceName.type","path":"Device.deviceName.type","short":"udi-label-name | user-friendly-name | patient-reported-name | manufacturer-name | model-name | other","definition":"The type of deviceName.\nUDILabelName | UserFriendlyName | PatientReportedName | ManufactureDeviceName | ModelName.","min":1,"max":"1","base":{"path":"Device.deviceName.type","min":1,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"DeviceNameType"}],"strength":"required","description":"The type of name the device is referred by.","valueSet":"http://hl7.org/fhir/ValueSet/device-nametype|4.0.1"}},{"id":"Device.modelNumber","path":"Device.modelNumber","short":"The model number for the device","definition":"The model number for the device.","min":0,"max":"1","base":{"path":"Device.modelNumber","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.partNumber","path":"Device.partNumber","short":"The part number of the device","definition":"The part number of the device.","comment":"Alphanumeric Maximum 20.","min":0,"max":"1","base":{"path":"Device.partNumber","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.type","path":"Device.type","short":"The kind or type of device","definition":"The kind or type of device.","min":1,"max":"1","base":{"path":"Device.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"strength":"extensible","description":"Codes to identify medical devices","valueSet":"http://hl7.org/fhir/ValueSet/device-kind"}},{"id":"Device.specialization","path":"Device.specialization","short":"The capabilities supported on a device, the standards to which the device conforms for a particular purpose, and used for the communication","definition":"The capabilities supported on a device, the standards to which the device conforms for a particular purpose, and used for the communication.","min":0,"max":"*","base":{"path":"Device.specialization","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.specialization.id","path":"Device.specialization.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.specialization.extension","path":"Device.specialization.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.specialization.modifierExtension","path":"Device.specialization.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.specialization.systemType","path":"Device.specialization.systemType","short":"The standard that is used to operate and communicate","definition":"The standard that is used to operate and communicate.","alias":["Σ"],"min":1,"max":"1","base":{"path":"Device.specialization.systemType","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.specialization.version","path":"Device.specialization.version","short":"The version of the standard that is used to operate and communicate","definition":"The version of the standard that is used to operate and communicate.","min":0,"max":"1","base":{"path":"Device.specialization.version","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.version","path":"Device.version","short":"The actual design of the device or software version running on the device","definition":"The actual design of the device or software version running on the device.","min":0,"max":"*","base":{"path":"Device.version","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.version.id","path":"Device.version.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.version.extension","path":"Device.version.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.version.modifierExtension","path":"Device.version.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.version.type","path":"Device.version.type","short":"The type of the device version","definition":"The type of the device version.","alias":["Σ"],"min":0,"max":"1","base":{"path":"Device.version.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.version.component","path":"Device.version.component","short":"A single component of the device version","definition":"A single component of the device version.","min":0,"max":"1","base":{"path":"Device.version.component","min":0,"max":"1"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.version.value","path":"Device.version.value","short":"The version text","definition":"The version text.","min":1,"max":"1","base":{"path":"Device.version.value","min":1,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.property","path":"Device.property","short":"The actual configuration settings of a device as it actually operates, e.g., regulation status, time properties","definition":"The actual configuration settings of a device as it actually operates, e.g., regulation status, time properties.","min":0,"max":"*","base":{"path":"Device.property","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.property.id","path":"Device.property.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.property.extension","path":"Device.property.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.property.modifierExtension","path":"Device.property.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.property.type","path":"Device.property.type","short":"Code that specifies the property DeviceDefinitionPropetyCode (Extensible)","definition":"Code that specifies the property DeviceDefinitionPropetyCode (Extensible).","min":1,"max":"1","base":{"path":"Device.property.type","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.property.valueQuantity","path":"Device.property.valueQuantity","short":"Property value as a quantity","definition":"Property value as a quantity.","min":0,"max":"*","base":{"path":"Device.property.valueQuantity","min":0,"max":"*"},"type":[{"code":"Quantity"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.property.valueCode","path":"Device.property.valueCode","short":"Property value as a code, e.g., NTP4 (synced to NTP)","definition":"Property value as a code, e.g., NTP4 (synced to NTP).","min":0,"max":"*","base":{"path":"Device.property.valueCode","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.patient","path":"Device.patient","short":"Patient to whom Device is affixed","definition":"Patient information, If the device is affixed to a person.","requirements":"If the device is implanted in a patient, then need to associate the device to the patient.","min":1,"max":"1","base":{"path":"Device.patient","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.owner","path":"Device.owner","short":"Organization responsible for device","definition":"An organization that is responsible for the provision and ongoing maintenance of the device.","min":0,"max":"1","base":{"path":"Device.owner","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.contact","path":"Device.contact","short":"Details for human/organization for support","definition":"Contact details for an organization or a particular human that is responsible for the device.","comment":"used for troubleshooting etc.","min":0,"max":"*","base":{"path":"Device.contact","min":0,"max":"*"},"type":[{"code":"ContactPoint"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.location","path":"Device.location","short":"Where the device is found","definition":"The place where the device can be found.","requirements":"Device.location can be used to track device location.","min":0,"max":"1","base":{"path":"Device.location","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Location"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.url","path":"Device.url","short":"Network address to contact device","definition":"A network address on which the device may be contacted directly.","comment":"If the device is running a FHIR server, the network address should be the Base URL from which a conformance statement may be retrieved.","min":0,"max":"1","base":{"path":"Device.url","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.note","path":"Device.note","short":"Device notes and comments","definition":"Descriptive information, usage information or implantation information that is not captured in an existing element.","min":0,"max":"*","base":{"path":"Device.note","min":0,"max":"*"},"type":[{"code":"Annotation"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.safety","path":"Device.safety","short":"Safety Characteristics of Device","definition":"Provides additional safety characteristics about a medical device. For example devices containing latex.","min":0,"max":"*","base":{"path":"Device.safety","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Device.parent","path":"Device.parent","short":"The parent device","definition":"The parent device.","min":0,"max":"1","base":{"path":"Device.parent","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Device"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false}]}}] \ No newline at end of file +[{"resourceType":"StructureDefinition","id":"us-core-patient","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient","version":"5.0.1","name":"USCorePatientProfile","title":"US Core Patient Profile","status":"active","experimental":false,"date":"2022-04-20T15:02:49-07:00","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"The US Core Patient Profile meets the U.S. Core Data for Interoperability (USCDI) v2 'Patient Demographics' requirements. This profile sets minimum expectations for the Patient resource to record, search, and fetch basic demographics and other administrative information about an individual patient. It identifies which core elements, extensions, vocabularies and value sets **SHALL** be present in the resource when using this profile to promote interoperability and adoption through common implementation. It identifies which core elements, extensions, vocabularies and value sets **SHALL** be present in the resource when using this profile. It provides the floor for standards development for specific uses cases.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"resource","abstract":false,"type":"Patient","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Patient","derivation":"constraint","snapshot":{"element":[{"id":"Patient","path":"Patient","short":"Information about an individual or animal receiving health care services","definition":"\\-","comment":"\\-","alias":["SubjectOfCare Client Resident"],"min":0,"max":"*","base":{"path":"Patient","min":0,"max":"*"},"constraint":[{"key":"dom-2","severity":"error","human":"If the resource is contained in another resource, it SHALL NOT contain nested Resources","expression":"contained.contained.empty()","xpath":"not(parent::f:contained and f:contained)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-3","severity":"error","human":"If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource","expression":"contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()","xpath":"not(exists(for $id in f:contained/*/f:id/@value return $contained[not(parent::*/descendant::f:reference/@value=concat('#', $contained/*/id/@value) or descendant::f:reference[@value='#'])]))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-4","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated","expression":"contained.meta.versionId.empty() and contained.meta.lastUpdated.empty()","xpath":"not(exists(f:contained/*/f:meta/f:versionId)) and not(exists(f:contained/*/f:meta/f:lastUpdated))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-5","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a security label","expression":"contained.meta.security.empty()","xpath":"not(exists(f:contained/*/f:meta/f:security))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice","valueBoolean":true},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice-explanation","valueMarkdown":"When a resource has no narrative, only systems that fully understand the data can display the resource to a human safely. Including a human readable representation in the resource makes for a much more robust eco-system and cheaper handling of resources by intermediary systems. Some ecosystems restrict distribution of resources to only those systems that do fully understand the resources, and as a consequence implementers may believe that the narrative is superfluous. However experience shows that such eco-systems often open up to new participants over time."}],"key":"dom-6","severity":"warning","human":"A resource should have narrative for robust management","expression":"text.`div`.exists()","xpath":"exists(f:text/h:div)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"us-core-6","severity":"error","human":"Either Patient.name.given and/or Patient.name.family SHALL be present or a Data Absent Reason Extension SHALL be present.","expression":"(name.family.exists() or name.given.exists()) xor extension.where(url='http://hl7.org/fhir/StructureDefinition/data-absent-reason').exists()","xpath":"(/f:name/f:extension/@url='http://hl7.org/fhir/StructureDefinition/data-absent-reason' and not(/f:name/f:family or /f:name/f:given)) or (not(/f:name/f:extension/@url='http://hl7.org/fhir/StructureDefinition/data-absent-reason') and (/f:name/f:family or /f:name/f:given))"}],"mustSupport":false,"isModifier":false,"isSummary":false},{"id":"Patient.id","path":"Patient.id","short":"Logical id of this artifact","definition":"The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.","comment":"The only time that a resource does not have an id is when it is being submitted to the server using a create operation.","min":0,"max":"1","base":{"path":"Resource.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":true},{"id":"Patient.meta","path":"Patient.meta","short":"Metadata about the resource","definition":"The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource.","min":0,"max":"1","base":{"path":"Resource.meta","min":0,"max":"1"},"type":[{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.implicitRules","path":"Patient.implicitRules","short":"A set of rules under which this content was created","definition":"A reference to a set of rules that were followed when the resource was constructed, and which must be understood when processing the content. Often, this is a reference to an implementation guide that defines the special rules along with other profiles etc.","comment":"Asserting this rule set restricts the content to be only understood by a limited set of trading partners. This inherently limits the usefulness of the data in the long term. However, the existing health eco-system is highly fractured, and not yet ready to define, collect, and exchange data in a generally computable sense. Wherever possible, implementers and/or specification writers should avoid using this element. Often, when used, the URL is a reference to an implementation guide that defines these special rules as part of it's narrative along with other profiles, value sets, etc.","min":0,"max":"1","base":{"path":"Resource.implicitRules","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because the implicit rules may provide additional knowledge about the resource that modifies it's meaning or interpretation","isSummary":true},{"id":"Patient.language","path":"Patient.language","short":"Language of the resource content","definition":"The base language in which the resource is written.","comment":"Language is provided to support indexing and accessibility (typically, services such as text to speech use the language tag). The html language tag in the narrative applies to the narrative. The language tag on the resource may be used to specify the language of other presentations generated from the data in the resource. Not all the content has to be in the base language. The Resource.language should not be assumed to apply to the narrative automatically. If a language is specified, it should it also be specified on the div element in the html (see rules in HTML5 for information about the relationship between xml:lang and the html lang attribute).","min":0,"max":"1","base":{"path":"Resource.language","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet","valueCanonical":"http://hl7.org/fhir/ValueSet/all-languages"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"Language"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"preferred","description":"A human language.","valueSet":"http://hl7.org/fhir/ValueSet/languages"}},{"id":"Patient.text","path":"Patient.text","short":"Text summary of the resource, for human interpretation","definition":"A human-readable narrative that contains a summary of the resource and can be used to represent the content of the resource to a human. The narrative need not encode all the structured data, but is required to contain sufficient detail to make it \"clinically safe\" for a human to just read the narrative. Resource definitions may define what content should be represented in the narrative to ensure clinical safety.","comment":"Contained resources do not have narrative. Resources that are not contained SHOULD have a narrative. In some cases, a resource may only have text with little or no additional discrete data (as long as all minOccurs=1 elements are satisfied). This may be necessary for data from legacy systems where information is captured as a \"text blob\" or where text is additionally entered raw or narrated and encoded information is added later.","alias":["narrative","html","xhtml","display"],"min":0,"max":"1","base":{"path":"DomainResource.text","min":0,"max":"1"},"type":[{"code":"Narrative"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contained","path":"Patient.contained","short":"Contained, inline Resources","definition":"These resources do not have an independent existence apart from the resource that contains them - they cannot be identified independently, and nor can they have their own independent transaction scope.","comment":"This should never be done when the content can be identified properly, as once identification is lost, it is extremely difficult (and context dependent) to restore it again. Contained resources may have profiles and tags In their meta elements, but SHALL NOT have security labels.","alias":["inline resources","anonymous resources","contained resources"],"min":0,"max":"*","base":{"path":"DomainResource.contained","min":0,"max":"*"},"type":[{"code":"Resource"}],"isModifier":false,"isSummary":false},{"id":"Patient.extension","path":"Patient.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"ordered":false,"rules":"open"},"short":"Extension","definition":"An Extension","min":0,"max":"*","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.extension:race","path":"Patient.extension","sliceName":"race","short":"US Core Race Extension","definition":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The race codes used to represent these concepts are based upon the [CDC Race and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 921 reference race. The race concepts are grouped by and pre-mapped to the 5 OMB race categories:\n\n - American Indian or Alaska Native\n - Asian\n - Black or African American\n - Native Hawaiian or Other Pacific Islander\n - White.","min":0,"max":"1","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension","profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-race"]}],"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":false,"isModifier":false},{"id":"Patient.extension:ethnicity","path":"Patient.extension","sliceName":"ethnicity","short":"US Core ethnicity Extension","definition":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The ethnicity codes used to represent these concepts are based upon the [CDC ethnicity and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 43 reference ethnicity. The ethnicity concepts are grouped by and pre-mapped to the 2 OMB ethnicity categories: - Hispanic or Latino - Not Hispanic or Latino.","min":0,"max":"1","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension","profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity"]}],"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":false,"isModifier":false},{"id":"Patient.extension:birthsex","path":"Patient.extension","sliceName":"birthsex","short":"Extension","definition":"A code classifying the person's sex assigned at birth as specified by the [Office of the National Coordinator for Health IT (ONC)](https://www.healthit.gov/newsroom/about-onc).","comment":"The codes required are intended to present birth sex (i.e., the sex recorded on the patient’s birth certificate) and not gender identity or reassigned sex.","min":0,"max":"1","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension","profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex"]}],"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":false,"isModifier":false},{"id":"Patient.extension:genderIdentity","path":"Patient.extension","sliceName":"genderIdentity","short":"Extension","definition":"An Extension","min":0,"max":"1","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension","profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity"]}],"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":false,"isModifier":false},{"id":"Patient.modifierExtension","path":"Patient.modifierExtension","short":"Extensions that cannot be ignored","definition":"May be used to represent additional information that is not part of the basic definition of the resource and that modifies the understanding of the element that contains it and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the resource that contains them","isSummary":false},{"id":"Patient.identifier","path":"Patient.identifier","short":"An identifier for this patient","definition":"An identifier for this patient.","requirements":"Patients are almost always assigned specific numerical identifiers.","min":1,"max":"*","base":{"path":"Patient.identifier","min":0,"max":"*"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.identifier.id","path":"Patient.identifier.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.identifier.extension","path":"Patient.identifier.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.identifier.use","path":"Patient.identifier.use","short":"usual | official | temp | secondary | old (If known)","definition":"The purpose of this identifier.","comment":"Applications can assume that an identifier is permanent unless it explicitly says that it is temporary.","requirements":"Allows the appropriate identifier for a particular context of use to be selected from among a set of identifiers.","min":0,"max":"1","base":{"path":"Identifier.use","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because applications should not mistake a temporary id for a permanent one.","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"IdentifierUse"}],"strength":"required","description":"Identifies the purpose for this identifier, if known .","valueSet":"http://hl7.org/fhir/ValueSet/identifier-use|4.0.1"}},{"id":"Patient.identifier.type","path":"Patient.identifier.type","short":"Description of identifier","definition":"A coded type for the identifier that can be used to determine which identifier to use for a specific purpose.","comment":"This element deals only with general categories of identifiers. It SHOULD not be used for codes that correspond 1..1 with the Identifier.system. Some identifiers may fall into multiple categories due to common usage. Where the system is known, a type is unnecessary because the type is always part of the system definition. However systems often need to handle identifiers where the system is not known. There is not a 1:1 relationship between type and system, since many different systems have the same type.","requirements":"Allows users to make use of identifiers when the identifier system is not known.","min":0,"max":"1","base":{"path":"Identifier.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"IdentifierType"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"extensible","description":"A coded type for an identifier that can be used to determine which identifier to use for a specific purpose.","valueSet":"http://hl7.org/fhir/ValueSet/identifier-type"}},{"id":"Patient.identifier.system","path":"Patient.identifier.system","short":"The namespace for the identifier value","definition":"Establishes the namespace for the value - that is, a URL that describes a set values that are unique.","comment":"Identifier.system is always case sensitive.","requirements":"There are many sets of identifiers. To perform matching of two identifiers, we need to know what set we're dealing with. The system identifies a particular set of unique identifiers.","min":1,"max":"1","base":{"path":"Identifier.system","min":0,"max":"1"},"type":[{"code":"uri"}],"example":[{"label":"General","valueUri":"http://www.acme.com/identifiers/patient"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.identifier.value","path":"Patient.identifier.value","short":"The value that is unique within the system.","definition":"The portion of the identifier typically relevant to the user and which is unique within the context of the system.","comment":"If the value is a full URI, then the system SHALL be urn:ietf:rfc:3986. The value's primary purpose is computational mapping. As a result, it may be normalized for comparison purposes (e.g. removing non-significant whitespace, dashes, etc.) A value formatted for human display can be conveyed using the [Rendered Value extension](http://hl7.org/fhir/R4/extension-rendered-value.html). Identifier.value is to be treated as case sensitive unless knowledge of the Identifier.system allows the processer to be confident that non-case-sensitive processing is safe.","min":1,"max":"1","base":{"path":"Identifier.value","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"123456"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.identifier.period","path":"Patient.identifier.period","short":"Time period when id is/was valid for use","definition":"Time period during which identifier is/was valid for use.","min":0,"max":"1","base":{"path":"Identifier.period","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.identifier.assigner","path":"Patient.identifier.assigner","short":"Organization that issued id (may be just text)","definition":"Organization that issued/manages the identifier.","comment":"The Identifier.assigner may omit the .reference element and only contain a .display element reflecting the name or other textual information about the assigning organization.","min":0,"max":"1","base":{"path":"Identifier.assigner","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.active","path":"Patient.active","short":"Whether this patient's record is in active use","definition":"Whether this patient record is in active use. \nMany systems use this property to mark as non-current patients, such as those that have not been seen for a period of time based on an organization's business rules.\n\nIt is often used to filter patient lists to exclude inactive patients\n\nDeceased patients may also be marked as inactive for the same reasons, but may be active for some time after death.","comment":"If a record is inactive, and linked to an active record, then future patient/record updates should occur on the other patient.","requirements":"Need to be able to mark a patient record as not to be used because it was created in error.","min":0,"max":"1","base":{"path":"Patient.active","min":0,"max":"1"},"type":[{"code":"boolean"}],"meaningWhenMissing":"This resource is generally assumed to be active if no value is provided for the active element","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labelled as a modifier because it is a status element that can indicate that a record should not be treated as valid","isSummary":true},{"id":"Patient.name","path":"Patient.name","short":"A name associated with the patient","definition":"A name associated with the individual.","comment":"A patient may have multiple names with different uses or applicable periods. For animals, the name is a \"HumanName\" in the sense that is assigned and used by humans and has the same patterns.","requirements":"Need to be able to track the patient by multiple names. Examples are your official name and a partner name.","min":1,"max":"*","base":{"path":"Patient.name","min":0,"max":"*"},"type":[{"code":"HumanName"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.name.id","path":"Patient.name.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.name.extension","path":"Patient.name.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.name.use","path":"Patient.name.use","short":"usual | official | temp | nickname | anonymous | old | maiden","definition":"Identifies the purpose for this name.","comment":"Applications can assume that a name is current unless it explicitly says that it is temporary or old.","requirements":"Allows the appropriate name for a particular context of use to be selected from among a set of names.","min":0,"max":"1","base":{"path":"HumanName.use","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because applications should not mistake a temporary or old name etc.for a current/permanent one","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"NameUse"}],"strength":"required","description":"The use of a human name.","valueSet":"http://hl7.org/fhir/ValueSet/name-use|4.0.1"}},{"id":"Patient.name.text","path":"Patient.name.text","short":"Text representation of the full name","definition":"Specifies the entire name as it should be displayed e.g. on an application UI. This may be provided instead of or as well as the specific parts.","comment":"Can provide both a text representation and parts. Applications updating a name SHALL ensure that when both text and parts are present, no content is included in the text that isn't found in a part.","requirements":"A renderable, unencoded form.","min":0,"max":"1","base":{"path":"HumanName.text","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.name.family","path":"Patient.name.family","short":"Family name (often called 'Surname')","definition":"The part of a name that links to the genealogy. In some cultures (e.g. Eritrea) the family name of a son is the first name of his father.","comment":"Family Name may be decomposed into specific parts using extensions (de, nl, es related cultures).","alias":["surname"],"min":0,"max":"1","base":{"path":"HumanName.family","min":0,"max":"1"},"type":[{"code":"string"}],"condition":["us-core-6"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.name.given","path":"Patient.name.given","short":"Given names (not always 'first'). Includes middle names","definition":"Given name.","comment":"If only initials are recorded, they may be used in place of the full name parts. Initials may be separated into multiple given names but often aren't due to paractical limitations. This element is not called \"first name\" since given names do not always come first.","alias":["first name","middle name"],"min":0,"max":"*","base":{"path":"HumanName.given","min":0,"max":"*"},"type":[{"code":"string"}],"orderMeaning":"Given Names appear in the correct order for presenting the name","condition":["us-core-6"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.name.prefix","path":"Patient.name.prefix","short":"Parts that come before the name","definition":"Part of the name that is acquired as a title due to academic, legal, employment or nobility status, etc. and that appears at the start of the name.","min":0,"max":"*","base":{"path":"HumanName.prefix","min":0,"max":"*"},"type":[{"code":"string"}],"orderMeaning":"Prefixes appear in the correct order for presenting the name","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.name.suffix","path":"Patient.name.suffix","short":"Parts that come after the name","definition":"Part of the name that is acquired as a title due to academic, legal, employment or nobility status, etc. and that appears at the end of the name.","min":0,"max":"*","base":{"path":"HumanName.suffix","min":0,"max":"*"},"type":[{"code":"string"}],"orderMeaning":"Suffixes appear in the correct order for presenting the name","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":false,"isModifier":false,"isSummary":true},{"id":"Patient.name.period","path":"Patient.name.period","short":"Time period when name was/is in use","definition":"Indicates the period of time when this name was valid for the named person.","requirements":"Allows names to be placed in historical context.","min":0,"max":"1","base":{"path":"HumanName.period","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":false,"isModifier":false,"isSummary":true},{"id":"Patient.telecom","path":"Patient.telecom","short":"A contact detail for the individual","definition":"A contact detail (e.g. a telephone number or an email address) by which the individual may be contacted.","comment":"A Patient may have multiple ways to be contacted with different uses or applicable periods. May need to have options for contacting the person urgently and also to help with identification. The address might not go directly to the individual, but may reach another party that is able to proxy for the patient (i.e. home phone, or pet owner's phone).","requirements":"People have (primary) ways to contact them in some way such as phone, email.","min":0,"max":"*","base":{"path":"Patient.telecom","min":0,"max":"*"},"type":[{"code":"ContactPoint"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":false,"isModifier":false,"isSummary":true},{"id":"Patient.telecom.id","path":"Patient.telecom.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.telecom.extension","path":"Patient.telecom.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.telecom.system","path":"Patient.telecom.system","short":"phone | fax | email | pager | url | sms | other","definition":"Telecommunications form for contact point - what communications system is required to make use of the contact.","min":1,"max":"1","base":{"path":"ContactPoint.system","min":0,"max":"1"},"type":[{"code":"code"}],"condition":["cpt-2"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"required","description":"Telecommunications form for contact point.","valueSet":"http://hl7.org/fhir/ValueSet/contact-point-system"}},{"id":"Patient.telecom.value","path":"Patient.telecom.value","short":"The actual contact point details","definition":"The actual contact point details, in a form that is meaningful to the designated communication system (i.e. phone number or email address).","comment":"Additional text data such as phone extension numbers, or notes about use of the contact are sometimes included in the value.","requirements":"Need to support legacy numbers that are not in a tightly controlled format.","min":1,"max":"1","base":{"path":"ContactPoint.value","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.telecom.use","path":"Patient.telecom.use","short":"home | work | temp | old | mobile - purpose of this contact point","definition":"Identifies the purpose for the contact point.","comment":"Applications can assume that a contact is current unless it explicitly says that it is temporary or old.","requirements":"Need to track the way a person uses this contact, so a user can choose which is appropriate for their purpose.","min":0,"max":"1","base":{"path":"ContactPoint.use","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because applications should not mistake a temporary or old contact etc.for a current/permanent one","isSummary":true,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/ValueSet/contact-point-use"}},{"id":"Patient.telecom.rank","path":"Patient.telecom.rank","short":"Specify preferred order of use (1 = highest)","definition":"Specifies a preferred order in which to use a set of contacts. ContactPoints with lower rank values are more preferred than those with higher rank values.","comment":"Note that rank does not necessarily follow the order in which the contacts are represented in the instance.","min":0,"max":"1","base":{"path":"ContactPoint.rank","min":0,"max":"1"},"type":[{"code":"positiveInt"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.telecom.period","path":"Patient.telecom.period","short":"Time period when the contact point was/is in use","definition":"Time period when the contact point was/is in use.","min":0,"max":"1","base":{"path":"ContactPoint.period","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.gender","path":"Patient.gender","short":"male | female | other | unknown","definition":"Administrative Gender - the gender that the patient is considered to have for administration and record keeping purposes.","comment":"The gender might not match the biological sex as determined by genetics or the individual's preferred identification. Note that for both humans and particularly animals, there are other legitimate possibilities than male and female, though the vast majority of systems and contexts only support male and female. Systems providing decision support or enforcing business rules should ideally do this on the basis of Observations dealing with the specific sex or gender aspect of interest (anatomical, chromosomal, social, etc.) However, because these observations are infrequently recorded, defaulting to the administrative gender is common practice. Where such defaulting occurs, rule enforcement should allow for the variation between administrative and biological, chromosomal and other gender aspects. For example, an alert about a hysterectomy on a male should be handled as a warning or overridable error, not a \"hard\" error. See the Patient Gender and Sex section for additional information about communicating patient gender and sex.","requirements":"Needed for identification of the individual, in combination with (at least) name and birth date.","min":1,"max":"1","base":{"path":"Patient.gender","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/ValueSet/administrative-gender"}},{"id":"Patient.birthDate","path":"Patient.birthDate","short":"The date of birth for the individual","definition":"The date of birth for the individual.","comment":"At least an estimated year should be provided as a guess if the real DOB is unknown There is a standard extension \"patient-birthTime\" available that should be used where Time is required (such as in maternity/infant care systems).","requirements":"Age of the individual drives many clinical processes.","min":0,"max":"1","base":{"path":"Patient.birthDate","min":0,"max":"1"},"type":[{"code":"date"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.deceased[x]","path":"Patient.deceased[x]","short":"Indicates if the individual is deceased or not","definition":"Indicates if the individual is deceased or not.","comment":"If there's no value in the instance, it means there is no statement on whether or not the individual is deceased. Most systems will interpret the absence of a value as a sign of the person being alive.","requirements":"The fact that a patient is deceased influences the clinical process. Also, in human communication and relation management it is necessary to know whether the person is alive.","min":0,"max":"1","base":{"path":"Patient.deceased[x]","min":0,"max":"1"},"type":[{"code":"boolean"},{"code":"dateTime"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because once a patient is marked as deceased, the actions that are appropriate to perform on the patient may be significantly different.","isSummary":true},{"id":"Patient.address","path":"Patient.address","short":"An address for the individual","definition":"An address for the individual.","comment":"Patient may have multiple addresses with different uses or applicable periods.","requirements":"May need to keep track of patient addresses for contacting, billing or reporting requirements and also to help with identification.","min":0,"max":"*","base":{"path":"Patient.address","min":0,"max":"*"},"type":[{"code":"Address"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.address.id","path":"Patient.address.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.address.extension","path":"Patient.address.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.address.use","path":"Patient.address.use","short":"home | work | temp | old | billing - purpose of this address","definition":"The purpose of this address.","comment":"Applications can assume that an address is current unless it explicitly says that it is temporary or old.","requirements":"Allows an appropriate address to be chosen from a list of many.","min":0,"max":"1","base":{"path":"Address.use","min":0,"max":"1"},"type":[{"code":"code"}],"example":[{"label":"General","valueCode":"home"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because applications should not mistake a temporary or old address etc.for a current/permanent one","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"AddressUse"}],"strength":"required","description":"The use of an address.","valueSet":"http://hl7.org/fhir/ValueSet/address-use|4.0.1"}},{"id":"Patient.address.type","path":"Patient.address.type","short":"postal | physical | both","definition":"Distinguishes between physical addresses (those you can visit) and mailing addresses (e.g. PO Boxes and care-of addresses). Most addresses are both.","comment":"The definition of Address states that \"address is intended to describe postal addresses, not physical locations\". However, many applications track whether an address has a dual purpose of being a location that can be visited as well as being a valid delivery destination, and Postal addresses are often used as proxies for physical locations (also see the [Location](http://hl7.org/fhir/R4/location.html#) resource).","min":0,"max":"1","base":{"path":"Address.type","min":0,"max":"1"},"type":[{"code":"code"}],"example":[{"label":"General","valueCode":"both"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"AddressType"}],"strength":"required","description":"The type of an address (physical / postal).","valueSet":"http://hl7.org/fhir/ValueSet/address-type|4.0.1"}},{"id":"Patient.address.text","path":"Patient.address.text","short":"Text representation of the address","definition":"Specifies the entire address as it should be displayed e.g. on a postal label. This may be provided instead of or as well as the specific parts.","comment":"Can provide both a text representation and parts. Applications updating an address SHALL ensure that when both text and parts are present, no content is included in the text that isn't found in a part.","requirements":"A renderable, unencoded form.","min":0,"max":"1","base":{"path":"Address.text","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"137 Nowhere Street, Erewhon 9132"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.address.line","path":"Patient.address.line","short":"Street name, number, direction & P.O. Box etc.","definition":"This component contains the house number, apartment number, street name, street direction, P.O. Box number, delivery hints, and similar address information.","min":0,"max":"*","base":{"path":"Address.line","min":0,"max":"*"},"type":[{"code":"string"}],"orderMeaning":"The order in which lines should appear in an address label","example":[{"label":"General","valueString":"137 Nowhere Street"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.address.city","path":"Patient.address.city","short":"Name of city, town etc.","definition":"The name of the city, town, suburb, village or other community or delivery center.","alias":["Municpality"],"min":0,"max":"1","base":{"path":"Address.city","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"Erewhon"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.address.district","path":"Patient.address.district","short":"District name (aka county)","definition":"The name of the administrative area (county).","comment":"District is sometimes known as county, but in some regions 'county' is used in place of city (municipality), so county name should be conveyed in city instead.","alias":["County"],"min":0,"max":"1","base":{"path":"Address.district","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"Madison"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.address.state","path":"Patient.address.state","short":"Sub-unit of country (abbreviations ok)","definition":"Sub-unit of a country with limited sovereignty in a federally organized country. A code may be used if codes are in common use (e.g. US 2 letter state codes).","alias":["Province","Territory"],"min":0,"max":"1","base":{"path":"Address.state","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"Two Letter USPS alphabetic codes.","valueSet":"http://hl7.org/fhir/us/core/ValueSet/us-core-usps-state"}},{"id":"Patient.address.postalCode","path":"Patient.address.postalCode","short":"US Zip Codes","definition":"A postal code designating a region defined by the postal service.","alias":["Zip","Zip Code"],"min":0,"max":"1","base":{"path":"Address.postalCode","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"9132"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.address.country","path":"Patient.address.country","short":"Country (e.g. can be ISO 3166 2 or 3 letter code)","definition":"Country - a nation as commonly understood or generally accepted.","comment":"ISO 3166 3 letter codes can be used in place of a human readable country name.","min":0,"max":"1","base":{"path":"Address.country","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.address.period","path":"Patient.address.period","short":"Time period when address was/is in use","definition":"Time period when address was/is in use.","requirements":"Allows addresses to be placed in historical context.","min":0,"max":"1","base":{"path":"Address.period","min":0,"max":"1"},"type":[{"code":"Period"}],"example":[{"label":"General","valuePeriod":{"start":"2010-03-23","end":"2010-07-01"}}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.maritalStatus","path":"Patient.maritalStatus","short":"Marital (civil) status of a patient","definition":"This field contains a patient's most recent marital (civil) status.","requirements":"Most, if not all systems capture it.","min":0,"max":"1","base":{"path":"Patient.maritalStatus","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MaritalStatus"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"extensible","description":"The domestic partnership status of a person.","valueSet":"http://hl7.org/fhir/ValueSet/marital-status"}},{"id":"Patient.multipleBirth[x]","path":"Patient.multipleBirth[x]","short":"Whether patient is part of a multiple birth","definition":"Indicates whether the patient is part of a multiple (boolean) or indicates the actual birth order (integer).","comment":"Where the valueInteger is provided, the number is the birth number in the sequence. E.g. The middle birth in triplets would be valueInteger=2 and the third born would have valueInteger=3 If a boolean value was provided for this triplets example, then all 3 patient records would have valueBoolean=true (the ordering is not indicated).","requirements":"For disambiguation of multiple-birth children, especially relevant where the care provider doesn't meet the patient, such as labs.","min":0,"max":"1","base":{"path":"Patient.multipleBirth[x]","min":0,"max":"1"},"type":[{"code":"boolean"},{"code":"integer"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.photo","path":"Patient.photo","short":"Image of the patient","definition":"Image of the patient.","comment":"Guidelines:\n* Use id photos, not clinical photos.\n* Limit dimensions to thumbnail.\n* Keep byte count low to ease resource updates.","requirements":"Many EHR systems have the capability to capture an image of the patient. Fits with newer social media usage too.","min":0,"max":"*","base":{"path":"Patient.photo","min":0,"max":"*"},"type":[{"code":"Attachment"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name","valueString":"Contact"}],"path":"Patient.contact","short":"A contact party (e.g. guardian, partner, friend) for the patient","definition":"A contact party (e.g. guardian, partner, friend) for the patient.","comment":"Contact covers all kinds of contact parties: family members, business contacts, guardians, caregivers. Not applicable to register pedigree and family ties beyond use of having contact.","requirements":"Need to track people you can contact about the patient.","min":0,"max":"*","base":{"path":"Patient.contact","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"pat-1","severity":"error","human":"SHALL at least contain a contact's details or a reference to an organization","expression":"name.exists() or telecom.exists() or address.exists() or organization.exists()","xpath":"exists(f:name) or exists(f:telecom) or exists(f:address) or exists(f:organization)","source":"http://hl7.org/fhir/StructureDefinition/Patient"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.id","path":"Patient.contact.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.extension","path":"Patient.contact.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.modifierExtension","path":"Patient.contact.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Patient.contact.relationship","path":"Patient.contact.relationship","short":"The kind of relationship","definition":"The nature of the relationship between the patient and the contact person.","requirements":"Used to determine which contact person is the most relevant to approach, depending on circumstances.","min":0,"max":"*","base":{"path":"Patient.contact.relationship","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ContactRelationship"}],"strength":"extensible","description":"The nature of the relationship between a patient and a contact person for that patient.","valueSet":"http://hl7.org/fhir/ValueSet/patient-contactrelationship"}},{"id":"Patient.contact.name","path":"Patient.contact.name","short":"A name associated with the contact person","definition":"A name associated with the contact person.","requirements":"Contact persons need to be identified by name, but it is uncommon to need details about multiple other names for that contact person.","min":0,"max":"1","base":{"path":"Patient.contact.name","min":0,"max":"1"},"type":[{"code":"HumanName"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.telecom","path":"Patient.contact.telecom","short":"A contact detail for the person","definition":"A contact detail for the person, e.g. a telephone number or an email address.","comment":"Contact may have multiple ways to be contacted with different uses or applicable periods. May need to have options for contacting the person urgently, and also to help with identification.","requirements":"People have (primary) ways to contact them in some way such as phone, email.","min":0,"max":"*","base":{"path":"Patient.contact.telecom","min":0,"max":"*"},"type":[{"code":"ContactPoint"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.address","path":"Patient.contact.address","short":"Address for the contact person","definition":"Address for the contact person.","requirements":"Need to keep track where the contact person can be contacted per postal mail or visited.","min":0,"max":"1","base":{"path":"Patient.contact.address","min":0,"max":"1"},"type":[{"code":"Address"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.gender","path":"Patient.contact.gender","short":"male | female | other | unknown","definition":"Administrative Gender - the gender that the contact person is considered to have for administration and record keeping purposes.","requirements":"Needed to address the person correctly.","min":0,"max":"1","base":{"path":"Patient.contact.gender","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"AdministrativeGender"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"required","description":"The gender of a person used for administrative purposes.","valueSet":"http://hl7.org/fhir/ValueSet/administrative-gender|4.0.1"}},{"id":"Patient.contact.organization","path":"Patient.contact.organization","short":"Organization that is associated with the contact","definition":"Organization on behalf of which the contact is acting or for which the contact is working.","requirements":"For guardians or business related contacts, the organization is relevant.","min":0,"max":"1","base":{"path":"Patient.contact.organization","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"condition":["pat-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.period","path":"Patient.contact.period","short":"The period during which this contact person or organization is valid to be contacted relating to this patient","definition":"The period during which this contact person or organization is valid to be contacted relating to this patient.","min":0,"max":"1","base":{"path":"Patient.contact.period","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.communication","path":"Patient.communication","short":"A language which may be used to communicate with the patient about his or her health","definition":"A language which may be used to communicate with the patient about his or her health.","comment":"If no language is specified, this *implies* that the default local language is spoken. If you need to convey proficiency for multiple modes, then you need multiple Patient.Communication associations. For animals, language is not a relevant field, and should be absent from the instance. If the Patient does not speak the default local language, then the Interpreter Required Standard can be used to explicitly declare that an interpreter is required.","requirements":"If a patient does not speak the local language, interpreters may be required, so languages spoken and proficiency are important things to keep track of both for patient and other persons of interest.","min":0,"max":"*","base":{"path":"Patient.communication","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":false,"isModifier":false,"isSummary":false},{"id":"Patient.communication.id","path":"Patient.communication.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.communication.extension","path":"Patient.communication.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.communication.modifierExtension","path":"Patient.communication.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Patient.communication.language","path":"Patient.communication.language","short":"The language which can be used to communicate with the patient about his or her health","definition":"The ISO-639-1 alpha 2 code in lower case for the language, optionally followed by a hyphen and the ISO-3166-1 alpha 2 code for the region in upper case; e.g. \"en\" for English, or \"en-US\" for American English versus \"en-EN\" for England English.","comment":"The structure aa-BB with this exact casing is one the most widely used notations for locale. However not all systems actually code this but instead have it as free text. Hence CodeableConcept instead of code as the data type.","requirements":"Most systems in multilingual countries will want to convey language. Not all systems actually need the regional dialect.","min":1,"max":"1","base":{"path":"Patient.communication.language","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"strength":"extensible","valueSet":"http://hl7.org/fhir/us/core/ValueSet/simple-language"}},{"id":"Patient.communication.preferred","path":"Patient.communication.preferred","short":"Language preference indicator","definition":"Indicates whether or not the patient prefers this language (over other languages he masters up a certain level).","comment":"This language is specifically identified for communicating healthcare information.","requirements":"People that master multiple languages up to certain level may prefer one or more, i.e. feel more confident in communicating in a particular language making other languages sort of a fall back method.","min":0,"max":"1","base":{"path":"Patient.communication.preferred","min":0,"max":"1"},"type":[{"code":"boolean"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.generalPractitioner","path":"Patient.generalPractitioner","short":"Patient's nominated primary care provider","definition":"Patient's nominated care provider.","comment":"This may be the primary care provider (in a GP context), or it may be a patient nominated care manager in a community/disability setting, or even organization that will provide people to perform the care provider roles. It is not to be used to record Care Teams, these should be in a CareTeam resource that may be linked to the CarePlan or EpisodeOfCare resources.\nMultiple GPs may be recorded against the patient for various reasons, such as a student that has his home GP listed along with the GP at university during the school semesters, or a \"fly-in/fly-out\" worker that has the onsite GP also included with his home GP to remain aware of medical issues.\n\nJurisdictions may decide that they can profile this down to 1 if desired, or 1 per type.","alias":["careProvider"],"min":0,"max":"*","base":{"path":"Patient.generalPractitioner","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization","http://hl7.org/fhir/StructureDefinition/Practitioner","http://hl7.org/fhir/StructureDefinition/PractitionerRole"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.managingOrganization","path":"Patient.managingOrganization","short":"Organization that is the custodian of the patient record","definition":"Organization that is the custodian of the patient record.","comment":"There is only one managing organization for a specific patient record. Other organizations will have their own Patient record, and may use the Link property to join the records together (or a Person resource which can include confidence ratings for the association).","requirements":"Need to know who recognizes this patient record, manages and updates it.","min":0,"max":"1","base":{"path":"Patient.managingOrganization","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.link","path":"Patient.link","short":"Link to another patient resource that concerns the same actual person","definition":"Link to another patient resource that concerns the same actual patient.","comment":"There is no assumption that linked patient records have mutual links.","requirements":"There are multiple use cases: \n\n* Duplicate patient records due to the clerical errors associated with the difficulties of identifying humans consistently, and \n* Distribution of patient information across multiple servers.","min":0,"max":"*","base":{"path":"Patient.link","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because it might not be the main Patient resource, and the referenced patient should be used instead of this Patient record. This is when the link.type value is 'replaced-by'","isSummary":true},{"id":"Patient.link.id","path":"Patient.link.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.link.extension","path":"Patient.link.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.link.modifierExtension","path":"Patient.link.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Patient.link.other","path":"Patient.link.other","short":"The other patient or related person resource that the link refers to","definition":"The other patient resource that the link refers to.","comment":"Referencing a RelatedPerson here removes the need to use a Person record to associate a Patient and RelatedPerson as the same individual.","min":1,"max":"1","base":{"path":"Patient.link.other","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-hierarchy","valueBoolean":false}],"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Patient","http://hl7.org/fhir/StructureDefinition/RelatedPerson"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.link.type","path":"Patient.link.type","short":"replaced-by | replaces | refer | seealso","definition":"The type of link between this patient resource and another patient resource.","min":1,"max":"1","base":{"path":"Patient.link.type","min":1,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"LinkType"}],"strength":"required","description":"The type of link between this patient resource and another patient resource.","valueSet":"http://hl7.org/fhir/ValueSet/link-type|4.0.1"}}]}},{"resourceType":"StructureDefinition","id":"us-core-race","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","version":"5.0.1","name":"USCoreRaceExtension","title":"US Core Race Extension","status":"active","date":"2019-05-21","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The race codes used to represent these concepts are based upon the [CDC Race and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 921 reference race. The race concepts are grouped by and pre-mapped to the 5 OMB race categories:\n\n - American Indian or Alaska Native\n - Asian\n - Black or African American\n - Native Hawaiian or Other Pacific Islander\n - White.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"purpose":"Complies with 2015 Edition Common Clinical Data Set for patient race.","copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"complex-type","abstract":false,"context":[{"type":"element","expression":"Patient"},{"type":"element","expression":"RelatedPerson"},{"type":"element","expression":"Person"},{"type":"element","expression":"Practitioner"},{"type":"element","expression":"FamilyMemberHistory"}],"type":"Extension","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Extension","derivation":"constraint","snapshot":{"element":[{"id":"Extension","path":"Extension","short":"US Core Race Extension","definition":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The race codes used to represent these concepts are based upon the [CDC Race and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 921 reference race. The race concepts are grouped by and pre-mapped to the 5 OMB race categories:\n\n - American Indian or Alaska Native\n - Asian\n - Black or African American\n - Native Hawaiian or Other Pacific Islander\n - White.","min":0,"max":"1","base":{"path":"Extension","min":0,"max":"*"},"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false},{"id":"Extension.id","path":"Extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension","path":"Extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory","path":"Extension.extension","sliceName":"ombCategory","short":"American Indian or Alaska Native|Asian|Black or African American|Native Hawaiian or Other Pacific Islander|White","definition":"The 5 race category codes according to the [OMB Standards for Maintaining, Collecting, and Presenting Federal Data on Race and Ethnicity, Statistical Policy Directive No. 15, as revised, October 30, 1997](https://www.govinfo.gov/content/pkg/FR-1997-10-30/pdf/97-28653.pdf).","min":0,"max":"5","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"ombCategory","isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","description":"The 5 race category codes according to the [OMB Standards for Maintaining, Collecting, and Presenting Federal Data on Race and Ethnicity, Statistical Policy Directive No. 15, as revised, October 30, 1997](https://www.govinfo.gov/content/pkg/FR-1997-10-30/pdf/97-28653.pdf).","valueSet":"http://hl7.org/fhir/us/core/ValueSet/omb-race-category"}},{"id":"Extension.extension:detailed","path":"Extension.extension","sliceName":"detailed","short":"Extended race codes","definition":"The 900+ CDC race codes that are grouped under one of the 5 OMB race category codes:.","min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"detailed","isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/us/core/ValueSet/detailed-race"}},{"id":"Extension.extension:text","path":"Extension.extension","sliceName":"text","short":"Race Text","definition":"Plain text representation of the race concept(s).","min":1,"max":"1","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"text","isModifier":false,"isSummary":false},{"id":"Extension.extension:text.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Extension.url","path":"Extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"uri"}],"code":"http://hl7.org/fhirpath/System.String"}],"fixedUri":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","isModifier":false,"isSummary":false},{"id":"Extension.value[x]","path":"Extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":0,"max":"0","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"base64Binary"},{"code":"boolean"},{"code":"canonical"},{"code":"code"},{"code":"date"},{"code":"dateTime"},{"code":"decimal"},{"code":"id"},{"code":"instant"},{"code":"integer"},{"code":"markdown"},{"code":"oid"},{"code":"positiveInt"},{"code":"string"},{"code":"time"},{"code":"unsignedInt"},{"code":"uri"},{"code":"url"},{"code":"uuid"},{"code":"Address"},{"code":"Age"},{"code":"Annotation"},{"code":"Attachment"},{"code":"CodeableConcept"},{"code":"Coding"},{"code":"ContactPoint"},{"code":"Count"},{"code":"Distance"},{"code":"Duration"},{"code":"HumanName"},{"code":"Identifier"},{"code":"Money"},{"code":"Period"},{"code":"Quantity"},{"code":"Range"},{"code":"Ratio"},{"code":"Reference"},{"code":"SampledData"},{"code":"Signature"},{"code":"Timing"},{"code":"ContactDetail"},{"code":"Contributor"},{"code":"DataRequirement"},{"code":"Expression"},{"code":"ParameterDefinition"},{"code":"RelatedArtifact"},{"code":"TriggerDefinition"},{"code":"UsageContext"},{"code":"Dosage"},{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false}]}},{"resourceType":"StructureDefinition","id":"us-core-ethnicity","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","version":"5.0.1","name":"USCoreEthnicityExtension","title":"US Core Ethnicity Extension","status":"active","date":"2019-05-21T00:00:00-04:00","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The ethnicity codes used to represent these concepts are based upon the [CDC ethnicity and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 43 reference ethnicity. The ethnicity concepts are grouped by and pre-mapped to the 2 OMB ethnicity categories: - Hispanic or Latino - Not Hispanic or Latino.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"purpose":"Complies with 2015 Edition Common Clinical Data Set for patient race.","copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"complex-type","abstract":false,"context":[{"type":"element","expression":"Patient"},{"type":"element","expression":"RelatedPerson"},{"type":"element","expression":"Person"},{"type":"element","expression":"Practitioner"},{"type":"element","expression":"FamilyMemberHistory"}],"type":"Extension","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Extension","derivation":"constraint","snapshot":{"element":[{"id":"Extension","path":"Extension","short":"US Core ethnicity Extension","definition":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The ethnicity codes used to represent these concepts are based upon the [CDC ethnicity and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 43 reference ethnicity. The ethnicity concepts are grouped by and pre-mapped to the 2 OMB ethnicity categories: - Hispanic or Latino - Not Hispanic or Latino.","min":0,"max":"1","base":{"path":"Extension","min":0,"max":"*"},"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false},{"id":"Extension.id","path":"Extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension","path":"Extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory","path":"Extension.extension","sliceName":"ombCategory","short":"Hispanic or Latino|Not Hispanic or Latino","definition":"The 2 ethnicity category codes according to the [OMB Standards for Maintaining, Collecting, and Presenting Federal Data on Race and Ethnicity, Statistical Policy Directive No. 15, as revised, October 30, 1997](https://www.govinfo.gov/content/pkg/FR-1997-10-30/pdf/97-28653.pdf).","min":0,"max":"1","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"ombCategory","isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/us/core/ValueSet/omb-ethnicity-category"}},{"id":"Extension.extension:detailed","path":"Extension.extension","sliceName":"detailed","short":"Extended ethnicity codes","definition":"The 41 CDC ethnicity codes that are grouped under one of the 2 OMB ethnicity category codes.","min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"detailed","isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/us/core/ValueSet/detailed-ethnicity"}},{"id":"Extension.extension:text","path":"Extension.extension","sliceName":"text","short":"ethnicity Text","definition":"Plain text representation of the ethnicity concept(s).","min":1,"max":"1","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"text","isModifier":false,"isSummary":false},{"id":"Extension.extension:text.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Extension.url","path":"Extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"uri"}],"code":"http://hl7.org/fhirpath/System.String"}],"fixedUri":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","isModifier":false,"isSummary":false},{"id":"Extension.value[x]","path":"Extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":0,"max":"0","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"base64Binary"},{"code":"boolean"},{"code":"canonical"},{"code":"code"},{"code":"date"},{"code":"dateTime"},{"code":"decimal"},{"code":"id"},{"code":"instant"},{"code":"integer"},{"code":"markdown"},{"code":"oid"},{"code":"positiveInt"},{"code":"string"},{"code":"time"},{"code":"unsignedInt"},{"code":"uri"},{"code":"url"},{"code":"uuid"},{"code":"Address"},{"code":"Age"},{"code":"Annotation"},{"code":"Attachment"},{"code":"CodeableConcept"},{"code":"Coding"},{"code":"ContactPoint"},{"code":"Count"},{"code":"Distance"},{"code":"Duration"},{"code":"HumanName"},{"code":"Identifier"},{"code":"Money"},{"code":"Period"},{"code":"Quantity"},{"code":"Range"},{"code":"Ratio"},{"code":"Reference"},{"code":"SampledData"},{"code":"Signature"},{"code":"Timing"},{"code":"ContactDetail"},{"code":"Contributor"},{"code":"DataRequirement"},{"code":"Expression"},{"code":"ParameterDefinition"},{"code":"RelatedArtifact"},{"code":"TriggerDefinition"},{"code":"UsageContext"},{"code":"Dosage"},{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false}]}},{"resourceType":"StructureDefinition","id":"us-core-birthsex","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","version":"5.0.1","name":"USCoreBirthSexExtension","title":"US Core Birth Sex Extension","status":"active","date":"2019-05-21","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"A code classifying the person's sex assigned at birth as specified by the [Office of the National Coordinator for Health IT (ONC)](https://www.healthit.gov/newsroom/about-onc). This extension aligns with the C-CDA Birth Sex Observation (LOINC 76689-9).","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"complex-type","abstract":false,"context":[{"type":"element","expression":"Patient"}],"type":"Extension","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Extension","derivation":"constraint","snapshot":{"element":[{"id":"Extension","path":"Extension","short":"Extension","definition":"A code classifying the person's sex assigned at birth as specified by the [Office of the National Coordinator for Health IT (ONC)](https://www.healthit.gov/newsroom/about-onc).","comment":"The codes required are intended to present birth sex (i.e., the sex recorded on the patient’s birth certificate) and not gender identity or reassigned sex.","min":0,"max":"1","base":{"path":"Extension","min":0,"max":"*"},"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false},{"id":"Extension.id","path":"Extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension","path":"Extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.url","path":"Extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"uri"}],"code":"http://hl7.org/fhirpath/System.String"}],"fixedUri":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","isModifier":false,"isSummary":false},{"id":"Extension.value[x]","path":"Extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","description":"Code for sex assigned at birth","valueSet":"http://hl7.org/fhir/us/core/ValueSet/birthsex"}}]}},{"resourceType":"StructureDefinition","id":"us-core-genderIdentity","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity","version":"5.0.1","name":"USCoreGenderIdentityExtension","title":"US Core Gender Identity Extension","status":"active","date":"2022-01-22","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"This extension provides concepts to describe the gender a person identifies as.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"purpose":"Complies with USCDI v2","copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"complex-type","abstract":false,"context":[{"type":"element","expression":"Patient"},{"type":"element","expression":"RelatedPerson"},{"type":"element","expression":"Person"},{"type":"element","expression":"Practitioner"}],"type":"Extension","baseDefinition":"http://hl7.org/fhir/StructureDefinition/patient-genderIdentity","derivation":"constraint","snapshot":{"element":[{"id":"Extension","path":"Extension","short":"Extension","definition":"An Extension","min":0,"max":"1","base":{"path":"Extension","min":0,"max":"*"},"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false},{"id":"Extension.id","path":"Extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension","path":"Extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Extension","definition":"An Extension","min":0,"max":"0","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.url","path":"Extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"uri"}],"code":"http://hl7.org/fhirpath/System.String"}],"fixedUri":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity","isModifier":false,"isSummary":false},{"id":"Extension.value[x]","path":"Extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"extensible","valueSet":"http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1021.32"}}]}},{"resourceType":"StructureDefinition","id":"us-core-implantable-device","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-implantable-device","version":"5.0.1","name":"USCoreImplantableDeviceProfile","title":"US Core Implantable Device Profile","status":"active","experimental":false,"date":"2022-04-20","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"The US Core Implantable Device Profile is based upon the core FHIR Device Resource and meets the U.S. Core Data for Interoperability (USCDI) v2 ‘Unique Device Identifier(s) for a Patient’s Implantable Device(s)’ requirements. To promote interoperability and adoption through common implementation, this profile sets minimum expectations for the Device resource to record, search, and fetch UDI information associated with a patient's implantable device(s). It identifies which core elements, extensions, vocabularies and value sets **SHALL** be present in the resource when using this profile. It provides the floor for standards development for specific uses cases.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"resource","abstract":false,"type":"Device","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Device","derivation":"constraint","snapshot":{"element":[{"id":"Device","path":"Device","short":"Item used in healthcare","definition":"\\-","comment":"\\-","min":0,"max":"*","base":{"path":"Device","min":0,"max":"*"},"constraint":[{"key":"dom-2","severity":"error","human":"If the resource is contained in another resource, it SHALL NOT contain nested Resources","expression":"contained.contained.empty()","xpath":"not(parent::f:contained and f:contained)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-3","severity":"error","human":"If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource","expression":"contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()","xpath":"not(exists(for $id in f:contained/*/f:id/@value return $contained[not(parent::*/descendant::f:reference/@value=concat('#', $contained/*/id/@value) or descendant::f:reference[@value='#'])]))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-4","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated","expression":"contained.meta.versionId.empty() and contained.meta.lastUpdated.empty()","xpath":"not(exists(f:contained/*/f:meta/f:versionId)) and not(exists(f:contained/*/f:meta/f:lastUpdated))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-5","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a security label","expression":"contained.meta.security.empty()","xpath":"not(exists(f:contained/*/f:meta/f:security))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice","valueBoolean":true},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice-explanation","valueMarkdown":"When a resource has no narrative, only systems that fully understand the data can display the resource to a human safely. Including a human readable representation in the resource makes for a much more robust eco-system and cheaper handling of resources by intermediary systems. Some ecosystems restrict distribution of resources to only those systems that do fully understand the resources, and as a consequence implementers may believe that the narrative is superfluous. However experience shows that such eco-systems often open up to new participants over time."}],"key":"dom-6","severity":"warning","human":"A resource should have narrative for robust management","expression":"text.`div`.exists()","xpath":"exists(f:text/h:div)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"}],"mustSupport":false,"isModifier":false,"isSummary":false},{"id":"Device.id","path":"Device.id","short":"Logical id of this artifact","definition":"The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.","comment":"The only time that a resource does not have an id is when it is being submitted to the server using a create operation.","min":0,"max":"1","base":{"path":"Resource.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":true},{"id":"Device.meta","path":"Device.meta","short":"Metadata about the resource","definition":"The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource.","min":0,"max":"1","base":{"path":"Resource.meta","min":0,"max":"1"},"type":[{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Device.implicitRules","path":"Device.implicitRules","short":"A set of rules under which this content was created","definition":"A reference to a set of rules that were followed when the resource was constructed, and which must be understood when processing the content. Often, this is a reference to an implementation guide that defines the special rules along with other profiles etc.","comment":"Asserting this rule set restricts the content to be only understood by a limited set of trading partners. This inherently limits the usefulness of the data in the long term. However, the existing health eco-system is highly fractured, and not yet ready to define, collect, and exchange data in a generally computable sense. Wherever possible, implementers and/or specification writers should avoid using this element. Often, when used, the URL is a reference to an implementation guide that defines these special rules as part of it's narrative along with other profiles, value sets, etc.","min":0,"max":"1","base":{"path":"Resource.implicitRules","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because the implicit rules may provide additional knowledge about the resource that modifies it's meaning or interpretation","isSummary":true},{"id":"Device.language","path":"Device.language","short":"Language of the resource content","definition":"The base language in which the resource is written.","comment":"Language is provided to support indexing and accessibility (typically, services such as text to speech use the language tag). The html language tag in the narrative applies to the narrative. The language tag on the resource may be used to specify the language of other presentations generated from the data in the resource. Not all the content has to be in the base language. The Resource.language should not be assumed to apply to the narrative automatically. If a language is specified, it should it also be specified on the div element in the html (see rules in HTML5 for information about the relationship between xml:lang and the html lang attribute).","min":0,"max":"1","base":{"path":"Resource.language","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet","valueCanonical":"http://hl7.org/fhir/ValueSet/all-languages"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"Language"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"preferred","description":"A human language.","valueSet":"http://hl7.org/fhir/ValueSet/languages"}},{"id":"Device.text","path":"Device.text","short":"Text summary of the resource, for human interpretation","definition":"A human-readable narrative that contains a summary of the resource and can be used to represent the content of the resource to a human. The narrative need not encode all the structured data, but is required to contain sufficient detail to make it \"clinically safe\" for a human to just read the narrative. Resource definitions may define what content should be represented in the narrative to ensure clinical safety.","comment":"Contained resources do not have narrative. Resources that are not contained SHOULD have a narrative. In some cases, a resource may only have text with little or no additional discrete data (as long as all minOccurs=1 elements are satisfied). This may be necessary for data from legacy systems where information is captured as a \"text blob\" or where text is additionally entered raw or narrated and encoded information is added later.","alias":["narrative","html","xhtml","display"],"min":0,"max":"1","base":{"path":"DomainResource.text","min":0,"max":"1"},"type":[{"code":"Narrative"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.contained","path":"Device.contained","short":"Contained, inline Resources","definition":"These resources do not have an independent existence apart from the resource that contains them - they cannot be identified independently, and nor can they have their own independent transaction scope.","comment":"This should never be done when the content can be identified properly, as once identification is lost, it is extremely difficult (and context dependent) to restore it again. Contained resources may have profiles and tags In their meta elements, but SHALL NOT have security labels.","alias":["inline resources","anonymous resources","contained resources"],"min":0,"max":"*","base":{"path":"DomainResource.contained","min":0,"max":"*"},"type":[{"code":"Resource"}],"isModifier":false,"isSummary":false},{"id":"Device.extension","path":"Device.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the resource. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.modifierExtension","path":"Device.modifierExtension","short":"Extensions that cannot be ignored","definition":"May be used to represent additional information that is not part of the basic definition of the resource and that modifies the understanding of the element that contains it and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the resource that contains them","isSummary":false},{"id":"Device.identifier","path":"Device.identifier","short":"Instance identifier","definition":"Unique instance identifiers assigned to a device by manufacturers other organizations or owners.","comment":"The barcode string from a barcode present on a device label or package may identify the instance, include names given to the device in local usage, or may identify the type of device. If the identifier identifies the type of device, Device.type element should be used.","min":0,"max":"*","base":{"path":"Device.identifier","min":0,"max":"*"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.definition","path":"Device.definition","short":"The reference to the definition for the device","definition":"The reference to the definition for the device.","min":0,"max":"1","base":{"path":"Device.definition","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/DeviceDefinition"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier","path":"Device.udiCarrier","short":"Unique Device Identifier (UDI) Barcode string","definition":"Unique device identifier (UDI) assigned to device label or package. Note that the Device may include multiple udiCarriers as it either may include just the udiCarrier for the jurisdiction it is sold, or for multiple jurisdictions it could have been sold.","comment":"Some devices may not have UDI information (for example. historical data or patient reported data).","min":0,"max":"1","base":{"path":"Device.udiCarrier","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Device.udiCarrier.id","path":"Device.udiCarrier.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier.extension","path":"Device.udiCarrier.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier.modifierExtension","path":"Device.udiCarrier.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.udiCarrier.deviceIdentifier","path":"Device.udiCarrier.deviceIdentifier","short":"Mandatory fixed portion of UDI","definition":"The device identifier (DI) is a mandatory, fixed portion of a UDI that identifies the labeler and the specific version or model of a device.","alias":["DI"],"min":1,"max":"1","base":{"path":"Device.udiCarrier.deviceIdentifier","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Device.udiCarrier.issuer","path":"Device.udiCarrier.issuer","short":"UDI Issuing Organization","definition":"Organization that is charged with issuing UDIs for devices. For example, the US FDA issuers include :\n1) GS1: \nhttp://hl7.org/fhir/NamingSystem/gs1-di, \n2) HIBCC:\nhttp://hl7.org/fhir/NamingSystem/hibcc-dI, \n3) ICCBBA for blood containers:\nhttp://hl7.org/fhir/NamingSystem/iccbba-blood-di, \n4) ICCBA for other devices:\nhttp://hl7.org/fhir/NamingSystem/iccbba-other-di.","alias":["Barcode System"],"min":0,"max":"1","base":{"path":"Device.udiCarrier.issuer","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier.jurisdiction","path":"Device.udiCarrier.jurisdiction","short":"Regional UDI authority","definition":"The identity of the authoritative source for UDI generation within a jurisdiction. All UDIs are globally unique within a single namespace with the appropriate repository uri as the system. For example, UDIs of devices managed in the U.S. by the FDA, the value is http://hl7.org/fhir/NamingSystem/fda-udi.","requirements":"Allows a recipient of a UDI to know which database will contain the UDI-associated metadata.","min":0,"max":"1","base":{"path":"Device.udiCarrier.jurisdiction","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier.carrierAIDC","path":"Device.udiCarrier.carrierAIDC","short":"UDI Machine Readable Barcode String","definition":"The full UDI carrier of the Automatic Identification and Data Capture (AIDC) technology representation of the barcode string as printed on the packaging of the device - e.g., a barcode or RFID. Because of limitations on character sets in XML and the need to round-trip JSON data through XML, AIDC Formats *SHALL* be base64 encoded.","comment":"The AIDC form of UDIs should be scanned or otherwise used for the identification of the device whenever possible to minimize errors in records resulting from manual transcriptions. If separate barcodes for DI and PI are present, concatenate the string with DI first and in order of human readable expression on label.","alias":["Automatic Identification and Data Capture"],"min":0,"max":"1","base":{"path":"Device.udiCarrier.carrierAIDC","min":0,"max":"1"},"type":[{"code":"base64Binary"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Device.udiCarrier.carrierHRF","path":"Device.udiCarrier.carrierHRF","short":"UDI Human Readable Barcode String","definition":"The full UDI carrier as the human readable form (HRF) representation of the barcode string as printed on the packaging of the device.","comment":"If separate barcodes for DI and PI are present, concatenate the string with DI first and in order of human readable expression on label.","alias":["Human Readable Form","UDI","Barcode String"],"min":0,"max":"1","base":{"path":"Device.udiCarrier.carrierHRF","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Device.udiCarrier.entryType","path":"Device.udiCarrier.entryType","short":"barcode | rfid | manual +","definition":"A coded entry to indicate how the data was entered.","requirements":"Supports a way to distinguish hand entered from machine read data.","min":0,"max":"1","base":{"path":"Device.udiCarrier.entryType","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"UDIEntryType"}],"strength":"required","description":"Codes to identify how UDI data was entered.","valueSet":"http://hl7.org/fhir/ValueSet/udi-entry-type|4.0.1"}},{"id":"Device.status","path":"Device.status","short":"active | inactive | entered-in-error | unknown","definition":"Status of the Device availability.","comment":"This element is labeled as a modifier because the status contains the codes inactive and entered-in-error that mark the device (record)as not currently valid.","min":0,"max":"1","base":{"path":"Device.status","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labelled as a modifier because it is a status element that contains status entered-in-error which means that the resource should not be treated as valid","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"FHIRDeviceStatus"}],"strength":"required","description":"The availability status of the device.","valueSet":"http://hl7.org/fhir/ValueSet/device-status|4.0.1"}},{"id":"Device.statusReason","path":"Device.statusReason","short":"online | paused | standby | offline | not-ready | transduc-discon | hw-discon | off","definition":"Reason for the dtatus of the Device availability.","min":0,"max":"*","base":{"path":"Device.statusReason","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"FHIRDeviceStatusReason"}],"strength":"extensible","description":"The availability status reason of the device.","valueSet":"http://hl7.org/fhir/ValueSet/device-status-reason"}},{"id":"Device.distinctIdentifier","path":"Device.distinctIdentifier","short":"The distinct identification string","definition":"The distinct identification string as required by regulation for a human cell, tissue, or cellular and tissue-based product.","comment":"For example, this applies to devices in the United States regulated under *Code of Federal Regulation 21CFR§1271.290(c)*.","alias":["Distinct Identification Code (DIC)"],"min":0,"max":"1","base":{"path":"Device.distinctIdentifier","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.manufacturer","path":"Device.manufacturer","short":"Name of device manufacturer","definition":"A name of the manufacturer.","min":0,"max":"1","base":{"path":"Device.manufacturer","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.manufactureDate","path":"Device.manufactureDate","short":"Date when the device was made","definition":"The date and time when the device was manufactured.","min":0,"max":"1","base":{"path":"Device.manufactureDate","min":0,"max":"1"},"type":[{"code":"dateTime"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.expirationDate","path":"Device.expirationDate","short":"Date and time of expiry of this device (if applicable)","definition":"The date and time beyond which this device is no longer valid or should not be used (if applicable).","min":0,"max":"1","base":{"path":"Device.expirationDate","min":0,"max":"1"},"type":[{"code":"dateTime"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.lotNumber","path":"Device.lotNumber","short":"Lot number of manufacture","definition":"Lot number assigned by the manufacturer.","min":0,"max":"1","base":{"path":"Device.lotNumber","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.serialNumber","path":"Device.serialNumber","short":"Serial number assigned by the manufacturer","definition":"The serial number assigned by the organization when the device was manufactured.","comment":"Alphanumeric Maximum 20.","min":0,"max":"1","base":{"path":"Device.serialNumber","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.deviceName","path":"Device.deviceName","short":"The name of the device as given by the manufacturer","definition":"This represents the manufacturer's name of the device as provided by the device, from a UDI label, or by a person describing the Device. This typically would be used when a person provides the name(s) or when the device represents one of the names available from DeviceDefinition.","min":0,"max":"*","base":{"path":"Device.deviceName","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.deviceName.id","path":"Device.deviceName.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.deviceName.extension","path":"Device.deviceName.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.deviceName.modifierExtension","path":"Device.deviceName.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.deviceName.name","path":"Device.deviceName.name","short":"The name of the device","definition":"The name of the device.","alias":["Σ"],"min":1,"max":"1","base":{"path":"Device.deviceName.name","min":1,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.deviceName.type","path":"Device.deviceName.type","short":"udi-label-name | user-friendly-name | patient-reported-name | manufacturer-name | model-name | other","definition":"The type of deviceName.\nUDILabelName | UserFriendlyName | PatientReportedName | ManufactureDeviceName | ModelName.","min":1,"max":"1","base":{"path":"Device.deviceName.type","min":1,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"DeviceNameType"}],"strength":"required","description":"The type of name the device is referred by.","valueSet":"http://hl7.org/fhir/ValueSet/device-nametype|4.0.1"}},{"id":"Device.modelNumber","path":"Device.modelNumber","short":"The model number for the device","definition":"The model number for the device.","min":0,"max":"1","base":{"path":"Device.modelNumber","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.partNumber","path":"Device.partNumber","short":"The part number of the device","definition":"The part number of the device.","comment":"Alphanumeric Maximum 20.","min":0,"max":"1","base":{"path":"Device.partNumber","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.type","path":"Device.type","short":"The kind or type of device","definition":"The kind or type of device.","min":1,"max":"1","base":{"path":"Device.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"strength":"extensible","description":"Codes to identify medical devices","valueSet":"http://hl7.org/fhir/ValueSet/device-kind"}},{"id":"Device.specialization","path":"Device.specialization","short":"The capabilities supported on a device, the standards to which the device conforms for a particular purpose, and used for the communication","definition":"The capabilities supported on a device, the standards to which the device conforms for a particular purpose, and used for the communication.","min":0,"max":"*","base":{"path":"Device.specialization","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.specialization.id","path":"Device.specialization.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.specialization.extension","path":"Device.specialization.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.specialization.modifierExtension","path":"Device.specialization.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.specialization.systemType","path":"Device.specialization.systemType","short":"The standard that is used to operate and communicate","definition":"The standard that is used to operate and communicate.","alias":["Σ"],"min":1,"max":"1","base":{"path":"Device.specialization.systemType","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.specialization.version","path":"Device.specialization.version","short":"The version of the standard that is used to operate and communicate","definition":"The version of the standard that is used to operate and communicate.","min":0,"max":"1","base":{"path":"Device.specialization.version","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.version","path":"Device.version","short":"The actual design of the device or software version running on the device","definition":"The actual design of the device or software version running on the device.","min":0,"max":"*","base":{"path":"Device.version","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.version.id","path":"Device.version.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.version.extension","path":"Device.version.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.version.modifierExtension","path":"Device.version.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.version.type","path":"Device.version.type","short":"The type of the device version","definition":"The type of the device version.","alias":["Σ"],"min":0,"max":"1","base":{"path":"Device.version.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.version.component","path":"Device.version.component","short":"A single component of the device version","definition":"A single component of the device version.","min":0,"max":"1","base":{"path":"Device.version.component","min":0,"max":"1"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.version.value","path":"Device.version.value","short":"The version text","definition":"The version text.","min":1,"max":"1","base":{"path":"Device.version.value","min":1,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.property","path":"Device.property","short":"The actual configuration settings of a device as it actually operates, e.g., regulation status, time properties","definition":"The actual configuration settings of a device as it actually operates, e.g., regulation status, time properties.","min":0,"max":"*","base":{"path":"Device.property","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.property.id","path":"Device.property.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.property.extension","path":"Device.property.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.property.modifierExtension","path":"Device.property.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.property.type","path":"Device.property.type","short":"Code that specifies the property DeviceDefinitionPropetyCode (Extensible)","definition":"Code that specifies the property DeviceDefinitionPropetyCode (Extensible).","min":1,"max":"1","base":{"path":"Device.property.type","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.property.valueQuantity","path":"Device.property.valueQuantity","short":"Property value as a quantity","definition":"Property value as a quantity.","min":0,"max":"*","base":{"path":"Device.property.valueQuantity","min":0,"max":"*"},"type":[{"code":"Quantity"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.property.valueCode","path":"Device.property.valueCode","short":"Property value as a code, e.g., NTP4 (synced to NTP)","definition":"Property value as a code, e.g., NTP4 (synced to NTP).","min":0,"max":"*","base":{"path":"Device.property.valueCode","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.patient","path":"Device.patient","short":"Patient to whom Device is affixed","definition":"Patient information, If the device is affixed to a person.","requirements":"If the device is implanted in a patient, then need to associate the device to the patient.","min":1,"max":"1","base":{"path":"Device.patient","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.owner","path":"Device.owner","short":"Organization responsible for device","definition":"An organization that is responsible for the provision and ongoing maintenance of the device.","min":0,"max":"1","base":{"path":"Device.owner","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.contact","path":"Device.contact","short":"Details for human/organization for support","definition":"Contact details for an organization or a particular human that is responsible for the device.","comment":"used for troubleshooting etc.","min":0,"max":"*","base":{"path":"Device.contact","min":0,"max":"*"},"type":[{"code":"ContactPoint"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.location","path":"Device.location","short":"Where the device is found","definition":"The place where the device can be found.","requirements":"Device.location can be used to track device location.","min":0,"max":"1","base":{"path":"Device.location","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Location"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.url","path":"Device.url","short":"Network address to contact device","definition":"A network address on which the device may be contacted directly.","comment":"If the device is running a FHIR server, the network address should be the Base URL from which a conformance statement may be retrieved.","min":0,"max":"1","base":{"path":"Device.url","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.note","path":"Device.note","short":"Device notes and comments","definition":"Descriptive information, usage information or implantation information that is not captured in an existing element.","min":0,"max":"*","base":{"path":"Device.note","min":0,"max":"*"},"type":[{"code":"Annotation"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.safety","path":"Device.safety","short":"Safety Characteristics of Device","definition":"Provides additional safety characteristics about a medical device. For example devices containing latex.","min":0,"max":"*","base":{"path":"Device.safety","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Device.parent","path":"Device.parent","short":"The parent device","definition":"The parent device.","min":0,"max":"1","base":{"path":"Device.parent","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Device"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false}]}},{"resourceType":"StructureDefinition","id":"us-core-blood-pressure","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-blood-pressure","version":"5.0.1","name":"USCoreBloodPressureProfile","title":"US Core Blood Pressure Profile","status":"active","experimental":false,"date":"2022-04-20","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"To promote interoperability and adoption through common implementation, this profile sets minimum expectations for the Observation resource to record, search, and fetch diastolic and systolic blood pressure observations with standard LOINC codes and UCUM units of measure. It is based on the US Core Vital Signs Profile and identifies the *additional* mandatory core elements, extensions, vocabularies and value sets which **SHALL** be present in the Observation resource when using this profile. It provides the floor for standards development for specific uses cases.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"resource","abstract":false,"type":"Observation","baseDefinition":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-vital-signs","derivation":"constraint","snapshot":{"element":[{"id":"Observation","path":"Observation","short":"US Core Blood Pressure Profile","definition":"\\-","comment":"\\-","alias":["Vital Signs","Measurement","Results","Tests"],"min":0,"max":"*","base":{"path":"Observation","min":0,"max":"*"},"constraint":[{"key":"dom-2","severity":"error","human":"If the resource is contained in another resource, it SHALL NOT contain nested Resources","expression":"contained.contained.empty()","xpath":"not(parent::f:contained and f:contained)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-3","severity":"error","human":"If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource","expression":"contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()","xpath":"not(exists(for $id in f:contained/*/f:id/@value return $contained[not(parent::*/descendant::f:reference/@value=concat('#', $contained/*/id/@value) or descendant::f:reference[@value='#'])]))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-4","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated","expression":"contained.meta.versionId.empty() and contained.meta.lastUpdated.empty()","xpath":"not(exists(f:contained/*/f:meta/f:versionId)) and not(exists(f:contained/*/f:meta/f:lastUpdated))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-5","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a security label","expression":"contained.meta.security.empty()","xpath":"not(exists(f:contained/*/f:meta/f:security))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice","valueBoolean":true},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice-explanation","valueMarkdown":"When a resource has no narrative, only systems that fully understand the data can display the resource to a human safely. Including a human readable representation in the resource makes for a much more robust eco-system and cheaper handling of resources by intermediary systems. Some ecosystems restrict distribution of resources to only those systems that do fully understand the resources, and as a consequence implementers may believe that the narrative is superfluous. However experience shows that such eco-systems often open up to new participants over time."}],"key":"dom-6","severity":"warning","human":"A resource should have narrative for robust management","expression":"text.`div`.exists()","xpath":"exists(f:text/h:div)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"obs-6","severity":"error","human":"dataAbsentReason SHALL only be present if Observation.value[x] is not present","expression":"dataAbsentReason.empty() or value.empty()","xpath":"not(exists(f:dataAbsentReason)) or (not(exists(*[starts-with(local-name(.), 'value')])))","source":"http://hl7.org/fhir/StructureDefinition/Observation"},{"key":"obs-7","severity":"error","human":"If Observation.code is the same as an Observation.component.code then the value element associated with the code SHALL NOT be present","expression":"value.empty() or component.code.where(coding.intersect(%resource.code.coding).exists()).empty()","xpath":"not(f:*[starts-with(local-name(.), 'value')] and (for $coding in f:code/f:coding return f:component/f:code/f:coding[f:code/@value=$coding/f:code/@value] [f:system/@value=$coding/f:system/@value]))","source":"http://hl7.org/fhir/StructureDefinition/Observation"},{"key":"vs-2","severity":"error","human":"If there is no component or hasMember element then either a value[x] or a data absent reason must be present.","expression":"(component.empty() and hasMember.empty()) implies (dataAbsentReason.exists() or value.exists())","xpath":"f:component or f:memberOF or f:*[starts-with(local-name(.), 'value')] or f:dataAbsentReason","source":"http://hl7.org/fhir/StructureDefinition/vitalsigns"}],"isModifier":false,"isSummary":false},{"id":"Observation.id","path":"Observation.id","short":"Logical id of this artifact","definition":"The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.","comment":"The only time that a resource does not have an id is when it is being submitted to the server using a create operation.","min":0,"max":"1","base":{"path":"Resource.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":true},{"id":"Observation.meta","path":"Observation.meta","short":"Metadata about the resource","definition":"The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource.","min":0,"max":"1","base":{"path":"Resource.meta","min":0,"max":"1"},"type":[{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.implicitRules","path":"Observation.implicitRules","short":"A set of rules under which this content was created","definition":"A reference to a set of rules that were followed when the resource was constructed, and which must be understood when processing the content. Often, this is a reference to an implementation guide that defines the special rules along with other profiles etc.","comment":"Asserting this rule set restricts the content to be only understood by a limited set of trading partners. This inherently limits the usefulness of the data in the long term. However, the existing health eco-system is highly fractured, and not yet ready to define, collect, and exchange data in a generally computable sense. Wherever possible, implementers and/or specification writers should avoid using this element. Often, when used, the URL is a reference to an implementation guide that defines these special rules as part of it's narrative along with other profiles, value sets, etc.","min":0,"max":"1","base":{"path":"Resource.implicitRules","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because the implicit rules may provide additional knowledge about the resource that modifies it's meaning or interpretation","isSummary":true},{"id":"Observation.language","path":"Observation.language","short":"Language of the resource content","definition":"The base language in which the resource is written.","comment":"Language is provided to support indexing and accessibility (typically, services such as text to speech use the language tag). The html language tag in the narrative applies to the narrative. The language tag on the resource may be used to specify the language of other presentations generated from the data in the resource. Not all the content has to be in the base language. The Resource.language should not be assumed to apply to the narrative automatically. If a language is specified, it should it also be specified on the div element in the html (see rules in HTML5 for information about the relationship between xml:lang and the html lang attribute).","min":0,"max":"1","base":{"path":"Resource.language","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet","valueCanonical":"http://hl7.org/fhir/ValueSet/all-languages"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"Language"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"preferred","description":"A human language.","valueSet":"http://hl7.org/fhir/ValueSet/languages"}},{"id":"Observation.text","path":"Observation.text","short":"Text summary of the resource, for human interpretation","definition":"A human-readable narrative that contains a summary of the resource and can be used to represent the content of the resource to a human. The narrative need not encode all the structured data, but is required to contain sufficient detail to make it \"clinically safe\" for a human to just read the narrative. Resource definitions may define what content should be represented in the narrative to ensure clinical safety.","comment":"Contained resources do not have narrative. Resources that are not contained SHOULD have a narrative. In some cases, a resource may only have text with little or no additional discrete data (as long as all minOccurs=1 elements are satisfied). This may be necessary for data from legacy systems where information is captured as a \"text blob\" or where text is additionally entered raw or narrated and encoded information is added later.","alias":["narrative","html","xhtml","display"],"min":0,"max":"1","base":{"path":"DomainResource.text","min":0,"max":"1"},"type":[{"code":"Narrative"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.contained","path":"Observation.contained","short":"Contained, inline Resources","definition":"These resources do not have an independent existence apart from the resource that contains them - they cannot be identified independently, and nor can they have their own independent transaction scope.","comment":"This should never be done when the content can be identified properly, as once identification is lost, it is extremely difficult (and context dependent) to restore it again. Contained resources may have profiles and tags In their meta elements, but SHALL NOT have security labels.","alias":["inline resources","anonymous resources","contained resources"],"min":0,"max":"*","base":{"path":"DomainResource.contained","min":0,"max":"*"},"type":[{"code":"Resource"}],"isModifier":false,"isSummary":false},{"id":"Observation.extension","path":"Observation.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the resource. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.modifierExtension","path":"Observation.modifierExtension","short":"Extensions that cannot be ignored","definition":"May be used to represent additional information that is not part of the basic definition of the resource and that modifies the understanding of the element that contains it and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/extensibility.html#modifierExtension).","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the resource that contains them","isSummary":false},{"id":"Observation.identifier","path":"Observation.identifier","short":"Business Identifier for observation","definition":"A unique identifier assigned to this observation.","requirements":"Allows observations to be distinguished and referenced.","min":0,"max":"*","base":{"path":"Observation.identifier","min":0,"max":"*"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.basedOn","path":"Observation.basedOn","short":"Fulfills plan, proposal or order","definition":"A plan, proposal or order that is fulfilled in whole or in part by this event. For example, a MedicationRequest may require a patient to have laboratory test performed before it is dispensed.","requirements":"Allows tracing of authorization for the event and tracking whether proposals/recommendations were acted upon.","alias":["Fulfills"],"min":0,"max":"*","base":{"path":"Observation.basedOn","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/CarePlan","http://hl7.org/fhir/StructureDefinition/DeviceRequest","http://hl7.org/fhir/StructureDefinition/ImmunizationRecommendation","http://hl7.org/fhir/StructureDefinition/MedicationRequest","http://hl7.org/fhir/StructureDefinition/NutritionOrder","http://hl7.org/fhir/StructureDefinition/ServiceRequest"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.partOf","path":"Observation.partOf","short":"Part of referenced event","definition":"A larger event of which this particular Observation is a component or step. For example, an observation as part of a procedure.","comment":"To link an Observation to an Encounter use `encounter`. See the [Notes](http://hl7.org/fhir/observation.html#obsgrouping) below for guidance on referencing another Observation.","alias":["Container"],"min":0,"max":"*","base":{"path":"Observation.partOf","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/MedicationAdministration","http://hl7.org/fhir/StructureDefinition/MedicationDispense","http://hl7.org/fhir/StructureDefinition/MedicationStatement","http://hl7.org/fhir/StructureDefinition/Procedure","http://hl7.org/fhir/StructureDefinition/Immunization","http://hl7.org/fhir/StructureDefinition/ImagingStudy"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.status","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-display-hint","valueString":"default: final"}],"path":"Observation.status","short":"registered | preliminary | final | amended +","definition":"The status of the result value.","comment":"This element is labeled as a modifier because the status contains codes that mark the resource as not currently valid.","requirements":"Need to track the status of individual results. Some results are finalized before the whole report is finalized.","min":1,"max":"1","base":{"path":"Observation.status","min":1,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":true,"isModifierReason":"This element is labeled as a modifier because it is a status element that contains status entered-in-error which means that the resource should not be treated as valid","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"Status"}],"strength":"required","valueSet":"http://hl7.org/fhir/ValueSet/observation-status|4.0.1"}},{"id":"Observation.category","path":"Observation.category","slicing":{"discriminator":[{"type":"value","path":"coding.code"},{"type":"value","path":"coding.system"}],"ordered":false,"rules":"open"},"short":"Classification of type of observation","definition":"A code that classifies the general type of observation being made.","comment":"In addition to the required category valueset, this element allows various categorization schemes based on the owner’s definition of the category and effectively multiple categories can be used at once. The level of granularity is defined by the category concepts in the value set.","requirements":"Used for filtering what observations are retrieved and displayed.","min":1,"max":"*","base":{"path":"Observation.category","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationCategory"}],"strength":"preferred","description":"Codes for high level observation categories.","valueSet":"http://hl7.org/fhir/ValueSet/observation-category"}},{"id":"Observation.category:VSCat","path":"Observation.category","sliceName":"VSCat","short":"Classification of type of observation","definition":"A code that classifies the general type of observation being made.","comment":"In addition to the required category valueset, this element allows various categorization schemes based on the owner’s definition of the category and effectively multiple categories can be used at once. The level of granularity is defined by the category concepts in the value set.","requirements":"Used for filtering what observations are retrieved and displayed.","min":1,"max":"1","base":{"path":"Observation.category","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationCategory"}],"strength":"preferred","description":"Codes for high level observation categories.","valueSet":"http://hl7.org/fhir/ValueSet/observation-category"}},{"id":"Observation.category:VSCat.id","path":"Observation.category.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.category:VSCat.extension","path":"Observation.category.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.category:VSCat.coding","path":"Observation.category.coding","short":"Code defined by a terminology system","definition":"A reference to a code defined by a terminology system.","comment":"Codes may be defined very casually in enumerations, or code lists, up to very formal definitions such as SNOMED CT - see the HL7 v3 Core Principles for more information. Ordering of codings is undefined and SHALL NOT be used to infer meaning. Generally, at most only one of the coding values will be labeled as UserSelected = true.","requirements":"Allows for alternative encodings within a code system, and translations to other code systems.","min":1,"max":"*","base":{"path":"CodeableConcept.coding","min":0,"max":"*"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.category:VSCat.coding.id","path":"Observation.category.coding.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.category:VSCat.coding.extension","path":"Observation.category.coding.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.category:VSCat.coding.system","path":"Observation.category.coding.system","short":"Identity of the terminology system","definition":"The identification of the code system that defines the meaning of the symbol in the code.","comment":"The URI may be an OID (urn:oid:...) or a UUID (urn:uuid:...). OIDs and UUIDs SHALL be references to the HL7 OID registry. Otherwise, the URI should come from HL7's list of FHIR defined special URIs or it should reference to some definition that establishes the system clearly and unambiguously.","requirements":"Need to be unambiguous about the source of the definition of the symbol.","min":1,"max":"1","base":{"path":"Coding.system","min":0,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"http://terminology.hl7.org/CodeSystem/observation-category","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.category:VSCat.coding.version","path":"Observation.category.coding.version","short":"Version of the system - if relevant","definition":"The version of the code system which was used when choosing this code. Note that a well-maintained code system does not need the version reported, because the meaning of codes is consistent across versions. However this cannot consistently be assured, and when the meaning is not guaranteed to be consistent, the version SHOULD be exchanged.","comment":"Where the terminology does not clearly define what string should be used to identify code system versions, the recommendation is to use the date (expressed in FHIR date format) on which that version was officially published as the version date.","min":0,"max":"1","base":{"path":"Coding.version","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.category:VSCat.coding.code","path":"Observation.category.coding.code","short":"Symbol in syntax defined by the system","definition":"A symbol in syntax defined by the system. The symbol may be a predefined code or an expression in a syntax defined by the coding system (e.g. post-coordination).","requirements":"Need to refer to a particular code in the system.","min":1,"max":"1","base":{"path":"Coding.code","min":0,"max":"1"},"type":[{"code":"code"}],"fixedCode":"vital-signs","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.category:VSCat.coding.display","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-translatable","valueBoolean":true}],"path":"Observation.category.coding.display","short":"Representation defined by the system","definition":"A representation of the meaning of the code in the system, following the rules of the system.","requirements":"Need to be able to carry a human-readable meaning of the code for readers that do not know the system.","min":0,"max":"1","base":{"path":"Coding.display","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.category:VSCat.coding.userSelected","path":"Observation.category.coding.userSelected","short":"If this coding was chosen directly by the user","definition":"Indicates that this coding was chosen by a user directly - e.g. off a pick list of available items (codes or displays).","comment":"Amongst a set of alternatives, a directly chosen code is the most appropriate starting point for new translations. There is some ambiguity about what exactly 'directly chosen' implies, and trading partner agreement may be needed to clarify the use of this element and its consequences more completely.","requirements":"This has been identified as a clinical safety criterium - that this exact system/code pair was chosen explicitly, rather than inferred by the system based on some rules or language processing.","min":0,"max":"1","base":{"path":"Coding.userSelected","min":0,"max":"1"},"type":[{"code":"boolean"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.category:VSCat.text","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-translatable","valueBoolean":true}],"path":"Observation.category.text","short":"Plain text representation of the concept","definition":"A human language representation of the concept as seen/selected/uttered by the user who entered the data and/or which represents the intended meaning of the user.","comment":"Very often the text is the same as a displayName of one of the codings.","requirements":"The codes from the terminologies do not always capture the correct meaning with all the nuances of the human using them, or sometimes there is no appropriate code at all. In these cases, the text is used to capture the full meaning of the source.","min":0,"max":"1","base":{"path":"CodeableConcept.text","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.code","path":"Observation.code","short":"Blood Pressure","definition":"Coded Responses from C-CDA Vital Sign Results.","comment":"*All* code-value and, if present, component.code-component.value pairs need to be taken into account to correctly understand the meaning of the observation.","requirements":"5. SHALL contain exactly one [1..1] code, where the @code SHOULD be selected from ValueSet HITSP Vital Sign Result Type 2.16.840.1.113883.3.88.12.80.62 DYNAMIC (CONF:7301).","alias":["Name"],"min":1,"max":"1","base":{"path":"Observation.code","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"patternCodeableConcept":{"coding":[{"system":"http://loinc.org","code":"85354-9"}]},"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"The vital sign codes from the base FHIR and US Core Vital Signs.","valueSet":"http://hl7.org/fhir/us/core/ValueSet/us-core-vital-signs"}},{"id":"Observation.subject","path":"Observation.subject","short":"Who and/or what the observation is about","definition":"The patient, or group of patients, location, or device this observation is about and into whose record the observation is placed. If the actual focus of the observation is different from the subject (or a sample of, part, or region of the subject), the `focus` element or the `code` itself specifies the actual focus of the observation.","comment":"One would expect this element to be a cardinality of 1..1. The only circumstance in which the subject can be missing is when the observation is made by a device that does not know the patient. In this case, the observation SHALL be matched to a patient through some context/channel matching technique, and at this point, the observation should be updated.","requirements":"Observations have no value if you don't know who or what they're about.","min":1,"max":"1","base":{"path":"Observation.subject","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.focus","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status","valueCode":"trial-use"}],"path":"Observation.focus","short":"What the observation is about, when it is not about the subject of record","definition":"The actual focus of an observation when it is not the patient of record representing something or someone associated with the patient such as a spouse, parent, fetus, or donor. For example, fetus observations in a mother's record. The focus of an observation could also be an existing condition, an intervention, the subject's diet, another observation of the subject, or a body structure such as tumor or implanted device. An example use case would be using the Observation resource to capture whether the mother is trained to change her child's tracheostomy tube. In this example, the child is the patient of record and the mother is the focus.","comment":"Typically, an observation is made about the subject - a patient, or group of patients, location, or device - and the distinction between the subject and what is directly measured for an observation is specified in the observation code itself ( e.g., \"Blood Glucose\") and does not need to be represented separately using this element. Use `specimen` if a reference to a specimen is required. If a code is required instead of a resource use either `bodysite` for bodysites or the standard extension [focusCode](http://hl7.org/fhir/extension-observation-focuscode.html).","min":0,"max":"*","base":{"path":"Observation.focus","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Resource"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.encounter","path":"Observation.encounter","short":"Healthcare event during which this observation is made","definition":"The healthcare event (e.g. a patient and healthcare provider interaction) during which this observation is made.","comment":"This will typically be the encounter the event occurred within, but some events may be initiated prior to or after the official completion of an encounter but still be tied to the context of the encounter (e.g. pre-admission laboratory tests).","requirements":"For some observations it may be important to know the link between an observation and a particular encounter.","alias":["Context"],"min":0,"max":"1","base":{"path":"Observation.encounter","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Encounter"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.effective[x]","path":"Observation.effective[x]","short":"Often just a dateTime for Vital Signs","definition":"Often just a dateTime for Vital Signs.","comment":"At least a date should be present unless this observation is a historical report. For recording imprecise or \"fuzzy\" times (For example, a blood glucose measurement taken \"after breakfast\") use the [Timing](http://hl7.org/fhir/datatypes.html#timing) datatype which allow the measurement to be tied to regular life events.","requirements":"Knowing when an observation was deemed true is important to its relevance as well as determining trends.","alias":["Occurrence"],"min":1,"max":"1","base":{"path":"Observation.effective[x]","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"dateTime"},{"code":"Period"}],"condition":["vs-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"vs-1","severity":"error","human":"if Observation.effective[x] is dateTime and has a value then that value shall be precise to the day","expression":"($this as dateTime).toString().length() >= 8","xpath":"f:effectiveDateTime[matches(@value, '^\\d{4}-\\d{2}-\\d{2}')]","source":"http://hl7.org/fhir/StructureDefinition/vitalsigns"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.issued","path":"Observation.issued","short":"Date/Time this version was made available","definition":"The date and time this version of the observation was made available to providers, typically after the results have been reviewed and verified.","comment":"For Observations that don’t require review and verification, it may be the same as the [`lastUpdated` ](http://hl7.org/fhir/resource-definitions.html#Meta.lastUpdated) time of the resource itself. For Observations that do require review and verification for certain updates, it might not be the same as the `lastUpdated` time of the resource itself due to a non-clinically significant update that doesn’t require the new version to be reviewed and verified again.","min":0,"max":"1","base":{"path":"Observation.issued","min":0,"max":"1"},"type":[{"code":"instant"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.performer","path":"Observation.performer","short":"Who is responsible for the observation","definition":"Who was responsible for asserting the observed value as \"true\".","requirements":"May give a degree of confidence in the observation and also indicates where follow-up questions should be directed.","min":0,"max":"*","base":{"path":"Observation.performer","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Practitioner","http://hl7.org/fhir/StructureDefinition/PractitionerRole","http://hl7.org/fhir/StructureDefinition/Organization","http://hl7.org/fhir/StructureDefinition/CareTeam","http://hl7.org/fhir/StructureDefinition/Patient","http://hl7.org/fhir/StructureDefinition/RelatedPerson"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.value[x]","path":"Observation.value[x]","short":"Vital Signs Value","definition":"Vital Signs value are typically recorded using the Quantity data type.","comment":"An observation may have; 1) a single value here, 2) both a value and a set of related or component values, or 3) only a set of related or component values. If a value is present, the datatype for this element should be determined by Observation.code. A CodeableConcept with just a text would be used instead of a string if the field was usually coded, or if the type associated with the Observation.code defines a coded value. For additional guidance, see the [Notes section](http://hl7.org/fhir/observation.html#notes) below.","requirements":"9. SHALL contain exactly one [1..1] value with @xsi:type=\"PQ\" (CONF:7305).","min":0,"max":"1","base":{"path":"Observation.value[x]","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"Quantity"},{"code":"CodeableConcept"},{"code":"string"},{"code":"boolean"},{"code":"integer"},{"code":"Range"},{"code":"Ratio"},{"code":"SampledData"},{"code":"time"},{"code":"dateTime"},{"code":"Period"}],"condition":["obs-7","vs-2"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"Common UCUM units for recording Vital Signs.","valueSet":"http://hl7.org/fhir/ValueSet/ucum-vitals-common|4.0.1"}},{"id":"Observation.dataAbsentReason","path":"Observation.dataAbsentReason","short":"Why the result is missing","definition":"Provides a reason why the expected value in the element Observation.value[x] is missing.","comment":"Null or exceptional values can be represented two ways in FHIR Observations. One way is to simply include them in the value set and represent the exceptions in the value. For example, measurement values for a serology test could be \"detected\", \"not detected\", \"inconclusive\", or \"specimen unsatisfactory\". \n\nThe alternate way is to use the value element for actual observations and use the explicit dataAbsentReason element to record exceptional values. For example, the dataAbsentReason code \"error\" could be used when the measurement was not completed. Note that an observation may only be reported if there are values to report. For example differential cell counts values may be reported only when > 0. Because of these options, use-case agreements are required to interpret general observations for null or exceptional values.","requirements":"For many results it is necessary to handle exceptional values in measurements.","min":0,"max":"1","base":{"path":"Observation.dataAbsentReason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"condition":["obs-6","vs-2"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationValueAbsentReason"}],"strength":"extensible","description":"Codes specifying why the result (`Observation.value[x]`) is missing.","valueSet":"http://hl7.org/fhir/ValueSet/data-absent-reason"}},{"id":"Observation.interpretation","path":"Observation.interpretation","short":"High, low, normal, etc.","definition":"A categorical assessment of an observation value. For example, high, low, normal.","comment":"Historically used for laboratory results (known as 'abnormal flag' ), its use extends to other use cases where coded interpretations are relevant. Often reported as one or more simple compact codes this element is often placed adjacent to the result value in reports and flow sheets to signal the meaning/normalcy status of the result.","requirements":"For some results, particularly numeric results, an interpretation is necessary to fully understand the significance of a result.","alias":["Abnormal Flag"],"min":0,"max":"*","base":{"path":"Observation.interpretation","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationInterpretation"}],"strength":"extensible","description":"Codes identifying interpretations of observations.","valueSet":"http://hl7.org/fhir/ValueSet/observation-interpretation"}},{"id":"Observation.note","path":"Observation.note","short":"Comments about the observation","definition":"Comments about the observation or the results.","comment":"May include general statements about the observation, or statements about significant, unexpected or unreliable results values, or information about its source when relevant to its interpretation.","requirements":"Need to be able to provide free text additional information.","min":0,"max":"*","base":{"path":"Observation.note","min":0,"max":"*"},"type":[{"code":"Annotation"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.bodySite","path":"Observation.bodySite","short":"Observed body part","definition":"Indicates the site on the subject's body where the observation was made (i.e. the target site).","comment":"Only used if not implicit in code found in Observation.code. In many systems, this may be represented as a related observation instead of an inline component. \n\nIf the use case requires BodySite to be handled as a separate resource (e.g. to identify and track separately) then use the standard extension[ bodySite](http://hl7.org/fhir/extension-bodysite.html).","min":0,"max":"1","base":{"path":"Observation.bodySite","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"BodySite"}],"strength":"example","description":"Codes describing anatomical locations. May include laterality.","valueSet":"http://hl7.org/fhir/ValueSet/body-site"}},{"id":"Observation.method","path":"Observation.method","short":"How it was done","definition":"Indicates the mechanism used to perform the observation.","comment":"Only used if not implicit in code for Observation.code.","requirements":"In some cases, method can impact results and is thus used for determining whether results can be compared or determining significance of results.","min":0,"max":"1","base":{"path":"Observation.method","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationMethod"}],"strength":"example","description":"Methods for simple observations.","valueSet":"http://hl7.org/fhir/ValueSet/observation-methods"}},{"id":"Observation.specimen","path":"Observation.specimen","short":"Specimen used for this observation","definition":"The specimen that was used when this observation was made.","comment":"Should only be used if not implicit in code found in `Observation.code`. Observations are not made on specimens themselves; they are made on a subject, but in many cases by the means of a specimen. Note that although specimens are often involved, they are not always tracked and reported explicitly. Also note that observation resources may be used in contexts that track the specimen explicitly (e.g. Diagnostic Report).","min":0,"max":"1","base":{"path":"Observation.specimen","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Specimen"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.device","path":"Observation.device","short":"(Measurement) Device","definition":"The device used to generate the observation data.","comment":"Note that this is not meant to represent a device involved in the transmission of the result, e.g., a gateway. Such devices may be documented using the Provenance resource where relevant.","min":0,"max":"1","base":{"path":"Observation.device","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Device","http://hl7.org/fhir/StructureDefinition/DeviceMetric"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange","path":"Observation.referenceRange","short":"Provides guide for interpretation","definition":"Guidance on how to interpret the value by comparison to a normal or recommended range. Multiple reference ranges are interpreted as an \"OR\". In other words, to represent two distinct target populations, two `referenceRange` elements would be used.","comment":"Most observations only have one generic reference range. Systems MAY choose to restrict to only supplying the relevant reference range based on knowledge about the patient (e.g., specific to the patient's age, gender, weight and other factors), but this might not be possible or appropriate. Whenever more than one reference range is supplied, the differences between them SHOULD be provided in the reference range and/or age properties.","requirements":"Knowing what values are considered \"normal\" can help evaluate the significance of a particular result. Need to be able to provide multiple reference ranges for different contexts.","min":0,"max":"*","base":{"path":"Observation.referenceRange","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"obs-3","severity":"error","human":"Must have at least a low or a high or text","expression":"low.exists() or high.exists() or text.exists()","xpath":"(exists(f:low) or exists(f:high)or exists(f:text))","source":"http://hl7.org/fhir/StructureDefinition/vitalsigns"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange.id","path":"Observation.referenceRange.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange.extension","path":"Observation.referenceRange.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange.modifierExtension","path":"Observation.referenceRange.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Observation.referenceRange.low","path":"Observation.referenceRange.low","short":"Low Range, if relevant","definition":"The value of the low bound of the reference range. The low bound of the reference range endpoint is inclusive of the value (e.g. reference range is >=5 - <=9). If the low bound is omitted, it is assumed to be meaningless (e.g. reference range is <=2.3).","min":0,"max":"1","base":{"path":"Observation.referenceRange.low","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"condition":["obs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange.high","path":"Observation.referenceRange.high","short":"High Range, if relevant","definition":"The value of the high bound of the reference range. The high bound of the reference range endpoint is inclusive of the value (e.g. reference range is >=5 - <=9). If the high bound is omitted, it is assumed to be meaningless (e.g. reference range is >= 2.3).","min":0,"max":"1","base":{"path":"Observation.referenceRange.high","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"condition":["obs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange.type","path":"Observation.referenceRange.type","short":"Reference range qualifier","definition":"Codes to indicate the what part of the targeted reference population it applies to. For example, the normal or therapeutic range.","comment":"This SHOULD be populated if there is more than one range. If this element is not present then the normal range is assumed.","requirements":"Need to be able to say what kind of reference range this is - normal, recommended, therapeutic, etc., - for proper interpretation.","min":0,"max":"1","base":{"path":"Observation.referenceRange.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationRangeMeaning"}],"strength":"preferred","description":"Code for the meaning of a reference range.","valueSet":"http://hl7.org/fhir/ValueSet/referencerange-meaning"}},{"id":"Observation.referenceRange.appliesTo","path":"Observation.referenceRange.appliesTo","short":"Reference range population","definition":"Codes to indicate the target population this reference range applies to. For example, a reference range may be based on the normal population or a particular sex or race. Multiple `appliesTo` are interpreted as an \"AND\" of the target populations. For example, to represent a target population of African American females, both a code of female and a code for African American would be used.","comment":"This SHOULD be populated if there is more than one range. If this element is not present then the normal population is assumed.","requirements":"Need to be able to identify the target population for proper interpretation.","min":0,"max":"*","base":{"path":"Observation.referenceRange.appliesTo","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationRangeType"}],"strength":"example","description":"Codes identifying the population the reference range applies to.","valueSet":"http://hl7.org/fhir/ValueSet/referencerange-appliesto"}},{"id":"Observation.referenceRange.age","path":"Observation.referenceRange.age","short":"Applicable age range, if relevant","definition":"The age at which this reference range is applicable. This is a neonatal age (e.g. number of weeks at term) if the meaning says so.","requirements":"Some analytes vary greatly over age.","min":0,"max":"1","base":{"path":"Observation.referenceRange.age","min":0,"max":"1"},"type":[{"code":"Range"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange.text","path":"Observation.referenceRange.text","short":"Text based reference range in an observation","definition":"Text based reference range in an observation which may be used when a quantitative range is not appropriate for an observation. An example would be a reference value of \"Negative\" or a list or table of \"normals\".","min":0,"max":"1","base":{"path":"Observation.referenceRange.text","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.hasMember","path":"Observation.hasMember","short":"Used when reporting vital signs panel components","definition":"Used when reporting vital signs panel components.","comment":"When using this element, an observation will typically have either a value or a set of related resources, although both may be present in some cases. For a discussion on the ways Observations can assembled in groups together, see [Notes](http://hl7.org/fhir/observation.html#obsgrouping) below. Note that a system may calculate results from [QuestionnaireResponse](http://hl7.org/fhir/questionnaireresponse.html) into a final score and represent the score as an Observation.","min":0,"max":"*","base":{"path":"Observation.hasMember","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/QuestionnaireResponse","http://hl7.org/fhir/StructureDefinition/MolecularSequence","http://hl7.org/fhir/StructureDefinition/vitalsigns"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.derivedFrom","path":"Observation.derivedFrom","short":"Related measurements the observation is made from","definition":"The target resource that represents a measurement from which this observation value is derived. For example, a calculated anion gap or a fetal measurement based on an ultrasound image.","comment":"All the reference choices that are listed in this element can represent clinical observations and other measurements that may be the source for a derived value. The most common reference will be another Observation. For a discussion on the ways Observations can assembled in groups together, see [Notes](http://hl7.org/fhir/observation.html#obsgrouping) below.","min":0,"max":"*","base":{"path":"Observation.derivedFrom","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/DocumentReference","http://hl7.org/fhir/StructureDefinition/ImagingStudy","http://hl7.org/fhir/StructureDefinition/Media","http://hl7.org/fhir/StructureDefinition/QuestionnaireResponse","http://hl7.org/fhir/StructureDefinition/MolecularSequence","http://hl7.org/fhir/StructureDefinition/vitalsigns"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.component","path":"Observation.component","slicing":{"discriminator":[{"type":"pattern","path":"code"}],"ordered":false,"rules":"open"},"short":"Component observations","definition":"Used when reporting component observation such as systolic and diastolic blood pressure.","comment":"For a discussion on the ways Observations can be assembled in groups together see [Notes](http://hl7.org/fhir/observation.html#notes) below.","requirements":"Component observations share the same attributes in the Observation resource as the primary observation and are always treated a part of a single observation (they are not separable). However, the reference range for the primary observation value is not inherited by the component values and is required when appropriate for each component observation.","min":2,"max":"*","base":{"path":"Observation.component","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"vs-3","severity":"error","human":"If there is no a value a data absent reason must be present","expression":"value.exists() or dataAbsentReason.exists()","xpath":"f:*[starts-with(local-name(.), 'value')] or f:dataAbsentReason","source":"http://hl7.org/fhir/StructureDefinition/vitalsigns"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component.id","path":"Observation.component.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.component.extension","path":"Observation.component.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.component.modifierExtension","path":"Observation.component.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Observation.component.code","path":"Observation.component.code","short":"Type of component observation (code / type)","definition":"Describes what was observed. Sometimes this is called the observation \"code\".","comment":"*All* code-value and component.code-component.value pairs need to be taken into account to correctly understand the meaning of the observation.","requirements":"Knowing what kind of observation is being made is essential to understanding the observation.","min":1,"max":"1","base":{"path":"Observation.component.code","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"The vital sign codes from the base FHIR and US Core Vital Signs.","valueSet":"http://hl7.org/fhir/us/core/ValueSet/us-core-vital-signs"}},{"id":"Observation.component.value[x]","path":"Observation.component.value[x]","short":"Vital Sign Component Value","definition":"Vital Signs value are typically recorded using the Quantity data type. For supporting observations such as cuff size could use other datatypes such as CodeableConcept.","comment":"Used when observation has a set of component observations. An observation may have both a value (e.g. an Apgar score) and component observations (the observations from which the Apgar score was derived). If a value is present, the datatype for this element should be determined by Observation.code. A CodeableConcept with just a text would be used instead of a string if the field was usually coded, or if the type associated with the Observation.code defines a coded value. For additional guidance, see the [Notes section](http://hl7.org/fhir/observation.html#notes) below.","requirements":"9. SHALL contain exactly one [1..1] value with @xsi:type=\"PQ\" (CONF:7305).","min":0,"max":"1","base":{"path":"Observation.component.value[x]","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"Quantity"},{"code":"CodeableConcept"},{"code":"string"},{"code":"boolean"},{"code":"integer"},{"code":"Range"},{"code":"Ratio"},{"code":"SampledData"},{"code":"time"},{"code":"dateTime"},{"code":"Period"}],"condition":["vs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"Common UCUM units for recording Vital Signs.","valueSet":"http://hl7.org/fhir/ValueSet/ucum-vitals-common|4.0.1"}},{"id":"Observation.component.dataAbsentReason","path":"Observation.component.dataAbsentReason","short":"Why the component result is missing","definition":"Provides a reason why the expected value in the element Observation.component.value[x] is missing.","comment":"\"Null\" or exceptional values can be represented two ways in FHIR Observations. One way is to simply include them in the value set and represent the exceptions in the value. For example, measurement values for a serology test could be \"detected\", \"not detected\", \"inconclusive\", or \"test not done\". \n\nThe alternate way is to use the value element for actual observations and use the explicit dataAbsentReason element to record exceptional values. For example, the dataAbsentReason code \"error\" could be used when the measurement was not completed. Because of these options, use-case agreements are required to interpret general observations for exceptional values.","requirements":"For many results it is necessary to handle exceptional values in measurements.","min":0,"max":"1","base":{"path":"Observation.component.dataAbsentReason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"condition":["obs-6","vs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationValueAbsentReason"}],"strength":"extensible","description":"Codes specifying why the result (`Observation.value[x]`) is missing.","valueSet":"http://hl7.org/fhir/ValueSet/data-absent-reason"}},{"id":"Observation.component.interpretation","path":"Observation.component.interpretation","short":"High, low, normal, etc.","definition":"A categorical assessment of an observation value. For example, high, low, normal.","comment":"Historically used for laboratory results (known as 'abnormal flag' ), its use extends to other use cases where coded interpretations are relevant. Often reported as one or more simple compact codes this element is often placed adjacent to the result value in reports and flow sheets to signal the meaning/normalcy status of the result.","requirements":"For some results, particularly numeric results, an interpretation is necessary to fully understand the significance of a result.","alias":["Abnormal Flag"],"min":0,"max":"*","base":{"path":"Observation.component.interpretation","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationInterpretation"}],"strength":"extensible","description":"Codes identifying interpretations of observations.","valueSet":"http://hl7.org/fhir/ValueSet/observation-interpretation"}},{"id":"Observation.component.referenceRange","path":"Observation.component.referenceRange","short":"Provides guide for interpretation of component result","definition":"Guidance on how to interpret the value by comparison to a normal or recommended range.","comment":"Most observations only have one generic reference range. Systems MAY choose to restrict to only supplying the relevant reference range based on knowledge about the patient (e.g., specific to the patient's age, gender, weight and other factors), but this might not be possible or appropriate. Whenever more than one reference range is supplied, the differences between them SHOULD be provided in the reference range and/or age properties.","requirements":"Knowing what values are considered \"normal\" can help evaluate the significance of a particular result. Need to be able to provide multiple reference ranges for different contexts.","min":0,"max":"*","base":{"path":"Observation.component.referenceRange","min":0,"max":"*"},"contentReference":"http://hl7.org/fhir/StructureDefinition/Observation#Observation.referenceRange","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:systolic","path":"Observation.component","sliceName":"systolic","short":"Systolic Blood Pressure","definition":"Used when reporting component observation such as systolic and diastolic blood pressure.","comment":"For a discussion on the ways Observations can be assembled in groups together see [Notes](http://hl7.org/fhir/observation.html#notes) below.","requirements":"Component observations share the same attributes in the Observation resource as the primary observation and are always treated a part of a single observation (they are not separable). However, the reference range for the primary observation value is not inherited by the component values and is required when appropriate for each component observation.","min":1,"max":"1","base":{"path":"Observation.component","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"vs-3","severity":"error","human":"If there is no a value a data absent reason must be present","expression":"value.exists() or dataAbsentReason.exists()","xpath":"f:*[starts-with(local-name(.), 'value')] or f:dataAbsentReason","source":"http://hl7.org/fhir/StructureDefinition/vitalsigns"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:systolic.id","path":"Observation.component.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:systolic.extension","path":"Observation.component.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:systolic.modifierExtension","path":"Observation.component.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Observation.component:systolic.code","path":"Observation.component.code","short":"Systolic Blood Pressure Code","definition":"Describes what was observed. Sometimes this is called the observation \"code\".","comment":"*All* code-value and component.code-component.value pairs need to be taken into account to correctly understand the meaning of the observation.","requirements":"Knowing what kind of observation is being made is essential to understanding the observation.","min":1,"max":"1","base":{"path":"Observation.component.code","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"patternCodeableConcept":{"coding":[{"system":"http://loinc.org","code":"8480-6"}]},"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"The vital sign codes from the base FHIR and US Core Vital Signs.","valueSet":"http://hl7.org/fhir/us/core/ValueSet/us-core-vital-signs"}},{"id":"Observation.component:systolic.value[x]","path":"Observation.component.value[x]","short":"Vital Sign Component Value","definition":"Vital Signs value are typically recorded using the Quantity data type. For supporting observations such as cuff size could use other datatypes such as CodeableConcept.","comment":"Used when observation has a set of component observations. An observation may have both a value (e.g. an Apgar score) and component observations (the observations from which the Apgar score was derived). If a value is present, the datatype for this element should be determined by Observation.code. A CodeableConcept with just a text would be used instead of a string if the field was usually coded, or if the type associated with the Observation.code defines a coded value. For additional guidance, see the [Notes section](http://hl7.org/fhir/observation.html#notes) below.","requirements":"9. SHALL contain exactly one [1..1] value with @xsi:type=\"PQ\" (CONF:7305).","min":0,"max":"1","base":{"path":"Observation.component.value[x]","min":0,"max":"1"},"type":[{"code":"Quantity"}],"condition":["vs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"Common UCUM units for recording Vital Signs.","valueSet":"http://hl7.org/fhir/ValueSet/ucum-vitals-common|4.0.1"}},{"id":"Observation.component:systolic.value[x].id","path":"Observation.component.value[x].id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:systolic.value[x].extension","path":"Observation.component.value[x].extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:systolic.value[x].value","path":"Observation.component.value[x].value","short":"Numerical value (with implicit precision)","definition":"The value of the measured amount. The value includes an implicit precision in the presentation of the value.","comment":"The implicit precision in the value should always be honored. Monetary values have their own rules for handling precision (refer to standard accounting text books).","requirements":"Precision is handled implicitly in almost all cases of measurement.","min":1,"max":"1","base":{"path":"Quantity.value","min":0,"max":"1"},"type":[{"code":"decimal"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:systolic.value[x].comparator","path":"Observation.component.value[x].comparator","short":"< | <= | >= | > - how to understand the value","definition":"How the value should be understood and represented - whether the actual value is greater or less than the stated value due to measurement issues; e.g. if the comparator is \"<\" , then the real value is < stated value.","requirements":"Need a framework for handling measures where the value is <5ug/L or >400mg/L due to the limitations of measuring methodology.","min":0,"max":"1","base":{"path":"Quantity.comparator","min":0,"max":"1"},"type":[{"code":"code"}],"meaningWhenMissing":"If there is no comparator, then there is no modification of the value","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because the comparator modifies the interpretation of the value significantly. If there is no comparator, then there is no modification of the value","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"QuantityComparator"}],"strength":"required","description":"How the Quantity should be understood and represented.","valueSet":"http://hl7.org/fhir/ValueSet/quantity-comparator|4.0.1"}},{"id":"Observation.component:systolic.value[x].unit","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-translatable","valueBoolean":true}],"path":"Observation.component.value[x].unit","short":"Unit representation","definition":"A human-readable form of the unit.","requirements":"There are many representations for units of measure and in many contexts, particular representations are fixed and required. I.e. mcg for micrograms.","min":1,"max":"1","base":{"path":"Quantity.unit","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:systolic.value[x].system","path":"Observation.component.value[x].system","short":"System that defines coded unit form","definition":"The identification of the system that provides the coded form of the unit.","requirements":"Need to know the system that defines the coded form of the unit.","min":1,"max":"1","base":{"path":"Quantity.system","min":0,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"http://unitsofmeasure.org","condition":["qty-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:systolic.value[x].code","path":"Observation.component.value[x].code","short":"Coded form of the unit","definition":"A computer processable form of the unit in some unit representation system.","comment":"The preferred system is UCUM, but SNOMED CT can also be used (for customary units) or ISO 4217 for currency. The context of use may additionally require a code from a particular system.","requirements":"Need a computable form of the unit that is fixed across all forms. UCUM provides this for quantities, but SNOMED CT provides many units of interest.","min":1,"max":"1","base":{"path":"Quantity.code","min":0,"max":"1"},"type":[{"code":"code"}],"fixedCode":"mm[Hg]","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:systolic.dataAbsentReason","path":"Observation.component.dataAbsentReason","short":"Why the component result is missing","definition":"Provides a reason why the expected value in the element Observation.component.value[x] is missing.","comment":"\"Null\" or exceptional values can be represented two ways in FHIR Observations. One way is to simply include them in the value set and represent the exceptions in the value. For example, measurement values for a serology test could be \"detected\", \"not detected\", \"inconclusive\", or \"test not done\". \n\nThe alternate way is to use the value element for actual observations and use the explicit dataAbsentReason element to record exceptional values. For example, the dataAbsentReason code \"error\" could be used when the measurement was not completed. Because of these options, use-case agreements are required to interpret general observations for exceptional values.","requirements":"For many results it is necessary to handle exceptional values in measurements.","min":0,"max":"1","base":{"path":"Observation.component.dataAbsentReason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"condition":["obs-6","vs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationValueAbsentReason"}],"strength":"extensible","description":"Codes specifying why the result (`Observation.value[x]`) is missing.","valueSet":"http://hl7.org/fhir/ValueSet/data-absent-reason"}},{"id":"Observation.component:systolic.interpretation","path":"Observation.component.interpretation","short":"High, low, normal, etc.","definition":"A categorical assessment of an observation value. For example, high, low, normal.","comment":"Historically used for laboratory results (known as 'abnormal flag' ), its use extends to other use cases where coded interpretations are relevant. Often reported as one or more simple compact codes this element is often placed adjacent to the result value in reports and flow sheets to signal the meaning/normalcy status of the result.","requirements":"For some results, particularly numeric results, an interpretation is necessary to fully understand the significance of a result.","alias":["Abnormal Flag"],"min":0,"max":"*","base":{"path":"Observation.component.interpretation","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationInterpretation"}],"strength":"extensible","description":"Codes identifying interpretations of observations.","valueSet":"http://hl7.org/fhir/ValueSet/observation-interpretation"}},{"id":"Observation.component:systolic.referenceRange","path":"Observation.component.referenceRange","short":"Provides guide for interpretation of component result","definition":"Guidance on how to interpret the value by comparison to a normal or recommended range.","comment":"Most observations only have one generic reference range. Systems MAY choose to restrict to only supplying the relevant reference range based on knowledge about the patient (e.g., specific to the patient's age, gender, weight and other factors), but this might not be possible or appropriate. Whenever more than one reference range is supplied, the differences between them SHOULD be provided in the reference range and/or age properties.","requirements":"Knowing what values are considered \"normal\" can help evaluate the significance of a particular result. Need to be able to provide multiple reference ranges for different contexts.","min":0,"max":"*","base":{"path":"Observation.component.referenceRange","min":0,"max":"*"},"contentReference":"http://hl7.org/fhir/StructureDefinition/Observation#Observation.referenceRange","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:diastolic","path":"Observation.component","sliceName":"diastolic","short":"Diastolic Blood Pressure","definition":"Used when reporting component observation such as systolic and diastolic blood pressure.","comment":"For a discussion on the ways Observations can be assembled in groups together see [Notes](http://hl7.org/fhir/observation.html#notes) below.","requirements":"Component observations share the same attributes in the Observation resource as the primary observation and are always treated a part of a single observation (they are not separable). However, the reference range for the primary observation value is not inherited by the component values and is required when appropriate for each component observation.","min":1,"max":"1","base":{"path":"Observation.component","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"vs-3","severity":"error","human":"If there is no a value a data absent reason must be present","expression":"value.exists() or dataAbsentReason.exists()","xpath":"f:*[starts-with(local-name(.), 'value')] or f:dataAbsentReason","source":"http://hl7.org/fhir/StructureDefinition/vitalsigns"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:diastolic.id","path":"Observation.component.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:diastolic.extension","path":"Observation.component.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:diastolic.modifierExtension","path":"Observation.component.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Observation.component:diastolic.code","path":"Observation.component.code","short":"Diastolic Blood Pressure Code","definition":"Describes what was observed. Sometimes this is called the observation \"code\".","comment":"*All* code-value and component.code-component.value pairs need to be taken into account to correctly understand the meaning of the observation.","requirements":"Knowing what kind of observation is being made is essential to understanding the observation.","min":1,"max":"1","base":{"path":"Observation.component.code","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"patternCodeableConcept":{"coding":[{"system":"http://loinc.org","code":"8462-4"}]},"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"The vital sign codes from the base FHIR and US Core Vital Signs.","valueSet":"http://hl7.org/fhir/us/core/ValueSet/us-core-vital-signs"}},{"id":"Observation.component:diastolic.value[x]","path":"Observation.component.value[x]","short":"Vital Sign Component Value","definition":"Vital Signs value are typically recorded using the Quantity data type. For supporting observations such as cuff size could use other datatypes such as CodeableConcept.","comment":"Used when observation has a set of component observations. An observation may have both a value (e.g. an Apgar score) and component observations (the observations from which the Apgar score was derived). If a value is present, the datatype for this element should be determined by Observation.code. A CodeableConcept with just a text would be used instead of a string if the field was usually coded, or if the type associated with the Observation.code defines a coded value. For additional guidance, see the [Notes section](http://hl7.org/fhir/observation.html#notes) below.","requirements":"9. SHALL contain exactly one [1..1] value with @xsi:type=\"PQ\" (CONF:7305).","min":0,"max":"1","base":{"path":"Observation.component.value[x]","min":0,"max":"1"},"type":[{"code":"Quantity"}],"condition":["vs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"Common UCUM units for recording Vital Signs.","valueSet":"http://hl7.org/fhir/ValueSet/ucum-vitals-common|4.0.1"}},{"id":"Observation.component:diastolic.value[x].id","path":"Observation.component.value[x].id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:diastolic.value[x].extension","path":"Observation.component.value[x].extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:diastolic.value[x].value","path":"Observation.component.value[x].value","short":"Numerical value (with implicit precision)","definition":"The value of the measured amount. The value includes an implicit precision in the presentation of the value.","comment":"The implicit precision in the value should always be honored. Monetary values have their own rules for handling precision (refer to standard accounting text books).","requirements":"Precision is handled implicitly in almost all cases of measurement.","min":1,"max":"1","base":{"path":"Quantity.value","min":0,"max":"1"},"type":[{"code":"decimal"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:diastolic.value[x].comparator","path":"Observation.component.value[x].comparator","short":"< | <= | >= | > - how to understand the value","definition":"How the value should be understood and represented - whether the actual value is greater or less than the stated value due to measurement issues; e.g. if the comparator is \"<\" , then the real value is < stated value.","requirements":"Need a framework for handling measures where the value is <5ug/L or >400mg/L due to the limitations of measuring methodology.","min":0,"max":"1","base":{"path":"Quantity.comparator","min":0,"max":"1"},"type":[{"code":"code"}],"meaningWhenMissing":"If there is no comparator, then there is no modification of the value","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because the comparator modifies the interpretation of the value significantly. If there is no comparator, then there is no modification of the value","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"QuantityComparator"}],"strength":"required","description":"How the Quantity should be understood and represented.","valueSet":"http://hl7.org/fhir/ValueSet/quantity-comparator|4.0.1"}},{"id":"Observation.component:diastolic.value[x].unit","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-translatable","valueBoolean":true}],"path":"Observation.component.value[x].unit","short":"Unit representation","definition":"A human-readable form of the unit.","requirements":"There are many representations for units of measure and in many contexts, particular representations are fixed and required. I.e. mcg for micrograms.","min":1,"max":"1","base":{"path":"Quantity.unit","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:diastolic.value[x].system","path":"Observation.component.value[x].system","short":"System that defines coded unit form","definition":"The identification of the system that provides the coded form of the unit.","requirements":"Need to know the system that defines the coded form of the unit.","min":1,"max":"1","base":{"path":"Quantity.system","min":0,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"http://unitsofmeasure.org","condition":["qty-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:diastolic.value[x].code","path":"Observation.component.value[x].code","short":"Coded form of the unit","definition":"A computer processable form of the unit in some unit representation system.","comment":"The preferred system is UCUM, but SNOMED CT can also be used (for customary units) or ISO 4217 for currency. The context of use may additionally require a code from a particular system.","requirements":"Need a computable form of the unit that is fixed across all forms. UCUM provides this for quantities, but SNOMED CT provides many units of interest.","min":1,"max":"1","base":{"path":"Quantity.code","min":0,"max":"1"},"type":[{"code":"code"}],"fixedCode":"mm[Hg]","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:diastolic.dataAbsentReason","path":"Observation.component.dataAbsentReason","short":"Why the component result is missing","definition":"Provides a reason why the expected value in the element Observation.component.value[x] is missing.","comment":"\"Null\" or exceptional values can be represented two ways in FHIR Observations. One way is to simply include them in the value set and represent the exceptions in the value. For example, measurement values for a serology test could be \"detected\", \"not detected\", \"inconclusive\", or \"test not done\". \n\nThe alternate way is to use the value element for actual observations and use the explicit dataAbsentReason element to record exceptional values. For example, the dataAbsentReason code \"error\" could be used when the measurement was not completed. Because of these options, use-case agreements are required to interpret general observations for exceptional values.","requirements":"For many results it is necessary to handle exceptional values in measurements.","min":0,"max":"1","base":{"path":"Observation.component.dataAbsentReason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"condition":["obs-6","vs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationValueAbsentReason"}],"strength":"extensible","description":"Codes specifying why the result (`Observation.value[x]`) is missing.","valueSet":"http://hl7.org/fhir/ValueSet/data-absent-reason"}},{"id":"Observation.component:diastolic.interpretation","path":"Observation.component.interpretation","short":"High, low, normal, etc.","definition":"A categorical assessment of an observation value. For example, high, low, normal.","comment":"Historically used for laboratory results (known as 'abnormal flag' ), its use extends to other use cases where coded interpretations are relevant. Often reported as one or more simple compact codes this element is often placed adjacent to the result value in reports and flow sheets to signal the meaning/normalcy status of the result.","requirements":"For some results, particularly numeric results, an interpretation is necessary to fully understand the significance of a result.","alias":["Abnormal Flag"],"min":0,"max":"*","base":{"path":"Observation.component.interpretation","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationInterpretation"}],"strength":"extensible","description":"Codes identifying interpretations of observations.","valueSet":"http://hl7.org/fhir/ValueSet/observation-interpretation"}},{"id":"Observation.component:diastolic.referenceRange","path":"Observation.component.referenceRange","short":"Provides guide for interpretation of component result","definition":"Guidance on how to interpret the value by comparison to a normal or recommended range.","comment":"Most observations only have one generic reference range. Systems MAY choose to restrict to only supplying the relevant reference range based on knowledge about the patient (e.g., specific to the patient's age, gender, weight and other factors), but this might not be possible or appropriate. Whenever more than one reference range is supplied, the differences between them SHOULD be provided in the reference range and/or age properties.","requirements":"Knowing what values are considered \"normal\" can help evaluate the significance of a particular result. Need to be able to provide multiple reference ranges for different contexts.","min":0,"max":"*","base":{"path":"Observation.component.referenceRange","min":0,"max":"*"},"contentReference":"http://hl7.org/fhir/StructureDefinition/Observation#Observation.referenceRange","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false}]}},{"resourceType":"StructureDefinition","id":"us-core-medicationrequest","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-medicationrequest","version":"5.0.1","name":"USCoreMedicationRequestProfile","title":"US Core MedicationRequest Profile","status":"active","experimental":false,"date":"2020-06-26","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"The US Core Medication Request Profile is based upon the core FHIR MedicationRequest Resource and meets the U.S. Core Data for Interoperability (USCDI) v2 'Medications' requirements. The MedicationRequest resource can be used to record a patient’s medication prescription or order. This profile sets minimum expectations for the MedicationRequest resource to record, search, and fetch a patient's medication to promote interoperability and adoption through common implementation. It identifies which core elements, extensions, vocabularies and value sets **SHALL** be present in the resource when using this profile. It provides the floor for standards development for specific uses cases.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"resource","abstract":false,"type":"MedicationRequest","baseDefinition":"http://hl7.org/fhir/StructureDefinition/MedicationRequest","derivation":"constraint","snapshot":{"element":[{"id":"MedicationRequest","path":"MedicationRequest","short":"Ordering of medication for patient or group","definition":"\\-","comment":"\\-","alias":["Prescription","Order"],"min":0,"max":"*","base":{"path":"MedicationRequest","min":0,"max":"*"},"constraint":[{"key":"dom-2","severity":"error","human":"If the resource is contained in another resource, it SHALL NOT contain nested Resources","expression":"contained.contained.empty()","xpath":"not(parent::f:contained and f:contained)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-3","severity":"error","human":"If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource","expression":"contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()","xpath":"not(exists(for $id in f:contained/*/f:id/@value return $contained[not(parent::*/descendant::f:reference/@value=concat('#', $contained/*/id/@value) or descendant::f:reference[@value='#'])]))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-4","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated","expression":"contained.meta.versionId.empty() and contained.meta.lastUpdated.empty()","xpath":"not(exists(f:contained/*/f:meta/f:versionId)) and not(exists(f:contained/*/f:meta/f:lastUpdated))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-5","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a security label","expression":"contained.meta.security.empty()","xpath":"not(exists(f:contained/*/f:meta/f:security))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice","valueBoolean":true},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice-explanation","valueMarkdown":"When a resource has no narrative, only systems that fully understand the data can display the resource to a human safely. Including a human readable representation in the resource makes for a much more robust eco-system and cheaper handling of resources by intermediary systems. Some ecosystems restrict distribution of resources to only those systems that do fully understand the resources, and as a consequence implementers may believe that the narrative is superfluous. However experience shows that such eco-systems often open up to new participants over time."}],"key":"dom-6","severity":"warning","human":"A resource should have narrative for robust management","expression":"text.`div`.exists()","xpath":"exists(f:text/h:div)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"}],"mustSupport":false,"isModifier":false,"isSummary":false},{"id":"MedicationRequest.id","path":"MedicationRequest.id","short":"Logical id of this artifact","definition":"The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.","comment":"The only time that a resource does not have an id is when it is being submitted to the server using a create operation.","min":0,"max":"1","base":{"path":"Resource.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.meta","path":"MedicationRequest.meta","short":"Metadata about the resource","definition":"The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource.","min":0,"max":"1","base":{"path":"Resource.meta","min":0,"max":"1"},"type":[{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.implicitRules","path":"MedicationRequest.implicitRules","short":"A set of rules under which this content was created","definition":"A reference to a set of rules that were followed when the resource was constructed, and which must be understood when processing the content. Often, this is a reference to an implementation guide that defines the special rules along with other profiles etc.","comment":"Asserting this rule set restricts the content to be only understood by a limited set of trading partners. This inherently limits the usefulness of the data in the long term. However, the existing health eco-system is highly fractured, and not yet ready to define, collect, and exchange data in a generally computable sense. Wherever possible, implementers and/or specification writers should avoid using this element. Often, when used, the URL is a reference to an implementation guide that defines these special rules as part of it's narrative along with other profiles, value sets, etc.","min":0,"max":"1","base":{"path":"Resource.implicitRules","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because the implicit rules may provide additional knowledge about the resource that modifies it's meaning or interpretation","isSummary":true},{"id":"MedicationRequest.language","path":"MedicationRequest.language","short":"Language of the resource content","definition":"The base language in which the resource is written.","comment":"Language is provided to support indexing and accessibility (typically, services such as text to speech use the language tag). The html language tag in the narrative applies to the narrative. The language tag on the resource may be used to specify the language of other presentations generated from the data in the resource. Not all the content has to be in the base language. The Resource.language should not be assumed to apply to the narrative automatically. If a language is specified, it should it also be specified on the div element in the html (see rules in HTML5 for information about the relationship between xml:lang and the html lang attribute).","min":0,"max":"1","base":{"path":"Resource.language","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet","valueCanonical":"http://hl7.org/fhir/ValueSet/all-languages"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"Language"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"preferred","description":"A human language.","valueSet":"http://hl7.org/fhir/ValueSet/languages"}},{"id":"MedicationRequest.text","path":"MedicationRequest.text","short":"Text summary of the resource, for human interpretation","definition":"A human-readable narrative that contains a summary of the resource and can be used to represent the content of the resource to a human. The narrative need not encode all the structured data, but is required to contain sufficient detail to make it \"clinically safe\" for a human to just read the narrative. Resource definitions may define what content should be represented in the narrative to ensure clinical safety.","comment":"Contained resources do not have narrative. Resources that are not contained SHOULD have a narrative. In some cases, a resource may only have text with little or no additional discrete data (as long as all minOccurs=1 elements are satisfied). This may be necessary for data from legacy systems where information is captured as a \"text blob\" or where text is additionally entered raw or narrated and encoded information is added later.","alias":["narrative","html","xhtml","display"],"min":0,"max":"1","base":{"path":"DomainResource.text","min":0,"max":"1"},"type":[{"code":"Narrative"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.contained","path":"MedicationRequest.contained","short":"Contained, inline Resources","definition":"These resources do not have an independent existence apart from the resource that contains them - they cannot be identified independently, and nor can they have their own independent transaction scope.","comment":"This should never be done when the content can be identified properly, as once identification is lost, it is extremely difficult (and context dependent) to restore it again. Contained resources may have profiles and tags In their meta elements, but SHALL NOT have security labels.","alias":["inline resources","anonymous resources","contained resources"],"min":0,"max":"*","base":{"path":"DomainResource.contained","min":0,"max":"*"},"type":[{"code":"Resource"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.extension","path":"MedicationRequest.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the resource. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.modifierExtension","path":"MedicationRequest.modifierExtension","short":"Extensions that cannot be ignored","definition":"May be used to represent additional information that is not part of the basic definition of the resource and that modifies the understanding of the element that contains it and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the resource that contains them","isSummary":false},{"id":"MedicationRequest.identifier","path":"MedicationRequest.identifier","short":"External ids for this request","definition":"Identifiers associated with this medication request that are defined by business processes and/or used to refer to it when a direct URL reference to the resource itself is not appropriate. They are business identifiers assigned to this resource by the performer or other systems and remain constant as the resource is updated and propagates from server to server.","comment":"This is a business identifier, not a resource identifier.","min":0,"max":"*","base":{"path":"MedicationRequest.identifier","min":0,"max":"*"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.status","path":"MedicationRequest.status","short":"active | on-hold | cancelled | completed | entered-in-error | stopped | draft | unknown","definition":"A code specifying the current state of the order. Generally, this will be active or completed state.","comment":"This element is labeled as a modifier because the status contains codes that mark the resource as not currently valid.","min":1,"max":"1","base":{"path":"MedicationRequest.status","min":1,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":true,"isModifierReason":"This element is labeled as a modifier because it is a status element that contains status entered-in-error which means that the resource should not be treated as valid","isSummary":true,"binding":{"strength":"required","description":"A code specifying the state of the prescribing event. Describes the lifecycle of the prescription.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-status"}},{"id":"MedicationRequest.statusReason","path":"MedicationRequest.statusReason","short":"Reason for current status","definition":"Captures the reason for the current state of the MedicationRequest.","comment":"This is generally only used for \"exception\" statuses such as \"suspended\" or \"cancelled\". The reason why the MedicationRequest was created at all is captured in reasonCode, not here.","min":0,"max":"1","base":{"path":"MedicationRequest.statusReason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestStatusReason"}],"strength":"example","description":"Identifies the reasons for a given status.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-status-reason"}},{"id":"MedicationRequest.intent","path":"MedicationRequest.intent","short":"proposal | plan | order | original-order | reflex-order | filler-order | instance-order | option","definition":"Whether the request is a proposal, plan, or an original order.","comment":"It is expected that the type of requester will be restricted for different stages of a MedicationRequest. For example, Proposals can be created by a patient, relatedPerson, Practitioner or Device. Plans can be created by Practitioners, Patients, RelatedPersons and Devices. Original orders can be created by a Practitioner only.\r\rAn instance-order is an instantiation of a request or order and may be used to populate Medication Administration Record.\r\rThis element is labeled as a modifier because the intent alters when and how the resource is actually applicable.","min":1,"max":"1","base":{"path":"MedicationRequest.intent","min":1,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":true,"isModifierReason":"This element changes the interpretation of all descriptive attributes. For example \"the time the request is recommended to occur\" vs. \"the time the request is authorized to occur\" or \"who is recommended to perform the request\" vs. \"who is authorized to perform the request","isSummary":true,"binding":{"strength":"required","description":"The kind of medication order.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-intent"}},{"id":"MedicationRequest.category","path":"MedicationRequest.category","slicing":{"discriminator":[{"type":"pattern","path":"$this"}],"rules":"open"},"short":"Type of medication usage","definition":"Indicates the type of medication request (for example, where the medication is expected to be consumed or administered (i.e. inpatient or outpatient)).","comment":"The category can be used to include where the medication is expected to be consumed or other types of requests.","min":0,"max":"*","base":{"path":"MedicationRequest.category","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestCategory"}],"strength":"example","description":"A coded concept identifying the category of medication request. For example, where the medication is to be consumed or administered, or the type of medication treatment.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-category"}},{"id":"MedicationRequest.category:us-core","path":"MedicationRequest.category","sliceName":"us-core","short":"Type of medication usage","definition":"Indicates the type of medication request (for example, where the medication is expected to be consumed or administered (i.e. inpatient or outpatient)).","comment":"The category can be used to include where the medication is expected to be consumed or other types of requests.","min":0,"max":"*","base":{"path":"MedicationRequest.category","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"strength":"required","description":"The type of medication order.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-category"}},{"id":"MedicationRequest.priority","path":"MedicationRequest.priority","short":"routine | urgent | asap | stat","definition":"Indicates how quickly the Medication Request should be addressed with respect to other requests.","min":0,"max":"1","base":{"path":"MedicationRequest.priority","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestPriority"}],"strength":"required","description":"Identifies the level of importance to be assigned to actioning the request.","valueSet":"http://hl7.org/fhir/ValueSet/request-priority|4.0.1"}},{"id":"MedicationRequest.doNotPerform","path":"MedicationRequest.doNotPerform","short":"True if request is prohibiting action","definition":"If true indicates that the provider is asking for the medication request not to occur.","comment":"If do not perform is not specified, the request is a positive request e.g. \"do perform\".","min":0,"max":"1","base":{"path":"MedicationRequest.doNotPerform","min":0,"max":"1"},"type":[{"code":"boolean"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because this element negates the request to occur (ie, this is a request for the medication not to be ordered or prescribed, etc)","isSummary":true},{"id":"MedicationRequest.reported[x]","path":"MedicationRequest.reported[x]","short":"Reported rather than primary record","definition":"Indicates if this record was captured as a secondary 'reported' record rather than as an original primary source-of-truth record. It may also indicate the source of the report.","min":0,"max":"1","base":{"path":"MedicationRequest.reported[x]","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"boolean"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner","http://hl7.org/fhir/us/core/StructureDefinition/us-core-organization","http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient","http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitionerrole","http://hl7.org/fhir/us/core/StructureDefinition/us-core-relatedperson"],"_targetProfile":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]}]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"MedicationRequest.medication[x]","path":"MedicationRequest.medication[x]","short":"Medication to be taken","definition":"Identifies the medication being requested. This is a link to a resource that represents the medication which may be the details of the medication or simply an attribute carrying a code that identifies the medication from a known list of medications.","comment":"If only a code is specified, then it needs to be a code for a specific product. If more information is required, then the use of the Medication resource is recommended. For example, if you require form or lot number or if the medication is compounded or extemporaneously prepared, then you must reference the Medication resource.","min":1,"max":"1","base":{"path":"MedicationRequest.medication[x]","min":1,"max":"1"},"type":[{"code":"CodeableConcept"},{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-medication"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","valueSet":"http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1010.4"}},{"id":"MedicationRequest.subject","path":"MedicationRequest.subject","short":"Who or group medication request is for","definition":"A link to a resource representing the person or set of individuals to whom the medication will be given.","comment":"The subject on a medication request is mandatory. For the secondary use case where the actual subject is not provided, there still must be an anonymized subject specified.","min":1,"max":"1","base":{"path":"MedicationRequest.subject","min":1,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"MedicationRequest.encounter","path":"MedicationRequest.encounter","short":"Encounter created as part of encounter/admission/stay","definition":"The Encounter during which this [x] was created or to which the creation of this record is tightly associated.","comment":"This will typically be the encounter the event occurred within, but some activities may be initiated prior to or after the official completion of an encounter but still be tied to the context of the encounter.\" If there is a need to link to episodes of care they will be handled with an extension.","min":0,"max":"1","base":{"path":"MedicationRequest.encounter","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-encounter"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"MedicationRequest.supportingInformation","path":"MedicationRequest.supportingInformation","short":"Information to support ordering of the medication","definition":"Include additional information (for example, patient height and weight) that supports the ordering of the medication.","min":0,"max":"*","base":{"path":"MedicationRequest.supportingInformation","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Resource"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.authoredOn","path":"MedicationRequest.authoredOn","short":"When request was initially authored","definition":"The date (and perhaps time) when the prescription was initially written or authored on.","min":0,"max":"1","base":{"path":"MedicationRequest.authoredOn","min":0,"max":"1"},"type":[{"code":"dateTime"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"MedicationRequest.requester","path":"MedicationRequest.requester","short":"Who/What requested the Request","definition":"The individual, organization, or device that initiated the request and has responsibility for its activation.","min":1,"max":"1","base":{"path":"MedicationRequest.requester","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner","http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient","http://hl7.org/fhir/us/core/StructureDefinition/us-core-organization","http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitionerrole","http://hl7.org/fhir/us/core/StructureDefinition/us-core-relatedperson","http://hl7.org/fhir/StructureDefinition/Device"],"_targetProfile":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]}]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"MedicationRequest.performer","path":"MedicationRequest.performer","short":"Intended performer of administration","definition":"The specified desired performer of the medication treatment (e.g. the performer of the medication administration).","min":0,"max":"1","base":{"path":"MedicationRequest.performer","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Practitioner","http://hl7.org/fhir/StructureDefinition/PractitionerRole","http://hl7.org/fhir/StructureDefinition/Organization","http://hl7.org/fhir/StructureDefinition/Patient","http://hl7.org/fhir/StructureDefinition/Device","http://hl7.org/fhir/StructureDefinition/RelatedPerson","http://hl7.org/fhir/StructureDefinition/CareTeam"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.performerType","path":"MedicationRequest.performerType","short":"Desired kind of performer of the medication administration","definition":"Indicates the type of performer of the administration of the medication.","comment":"If specified without indicating a performer, this indicates that the performer must be of the specified type. If specified with a performer then it indicates the requirements of the performer if the designated performer is not available.","min":0,"max":"1","base":{"path":"MedicationRequest.performerType","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestPerformerType"}],"strength":"example","description":"Identifies the type of individual that is desired to administer the medication.","valueSet":"http://hl7.org/fhir/ValueSet/performer-role"}},{"id":"MedicationRequest.recorder","path":"MedicationRequest.recorder","short":"Person who entered the request","definition":"The person who entered the order on behalf of another individual for example in the case of a verbal or a telephone order.","min":0,"max":"1","base":{"path":"MedicationRequest.recorder","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Practitioner","http://hl7.org/fhir/StructureDefinition/PractitionerRole"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.reasonCode","path":"MedicationRequest.reasonCode","short":"Reason or indication for ordering or not ordering the medication","definition":"The reason or the indication for ordering or not ordering the medication.","comment":"This could be a diagnosis code. If a full condition record exists or additional detail is needed, use reasonReference.","min":0,"max":"*","base":{"path":"MedicationRequest.reasonCode","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestReason"}],"strength":"example","description":"A coded concept indicating why the medication was ordered.","valueSet":"http://hl7.org/fhir/ValueSet/condition-code"}},{"id":"MedicationRequest.reasonReference","path":"MedicationRequest.reasonReference","short":"Condition or observation that supports why the prescription is being written","definition":"Condition or observation that supports why the medication was ordered.","comment":"This is a reference to a condition or observation that is the reason for the medication order. If only a code exists, use reasonCode.","min":0,"max":"*","base":{"path":"MedicationRequest.reasonReference","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Condition","http://hl7.org/fhir/StructureDefinition/Observation"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.instantiatesCanonical","path":"MedicationRequest.instantiatesCanonical","short":"Instantiates FHIR protocol or definition","definition":"The URL pointing to a protocol, guideline, orderset, or other definition that is adhered to in whole or in part by this MedicationRequest.","min":0,"max":"*","base":{"path":"MedicationRequest.instantiatesCanonical","min":0,"max":"*"},"type":[{"code":"canonical"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.instantiatesUri","path":"MedicationRequest.instantiatesUri","short":"Instantiates external protocol or definition","definition":"The URL pointing to an externally maintained protocol, guideline, orderset or other definition that is adhered to in whole or in part by this MedicationRequest.","min":0,"max":"*","base":{"path":"MedicationRequest.instantiatesUri","min":0,"max":"*"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.basedOn","path":"MedicationRequest.basedOn","short":"What request fulfills","definition":"A plan or request that is fulfilled in whole or in part by this medication request.","min":0,"max":"*","base":{"path":"MedicationRequest.basedOn","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/CarePlan","http://hl7.org/fhir/StructureDefinition/MedicationRequest","http://hl7.org/fhir/StructureDefinition/ServiceRequest","http://hl7.org/fhir/StructureDefinition/ImmunizationRecommendation"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.groupIdentifier","path":"MedicationRequest.groupIdentifier","short":"Composite request this is part of","definition":"A shared identifier common to all requests that were authorized more or less simultaneously by a single author, representing the identifier of the requisition or prescription.","requirements":"Requests are linked either by a \"basedOn\" relationship (i.e. one request is fulfilling another) or by having a common requisition. Requests that are part of the same requisition are generally treated independently from the perspective of changing their state or maintaining them after initial creation.","min":0,"max":"1","base":{"path":"MedicationRequest.groupIdentifier","min":0,"max":"1"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.courseOfTherapyType","path":"MedicationRequest.courseOfTherapyType","short":"Overall pattern of medication administration","definition":"The description of the overall patte3rn of the administration of the medication to the patient.","comment":"This attribute should not be confused with the protocol of the medication.","min":0,"max":"1","base":{"path":"MedicationRequest.courseOfTherapyType","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestCourseOfTherapy"}],"strength":"example","description":"Identifies the overall pattern of medication administratio.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-course-of-therapy"}},{"id":"MedicationRequest.insurance","path":"MedicationRequest.insurance","short":"Associated insurance coverage","definition":"Insurance plans, coverage extensions, pre-authorizations and/or pre-determinations that may be required for delivering the requested service.","min":0,"max":"*","base":{"path":"MedicationRequest.insurance","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Coverage","http://hl7.org/fhir/StructureDefinition/ClaimResponse"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.note","path":"MedicationRequest.note","short":"Information about the prescription","definition":"Extra information about the prescription that could not be conveyed by the other attributes.","min":0,"max":"*","base":{"path":"MedicationRequest.note","min":0,"max":"*"},"type":[{"code":"Annotation"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dosageInstruction","path":"MedicationRequest.dosageInstruction","short":"How the medication should be taken","definition":"Indicates how the medication is to be used by the patient.","comment":"There are examples where a medication request may include the option of an oral dose or an Intravenous or Intramuscular dose. For example, \"Ondansetron 8mg orally or IV twice a day as needed for nausea\" or \"Compazine® (prochlorperazine) 5-10mg PO or 25mg PR bid prn nausea or vomiting\". In these cases, two medication requests would be created that could be grouped together. The decision on which dose and route of administration to use is based on the patient's condition at the time the dose is needed.","min":0,"max":"*","base":{"path":"MedicationRequest.dosageInstruction","min":0,"max":"*"},"type":[{"code":"Dosage"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dosageInstruction.id","path":"MedicationRequest.dosageInstruction.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dosageInstruction.extension","path":"MedicationRequest.dosageInstruction.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dosageInstruction.modifierExtension","path":"MedicationRequest.dosageInstruction.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"MedicationRequest.dosageInstruction.sequence","path":"MedicationRequest.dosageInstruction.sequence","short":"The order of the dosage instructions","definition":"Indicates the order in which the dosage instructions should be applied or interpreted.","requirements":"If the sequence number of multiple Dosages is the same, then it is implied that the instructions are to be treated as concurrent. If the sequence number is different, then the Dosages are intended to be sequential.","min":0,"max":"1","base":{"path":"Dosage.sequence","min":0,"max":"1"},"type":[{"code":"integer"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.text","path":"MedicationRequest.dosageInstruction.text","short":"Free text dosage instructions e.g. SIG","definition":"Free text dosage instructions e.g. SIG.","requirements":"Free text dosage instructions can be used for cases where the instructions are too complex to code. The content of this attribute does not include the name or description of the medication. When coded instructions are present, the free text instructions may still be present for display to humans taking or administering the medication. It is expected that the text instructions will always be populated. If the dosage.timing attribute is also populated, then the dosage.text should reflect the same information as the timing. Additional information about administration or preparation of the medication should be included as text.","min":0,"max":"1","base":{"path":"Dosage.text","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.additionalInstruction","path":"MedicationRequest.dosageInstruction.additionalInstruction","short":"Supplemental instruction or warnings to the patient - e.g. \"with meals\", \"may cause drowsiness\"","definition":"Supplemental instructions to the patient on how to take the medication (e.g. \"with meals\" or\"take half to one hour before food\") or warnings for the patient about the medication (e.g. \"may cause drowsiness\" or \"avoid exposure of skin to direct sunlight or sunlamps\").","comment":"Information about administration or preparation of the medication (e.g. \"infuse as rapidly as possibly via intraperitoneal port\" or \"immediately following drug x\") should be populated in dosage.text.","requirements":"Additional instruction is intended to be coded, but where no code exists, the element could include text. For example, \"Swallow with plenty of water\" which might or might not be coded.","min":0,"max":"*","base":{"path":"Dosage.additionalInstruction","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"AdditionalInstruction"}],"strength":"example","description":"A coded concept identifying additional instructions such as \"take with water\" or \"avoid operating heavy machinery\".","valueSet":"http://hl7.org/fhir/ValueSet/additional-instruction-codes"}},{"id":"MedicationRequest.dosageInstruction.patientInstruction","path":"MedicationRequest.dosageInstruction.patientInstruction","short":"Patient or consumer oriented instructions","definition":"Instructions in terms that are understood by the patient or consumer.","min":0,"max":"1","base":{"path":"Dosage.patientInstruction","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.timing","path":"MedicationRequest.dosageInstruction.timing","short":"When medication should be administered","definition":"When medication should be administered.","comment":"This attribute might not always be populated while the Dosage.text is expected to be populated. If both are populated, then the Dosage.text should reflect the content of the Dosage.timing.","requirements":"The timing schedule for giving the medication to the patient. This data type allows many different expressions. For example: \"Every 8 hours\"; \"Three times a day\"; \"1/2 an hour before breakfast for 10 days from 23-Dec 2011:\"; \"15 Oct 2013, 17 Oct 2013 and 1 Nov 2013\". Sometimes, a rate can imply duration when expressed as total volume / duration (e.g. 500mL/2 hours implies a duration of 2 hours). However, when rate doesn't imply duration (e.g. 250mL/hour), then the timing.repeat.duration is needed to convey the infuse over time period.","min":0,"max":"1","base":{"path":"Dosage.timing","min":0,"max":"1"},"type":[{"code":"Timing"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.asNeeded[x]","path":"MedicationRequest.dosageInstruction.asNeeded[x]","short":"Take \"as needed\" (for x)","definition":"Indicates whether the Medication is only taken when needed within a specific dosing schedule (Boolean option), or it indicates the precondition for taking the Medication (CodeableConcept).","comment":"Can express \"as needed\" without a reason by setting the Boolean = True. In this case the CodeableConcept is not populated. Or you can express \"as needed\" with a reason by including the CodeableConcept. In this case the Boolean is assumed to be True. If you set the Boolean to False, then the dose is given according to the schedule and is not \"prn\" or \"as needed\".","min":0,"max":"1","base":{"path":"Dosage.asNeeded[x]","min":0,"max":"1"},"type":[{"code":"boolean"},{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationAsNeededReason"}],"strength":"example","description":"A coded concept identifying the precondition that should be met or evaluated prior to consuming or administering a medication dose. For example \"pain\", \"30 minutes prior to sexual intercourse\", \"on flare-up\" etc.","valueSet":"http://hl7.org/fhir/ValueSet/medication-as-needed-reason"}},{"id":"MedicationRequest.dosageInstruction.site","path":"MedicationRequest.dosageInstruction.site","short":"Body site to administer to","definition":"Body site to administer to.","comment":"If the use case requires attributes from the BodySite resource (e.g. to identify and track separately) then use the standard extension [bodySite](http://hl7.org/fhir/R4/extension-bodysite.html). May be a summary code, or a reference to a very precise definition of the location, or both.","requirements":"A coded specification of the anatomic site where the medication first enters the body.","min":0,"max":"1","base":{"path":"Dosage.site","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationAdministrationSite"}],"strength":"example","description":"A coded concept describing the site location the medicine enters into or onto the body.","valueSet":"http://hl7.org/fhir/ValueSet/approach-site-codes"}},{"id":"MedicationRequest.dosageInstruction.route","path":"MedicationRequest.dosageInstruction.route","short":"How drug should enter body","definition":"How drug should enter body.","requirements":"A code specifying the route or physiological path of administration of a therapeutic agent into or onto a patient's body.","min":0,"max":"1","base":{"path":"Dosage.route","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"RouteOfAdministration"}],"strength":"example","description":"A coded concept describing the route or physiological path of administration of a therapeutic agent into or onto the body of a subject.","valueSet":"http://hl7.org/fhir/ValueSet/route-codes"}},{"id":"MedicationRequest.dosageInstruction.method","path":"MedicationRequest.dosageInstruction.method","short":"Technique for administering medication","definition":"Technique for administering medication.","comment":"Terminologies used often pre-coordinate this term with the route and or form of administration.","requirements":"A coded value indicating the method by which the medication is introduced into or onto the body. Most commonly used for injections. For examples, Slow Push; Deep IV.","min":0,"max":"1","base":{"path":"Dosage.method","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationAdministrationMethod"}],"strength":"example","description":"A coded concept describing the technique by which the medicine is administered.","valueSet":"http://hl7.org/fhir/ValueSet/administration-method-codes"}},{"id":"MedicationRequest.dosageInstruction.doseAndRate","path":"MedicationRequest.dosageInstruction.doseAndRate","short":"Amount of medication administered","definition":"The amount of medication administered.","min":0,"max":"*","base":{"path":"Dosage.doseAndRate","min":0,"max":"*"},"type":[{"code":"Element"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.doseAndRate.id","path":"MedicationRequest.dosageInstruction.doseAndRate.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dosageInstruction.doseAndRate.extension","path":"MedicationRequest.dosageInstruction.doseAndRate.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dosageInstruction.doseAndRate.type","path":"MedicationRequest.dosageInstruction.doseAndRate.type","short":"The kind of dose or rate specified","definition":"The kind of dose or rate specified, for example, ordered or calculated.","requirements":"If the type is not populated, assume to be \"ordered\".","min":0,"max":"1","base":{"path":"Dosage.doseAndRate.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"DoseAndRateType"}],"strength":"example","description":"The kind of dose or rate specified.","valueSet":"http://hl7.org/fhir/ValueSet/dose-rate-type"}},{"id":"MedicationRequest.dosageInstruction.doseAndRate.dose[x]","path":"MedicationRequest.dosageInstruction.doseAndRate.dose[x]","short":"Amount of medication per dose","definition":"Amount of medication per dose.","comment":"Note that this specifies the quantity of the specified medication, not the quantity for each active ingredient(s). Each ingredient amount can be communicated in the Medication resource. For example, if one wants to communicate that a tablet was 375 mg, where the dose was one tablet, you can use the Medication resource to document that the tablet was comprised of 375 mg of drug XYZ. Alternatively if the dose was 375 mg, then you may only need to use the Medication resource to indicate this was a tablet. If the example were an IV such as dopamine and you wanted to communicate that 400mg of dopamine was mixed in 500 ml of some IV solution, then this would all be communicated in the Medication resource. If the administration is not intended to be instantaneous (rate is present or timing has a duration), this can be specified to convey the total amount to be administered over the period of time as indicated by the schedule e.g. 500 ml in dose, with timing used to convey that this should be done over 4 hours.","requirements":"The amount of therapeutic or other substance given at one administration event.","min":0,"max":"1","base":{"path":"Dosage.doseAndRate.dose[x]","min":0,"max":"1"},"type":[{"code":"Range"},{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.doseAndRate.rate[x]","path":"MedicationRequest.dosageInstruction.doseAndRate.rate[x]","short":"Amount of medication per unit of time","definition":"Amount of medication per unit of time.","comment":"It is possible to supply both a rate and a doseQuantity to provide full details about how the medication is to be administered and supplied. If the rate is intended to change over time, depending on local rules/regulations, each change should be captured as a new version of the MedicationRequest with an updated rate, or captured with a new MedicationRequest with the new rate.\r\rIt is possible to specify a rate over time (for example, 100 ml/hour) using either the rateRatio and rateQuantity. The rateQuantity approach requires systems to have the capability to parse UCUM grammer where ml/hour is included rather than a specific ratio where the time is specified as the denominator. Where a rate such as 500ml over 2 hours is specified, the use of rateRatio may be more semantically correct than specifying using a rateQuantity of 250 mg/hour.","requirements":"Identifies the speed with which the medication was or will be introduced into the patient. Typically the rate for an infusion e.g. 100 ml per 1 hour or 100 ml/hr. May also be expressed as a rate per unit of time e.g. 500 ml per 2 hours. Other examples: 200 mcg/min or 200 mcg/1 minute; 1 liter/8 hours. Sometimes, a rate can imply duration when expressed as total volume / duration (e.g. 500mL/2 hours implies a duration of 2 hours). However, when rate doesn't imply duration (e.g. 250mL/hour), then the timing.repeat.duration is needed to convey the infuse over time period.","min":0,"max":"1","base":{"path":"Dosage.doseAndRate.rate[x]","min":0,"max":"1"},"type":[{"code":"Ratio"},{"code":"Range"},{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.maxDosePerPeriod","path":"MedicationRequest.dosageInstruction.maxDosePerPeriod","short":"Upper limit on medication per unit of time","definition":"Upper limit on medication per unit of time.","comment":"This is intended for use as an adjunct to the dosage when there is an upper cap. For example \"2 tablets every 4 hours to a maximum of 8/day\".","requirements":"The maximum total quantity of a therapeutic substance that may be administered to a subject over the period of time. For example, 1000mg in 24 hours.","min":0,"max":"1","base":{"path":"Dosage.maxDosePerPeriod","min":0,"max":"1"},"type":[{"code":"Ratio"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.maxDosePerAdministration","path":"MedicationRequest.dosageInstruction.maxDosePerAdministration","short":"Upper limit on medication per administration","definition":"Upper limit on medication per administration.","comment":"This is intended for use as an adjunct to the dosage when there is an upper cap. For example, a body surface area related dose with a maximum amount, such as 1.5 mg/m2 (maximum 2 mg) IV over 5 – 10 minutes would have doseQuantity of 1.5 mg/m2 and maxDosePerAdministration of 2 mg.","requirements":"The maximum total quantity of a therapeutic substance that may be administered to a subject per administration.","min":0,"max":"1","base":{"path":"Dosage.maxDosePerAdministration","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.maxDosePerLifetime","path":"MedicationRequest.dosageInstruction.maxDosePerLifetime","short":"Upper limit on medication per lifetime of the patient","definition":"Upper limit on medication per lifetime of the patient.","requirements":"The maximum total quantity of a therapeutic substance that may be administered per lifetime of the subject.","min":0,"max":"1","base":{"path":"Dosage.maxDosePerLifetime","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dispenseRequest","path":"MedicationRequest.dispenseRequest","short":"Medication supply authorization","definition":"Indicates the specific details for the dispense or medication supply part of a medication request (also known as a Medication Prescription or Medication Order). Note that this information is not always sent with the order. There may be in some settings (e.g. hospitals) institutional or system support for completing the dispense details in the pharmacy department.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest","min":0,"max":"1"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.id","path":"MedicationRequest.dispenseRequest.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.extension","path":"MedicationRequest.dispenseRequest.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.modifierExtension","path":"MedicationRequest.dispenseRequest.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"MedicationRequest.dispenseRequest.initialFill","path":"MedicationRequest.dispenseRequest.initialFill","short":"First fill details","definition":"Indicates the quantity or duration for the first dispense of the medication.","comment":"If populating this element, either the quantity or the duration must be included.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.initialFill","min":0,"max":"1"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.initialFill.id","path":"MedicationRequest.dispenseRequest.initialFill.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.initialFill.extension","path":"MedicationRequest.dispenseRequest.initialFill.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.initialFill.modifierExtension","path":"MedicationRequest.dispenseRequest.initialFill.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"MedicationRequest.dispenseRequest.initialFill.quantity","path":"MedicationRequest.dispenseRequest.initialFill.quantity","short":"First fill quantity","definition":"The amount or quantity to provide as part of the first dispense.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.initialFill.quantity","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.initialFill.duration","path":"MedicationRequest.dispenseRequest.initialFill.duration","short":"First fill duration","definition":"The length of time that the first dispense is expected to last.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.initialFill.duration","min":0,"max":"1"},"type":[{"code":"Duration"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.dispenseInterval","path":"MedicationRequest.dispenseRequest.dispenseInterval","short":"Minimum period of time between dispenses","definition":"The minimum period of time that must occur between dispenses of the medication.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.dispenseInterval","min":0,"max":"1"},"type":[{"code":"Duration"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.validityPeriod","path":"MedicationRequest.dispenseRequest.validityPeriod","short":"Time period supply is authorized for","definition":"This indicates the validity period of a prescription (stale dating the Prescription).","comment":"It reflects the prescribers' perspective for the validity of the prescription. Dispenses must not be made against the prescription outside of this period. The lower-bound of the Dispensing Window signifies the earliest date that the prescription can be filled for the first time. If an upper-bound is not specified then the Prescription is open-ended or will default to a stale-date based on regulations.","requirements":"Indicates when the Prescription becomes valid, and when it ceases to be a dispensable Prescription.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.validityPeriod","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.numberOfRepeatsAllowed","path":"MedicationRequest.dispenseRequest.numberOfRepeatsAllowed","short":"Number of refills authorized","definition":"An integer indicating the number of times, in addition to the original dispense, (aka refills or repeats) that the patient can receive the prescribed medication. Usage Notes: This integer does not include the original order dispense. This means that if an order indicates dispense 30 tablets plus \"3 repeats\", then the order can be dispensed a total of 4 times and the patient can receive a total of 120 tablets. A prescriber may explicitly say that zero refills are permitted after the initial dispense.","comment":"If displaying \"number of authorized fills\", add 1 to this number.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.numberOfRepeatsAllowed","min":0,"max":"1"},"type":[{"code":"unsignedInt"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.quantity","path":"MedicationRequest.dispenseRequest.quantity","short":"Amount of medication to supply per dispense","definition":"The amount that is to be dispensed for one fill.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.quantity","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.expectedSupplyDuration","path":"MedicationRequest.dispenseRequest.expectedSupplyDuration","short":"Number of days supply per dispense","definition":"Identifies the period time over which the supplied product is expected to be used, or the length of time the dispense is expected to last.","comment":"In some situations, this attribute may be used instead of quantity to identify the amount supplied by how long it is expected to last, rather than the physical quantity issued, e.g. 90 days supply of medication (based on an ordered dosage). When possible, it is always better to specify quantity, as this tends to be more precise. expectedSupplyDuration will always be an estimate that can be influenced by external factors.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.expectedSupplyDuration","min":0,"max":"1"},"type":[{"code":"Duration"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.performer","path":"MedicationRequest.dispenseRequest.performer","short":"Intended dispenser","definition":"Indicates the intended dispensing Organization specified by the prescriber.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.performer","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.substitution","path":"MedicationRequest.substitution","short":"Any restrictions on medication substitution","definition":"Indicates whether or not substitution can or should be part of the dispense. In some cases, substitution must happen, in other cases substitution must not happen. This block explains the prescriber's intent. If nothing is specified substitution may be done.","min":0,"max":"1","base":{"path":"MedicationRequest.substitution","min":0,"max":"1"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.substitution.id","path":"MedicationRequest.substitution.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.substitution.extension","path":"MedicationRequest.substitution.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.substitution.modifierExtension","path":"MedicationRequest.substitution.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"MedicationRequest.substitution.allowed[x]","path":"MedicationRequest.substitution.allowed[x]","short":"Whether substitution is allowed or not","definition":"True if the prescriber allows a different drug to be dispensed from what was prescribed.","comment":"This element is labeled as a modifier because whether substitution is allow or not, it cannot be ignored.","min":1,"max":"1","base":{"path":"MedicationRequest.substitution.allowed[x]","min":1,"max":"1"},"type":[{"code":"boolean"},{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestSubstitution"}],"strength":"example","description":"Identifies the type of substitution allowed.","valueSet":"http://terminology.hl7.org/ValueSet/v3-ActSubstanceAdminSubstitutionCode"}},{"id":"MedicationRequest.substitution.reason","path":"MedicationRequest.substitution.reason","short":"Why should (not) substitution be made","definition":"Indicates the reason for the substitution, or why substitution must or must not be performed.","min":0,"max":"1","base":{"path":"MedicationRequest.substitution.reason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationIntendedSubstitutionReason"}],"strength":"example","description":"A coded concept describing the reason that a different medication should (or should not) be substituted from what was prescribed.","valueSet":"http://terminology.hl7.org/ValueSet/v3-SubstanceAdminSubstitutionReason"}},{"id":"MedicationRequest.priorPrescription","path":"MedicationRequest.priorPrescription","short":"An order/prescription that is being replaced","definition":"A link to a resource representing an earlier order related order or prescription.","min":0,"max":"1","base":{"path":"MedicationRequest.priorPrescription","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/MedicationRequest"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.detectedIssue","path":"MedicationRequest.detectedIssue","short":"Clinical Issue with action","definition":"Indicates an actual or potential clinical issue with or between one or more active or proposed clinical actions for a patient; e.g. Drug-drug interaction, duplicate therapy, dosage alert etc.","comment":"This element can include a detected issue that has been identified either by a decision support system or by a clinician and may include information on the steps that were taken to address the issue.","alias":["Contraindication","Drug Utilization Review (DUR)","Alert"],"min":0,"max":"*","base":{"path":"MedicationRequest.detectedIssue","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/DetectedIssue"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.eventHistory","path":"MedicationRequest.eventHistory","short":"A list of events of interest in the lifecycle","definition":"Links to Provenance records for past versions of this resource or fulfilling request or event resources that identify key state transitions or updates that are likely to be relevant to a user looking at the current version of the resource.","comment":"This might not include provenances for all versions of the request – only those deemed “relevant” or important. This SHALL NOT include the provenance associated with this current version of the resource. (If that provenance is deemed to be a “relevant” change, it will need to be added as part of a later update. Until then, it can be queried directly as the provenance that points to this version using _revinclude All Provenances should have some historical version of this Request as their subject.).","min":0,"max":"*","base":{"path":"MedicationRequest.eventHistory","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Provenance"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false}]}}] \ No newline at end of file diff --git a/packages/react/src/BackboneElementInput/BackboneElementInput.stories.tsx b/packages/react/src/BackboneElementInput/BackboneElementInput.stories.tsx index 2d20571791..35cbd86f76 100644 --- a/packages/react/src/BackboneElementInput/BackboneElementInput.stories.tsx +++ b/packages/react/src/BackboneElementInput/BackboneElementInput.stories.tsx @@ -9,6 +9,6 @@ export default { export const Basic = (): JSX.Element => ( - + ); diff --git a/packages/react/src/BackboneElementInput/BackboneElementInput.test.tsx b/packages/react/src/BackboneElementInput/BackboneElementInput.test.tsx index b47023c520..2a0c8a89e2 100644 --- a/packages/react/src/BackboneElementInput/BackboneElementInput.test.tsx +++ b/packages/react/src/BackboneElementInput/BackboneElementInput.test.tsx @@ -73,19 +73,19 @@ describe('BackboneElementInput', () => { test('Renders', async () => { await medplum.requestSchema('Patient'); - await setup({ typeName: 'PatientContact' }); + await setup({ typeName: 'PatientContact', path: 'Patient.contact' }); expect(screen.getByText('Name')).toBeDefined(); }); test('Handles content reference', async () => { await medplum.requestSchema('ValueSet'); - await setup({ typeName: 'ValueSetCompose' }); + await setup({ typeName: 'ValueSetCompose', path: 'ValueSet.compose' }); expect(screen.getByText('Locked Date')).toBeInTheDocument(); expect(screen.getByText('Exclude')).toBeInTheDocument(); }); test('Not implemented', async () => { - await setup({ typeName: 'Foo' }); + await setup({ typeName: 'Foo', path: 'Foo' }); expect(screen.getByText('Foo not implemented')).toBeInTheDocument(); }); @@ -98,6 +98,7 @@ describe('BackboneElementInput', () => { indexStructureDefinitionBundle([profile], profile.url); } await setup({ + path: fishPatientProfile.type, typeName: fishPatientProfile.name, profileUrl: fishPatientProfile.url, defaultValue: fishPatient, diff --git a/packages/react/src/BackboneElementInput/BackboneElementInput.tsx b/packages/react/src/BackboneElementInput/BackboneElementInput.tsx index b65a8a4383..e1dbd216e6 100644 --- a/packages/react/src/BackboneElementInput/BackboneElementInput.tsx +++ b/packages/react/src/BackboneElementInput/BackboneElementInput.tsx @@ -2,11 +2,13 @@ import { tryGetDataType } from '@medplum/core'; import { OperationOutcome } from '@medplum/fhirtypes'; import { useContext, useMemo, useState } from 'react'; import { ElementsInput } from '../ElementsInput/ElementsInput'; -import { BackboneElementContext, buildBackboneElementContext } from './BackboneElementInput.utils'; +import { ElementsContext, buildElementsContext } from '../ElementsInput/ElementsInput.utils'; export interface BackboneElementInputProps { /** Type name the backbone element represents */ readonly typeName: string; + /** The path identifies the element and is expressed as a "."-separated list of ancestor elements, beginning with the name of the resource or extension. */ + readonly path: string; /** (optional) The contents of the resource represented by the backbone element */ readonly defaultValue?: any; /** (optional) OperationOutcome from the last attempted system action*/ @@ -18,36 +20,35 @@ export interface BackboneElementInputProps { } export function BackboneElementInput(props: BackboneElementInputProps): JSX.Element { - const { typeName } = props; - const [value, setValue] = useState(props.defaultValue ?? {}); - const backboneContext = useContext(BackboneElementContext); - const profileUrl = props.profileUrl ?? backboneContext.profileUrl; - const typeSchema = useMemo(() => tryGetDataType(typeName, profileUrl), [typeName, profileUrl]); + const [defaultValue] = useState(() => props.defaultValue ?? {}); + const parentElementsContext = useContext(ElementsContext); + const profileUrl = props.profileUrl ?? parentElementsContext.profileUrl; + const typeSchema = useMemo(() => tryGetDataType(props.typeName, profileUrl), [props.typeName, profileUrl]); + const type = typeSchema?.type ?? props.typeName; - const context = useMemo(() => { - return buildBackboneElementContext(typeSchema, profileUrl); - }, [typeSchema, profileUrl]); + const elementsContext = useMemo(() => { + return buildElementsContext({ + parentContext: parentElementsContext, + elements: typeSchema?.elements, + parentPath: props.path, + parentType: type, + profileUrl, + }); + }, [parentElementsContext, typeSchema?.elements, props.path, type, profileUrl]); if (!typeSchema) { - return
{typeName} not implemented
; - } - - function setValueWrapper(newValue: any): void { - setValue(newValue); - if (props.onChange) { - props.onChange(newValue); - } + return
{type} not implemented
; } return ( - + - + ); } diff --git a/packages/react/src/BackboneElementInput/BackboneElementInput.utils.test.ts b/packages/react/src/BackboneElementInput/BackboneElementInput.utils.test.ts deleted file mode 100644 index 41646a5125..0000000000 --- a/packages/react/src/BackboneElementInput/BackboneElementInput.utils.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { parseStructureDefinition } from '@medplum/core'; -import { readFileSync } from 'fs'; -import { resolve } from 'path'; -import { buildBackboneElementContext } from './BackboneElementInput.utils'; - -describe('buildBackboneElementContext', () => { - test('deeply nested schema', () => { - const sd = JSON.parse( - readFileSync(resolve(__dirname, '__test__', 'StructureDefinition-us-core-medicationrequest.json'), 'utf8') - ); - const schema = parseStructureDefinition(sd); - - const context = buildBackboneElementContext(schema, sd.url); - - expect(context.profileUrl).toEqual(sd.url); - expect(context.getModifiedNestedElement('MedicationRequest.dosageInstruction.method')).toBeDefined(); - }); -}); diff --git a/packages/react/src/BackboneElementInput/BackboneElementInput.utils.ts b/packages/react/src/BackboneElementInput/BackboneElementInput.utils.ts deleted file mode 100644 index 5310b81ed8..0000000000 --- a/packages/react/src/BackboneElementInput/BackboneElementInput.utils.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { InternalSchemaElement, InternalTypeSchema } from '@medplum/core'; -import React from 'react'; - -/** - * Splits a string on the last occurrence of the delimiter - * @param str - The string to split - * @param delim - The delimiter string - * @returns An array of two strings; the first consisting of the beginning of the - * string up to the last occurrence of the delimiter. the second is the remainder of the - * string after the last occurrence of the delimiter. If the delimiter is not present - * in the string, the first element is empty and the second is the input string. - */ -function splitOnceRight(str: string, delim: string): [string, string] { - const delimIndex = str.lastIndexOf(delim); - if (delimIndex === -1) { - return ['', str]; - } - const beginning = str.substring(0, delimIndex); - const last = str.substring(delimIndex + delim.length); - return [beginning, last]; -} - -export type FlatWalkedPaths = { - [path: string]: InternalSchemaElement; -}; - -export type BackboneElementContextType = { - debugMode: boolean; - profileUrl: string | undefined; - /** - * Get the element definition for the specified path if it has been modified by a profile. - * @param nestedElementPath - The path of the nested element - * @returns The modified element definition if it has been modified by the active profile or undefined. If undefined, - * the element has the default definition for the given type. - */ - getModifiedNestedElement: (nestedElementPath: string) => InternalSchemaElement | undefined; -}; - -export const BackboneElementContext = React.createContext({ - profileUrl: undefined, - debugMode: false, - getModifiedNestedElement: () => undefined, -}); - -export function buildBackboneElementContext( - typeSchema: InternalTypeSchema | undefined, - profileUrl?: string | undefined, - debugMode?: boolean | undefined -): BackboneElementContextType { - const nestedPaths: FlatWalkedPaths = Object.create(null); - - function getModifiedNestedElement(nestedElementPath: string): InternalSchemaElement | undefined { - return nestedPaths[nestedElementPath]; - } - - const elements = typeSchema?.elements; - if (elements) { - const seenKeys = new Set(); - for (const [key, property] of Object.entries(elements)) { - const [beginning, _last] = splitOnceRight(key, '.'); - // assume paths are hierarchically sorted, e.g. identifier comes before identifier.id - if (seenKeys.has(beginning)) { - nestedPaths[typeSchema.type + '.' + key] = property; - } - seenKeys.add(key); - } - } - - return { debugMode: debugMode ?? false, profileUrl, getModifiedNestedElement }; -} diff --git a/packages/react/src/BackboneElementInput/__test__/StructureDefinition-us-core-medicationrequest.json b/packages/react/src/BackboneElementInput/__test__/StructureDefinition-us-core-medicationrequest.json deleted file mode 100644 index 0a037dd3fb..0000000000 --- a/packages/react/src/BackboneElementInput/__test__/StructureDefinition-us-core-medicationrequest.json +++ /dev/null @@ -1 +0,0 @@ -{"resourceType":"StructureDefinition","id":"us-core-medicationrequest","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-medicationrequest","version":"6.1.0","name":"USCoreMedicationRequestProfile","title":"US Core MedicationRequest Profile","status":"active","experimental":false,"date":"2020-06-26","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"The US Core Medication Request Profile is based upon the core FHIR MedicationRequest Resource and meets the U.S. Core Data for Interoperability (USCDI) v2 *Medications* requirements. The MedicationRequest resource can be used to record a patient's medication prescription or order. This profile sets minimum expectations for the MedicationRequest resource to record, search, and fetch a patient's medication to promote interoperability and adoption through common implementation. It identifies which core elements, extensions, vocabularies, and value sets **SHALL** be present in the resource and constrains the way the elements are used when using this profile. It provides the floor for standards development for specific use cases.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","mapping":[{"identity":"workflow","uri":"http://hl7.org/fhir/workflow","name":"Workflow Pattern"},{"identity":"script10.6","uri":"http://ncpdp.org/SCRIPT10_6","name":"Mapping to NCPDP SCRIPT 10.6"},{"identity":"rim","uri":"http://hl7.org/v3","name":"RIM Mapping"},{"identity":"w5","uri":"http://hl7.org/fhir/fivews","name":"FiveWs Pattern Mapping"},{"identity":"v2","uri":"http://hl7.org/v2","name":"HL7 v2 Mapping"}],"kind":"resource","abstract":false,"type":"MedicationRequest","baseDefinition":"http://hl7.org/fhir/StructureDefinition/MedicationRequest","derivation":"constraint","snapshot":{"element":[{"id":"MedicationRequest","path":"MedicationRequest","short":"Ordering of medication for patient or group","definition":"\\-","comment":"\\-","alias":["Prescription","Order"],"min":0,"max":"*","base":{"path":"MedicationRequest","min":0,"max":"*"},"constraint":[{"key":"dom-2","severity":"error","human":"If the resource is contained in another resource, it SHALL NOT contain nested Resources","expression":"contained.contained.empty()","xpath":"not(parent::f:contained and f:contained)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-3","severity":"error","human":"If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource","expression":"contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()","xpath":"not(exists(for $id in f:contained/*/f:id/@value return $contained[not(parent::*/descendant::f:reference/@value=concat('#', $contained/*/id/@value) or descendant::f:reference[@value='#'])]))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-4","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated","expression":"contained.meta.versionId.empty() and contained.meta.lastUpdated.empty()","xpath":"not(exists(f:contained/*/f:meta/f:versionId)) and not(exists(f:contained/*/f:meta/f:lastUpdated))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-5","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a security label","expression":"contained.meta.security.empty()","xpath":"not(exists(f:contained/*/f:meta/f:security))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice","valueBoolean":true},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice-explanation","valueMarkdown":"When a resource has no narrative, only systems that fully understand the data can display the resource to a human safely. Including a human readable representation in the resource makes for a much more robust eco-system and cheaper handling of resources by intermediary systems. Some ecosystems restrict distribution of resources to only those systems that do fully understand the resources, and as a consequence implementers may believe that the narrative is superfluous. However experience shows that such eco-systems often open up to new participants over time."}],"key":"dom-6","severity":"warning","human":"A resource should have narrative for robust management","expression":"text.`div`.exists()","xpath":"exists(f:text/h:div)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"us-core-21","severity":"error","human":"requester SHALL be present if intent is \"order\"","expression":"(intent='order' or intent='original-order' or intent='reflex-order'or intent='filler-order' or intent='instance-order') implies requester.exists()"}],"mustSupport":false,"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"Entity. Role, or Act"},{"identity":"workflow","map":"Request"},{"identity":"script10.6","map":"Message/Body/NewRx"},{"identity":"rim","map":"CombinedMedicationRequest"},{"identity":"argonaut-dq-dstu2","map":"MedicationOrder"}]},{"id":"MedicationRequest.id","path":"MedicationRequest.id","short":"Logical id of this artifact","definition":"The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.","comment":"The only time that a resource does not have an id is when it is being submitted to the server using a create operation.","min":0,"max":"1","base":{"path":"Resource.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"id"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.meta","path":"MedicationRequest.meta","short":"Metadata about the resource","definition":"The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource.","min":0,"max":"1","base":{"path":"Resource.meta","min":0,"max":"1"},"type":[{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.implicitRules","path":"MedicationRequest.implicitRules","short":"A set of rules under which this content was created","definition":"A reference to a set of rules that were followed when the resource was constructed, and which must be understood when processing the content. Often, this is a reference to an implementation guide that defines the special rules along with other profiles etc.","comment":"Asserting this rule set restricts the content to be only understood by a limited set of trading partners. This inherently limits the usefulness of the data in the long term. However, the existing health eco-system is highly fractured, and not yet ready to define, collect, and exchange data in a generally computable sense. Wherever possible, implementers and/or specification writers should avoid using this element. Often, when used, the URL is a reference to an implementation guide that defines these special rules as part of it's narrative along with other profiles, value sets, etc.","min":0,"max":"1","base":{"path":"Resource.implicitRules","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because the implicit rules may provide additional knowledge about the resource that modifies it's meaning or interpretation","isSummary":true},{"id":"MedicationRequest.language","path":"MedicationRequest.language","short":"Language of the resource content","definition":"The base language in which the resource is written.","comment":"Language is provided to support indexing and accessibility (typically, services such as text to speech use the language tag). The html language tag in the narrative applies to the narrative. The language tag on the resource may be used to specify the language of other presentations generated from the data in the resource. Not all the content has to be in the base language. The Resource.language should not be assumed to apply to the narrative automatically. If a language is specified, it should it also be specified on the div element in the html (see rules in HTML5 for information about the relationship between xml:lang and the html lang attribute).","min":0,"max":"1","base":{"path":"Resource.language","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet","valueCanonical":"http://hl7.org/fhir/ValueSet/all-languages"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"Language"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"preferred","description":"A human language.","valueSet":"http://hl7.org/fhir/ValueSet/languages"}},{"id":"MedicationRequest.text","path":"MedicationRequest.text","short":"Text summary of the resource, for human interpretation","definition":"A human-readable narrative that contains a summary of the resource and can be used to represent the content of the resource to a human. The narrative need not encode all the structured data, but is required to contain sufficient detail to make it \"clinically safe\" for a human to just read the narrative. Resource definitions may define what content should be represented in the narrative to ensure clinical safety.","comment":"Contained resources do not have narrative. Resources that are not contained SHOULD have a narrative. In some cases, a resource may only have text with little or no additional discrete data (as long as all minOccurs=1 elements are satisfied). This may be necessary for data from legacy systems where information is captured as a \"text blob\" or where text is additionally entered raw or narrated and encoded information is added later.","alias":["narrative","html","xhtml","display"],"min":0,"max":"1","base":{"path":"DomainResource.text","min":0,"max":"1"},"type":[{"code":"Narrative"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"Act.text?"}]},{"id":"MedicationRequest.contained","path":"MedicationRequest.contained","short":"Contained, inline Resources","definition":"These resources do not have an independent existence apart from the resource that contains them - they cannot be identified independently, and nor can they have their own independent transaction scope.","comment":"This should never be done when the content can be identified properly, as once identification is lost, it is extremely difficult (and context dependent) to restore it again. Contained resources may have profiles and tags In their meta elements, but SHALL NOT have security labels.","alias":["inline resources","anonymous resources","contained resources"],"min":0,"max":"*","base":{"path":"DomainResource.contained","min":0,"max":"*"},"type":[{"code":"Resource"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"N/A"}]},{"id":"MedicationRequest.extension","path":"MedicationRequest.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the resource. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"N/A"}]},{"id":"MedicationRequest.modifierExtension","path":"MedicationRequest.modifierExtension","short":"Extensions that cannot be ignored","definition":"May be used to represent additional information that is not part of the basic definition of the resource and that modifies the understanding of the element that contains it and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the resource that contains them","isSummary":false,"mapping":[{"identity":"rim","map":"N/A"}]},{"id":"MedicationRequest.identifier","path":"MedicationRequest.identifier","short":"External ids for this request","definition":"Identifiers associated with this medication request that are defined by business processes and/or used to refer to it when a direct URL reference to the resource itself is not appropriate. They are business identifiers assigned to this resource by the performer or other systems and remain constant as the resource is updated and propagates from server to server.","comment":"This is a business identifier, not a resource identifier.","min":0,"max":"*","base":{"path":"MedicationRequest.identifier","min":0,"max":"*"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"workflow","map":"Request.identifier"},{"identity":"script10.6","map":"Message/Header/PrescriberOrderNumber"},{"identity":"w5","map":"FiveWs.identifier"},{"identity":"v2","map":"ORC-2-Placer Order Number / ORC-3-Filler Order Number"},{"identity":"rim","map":".id"}]},{"id":"MedicationRequest.status","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.status","short":"(USCDI) active | on-hold | cancelled | completed | entered-in-error | stopped | draft | unknown","definition":"A code specifying the current state of the order. Generally, this will be active or completed state.","comment":"This element is labeled as a modifier because the status contains codes that mark the resource as not currently valid.","min":1,"max":"1","base":{"path":"MedicationRequest.status","min":1,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":true,"isModifierReason":"This element is labeled as a modifier because it is a status element that contains status entered-in-error which means that the resource should not be treated as valid","isSummary":true,"binding":{"strength":"required","description":"A code specifying the state of the prescribing event. Describes the lifecycle of the prescription.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-status"},"mapping":[{"identity":"workflow","map":"Request.status"},{"identity":"script10.6","map":"no mapping"},{"identity":"w5","map":"FiveWs.status"},{"identity":"rim","map":".statusCode"},{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.status"}]},{"id":"MedicationRequest.statusReason","path":"MedicationRequest.statusReason","short":"Reason for current status","definition":"Captures the reason for the current state of the MedicationRequest.","comment":"This is generally only used for \"exception\" statuses such as \"suspended\" or \"cancelled\". The reason why the MedicationRequest was created at all is captured in reasonCode, not here.","min":0,"max":"1","base":{"path":"MedicationRequest.statusReason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestStatusReason"}],"strength":"example","description":"Identifies the reasons for a given status.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-status-reason"},"mapping":[{"identity":"workflow","map":"Request.statusReason"},{"identity":"rim","map":".inboundRelationship[typeCode=SUBJ].source[classCode=CACT, moodCode=EVN].reasonCOde"}]},{"id":"MedicationRequest.intent","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.intent","short":"(USCDI) proposal | plan | order | original-order | reflex-order | filler-order | instance-order | option","definition":"Whether the request is a proposal, plan, or an original order.","comment":"It is expected that the type of requester will be restricted for different stages of a MedicationRequest. For example, Proposals can be created by a patient, relatedPerson, Practitioner or Device. Plans can be created by Practitioners, Patients, RelatedPersons and Devices. Original orders can be created by a Practitioner only.\r\rAn instance-order is an instantiation of a request or order and may be used to populate Medication Administration Record.\r\rThis element is labeled as a modifier because the intent alters when and how the resource is actually applicable.","min":1,"max":"1","base":{"path":"MedicationRequest.intent","min":1,"max":"1"},"type":[{"code":"code"}],"condition":["us-core-21"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":true,"isModifierReason":"This element changes the interpretation of all descriptive attributes. For example \"the time the request is recommended to occur\" vs. \"the time the request is authorized to occur\" or \"who is recommended to perform the request\" vs. \"who is authorized to perform the request","isSummary":true,"binding":{"strength":"required","description":"The kind of medication order.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-intent"},"mapping":[{"identity":"workflow","map":"Request.intent"},{"identity":"w5","map":"FiveWs.class"},{"identity":"rim","map":".moodCode (nuances beyond PRP/PLAN/RQO would need to be elsewhere)"},{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.status"}]},{"id":"MedicationRequest.category","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.category","slicing":{"discriminator":[{"type":"pattern","path":"$this"}],"rules":"open"},"short":"(USCDI) Type of medication usage","definition":"Indicates the type of medication request (for example, where the medication is expected to be consumed or administered (i.e. inpatient or outpatient)).","comment":"The category can be used to include where the medication is expected to be consumed or other types of requests.","min":0,"max":"*","base":{"path":"MedicationRequest.category","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestCategory"}],"strength":"example","description":"A coded concept identifying the category of medication request. For example, where the medication is to be consumed or administered, or the type of medication treatment.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-category"},"mapping":[{"identity":"script10.6","map":"Message/Body/NewRx/MedicationPrescribed/Directions\r\ror \r\rMessage/Body/NewRx/MedicationPrescribed/StructuredSIG"},{"identity":"w5","map":"FiveWs.class"},{"identity":"rim","map":".inboundRelationship[typeCode=COMP].source[classCode=OBS, moodCode=EVN, code=\"type of medication usage\"].value"}]},{"id":"MedicationRequest.category:us-core","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.category","sliceName":"us-core","short":"(USCDI) Type of medication usage","definition":"Indicates the type of medication request (for example, where the medication is expected to be consumed or administered (i.e. inpatient or outpatient)).","comment":"The category can be used to include where the medication is expected to be consumed or other types of requests.","min":0,"max":"*","base":{"path":"MedicationRequest.category","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"strength":"required","description":"The type of medication order. Note that other codes are permitted, see [Required Bindings When Slicing by Value Sets](http://hl7.org/fhir/us/core/general-requirements.html#required-bindings-when-slicing-by-valuesets)","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-category"},"mapping":[{"identity":"script10.6","map":"Message/Body/NewRx/MedicationPrescribed/Directions\r\ror \r\rMessage/Body/NewRx/MedicationPrescribed/StructuredSIG"},{"identity":"w5","map":"FiveWs.class"},{"identity":"rim","map":".inboundRelationship[typeCode=COMP].source[classCode=OBS, moodCode=EVN, code=\"type of medication usage\"].value"}]},{"id":"MedicationRequest.priority","path":"MedicationRequest.priority","short":"routine | urgent | asap | stat","definition":"Indicates how quickly the Medication Request should be addressed with respect to other requests.","min":0,"max":"1","base":{"path":"MedicationRequest.priority","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestPriority"}],"strength":"required","description":"Identifies the level of importance to be assigned to actioning the request.","valueSet":"http://hl7.org/fhir/ValueSet/request-priority|4.0.1"},"mapping":[{"identity":"workflow","map":"Request.priority"},{"identity":"w5","map":"FiveWs.grade"},{"identity":"rim","map":".priorityCode"}]},{"id":"MedicationRequest.doNotPerform","path":"MedicationRequest.doNotPerform","short":"True if request is prohibiting action","definition":"If true indicates that the provider is asking for the medication request not to occur.","comment":"If do not perform is not specified, the request is a positive request e.g. \"do perform\".","min":0,"max":"1","base":{"path":"MedicationRequest.doNotPerform","min":0,"max":"1"},"type":[{"code":"boolean"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because this element negates the request to occur (ie, this is a request for the medication not to be ordered or prescribed, etc)","isSummary":true,"mapping":[{"identity":"rim","map":"SubstanceAdministration.actionNegationInd"}]},{"id":"MedicationRequest.reported[x]","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.reported[x]","short":"(USCDI) Reported rather than primary record","definition":"Indicates if this record was captured as a secondary 'reported' record rather than as an original primary source-of-truth record. It may also indicate the source of the report.","min":0,"max":"1","base":{"path":"MedicationRequest.reported[x]","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"boolean"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner","http://hl7.org/fhir/us/core/StructureDefinition/us-core-organization","http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient","http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitionerrole","http://hl7.org/fhir/us/core/StructureDefinition/us-core-relatedperson"],"_targetProfile":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]}]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"mapping":[{"identity":"rim","map":".participation[typeCode=INF].role"},{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.status"}]},{"id":"MedicationRequest.medication[x]","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.medication[x]","short":"(USCDI) Medication to be taken","definition":"Identifies the medication being requested. This is a link to a resource that represents the medication which may be the details of the medication or simply an attribute carrying a code that identifies the medication from a known list of medications.","comment":"If only a code is specified, then it needs to be a code for a specific product. If more information is required, then the use of the Medication resource is recommended. For example, if you require form or lot number or if the medication is compounded or extemporaneously prepared, then you must reference the Medication resource.","min":1,"max":"1","base":{"path":"MedicationRequest.medication[x]","min":1,"max":"1"},"type":[{"code":"CodeableConcept"},{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-medication"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","valueSet":"http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1010.4"},"mapping":[{"identity":"workflow","map":"Request.code"},{"identity":"script10.6","map":"Message/Body/NewRx/MedicationPrescribed\r\rMedication.code.coding.code = Message/Body/NewRx/MedicationPrescribed/DrugCoded/ProductCode\r\rMedication.code.coding.system = Message/Body/NewRx/MedicationPrescribed/DrugCoded/ProductCodeQualifier\r\rMedication.code.coding.display = Message/Body/NewRx/MedicationPrescribed/DrugDescription"},{"identity":"w5","map":"FiveWs.what[x]"},{"identity":"v2","map":"RXE-2-Give Code / RXO-1-Requested Give Code / RXC-2-Component Code"},{"identity":"rim","map":"consumable.administrableMedication"},{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.medication[x]"}]},{"id":"MedicationRequest.subject","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.subject","short":"(USCDI) Who or group medication request is for","definition":"A link to a resource representing the person or set of individuals to whom the medication will be given.","comment":"The subject on a medication request is mandatory. For the secondary use case where the actual subject is not provided, there still must be an anonymized subject specified.","min":1,"max":"1","base":{"path":"MedicationRequest.subject","min":1,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"mapping":[{"identity":"workflow","map":"Request.subject"},{"identity":"script10.6","map":"Message/Body/NewRx/Patient\r\r(need detail to link to specific patient … Patient.Identification in SCRIPT)"},{"identity":"w5","map":"FiveWs.subject[x]"},{"identity":"v2","map":"PID-3-Patient ID List"},{"identity":"rim","map":".participation[typeCode=AUT].role"},{"identity":"w5","map":"FiveWs.subject"},{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.patient"}]},{"id":"MedicationRequest.encounter","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.encounter","short":"(USCDI) Encounter created as part of encounter/admission/stay","definition":"The Encounter during which this [x] was created or to which the creation of this record is tightly associated.","comment":"This will typically be the encounter the event occurred within, but some activities may be initiated prior to or after the official completion of an encounter but still be tied to the context of the encounter.\" If there is a need to link to episodes of care they will be handled with an extension.","min":0,"max":"1","base":{"path":"MedicationRequest.encounter","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-encounter"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"mapping":[{"identity":"workflow","map":"Request.context"},{"identity":"script10.6","map":"no mapping"},{"identity":"w5","map":"FiveWs.context"},{"identity":"v2","map":"PV1-19-Visit Number"},{"identity":"rim","map":".inboundRelationship[typeCode=COMP].source[classCode=ENC, moodCode=EVN, code=\"type of encounter or episode\"]"},{"identity":"argonaut-dq-dstu2","map":"NA"}]},{"id":"MedicationRequest.supportingInformation","path":"MedicationRequest.supportingInformation","short":"Information to support ordering of the medication","definition":"Include additional information (for example, patient height and weight) that supports the ordering of the medication.","min":0,"max":"*","base":{"path":"MedicationRequest.supportingInformation","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Resource"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"workflow","map":"Request.supportingInfo"},{"identity":"w5","map":"FiveWs.context"},{"identity":"rim","map":".outboundRelationship[typeCode=PERT].target[A_SupportingClinicalStatement CMET minimal with many different choices of classCodes(ORG, ENC, PROC, SPLY, SBADM, OBS) and each of the act class codes draws from one or more of the following moodCodes (EVN, DEF, INT PRMS, RQO, PRP, APT, ARQ, GOL)]"}]},{"id":"MedicationRequest.authoredOn","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.authoredOn","short":"(USCDI) When request was initially authored","definition":"The date (and perhaps time) when the prescription was initially written or authored on.","min":0,"max":"1","base":{"path":"MedicationRequest.authoredOn","min":0,"max":"1"},"type":[{"code":"dateTime"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"mapping":[{"identity":"workflow","map":"Request.authoredOn"},{"identity":"script10.6","map":"Message/Body/NewRx/MedicationPrescribed/WrittenDate"},{"identity":"w5","map":"FiveWs.recorded"},{"identity":"v2","map":"RXE-32-Original Order Date/Time / ORC-9-Date/Time of Transaction"},{"identity":"rim","map":"author.time"},{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.dateWritten"}]},{"id":"MedicationRequest.requester","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.requester","short":"(USCDI) Who/What requested the Request","definition":"The individual, organization, or device that initiated the request and has responsibility for its activation.","min":0,"max":"1","base":{"path":"MedicationRequest.requester","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner","http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient","http://hl7.org/fhir/us/core/StructureDefinition/us-core-organization","http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitionerrole","http://hl7.org/fhir/us/core/StructureDefinition/us-core-relatedperson","http://hl7.org/fhir/StructureDefinition/Device"],"_targetProfile":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]}]}],"condition":["us-core-21"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"mapping":[{"identity":"workflow","map":"Request.requester"},{"identity":"w5","map":"FiveWs.author"},{"identity":"rim","map":".participation[typeCode=AUT].role"},{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.prescriber"}]},{"id":"MedicationRequest.performer","path":"MedicationRequest.performer","short":"Intended performer of administration","definition":"The specified desired performer of the medication treatment (e.g. the performer of the medication administration).","min":0,"max":"1","base":{"path":"MedicationRequest.performer","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Practitioner","http://hl7.org/fhir/StructureDefinition/PractitionerRole","http://hl7.org/fhir/StructureDefinition/Organization","http://hl7.org/fhir/StructureDefinition/Patient","http://hl7.org/fhir/StructureDefinition/Device","http://hl7.org/fhir/StructureDefinition/RelatedPerson","http://hl7.org/fhir/StructureDefinition/CareTeam"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"workflow","map":"Request.performer"},{"identity":"w5","map":"FiveWs.actor"},{"identity":"rim","map":".participation[typeCode=PRF].role[scoper.determinerCode=INSTANCE]"}]},{"id":"MedicationRequest.performerType","path":"MedicationRequest.performerType","short":"Desired kind of performer of the medication administration","definition":"Indicates the type of performer of the administration of the medication.","comment":"If specified without indicating a performer, this indicates that the performer must be of the specified type. If specified with a performer then it indicates the requirements of the performer if the designated performer is not available.","min":0,"max":"1","base":{"path":"MedicationRequest.performerType","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestPerformerType"}],"strength":"example","description":"Identifies the type of individual that is desired to administer the medication.","valueSet":"http://hl7.org/fhir/ValueSet/performer-role"},"mapping":[{"identity":"workflow","map":"Request.performerType"},{"identity":"rim","map":".participation[typeCode=PRF].role[scoper.determinerCode=KIND].code"}]},{"id":"MedicationRequest.recorder","path":"MedicationRequest.recorder","short":"Person who entered the request","definition":"The person who entered the order on behalf of another individual for example in the case of a verbal or a telephone order.","min":0,"max":"1","base":{"path":"MedicationRequest.recorder","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Practitioner","http://hl7.org/fhir/StructureDefinition/PractitionerRole"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"w5","map":"FiveWs.who"},{"identity":"rim","map":".participation[typeCode=TRANS].role[classCode=ASSIGNED].code (HealthcareProviderType)"}]},{"id":"MedicationRequest.reasonCode","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.reasonCode","short":"(USCDI) Reason or indication for ordering or not ordering the medication","definition":"The reason or the indication for ordering or not ordering the medication.","comment":"This could be a diagnosis code. If a full condition record exists or additional detail is needed, use reasonReference.","min":0,"max":"*","base":{"path":"MedicationRequest.reasonCode","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"extensible","valueSet":"http://hl7.org/fhir/us/core/ValueSet/us-core-condition-code"},"mapping":[{"identity":"workflow","map":"Request.reasonCode"},{"identity":"script10.6","map":"Message/Body/NewRx/MedicationPrescribed/Diagnosis/Primary/Value"},{"identity":"w5","map":"FiveWs.why[x]"},{"identity":"v2","map":"ORC-16-Order Control Code Reason /RXE-27-Give Indication/RXO-20-Indication / RXD-21-Indication / RXG-22-Indication / RXA-19-Indication"},{"identity":"rim","map":"reason.observation.reasonCode"}]},{"id":"MedicationRequest.reasonReference","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.reasonReference","short":"(USCDI) US Core Condition or Observation that supports the prescription","definition":"Condition or observation that supports why the medication was ordered.","comment":"This is a reference to a condition or observation that is the reason for the medication order. If only a code exists, use reasonCode.","min":0,"max":"*","base":{"path":"MedicationRequest.reasonReference","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Condition","http://hl7.org/fhir/StructureDefinition/Observation"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"workflow","map":"Request.reasonReference"},{"identity":"script10.6","map":"no mapping"},{"identity":"w5","map":"FiveWs.why[x]"},{"identity":"rim","map":"reason.observation[code=ASSERTION].value"}]},{"id":"MedicationRequest.instantiatesCanonical","path":"MedicationRequest.instantiatesCanonical","short":"Instantiates FHIR protocol or definition","definition":"The URL pointing to a protocol, guideline, orderset, or other definition that is adhered to in whole or in part by this MedicationRequest.","min":0,"max":"*","base":{"path":"MedicationRequest.instantiatesCanonical","min":0,"max":"*"},"type":[{"code":"canonical"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"mapping":[{"identity":"workflow","map":"Request.instantiates"},{"identity":"rim","map":".outboundRelationship[typeCode=DEFN].target"}]},{"id":"MedicationRequest.instantiatesUri","path":"MedicationRequest.instantiatesUri","short":"Instantiates external protocol or definition","definition":"The URL pointing to an externally maintained protocol, guideline, orderset or other definition that is adhered to in whole or in part by this MedicationRequest.","min":0,"max":"*","base":{"path":"MedicationRequest.instantiatesUri","min":0,"max":"*"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"mapping":[{"identity":"rim","map":".outboundRelationship[typeCode=DEFN].target"}]},{"id":"MedicationRequest.basedOn","path":"MedicationRequest.basedOn","short":"What request fulfills","definition":"A plan or request that is fulfilled in whole or in part by this medication request.","min":0,"max":"*","base":{"path":"MedicationRequest.basedOn","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/CarePlan","http://hl7.org/fhir/StructureDefinition/MedicationRequest","http://hl7.org/fhir/StructureDefinition/ServiceRequest","http://hl7.org/fhir/StructureDefinition/ImmunizationRecommendation"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"mapping":[{"identity":"workflow","map":"Request.basedOn"},{"identity":"rim","map":".outboundRelationship[typeCode=FLFS].target[classCode=SBADM or PROC or PCPR or OBS, moodCode=RQO orPLAN or PRP]"}]},{"id":"MedicationRequest.groupIdentifier","path":"MedicationRequest.groupIdentifier","short":"Composite request this is part of","definition":"A shared identifier common to all requests that were authorized more or less simultaneously by a single author, representing the identifier of the requisition or prescription.","requirements":"Requests are linked either by a \"basedOn\" relationship (i.e. one request is fulfilling another) or by having a common requisition. Requests that are part of the same requisition are generally treated independently from the perspective of changing their state or maintaining them after initial creation.","min":0,"max":"1","base":{"path":"MedicationRequest.groupIdentifier","min":0,"max":"1"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"mapping":[{"identity":"workflow","map":"Request.groupIdentifier"},{"identity":"rim","map":".outboundRelationship(typeCode=COMP].target[classCode=SBADM, moodCode=INT].id"}]},{"id":"MedicationRequest.courseOfTherapyType","path":"MedicationRequest.courseOfTherapyType","short":"Overall pattern of medication administration","definition":"The description of the overall patte3rn of the administration of the medication to the patient.","comment":"This attribute should not be confused with the protocol of the medication.","min":0,"max":"1","base":{"path":"MedicationRequest.courseOfTherapyType","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestCourseOfTherapy"}],"strength":"example","description":"Identifies the overall pattern of medication administratio.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-course-of-therapy"},"mapping":[{"identity":"rim","map":"Act.code where classCode = LIST and moodCode = EVN"}]},{"id":"MedicationRequest.insurance","path":"MedicationRequest.insurance","short":"Associated insurance coverage","definition":"Insurance plans, coverage extensions, pre-authorizations and/or pre-determinations that may be required for delivering the requested service.","min":0,"max":"*","base":{"path":"MedicationRequest.insurance","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Coverage","http://hl7.org/fhir/StructureDefinition/ClaimResponse"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"workflow","map":"Request.insurance"},{"identity":"rim","map":".outboundRelationship[typeCode=COVBY].target"}]},{"id":"MedicationRequest.note","path":"MedicationRequest.note","short":"Information about the prescription","definition":"Extra information about the prescription that could not be conveyed by the other attributes.","min":0,"max":"*","base":{"path":"MedicationRequest.note","min":0,"max":"*"},"type":[{"code":"Annotation"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"workflow","map":"Request.note"},{"identity":"script10.6","map":"Message/Body/NewRx/MedicationPrescribed/Note"},{"identity":"rim","map":".inboundRelationship[typeCode=SUBJ]/source[classCode=OBS,moodCode=EVN,code=\"annotation\"].value"}]},{"id":"MedicationRequest.dosageInstruction","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dosageInstruction","short":"(USCDI) How the medication should be taken","definition":"Indicates how the medication is to be used by the patient.","comment":"There are examples where a medication request may include the option of an oral dose or an Intravenous or Intramuscular dose. For example, \"Ondansetron 8mg orally or IV twice a day as needed for nausea\" or \"Compazine® (prochlorperazine) 5-10mg PO or 25mg PR bid prn nausea or vomiting\". In these cases, two medication requests would be created that could be grouped together. The decision on which dose and route of administration to use is based on the patient's condition at the time the dose is needed.","min":0,"max":"*","base":{"path":"MedicationRequest.dosageInstruction","min":0,"max":"*"},"type":[{"code":"Dosage"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"mapping":[{"identity":"workflow","map":"Request.occurrence[x]"},{"identity":"rim","map":"see dosageInstruction mapping"}]},{"id":"MedicationRequest.dosageInstruction.id","path":"MedicationRequest.dosageInstruction.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"n/a"}]},{"id":"MedicationRequest.dosageInstruction.extension","path":"MedicationRequest.dosageInstruction.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"n/a"}]},{"id":"MedicationRequest.dosageInstruction.modifierExtension","path":"MedicationRequest.dosageInstruction.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true,"mapping":[{"identity":"rim","map":"N/A"}]},{"id":"MedicationRequest.dosageInstruction.sequence","path":"MedicationRequest.dosageInstruction.sequence","short":"The order of the dosage instructions","definition":"Indicates the order in which the dosage instructions should be applied or interpreted.","requirements":"If the sequence number of multiple Dosages is the same, then it is implied that the instructions are to be treated as concurrent. If the sequence number is different, then the Dosages are intended to be sequential.","min":0,"max":"1","base":{"path":"Dosage.sequence","min":0,"max":"1"},"type":[{"code":"integer"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"mapping":[{"identity":"v2","map":"TQ1-1"},{"identity":"rim","map":".text"}]},{"id":"MedicationRequest.dosageInstruction.text","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dosageInstruction.text","short":"(USCDI) Free text dosage instructions e.g. SIG","definition":"Free text dosage instructions e.g. SIG.","requirements":"Free text dosage instructions can be used for cases where the instructions are too complex to code. The content of this attribute does not include the name or description of the medication. When coded instructions are present, the free text instructions may still be present for display to humans taking or administering the medication. It is expected that the text instructions will always be populated. If the dosage.timing attribute is also populated, then the dosage.text should reflect the same information as the timing. Additional information about administration or preparation of the medication should be included as text.","min":0,"max":"1","base":{"path":"Dosage.text","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"mapping":[{"identity":"v2","map":"RXO-6; RXE-21"},{"identity":"rim","map":".text"}]},{"id":"MedicationRequest.dosageInstruction.additionalInstruction","path":"MedicationRequest.dosageInstruction.additionalInstruction","short":"Supplemental instruction or warnings to the patient - e.g. \"with meals\", \"may cause drowsiness\"","definition":"Supplemental instructions to the patient on how to take the medication (e.g. \"with meals\" or\"take half to one hour before food\") or warnings for the patient about the medication (e.g. \"may cause drowsiness\" or \"avoid exposure of skin to direct sunlight or sunlamps\").","comment":"Information about administration or preparation of the medication (e.g. \"infuse as rapidly as possibly via intraperitoneal port\" or \"immediately following drug x\") should be populated in dosage.text.","requirements":"Additional instruction is intended to be coded, but where no code exists, the element could include text. For example, \"Swallow with plenty of water\" which might or might not be coded.","min":0,"max":"*","base":{"path":"Dosage.additionalInstruction","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"AdditionalInstruction"}],"strength":"example","description":"A coded concept identifying additional instructions such as \"take with water\" or \"avoid operating heavy machinery\".","valueSet":"http://hl7.org/fhir/ValueSet/additional-instruction-codes"},"mapping":[{"identity":"v2","map":"RXO-7"},{"identity":"rim","map":".text"}]},{"id":"MedicationRequest.dosageInstruction.patientInstruction","path":"MedicationRequest.dosageInstruction.patientInstruction","short":"Patient or consumer oriented instructions","definition":"Instructions in terms that are understood by the patient or consumer.","min":0,"max":"1","base":{"path":"Dosage.patientInstruction","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"mapping":[{"identity":"v2","map":"RXO-7"},{"identity":"rim","map":".text"}]},{"id":"MedicationRequest.dosageInstruction.timing","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dosageInstruction.timing","short":"(USCDI) When medication should be administered","definition":"When medication should be administered.","comment":"This attribute might not always be populated while the Dosage.text is expected to be populated. If both are populated, then the Dosage.text should reflect the content of the Dosage.timing.","requirements":"The timing schedule for giving the medication to the patient. This data type allows many different expressions. For example: \"Every 8 hours\"; \"Three times a day\"; \"1/2 an hour before breakfast for 10 days from 23-Dec 2011:\"; \"15 Oct 2013, 17 Oct 2013 and 1 Nov 2013\". Sometimes, a rate can imply duration when expressed as total volume / duration (e.g. 500mL/2 hours implies a duration of 2 hours). However, when rate doesn't imply duration (e.g. 250mL/hour), then the timing.repeat.duration is needed to convey the infuse over time period.","min":0,"max":"1","base":{"path":"Dosage.timing","min":0,"max":"1"},"type":[{"code":"Timing"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"mapping":[{"identity":"rim","map":".effectiveTime"}]},{"id":"MedicationRequest.dosageInstruction.asNeeded[x]","path":"MedicationRequest.dosageInstruction.asNeeded[x]","short":"Take \"as needed\" (for x)","definition":"Indicates whether the Medication is only taken when needed within a specific dosing schedule (Boolean option), or it indicates the precondition for taking the Medication (CodeableConcept).","comment":"Can express \"as needed\" without a reason by setting the Boolean = True. In this case the CodeableConcept is not populated. Or you can express \"as needed\" with a reason by including the CodeableConcept. In this case the Boolean is assumed to be True. If you set the Boolean to False, then the dose is given according to the schedule and is not \"prn\" or \"as needed\".","min":0,"max":"1","base":{"path":"Dosage.asNeeded[x]","min":0,"max":"1"},"type":[{"code":"boolean"},{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationAsNeededReason"}],"strength":"example","description":"A coded concept identifying the precondition that should be met or evaluated prior to consuming or administering a medication dose. For example \"pain\", \"30 minutes prior to sexual intercourse\", \"on flare-up\" etc.","valueSet":"http://hl7.org/fhir/ValueSet/medication-as-needed-reason"},"mapping":[{"identity":"v2","map":"TQ1-9"},{"identity":"rim","map":".outboundRelationship[typeCode=PRCN].target[classCode=OBS, moodCode=EVN, code=\"as needed\"].value=boolean or codable concept"}]},{"id":"MedicationRequest.dosageInstruction.site","path":"MedicationRequest.dosageInstruction.site","short":"Body site to administer to","definition":"Body site to administer to.","comment":"If the use case requires attributes from the BodySite resource (e.g. to identify and track separately) then use the standard extension [bodySite](http://hl7.org/fhir/R4/extension-bodysite.html). May be a summary code, or a reference to a very precise definition of the location, or both.","requirements":"A coded specification of the anatomic site where the medication first enters the body.","min":0,"max":"1","base":{"path":"Dosage.site","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationAdministrationSite"}],"strength":"example","description":"A coded concept describing the site location the medicine enters into or onto the body.","valueSet":"http://hl7.org/fhir/ValueSet/approach-site-codes"},"mapping":[{"identity":"v2","map":"RXR-2"},{"identity":"rim","map":".approachSiteCode"}]},{"id":"MedicationRequest.dosageInstruction.route","path":"MedicationRequest.dosageInstruction.route","short":"How drug should enter body","definition":"How drug should enter body.","requirements":"A code specifying the route or physiological path of administration of a therapeutic agent into or onto a patient's body.","min":0,"max":"1","base":{"path":"Dosage.route","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"RouteOfAdministration"}],"strength":"example","description":"A coded concept describing the route or physiological path of administration of a therapeutic agent into or onto the body of a subject.","valueSet":"http://hl7.org/fhir/ValueSet/route-codes"},"mapping":[{"identity":"v2","map":"RXR-1"},{"identity":"rim","map":".routeCode"}]},{"id":"MedicationRequest.dosageInstruction.method","path":"MedicationRequest.dosageInstruction.method","short":"Technique for administering medication","definition":"Technique for administering medication.","comment":"Terminologies used often pre-coordinate this term with the route and or form of administration.","requirements":"A coded value indicating the method by which the medication is introduced into or onto the body. Most commonly used for injections. For examples, Slow Push; Deep IV.","min":0,"max":"1","base":{"path":"Dosage.method","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationAdministrationMethod"}],"strength":"example","description":"A coded concept describing the technique by which the medicine is administered.","valueSet":"http://hl7.org/fhir/ValueSet/administration-method-codes"},"mapping":[{"identity":"v2","map":"RXR-4"},{"identity":"rim","map":".doseQuantity"}]},{"id":"MedicationRequest.dosageInstruction.doseAndRate","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dosageInstruction.doseAndRate","short":"(USCDI) Amount of medication administered","definition":"The amount of medication administered.","min":0,"max":"*","base":{"path":"Dosage.doseAndRate","min":0,"max":"*"},"type":[{"code":"Element"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"mapping":[{"identity":"v2","map":"TQ1-2"}]},{"id":"MedicationRequest.dosageInstruction.doseAndRate.id","path":"MedicationRequest.dosageInstruction.doseAndRate.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"n/a"}]},{"id":"MedicationRequest.dosageInstruction.doseAndRate.extension","path":"MedicationRequest.dosageInstruction.doseAndRate.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"n/a"}]},{"id":"MedicationRequest.dosageInstruction.doseAndRate.type","path":"MedicationRequest.dosageInstruction.doseAndRate.type","short":"The kind of dose or rate specified","definition":"The kind of dose or rate specified, for example, ordered or calculated.","requirements":"If the type is not populated, assume to be \"ordered\".","min":0,"max":"1","base":{"path":"Dosage.doseAndRate.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"DoseAndRateType"}],"strength":"example","description":"The kind of dose or rate specified.","valueSet":"http://hl7.org/fhir/ValueSet/dose-rate-type"},"mapping":[{"identity":"v2","map":"RXO-21; RXE-23"}]},{"id":"MedicationRequest.dosageInstruction.doseAndRate.dose[x]","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dosageInstruction.doseAndRate.dose[x]","short":"(USCDI) Amount of medication per dose","definition":"Amount of medication per dose.","comment":"Note that this specifies the quantity of the specified medication, not the quantity for each active ingredient(s). Each ingredient amount can be communicated in the Medication resource. For example, if one wants to communicate that a tablet was 375 mg, where the dose was one tablet, you can use the Medication resource to document that the tablet was comprised of 375 mg of drug XYZ. Alternatively if the dose was 375 mg, then you may only need to use the Medication resource to indicate this was a tablet. If the example were an IV such as dopamine and you wanted to communicate that 400mg of dopamine was mixed in 500 ml of some IV solution, then this would all be communicated in the Medication resource. If the administration is not intended to be instantaneous (rate is present or timing has a duration), this can be specified to convey the total amount to be administered over the period of time as indicated by the schedule e.g. 500 ml in dose, with timing used to convey that this should be done over 4 hours.","requirements":"The amount of therapeutic or other substance given at one administration event.","min":0,"max":"1","base":{"path":"Dosage.doseAndRate.dose[x]","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"Quantity"},{"code":"Range"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet","valueCanonical":"http://hl7.org/fhir/ValueSet/ucum-units"}],"strength":"preferred","valueSet":"http://hl7.org/fhir/ValueSet/ucum-common"},"mapping":[{"identity":"v2","map":"RXO-2, RXE-3"},{"identity":"rim","map":".doseQuantity"}]},{"id":"MedicationRequest.dosageInstruction.doseAndRate.rate[x]","path":"MedicationRequest.dosageInstruction.doseAndRate.rate[x]","short":"Amount of medication per unit of time","definition":"Amount of medication per unit of time.","comment":"It is possible to supply both a rate and a doseQuantity to provide full details about how the medication is to be administered and supplied. If the rate is intended to change over time, depending on local rules/regulations, each change should be captured as a new version of the MedicationRequest with an updated rate, or captured with a new MedicationRequest with the new rate.\r\rIt is possible to specify a rate over time (for example, 100 ml/hour) using either the rateRatio and rateQuantity. The rateQuantity approach requires systems to have the capability to parse UCUM grammer where ml/hour is included rather than a specific ratio where the time is specified as the denominator. Where a rate such as 500ml over 2 hours is specified, the use of rateRatio may be more semantically correct than specifying using a rateQuantity of 250 mg/hour.","requirements":"Identifies the speed with which the medication was or will be introduced into the patient. Typically the rate for an infusion e.g. 100 ml per 1 hour or 100 ml/hr. May also be expressed as a rate per unit of time e.g. 500 ml per 2 hours. Other examples: 200 mcg/min or 200 mcg/1 minute; 1 liter/8 hours. Sometimes, a rate can imply duration when expressed as total volume / duration (e.g. 500mL/2 hours implies a duration of 2 hours). However, when rate doesn't imply duration (e.g. 250mL/hour), then the timing.repeat.duration is needed to convey the infuse over time period.","min":0,"max":"1","base":{"path":"Dosage.doseAndRate.rate[x]","min":0,"max":"1"},"type":[{"code":"Ratio"},{"code":"Range"},{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"mapping":[{"identity":"v2","map":"RXE22, RXE23, RXE-24"},{"identity":"rim","map":".rateQuantity"}]},{"id":"MedicationRequest.dosageInstruction.maxDosePerPeriod","path":"MedicationRequest.dosageInstruction.maxDosePerPeriod","short":"Upper limit on medication per unit of time","definition":"Upper limit on medication per unit of time.","comment":"This is intended for use as an adjunct to the dosage when there is an upper cap. For example \"2 tablets every 4 hours to a maximum of 8/day\".","requirements":"The maximum total quantity of a therapeutic substance that may be administered to a subject over the period of time. For example, 1000mg in 24 hours.","min":0,"max":"1","base":{"path":"Dosage.maxDosePerPeriod","min":0,"max":"1"},"type":[{"code":"Ratio"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"mapping":[{"identity":"v2","map":"RXO-23, RXE-19"},{"identity":"rim","map":".maxDoseQuantity"}]},{"id":"MedicationRequest.dosageInstruction.maxDosePerAdministration","path":"MedicationRequest.dosageInstruction.maxDosePerAdministration","short":"Upper limit on medication per administration","definition":"Upper limit on medication per administration.","comment":"This is intended for use as an adjunct to the dosage when there is an upper cap. For example, a body surface area related dose with a maximum amount, such as 1.5 mg/m2 (maximum 2 mg) IV over 5 – 10 minutes would have doseQuantity of 1.5 mg/m2 and maxDosePerAdministration of 2 mg.","requirements":"The maximum total quantity of a therapeutic substance that may be administered to a subject per administration.","min":0,"max":"1","base":{"path":"Dosage.maxDosePerAdministration","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"mapping":[{"identity":"rim","map":"not supported"}]},{"id":"MedicationRequest.dosageInstruction.maxDosePerLifetime","path":"MedicationRequest.dosageInstruction.maxDosePerLifetime","short":"Upper limit on medication per lifetime of the patient","definition":"Upper limit on medication per lifetime of the patient.","requirements":"The maximum total quantity of a therapeutic substance that may be administered per lifetime of the subject.","min":0,"max":"1","base":{"path":"Dosage.maxDosePerLifetime","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"mapping":[{"identity":"rim","map":"not supported"}]},{"id":"MedicationRequest.dispenseRequest","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dispenseRequest","short":"(USCDI) Medication supply authorization","definition":"Indicates the specific details for the dispense or medication supply part of a medication request (also known as a Medication Prescription or Medication Order). Note that this information is not always sent with the order. There may be in some settings (e.g. hospitals) institutional or system support for completing the dispense details in the pharmacy department.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest","min":0,"max":"1"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"mapping":[{"identity":"script10.6","map":"Message/Body/NewRx/MedicationPrescribed/ExpirationDate"},{"identity":"rim","map":"component.supplyEvent"}]},{"id":"MedicationRequest.dispenseRequest.id","path":"MedicationRequest.dispenseRequest.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"n/a"}]},{"id":"MedicationRequest.dispenseRequest.extension","path":"MedicationRequest.dispenseRequest.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"n/a"}]},{"id":"MedicationRequest.dispenseRequest.modifierExtension","path":"MedicationRequest.dispenseRequest.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true,"mapping":[{"identity":"rim","map":"N/A"}]},{"id":"MedicationRequest.dispenseRequest.initialFill","path":"MedicationRequest.dispenseRequest.initialFill","short":"First fill details","definition":"Indicates the quantity or duration for the first dispense of the medication.","comment":"If populating this element, either the quantity or the duration must be included.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.initialFill","min":0,"max":"1"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"SubstanceAdministration -> ActRelationship[sequenceNumber = '1'] -> Supply"}]},{"id":"MedicationRequest.dispenseRequest.initialFill.id","path":"MedicationRequest.dispenseRequest.initialFill.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"n/a"}]},{"id":"MedicationRequest.dispenseRequest.initialFill.extension","path":"MedicationRequest.dispenseRequest.initialFill.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"n/a"}]},{"id":"MedicationRequest.dispenseRequest.initialFill.modifierExtension","path":"MedicationRequest.dispenseRequest.initialFill.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true,"mapping":[{"identity":"rim","map":"N/A"}]},{"id":"MedicationRequest.dispenseRequest.initialFill.quantity","path":"MedicationRequest.dispenseRequest.initialFill.quantity","short":"First fill quantity","definition":"The amount or quantity to provide as part of the first dispense.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.initialFill.quantity","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"Supply.quantity[moodCode=RQO]"}]},{"id":"MedicationRequest.dispenseRequest.initialFill.duration","path":"MedicationRequest.dispenseRequest.initialFill.duration","short":"First fill duration","definition":"The length of time that the first dispense is expected to last.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.initialFill.duration","min":0,"max":"1"},"type":[{"code":"Duration"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"Supply.effectivetime[moodCode=RQO]"}]},{"id":"MedicationRequest.dispenseRequest.dispenseInterval","path":"MedicationRequest.dispenseRequest.dispenseInterval","short":"Minimum period of time between dispenses","definition":"The minimum period of time that must occur between dispenses of the medication.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.dispenseInterval","min":0,"max":"1"},"type":[{"code":"Duration"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"Supply.effectivetime[moodCode=RQO]"}]},{"id":"MedicationRequest.dispenseRequest.validityPeriod","path":"MedicationRequest.dispenseRequest.validityPeriod","short":"Time period supply is authorized for","definition":"This indicates the validity period of a prescription (stale dating the Prescription).","comment":"It reflects the prescribers' perspective for the validity of the prescription. Dispenses must not be made against the prescription outside of this period. The lower-bound of the Dispensing Window signifies the earliest date that the prescription can be filled for the first time. If an upper-bound is not specified then the Prescription is open-ended or will default to a stale-date based on regulations.","requirements":"Indicates when the Prescription becomes valid, and when it ceases to be a dispensable Prescription.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.validityPeriod","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"script10.6","map":"Message/Body/NewRx/MedicationPrescribed/Refills"},{"identity":"rim","map":"effectiveTime"}]},{"id":"MedicationRequest.dispenseRequest.numberOfRepeatsAllowed","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dispenseRequest.numberOfRepeatsAllowed","short":"(USCDI) Number of refills authorized","definition":"An integer indicating the number of times, in addition to the original dispense, (aka refills or repeats) that the patient can receive the prescribed medication. Usage Notes: This integer does not include the original order dispense. This means that if an order indicates dispense 30 tablets plus \"3 repeats\", then the order can be dispensed a total of 4 times and the patient can receive a total of 120 tablets. A prescriber may explicitly say that zero refills are permitted after the initial dispense.","comment":"If displaying \"number of authorized fills\", add 1 to this number.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.numberOfRepeatsAllowed","min":0,"max":"1"},"type":[{"code":"unsignedInt"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"mapping":[{"identity":"script10.6","map":"Message/Body/NewRx/MedicationPrescribed/Quantity"},{"identity":"v2","map":"RXE-12-Number of Refills"},{"identity":"rim","map":"repeatNumber"}]},{"id":"MedicationRequest.dispenseRequest.quantity","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dispenseRequest.quantity","short":"(USCDI) Amount of medication to supply per dispense","definition":"The amount that is to be dispensed for one fill.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.quantity","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"mapping":[{"identity":"script10.6","map":"Message/Body/NewRx/MedicationPrescribed/DaysSupply"},{"identity":"v2","map":"RXD-4-Actual Dispense Amount / RXD-5.1-Actual Dispense Units.code / RXD-5.3-Actual Dispense Units.name of coding system"},{"identity":"rim","map":"quantity"}]},{"id":"MedicationRequest.dispenseRequest.expectedSupplyDuration","path":"MedicationRequest.dispenseRequest.expectedSupplyDuration","short":"Number of days supply per dispense","definition":"Identifies the period time over which the supplied product is expected to be used, or the length of time the dispense is expected to last.","comment":"In some situations, this attribute may be used instead of quantity to identify the amount supplied by how long it is expected to last, rather than the physical quantity issued, e.g. 90 days supply of medication (based on an ordered dosage). When possible, it is always better to specify quantity, as this tends to be more precise. expectedSupplyDuration will always be an estimate that can be influenced by external factors.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.expectedSupplyDuration","min":0,"max":"1"},"type":[{"code":"Duration"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"script10.6","map":"Message/Body/NewRx/MedicationPrescribed/Substitutions"},{"identity":"rim","map":"expectedUseTime"}]},{"id":"MedicationRequest.dispenseRequest.performer","path":"MedicationRequest.dispenseRequest.performer","short":"Intended dispenser","definition":"Indicates the intended dispensing Organization specified by the prescriber.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.performer","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"w5","map":"FiveWs.who"},{"identity":"rim","map":".outboundRelationship[typeCode=COMP].target[classCode=SPLY, moodCode=RQO] .participation[typeCode=PRF].role[scoper.determinerCode=INSTANCE]"}]},{"id":"MedicationRequest.substitution","path":"MedicationRequest.substitution","short":"Any restrictions on medication substitution","definition":"Indicates whether or not substitution can or should be part of the dispense. In some cases, substitution must happen, in other cases substitution must not happen. This block explains the prescriber's intent. If nothing is specified substitution may be done.","min":0,"max":"1","base":{"path":"MedicationRequest.substitution","min":0,"max":"1"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"script10.6","map":"specific values within Message/Body/NewRx/MedicationPrescribed/Substitutions"},{"identity":"rim","map":"subjectOf.substitutionPersmission"}]},{"id":"MedicationRequest.substitution.id","path":"MedicationRequest.substitution.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"n/a"}]},{"id":"MedicationRequest.substitution.extension","path":"MedicationRequest.substitution.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"n/a"}]},{"id":"MedicationRequest.substitution.modifierExtension","path":"MedicationRequest.substitution.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true,"mapping":[{"identity":"rim","map":"N/A"}]},{"id":"MedicationRequest.substitution.allowed[x]","path":"MedicationRequest.substitution.allowed[x]","short":"Whether substitution is allowed or not","definition":"True if the prescriber allows a different drug to be dispensed from what was prescribed.","comment":"This element is labeled as a modifier because whether substitution is allow or not, it cannot be ignored.","min":1,"max":"1","base":{"path":"MedicationRequest.substitution.allowed[x]","min":1,"max":"1"},"type":[{"code":"boolean"},{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestSubstitution"}],"strength":"example","description":"Identifies the type of substitution allowed.","valueSet":"http://terminology.hl7.org/ValueSet/v3-ActSubstanceAdminSubstitutionCode"},"mapping":[{"identity":"script10.6","map":"specific values within Message/Body/NewRx/MedicationPrescribed/Substitutions"},{"identity":"v2","map":"RXO-9-Allow Substitutions / RXE-9-Substitution Status"},{"identity":"rim","map":"code"}]},{"id":"MedicationRequest.substitution.reason","path":"MedicationRequest.substitution.reason","short":"Why should (not) substitution be made","definition":"Indicates the reason for the substitution, or why substitution must or must not be performed.","min":0,"max":"1","base":{"path":"MedicationRequest.substitution.reason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationIntendedSubstitutionReason"}],"strength":"example","description":"A coded concept describing the reason that a different medication should (or should not) be substituted from what was prescribed.","valueSet":"http://terminology.hl7.org/ValueSet/v3-SubstanceAdminSubstitutionReason"},"mapping":[{"identity":"script10.6","map":"not mapped"},{"identity":"v2","map":"RXE-9 Substition status"},{"identity":"rim","map":"reasonCode"}]},{"id":"MedicationRequest.priorPrescription","path":"MedicationRequest.priorPrescription","short":"An order/prescription that is being replaced","definition":"A link to a resource representing an earlier order related order or prescription.","min":0,"max":"1","base":{"path":"MedicationRequest.priorPrescription","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/MedicationRequest"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"workflow","map":"Request.replaces"},{"identity":"script10.6","map":"not mapped"},{"identity":"rim","map":".outboundRelationship[typeCode=?RPLC or ?SUCC]/target[classCode=SBADM,moodCode=RQO]"}]},{"id":"MedicationRequest.detectedIssue","path":"MedicationRequest.detectedIssue","short":"Clinical Issue with action","definition":"Indicates an actual or potential clinical issue with or between one or more active or proposed clinical actions for a patient; e.g. Drug-drug interaction, duplicate therapy, dosage alert etc.","comment":"This element can include a detected issue that has been identified either by a decision support system or by a clinician and may include information on the steps that were taken to address the issue.","alias":["Contraindication","Drug Utilization Review (DUR)","Alert"],"min":0,"max":"*","base":{"path":"MedicationRequest.detectedIssue","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/DetectedIssue"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":".inboundRelationship[typeCode=SUBJ]/source[classCode=ALRT,moodCode=EVN].value"}]},{"id":"MedicationRequest.eventHistory","path":"MedicationRequest.eventHistory","short":"A list of events of interest in the lifecycle","definition":"Links to Provenance records for past versions of this resource or fulfilling request or event resources that identify key state transitions or updates that are likely to be relevant to a user looking at the current version of the resource.","comment":"This might not include provenances for all versions of the request – only those deemed “relevant” or important. This SHALL NOT include the provenance associated with this current version of the resource. (If that provenance is deemed to be a “relevant” change, it will need to be added as part of a later update. Until then, it can be queried directly as the provenance that points to this version using _revinclude All Provenances should have some historical version of this Request as their subject.).","min":0,"max":"*","base":{"path":"MedicationRequest.eventHistory","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Provenance"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"workflow","map":"Request.relevantHistory"},{"identity":"rim","map":".inboundRelationship(typeCode=SUBJ].source[classCode=CACT, moodCode=EVN]"}]}]},"differential":{"element":[{"id":"MedicationRequest","path":"MedicationRequest","definition":"\\-","comment":"\\-","constraint":[{"key":"us-core-21","severity":"error","human":"requester SHALL be present if intent is \"order\"","expression":"(intent='order' or intent='original-order' or intent='reflex-order'or intent='filler-order' or intent='instance-order') implies requester.exists()"}],"mustSupport":false,"mapping":[{"identity":"argonaut-dq-dstu2","map":"MedicationOrder"}]},{"id":"MedicationRequest.status","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.status","short":"(USCDI) active | on-hold | cancelled | completed | entered-in-error | stopped | draft | unknown","mustSupport":true,"binding":{"strength":"required","description":"A code specifying the state of the prescribing event. Describes the lifecycle of the prescription.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-status"},"mapping":[{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.status"}]},{"id":"MedicationRequest.intent","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.intent","short":"(USCDI) proposal | plan | order | original-order | reflex-order | filler-order | instance-order | option","condition":["us-core-21"],"mustSupport":true,"binding":{"strength":"required","description":"The kind of medication order.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-intent"},"mapping":[{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.status"}]},{"id":"MedicationRequest.category","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.category","slicing":{"discriminator":[{"type":"pattern","path":"$this"}],"rules":"open"},"short":"(USCDI) Type of medication usage","mustSupport":true},{"id":"MedicationRequest.category:us-core","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.category","sliceName":"us-core","short":"(USCDI) Type of medication usage","mustSupport":true,"binding":{"strength":"required","description":"The type of medication order. Note that other codes are permitted, see [Required Bindings When Slicing by Value Sets](http://hl7.org/fhir/us/core/general-requirements.html#required-bindings-when-slicing-by-valuesets)","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-category"}},{"id":"MedicationRequest.reported[x]","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.reported[x]","short":"(USCDI) Reported rather than primary record","type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"boolean"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner","http://hl7.org/fhir/us/core/StructureDefinition/us-core-organization","http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient","http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitionerrole","http://hl7.org/fhir/us/core/StructureDefinition/us-core-relatedperson"],"_targetProfile":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]}]}],"mustSupport":true,"mapping":[{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.status"}]},{"id":"MedicationRequest.medication[x]","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.medication[x]","short":"(USCDI) Medication to be taken","type":[{"code":"CodeableConcept"},{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-medication"]}],"mustSupport":true,"binding":{"strength":"extensible","valueSet":"http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1010.4"},"mapping":[{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.medication[x]"}]},{"id":"MedicationRequest.subject","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.subject","short":"(USCDI) Who or group medication request is for","type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"]}],"mustSupport":true,"mapping":[{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.patient"}]},{"id":"MedicationRequest.encounter","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.encounter","short":"(USCDI) Encounter created as part of encounter/admission/stay","type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-encounter"]}],"mustSupport":true,"mapping":[{"identity":"argonaut-dq-dstu2","map":"NA"}]},{"id":"MedicationRequest.authoredOn","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.authoredOn","short":"(USCDI) When request was initially authored","mustSupport":true,"mapping":[{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.dateWritten"}]},{"id":"MedicationRequest.requester","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.requester","short":"(USCDI) Who/What requested the Request","type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner","http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient","http://hl7.org/fhir/us/core/StructureDefinition/us-core-organization","http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitionerrole","http://hl7.org/fhir/us/core/StructureDefinition/us-core-relatedperson","http://hl7.org/fhir/StructureDefinition/Device"],"_targetProfile":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]}]}],"condition":["us-core-21"],"mustSupport":true,"mapping":[{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.prescriber"}]},{"id":"MedicationRequest.reasonCode","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.reasonCode","short":"(USCDI) Reason or indication for ordering or not ordering the medication","binding":{"strength":"extensible","valueSet":"http://hl7.org/fhir/us/core/ValueSet/us-core-condition-code"}},{"id":"MedicationRequest.reasonReference","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.reasonReference","short":"(USCDI) US Core Condition or Observation that supports the prescription"},{"id":"MedicationRequest.dosageInstruction","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dosageInstruction","short":"(USCDI) How the medication should be taken","mustSupport":true},{"id":"MedicationRequest.dosageInstruction.text","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dosageInstruction.text","short":"(USCDI) Free text dosage instructions e.g. SIG","mustSupport":true},{"id":"MedicationRequest.dosageInstruction.timing","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dosageInstruction.timing","short":"(USCDI) When medication should be administered","mustSupport":true},{"id":"MedicationRequest.dosageInstruction.doseAndRate","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dosageInstruction.doseAndRate","short":"(USCDI) Amount of medication administered","mustSupport":true},{"id":"MedicationRequest.dosageInstruction.doseAndRate.dose[x]","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dosageInstruction.doseAndRate.dose[x]","short":"(USCDI) Amount of medication per dose","type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"Quantity"},{"code":"Range"}],"mustSupport":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet","valueCanonical":"http://hl7.org/fhir/ValueSet/ucum-units"}],"strength":"preferred","valueSet":"http://hl7.org/fhir/ValueSet/ucum-common"}},{"id":"MedicationRequest.dispenseRequest","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dispenseRequest","short":"(USCDI) Medication supply authorization","mustSupport":true},{"id":"MedicationRequest.dispenseRequest.numberOfRepeatsAllowed","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dispenseRequest.numberOfRepeatsAllowed","short":"(USCDI) Number of refills authorized","mustSupport":true},{"id":"MedicationRequest.dispenseRequest.quantity","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dispenseRequest.quantity","short":"(USCDI) Amount of medication to supply per dispense","mustSupport":true}]}} diff --git a/packages/react/src/CheckboxFormSection/CheckboxFormSection.tsx b/packages/react/src/CheckboxFormSection/CheckboxFormSection.tsx index ecc2131419..6d985e7eee 100644 --- a/packages/react/src/CheckboxFormSection/CheckboxFormSection.tsx +++ b/packages/react/src/CheckboxFormSection/CheckboxFormSection.tsx @@ -1,6 +1,6 @@ import { Group, Input } from '@mantine/core'; import { ReactNode, useContext } from 'react'; -import { BackboneElementContext } from '../BackboneElementInput/BackboneElementInput.utils'; +import { ElementsContext } from '../ElementsInput/ElementsInput.utils'; export interface CheckboxFormSectionProps { readonly htmlFor?: string; @@ -13,7 +13,7 @@ export interface CheckboxFormSectionProps { } export function CheckboxFormSection(props: CheckboxFormSectionProps): JSX.Element { - const { debugMode } = useContext(BackboneElementContext); + const { debugMode } = useContext(ElementsContext); let label: React.ReactNode; if (debugMode && props.fhirPath) { diff --git a/packages/react/src/CodeableConceptInput/CodeableConceptInput.stories.tsx b/packages/react/src/CodeableConceptInput/CodeableConceptInput.stories.tsx index 76a037516c..952b887bb8 100644 --- a/packages/react/src/CodeableConceptInput/CodeableConceptInput.stories.tsx +++ b/packages/react/src/CodeableConceptInput/CodeableConceptInput.stories.tsx @@ -11,7 +11,13 @@ const valueSet = 'http://hl7.org/fhir/ValueSet/marital-status'; export const Basic = (): JSX.Element => ( - + ); @@ -22,6 +28,8 @@ export const DefaultValue = (): JSX.Element => ( binding={valueSet} defaultValue={{ coding: [{ code: 'M', display: 'Married' }] }} onChange={console.log} + path={'Patient.maritalStatus'} + outcome={undefined} /> ); diff --git a/packages/react/src/CodeableConceptInput/CodeableConceptInput.test.tsx b/packages/react/src/CodeableConceptInput/CodeableConceptInput.test.tsx index 4ab4074813..8341c4a36e 100644 --- a/packages/react/src/CodeableConceptInput/CodeableConceptInput.test.tsx +++ b/packages/react/src/CodeableConceptInput/CodeableConceptInput.test.tsx @@ -1,9 +1,8 @@ import { CodeableConcept } from '@medplum/fhirtypes'; import { MockClient } from '@medplum/mock'; import { MedplumProvider } from '@medplum/react-hooks'; -import { ReactNode } from 'react'; import { act, fireEvent, render, screen, waitFor } from '../test-utils/render'; -import { CodeableConceptInput } from './CodeableConceptInput'; +import { CodeableConceptInput, CodeableConceptInputProps } from './CodeableConceptInput'; const medplum = new MockClient(); const binding = 'https://example.com/test'; @@ -20,27 +19,39 @@ describe('CodeableConceptInput', () => { jest.useRealTimers(); }); - async function setup(child: ReactNode): Promise { + async function setup(props?: Partial): Promise { + const finalProps: CodeableConceptInputProps = { + binding, + name: 'test', + path: 'Resource.test', + outcome: undefined, + onChange: jest.fn(), + ...props, + }; await act(async () => { - render({child}); + render( + + + + ); }); } test('Renders', async () => { - await setup(); + await setup(); expect(screen.getByRole('searchbox')).toBeInTheDocument(); }); test('Renders CodeableConcept default value', async () => { - await setup(); + await setup({ defaultValue: { coding: [{ code: 'abc' }] } }); expect(screen.getByRole('searchbox')).toBeInTheDocument(); expect(screen.getByText('abc')).toBeDefined(); }); test('Searches for results', async () => { - await setup(); + await setup(); const input = screen.getByRole('searchbox') as HTMLInputElement; @@ -70,7 +81,7 @@ describe('CodeableConceptInput', () => { test('Create unstructured value', async () => { let currValue: CodeableConcept | undefined; - await setup( (currValue = newValue)} />); + await setup({ onChange: (newValue) => (currValue = newValue) }); const input = screen.getByRole('searchbox') as HTMLInputElement; @@ -111,9 +122,7 @@ describe('CodeableConceptInput', () => { ], }; - await setup( - - ); + await setup({ defaultValue }); const input = screen.getByRole('searchbox') as HTMLInputElement; diff --git a/packages/react/src/CodeableConceptInput/CodeableConceptInput.tsx b/packages/react/src/CodeableConceptInput/CodeableConceptInput.tsx index c106624768..238ee4c25e 100644 --- a/packages/react/src/CodeableConceptInput/CodeableConceptInput.tsx +++ b/packages/react/src/CodeableConceptInput/CodeableConceptInput.tsx @@ -1,10 +1,12 @@ import { CodeableConcept, ValueSetExpansionContains } from '@medplum/fhirtypes'; import { useState } from 'react'; import { ValueSetAutocomplete, ValueSetAutocompleteProps } from '../ValueSetAutocomplete/ValueSetAutocomplete'; +import { ComplexTypeInputProps } from '../ResourcePropertyInput/ResourcePropertyInput.utils'; -export interface CodeableConceptInputProps extends Omit { - readonly defaultValue?: CodeableConcept; - readonly onChange?: (value: CodeableConcept | undefined) => void; +export interface CodeableConceptInputProps + extends Omit, + ComplexTypeInputProps { + readonly onChange: ((value: CodeableConcept | undefined) => void) | undefined; } export function CodeableConceptInput(props: CodeableConceptInputProps): JSX.Element { diff --git a/packages/react/src/ContactPointInput/ContactPointInput.tsx b/packages/react/src/ContactPointInput/ContactPointInput.tsx index 4bd92780c0..adbea9ea6a 100644 --- a/packages/react/src/ContactPointInput/ContactPointInput.tsx +++ b/packages/react/src/ContactPointInput/ContactPointInput.tsx @@ -2,7 +2,7 @@ import { Group, NativeSelect, TextInput } from '@mantine/core'; import { ContactPoint } from '@medplum/fhirtypes'; import { useContext, useMemo, useRef, useState } from 'react'; import { ComplexTypeInputProps } from '../ResourcePropertyInput/ResourcePropertyInput.utils'; -import { BackboneElementContext } from '../BackboneElementInput/BackboneElementInput.utils'; +import { ElementsContext } from '../ElementsInput/ElementsInput.utils'; import { getErrorsForInput } from '../utils/outcomes'; export type ContactPointInputProps = ComplexTypeInputProps & { @@ -11,7 +11,7 @@ export type ContactPointInputProps = ComplexTypeInputProps & { export function ContactPointInput(props: ContactPointInputProps): JSX.Element { const { path, outcome } = props; - const { getModifiedNestedElement } = useContext(BackboneElementContext); + const { getModifiedNestedElement } = useContext(ElementsContext); const [contactPoint, setContactPoint] = useState(props.defaultValue); const ref = useRef(); diff --git a/packages/react/src/ElementsInput/ElementsInput.tsx b/packages/react/src/ElementsInput/ElementsInput.tsx index 8e21ef61d0..9cb782b7d2 100644 --- a/packages/react/src/ElementsInput/ElementsInput.tsx +++ b/packages/react/src/ElementsInput/ElementsInput.tsx @@ -1,20 +1,22 @@ import { Stack } from '@mantine/core'; import { InternalSchemaElement, TypedValue, getPathDisplayName, isPopulated } from '@medplum/core'; import { OperationOutcome } from '@medplum/fhirtypes'; -import { useMemo, useState } from 'react'; +import { useContext, useMemo, useState } from 'react'; import { CheckboxFormSection } from '../CheckboxFormSection/CheckboxFormSection'; import { FormSection } from '../FormSection/FormSection'; import { setPropertyValue } from '../ResourceForm/ResourceForm.utils'; import { getValueAndTypeFromElement } from '../ResourcePropertyDisplay/ResourcePropertyDisplay.utils'; import { ResourcePropertyInput } from '../ResourcePropertyInput/ResourcePropertyInput'; import { DEFAULT_IGNORED_NON_NESTED_PROPERTIES, DEFAULT_IGNORED_PROPERTIES } from '../constants'; +import { ElementsContext } from './ElementsInput.utils'; const EXTENSION_KEYS = new Set(['extension', 'modifierExtension']); const IGNORED_PROPERTIES = new Set(['id', ...DEFAULT_IGNORED_PROPERTIES].filter((prop) => !EXTENSION_KEYS.has(prop))); export interface ElementsInputProps { - readonly type: string | undefined; - readonly elements: { [key: string]: InternalSchemaElement }; + readonly type: string; + /** The path identifies the element and is expressed as a "."-separated list of ancestor elements, beginning with the name of the resource or extension. */ + readonly path: string; readonly defaultValue: any; readonly outcome: OperationOutcome | undefined; readonly onChange: ((value: any) => void) | undefined; @@ -22,22 +24,18 @@ export interface ElementsInputProps { } export function ElementsInput(props: ElementsInputProps): JSX.Element { - const { elements } = props; const [value, setValue] = useState(props.defaultValue ?? {}); - - const fixedProperties = useMemo(() => { - const result: { [key: string]: InternalSchemaElement & { fixed: TypedValue } } = Object.create(null); - for (const [key, property] of Object.entries(elements)) { - if (property.fixed) { - result[key] = property as any; - } - } - return result; - }, [elements]); + const elementsContext = useContext(ElementsContext); + const elementsToRender = useMemo(() => { + return getElementsToRender(elementsContext.elements); + }, [elementsContext.elements]); function setValueWrapper(newValue: any): void { - for (const [key, prop] of Object.entries(fixedProperties)) { - setPropertyValue(newValue, key, key, prop, prop.fixed.value); + for (const [key, prop] of Object.entries(elementsContext.fixedProperties)) { + // setPropertyValue cannot set nested properties + if (!key.includes('.')) { + setPropertyValue(newValue, key, key, prop, prop.fixed.value); + } } setValue(newValue); if (props.onChange) { @@ -45,47 +43,19 @@ export function ElementsInput(props: ElementsInputProps): JSX.Element { } } + const typedValue: TypedValue = { type: props.type, value }; + return ( - {Object.entries(elements).map(([key, element]) => { - if (!element.type) { - return null; - } - - if (element.max === 0) { - return null; - } - - // mostly for Extension.url - if (key === 'url' && element.fixed) { - return null; - } - - if (EXTENSION_KEYS.has(key) && !isPopulated(element.slicing?.slices)) { - // an extension property without slices has no nested extensions - return null; - } else if (IGNORED_PROPERTIES.has(key)) { - return null; - } else if (DEFAULT_IGNORED_NON_NESTED_PROPERTIES.includes(key) && element.path.split('.').length === 2) { - return null; - } - - // Profiles can include nested elements in addition to their containing element, e.g.: - // identifier, identifier.use, identifier.system - // Skip nested elements, e.g. identifier.use, since they are handled by the containing element - if (key.includes('.')) { - return null; - } - - const [propertyValue, propertyType] = getValueAndTypeFromElement(value, key, element); - + {elementsToRender.map(([key, element]) => { + const [propertyValue, propertyType] = getValueAndTypeFromElement(typedValue, key, element); const required = element.min !== undefined && element.min > 0; - const resourcePropertyInput = ( { @@ -133,3 +103,40 @@ export function ElementsInput(props: ElementsInputProps): JSX.Element { ); } + +function getElementsToRender(inputElements: Record): [string, InternalSchemaElement][] { + const result = Object.entries(inputElements).filter(([key, element]) => { + if (!isPopulated(element.type)) { + return false; + } + + if (element.max === 0) { + return false; + } + + // toLowerCase to handle Extension.url as well as Extension.extension.url, etc. + if (element.path.toLowerCase().endsWith('extension.url') && element.fixed) { + return false; + } + + if (EXTENSION_KEYS.has(key) && !isPopulated(element.slicing?.slices)) { + // an extension property without slices has no nested extensions + return false; + } else if (IGNORED_PROPERTIES.has(key)) { + return false; + } else if (DEFAULT_IGNORED_NON_NESTED_PROPERTIES.includes(key) && element.path.split('.').length === 2) { + return false; + } + + // Profiles can include nested elements in addition to their containing element, e.g.: + // identifier, identifier.use, identifier.system + // Skip nested elements, e.g. identifier.use, since they are handled by the containing element + if (key.includes('.')) { + return false; + } + + return true; + }); + + return result; +} diff --git a/packages/react/src/ElementsInput/ElementsInput.utils.test.ts b/packages/react/src/ElementsInput/ElementsInput.utils.test.ts new file mode 100644 index 0000000000..c25c6f9862 --- /dev/null +++ b/packages/react/src/ElementsInput/ElementsInput.utils.test.ts @@ -0,0 +1,25 @@ +import { HTTP_HL7_ORG, isPopulated, parseStructureDefinition } from '@medplum/core'; +import { buildElementsContext } from './ElementsInput.utils'; +import { USCoreStructureDefinitionList } from '@medplum/mock'; + +describe('buildElementsContext', () => { + test('deeply nested schema', () => { + const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-medicationrequest`; + const sd = USCoreStructureDefinitionList.find((sd) => sd.url === profileUrl); + if (!isPopulated(sd)) { + fail(`Expected structure definition for ${profileUrl} to be found`); + } + const schema = parseStructureDefinition(sd); + + const context = buildElementsContext({ + elements: schema.elements, + parentPath: 'MedicationRequest', + parentContext: undefined, + parentType: schema.type, + profileUrl, + }); + + expect(context.profileUrl).toEqual(sd.url); + expect(context.getModifiedNestedElement('MedicationRequest.dosageInstruction.method')).toBeDefined(); + }); +}); diff --git a/packages/react/src/ElementsInput/ElementsInput.utils.ts b/packages/react/src/ElementsInput/ElementsInput.utils.ts new file mode 100644 index 0000000000..77159dd852 --- /dev/null +++ b/packages/react/src/ElementsInput/ElementsInput.utils.ts @@ -0,0 +1,155 @@ +import { InternalSchemaElement, InternalTypeSchema, TypedValue, isPopulated } from '@medplum/core'; +import React from 'react'; + +/** + * Splits a string on the last occurrence of the delimiter + * @param str - The string to split + * @param delim - The delimiter string + * @returns An array of two strings; the first consisting of the beginning of the + * string up to the last occurrence of the delimiter. the second is the remainder of the + * string after the last occurrence of the delimiter. If the delimiter is not present + * in the string, the first element is empty and the second is the input string. + */ +function splitOnceRight(str: string, delim: string): [string, string] { + const delimIndex = str.lastIndexOf(delim); + if (delimIndex === -1) { + return ['', str]; + } + const beginning = str.substring(0, delimIndex); + const last = str.substring(delimIndex + delim.length); + return [beginning, last]; +} + +export type ElementsContextType = { + debugMode: boolean; + profileUrl: string | undefined; + /** + * Get the element definition for the specified path if it has been modified by a profile. + * @param nestedElementPath - The path of the nested element + * @returns The modified element definition if it has been modified by the active profile or undefined. If undefined, + * the element has the default definition for the given type. + */ + getModifiedNestedElement: (nestedElementPath: string) => InternalSchemaElement | undefined; + getElementByPath: (path: string) => InternalSchemaElement | undefined; + elements: Record; + elementsByPath: Record; + fixedProperties: { [key: string]: InternalSchemaElement & { fixed: TypedValue } }; +}; + +export const ElementsContext = React.createContext({ + profileUrl: undefined, + debugMode: false, + getModifiedNestedElement: () => undefined, + getElementByPath: () => undefined, + elements: Object.create(null), + elementsByPath: Object.create(null), + fixedProperties: Object.create(null), +}); +ElementsContext.displayName = 'ElementsContext'; + +export type BuildElementsContextArgs = { + elements: InternalTypeSchema['elements'] | undefined; + parentPath: string; + parentContext: ElementsContextType | undefined; + parentType: string | undefined; + profileUrl?: string; + debugMode?: boolean; +}; + +function hasFixed(element: InternalSchemaElement): element is InternalSchemaElement & { fixed: TypedValue } { + return isPopulated(element.fixed?.type) && 'value' in element.fixed; +} + +export function buildElementsContext({ + parentContext, + elements, + parentPath, + parentType, + profileUrl, + debugMode, +}: BuildElementsContextArgs): ElementsContextType { + if (debugMode) { + console.debug('Building ElementsContext', { parentPath, profileUrl, elements }); + } + const mergedElements: ElementsContextType['elements'] = mergeElementsForContext( + parentPath, + elements, + parentContext, + Boolean(debugMode) + ); + + const nestedPaths: Record = Object.create(null); + const elementsByPath: ElementsContextType['elementsByPath'] = Object.create(null); + const fixedProperties: ElementsContextType['fixedProperties'] = Object.create(null); + + const seenKeys = new Set(); + for (const [key, property] of Object.entries(mergedElements)) { + elementsByPath[parentPath + '.' + key] = property; + + if (hasFixed(property)) { + fixedProperties[key] = property; + } + + const [beginning, _last] = splitOnceRight(key, '.'); + // assume paths are hierarchically sorted, e.g. identifier comes before identifier.id + if (seenKeys.has(beginning)) { + nestedPaths[parentType + '.' + key] = property; + } + seenKeys.add(key); + } + + function getElementByPath(path: string): InternalSchemaElement | undefined { + return elementsByPath[path]; + } + + function getModifiedNestedElement(nestedElementPath: string): InternalSchemaElement | undefined { + return nestedPaths[nestedElementPath]; + } + + return { + debugMode: debugMode ?? parentContext?.debugMode ?? false, + profileUrl: profileUrl ?? parentContext?.profileUrl, + getModifiedNestedElement, + getElementByPath, + elements: mergedElements, + elementsByPath, + fixedProperties, + }; +} + +function mergeElementsForContext( + parentPath: string, + elements: BuildElementsContextArgs['elements'], + parentContext: BuildElementsContextArgs['parentContext'], + debugMode: boolean +): ElementsContextType['elements'] { + const result: ElementsContextType['elements'] = Object.create(null); + + if (parentContext) { + const parentPathPrefix = parentPath + '.'; + for (const [path, element] of Object.entries(parentContext.elementsByPath)) { + if (path.startsWith(parentPathPrefix)) { + const key = path.slice(parentPathPrefix.length); + result[key] = element; + } + } + } + + let usedNewElements = false; + if (elements) { + for (const [key, element] of Object.entries(elements)) { + if (!(key in result)) { + result[key] = element; + usedNewElements = true; + } + } + } + + // TODO if no new elements are used, the elementscontext is very likely not necessary; + // there could be an optimization where buildElementsContext returns undefined in this case + // to avoid needless contexts + if (debugMode && parentContext && !usedNewElements) { + console.debug('ElementsContext elements same as parent context'); + } + return result; +} diff --git a/packages/react/src/ExtensionInput/ExtensionInput.test.tsx b/packages/react/src/ExtensionInput/ExtensionInput.test.tsx index 610b37d8b3..0666ed51ef 100644 --- a/packages/react/src/ExtensionInput/ExtensionInput.test.tsx +++ b/packages/react/src/ExtensionInput/ExtensionInput.test.tsx @@ -28,16 +28,23 @@ describe('ExtensionInput', () => { test('Renders', async () => { await setup({ ...defaultProps, - defaultValue: { url: 'https://example.com' }, + defaultValue: { url: 'https://example.com', valueBoolean: true }, }); - expect(screen.getByTestId('extension-json-input')).toBeDefined(); + expect(screen.getByTestId('url')).toBeInTheDocument(); + expect(screen.getByTestId('url')).toHaveValue('https://example.com'); + expect(screen.getByTestId('value[x]-selector')).toBeInTheDocument(); + expect(screen.getByTestId('value[x]')).toBeInTheDocument(); + expect(screen.getByTestId('value[x]')).toBeChecked(); }); test('Renders undefined value', async () => { await setup({ ...defaultProps, }); - expect(screen.getByTestId('extension-json-input')).toBeDefined(); + expect(screen.getByTestId('url')).toBeInTheDocument(); + expect(screen.getByTestId('url')).toHaveValue(''); + expect(screen.getByTestId('value[x]')).toBeInTheDocument(); + expect(screen.getByTestId('value[x]')).toHaveValue(''); }); test('Set value', async () => { @@ -49,8 +56,8 @@ describe('ExtensionInput', () => { }); await act(async () => { - fireEvent.change(screen.getByTestId('extension-json-input'), { - target: { value: '{"url":"https://foo.com"}' }, + fireEvent.change(screen.getByTestId('url'), { + target: { value: 'https://foo.com' }, }); }); diff --git a/packages/react/src/ExtensionInput/ExtensionInput.tsx b/packages/react/src/ExtensionInput/ExtensionInput.tsx index bd6d3ca6ac..d49802a2f0 100644 --- a/packages/react/src/ExtensionInput/ExtensionInput.tsx +++ b/packages/react/src/ExtensionInput/ExtensionInput.tsx @@ -1,5 +1,4 @@ -import { JsonInput } from '@mantine/core'; -import { InternalTypeSchema, stringify, tryGetProfile, isProfileLoaded } from '@medplum/core'; +import { InternalTypeSchema, tryGetProfile, isProfileLoaded } from '@medplum/core'; import { ElementDefinitionType, Extension } from '@medplum/fhirtypes'; import { useMedplum } from '@medplum/react-hooks'; import { useEffect, useMemo, useState } from 'react'; @@ -41,25 +40,10 @@ export function ExtensionInput(props: ExtensionInputProps): JSX.Element | null { } }, [medplum, profileUrl]); - function onChange(newValue: any): void { - if (props.onChange) { - console.log('Extension', newValue); - props.onChange(newValue); - } - } - - if (!profileUrl) { - return ; - } - if (profileUrl && (loadingProfile || !isProfileLoaded(profileUrl))) { return
Loading...
; } - if (!typeSchema) { - return
StructureDefinition for {profileUrl} not found
; - } - /* From the spec: An extension SHALL have either a value (i.e. a value[x] element) or sub-extensions, but not both. @@ -75,26 +59,10 @@ export function ExtensionInput(props: ExtensionInputProps): JSX.Element | null { return ( - ); -} - -function ExtensionJsonInput(props: ExtensionInputProps): JSX.Element { - return ( - { - if (props.onChange) { - props.onChange(JSON.parse(newValue)); - } - }} + onChange={props.onChange} /> ); } diff --git a/packages/react/src/FormSection/FormSection.tsx b/packages/react/src/FormSection/FormSection.tsx index 723d91d8ad..c209d1cfe5 100644 --- a/packages/react/src/FormSection/FormSection.tsx +++ b/packages/react/src/FormSection/FormSection.tsx @@ -2,7 +2,7 @@ import { Input } from '@mantine/core'; import { OperationOutcome } from '@medplum/fhirtypes'; import { ReactNode, useContext } from 'react'; import { getErrorsForInput } from '../utils/outcomes'; -import { BackboneElementContext } from '../BackboneElementInput/BackboneElementInput.utils'; +import { ElementsContext } from '../ElementsInput/ElementsInput.utils'; export interface FormSectionProps { readonly title?: string; @@ -16,7 +16,7 @@ export interface FormSectionProps { } export function FormSection(props: FormSectionProps): JSX.Element { - const { debugMode } = useContext(BackboneElementContext); + const { debugMode } = useContext(ElementsContext); let label: React.ReactNode; if (debugMode && props.fhirPath) { diff --git a/packages/react/src/IdentifierInput/IdentifierInput.tsx b/packages/react/src/IdentifierInput/IdentifierInput.tsx index dbb3f82c24..ad2e6b8840 100644 --- a/packages/react/src/IdentifierInput/IdentifierInput.tsx +++ b/packages/react/src/IdentifierInput/IdentifierInput.tsx @@ -2,7 +2,7 @@ import { Group, TextInput } from '@mantine/core'; import { Identifier } from '@medplum/fhirtypes'; import { useContext, useMemo, useState } from 'react'; import { getErrorsForInput } from '../utils/outcomes'; -import { BackboneElementContext } from '../BackboneElementInput/BackboneElementInput.utils'; +import { ElementsContext } from '../ElementsInput/ElementsInput.utils'; import { ComplexTypeInputProps } from '../ResourcePropertyInput/ResourcePropertyInput.utils'; export type IdentifierInputProps = ComplexTypeInputProps; @@ -10,7 +10,7 @@ export type IdentifierInputProps = ComplexTypeInputProps; export function IdentifierInput(props: IdentifierInputProps): JSX.Element { const { path, outcome } = props; const [value, setValue] = useState(props.defaultValue); - const { getModifiedNestedElement } = useContext(BackboneElementContext); + const { getModifiedNestedElement } = useContext(ElementsContext); const [systemElement, valueElement] = useMemo( () => ['system', 'value'].map((field) => getModifiedNestedElement(path + '.' + field)), diff --git a/packages/react/src/PatientSummary/Allergies.tsx b/packages/react/src/PatientSummary/Allergies.tsx index 0e409f1284..78384e15b1 100644 --- a/packages/react/src/PatientSummary/Allergies.tsx +++ b/packages/react/src/PatientSummary/Allergies.tsx @@ -74,9 +74,11 @@ export function Allergies(props: AllergiesProps): JSX.Element { setCode(allergy)} + outcome={undefined} /> diff --git a/packages/react/src/PatientSummary/Medications.tsx b/packages/react/src/PatientSummary/Medications.tsx index 1b2c653563..6a161536da 100644 --- a/packages/react/src/PatientSummary/Medications.tsx +++ b/packages/react/src/PatientSummary/Medications.tsx @@ -80,9 +80,11 @@ export function Medications(props: MedicationsProps): JSX.Element { setCode(request)} + outcome={undefined} /> diff --git a/packages/react/src/PlanDefinitionBuilder/PlanDefinitionBuilder.tsx b/packages/react/src/PlanDefinitionBuilder/PlanDefinitionBuilder.tsx index 168eb337b7..152316f3b5 100644 --- a/packages/react/src/PlanDefinitionBuilder/PlanDefinitionBuilder.tsx +++ b/packages/react/src/PlanDefinitionBuilder/PlanDefinitionBuilder.tsx @@ -397,6 +397,7 @@ function ActionTimingInput(props: ActionTimingInputProps): JSX.Element { { diff --git a/packages/react/src/QuestionnaireBuilder/QuestionnaireBuilder.tsx b/packages/react/src/QuestionnaireBuilder/QuestionnaireBuilder.tsx index 329dc79d8d..6fd195590a 100644 --- a/packages/react/src/QuestionnaireBuilder/QuestionnaireBuilder.tsx +++ b/packages/react/src/QuestionnaireBuilder/QuestionnaireBuilder.tsx @@ -484,6 +484,7 @@ function AnswerOptionsInput(props: AnswerOptionsInputProps): JSX.Element { = { +const defaultProps: Pick = { name: 'myProp', + path: 'Fake.myProp', property, outcome: undefined, }; @@ -283,9 +284,8 @@ describe('ResourceArrayInput', () => { { url: 'greenVariety', valueString: 'Pistachio' }, ]; - expect(onChange.mock.calls.length).toBe(1); - expect(onChange.mock.calls[0][0].length).toBe(expectedValue.length); - expect(onChange).toHaveBeenCalledWith(expect.arrayContaining(expectedValue)); + expect(onChange).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenLastCalledWith(expectedValue); }); test('Hiding non-sliced values', async () => { diff --git a/packages/react/src/ResourceArrayInput/ResourceArrayInput.tsx b/packages/react/src/ResourceArrayInput/ResourceArrayInput.tsx index 56a4302ab7..06dc7dd526 100644 --- a/packages/react/src/ResourceArrayInput/ResourceArrayInput.tsx +++ b/packages/react/src/ResourceArrayInput/ResourceArrayInput.tsx @@ -1,29 +1,22 @@ -import { ActionIcon, Button, Group, Stack } from '@mantine/core'; -import { - InternalSchemaElement, - getPathDisplayName, - getPropertyDisplayName, - isEmpty, - tryGetProfile, -} from '@medplum/core'; +import { Group, Stack } from '@mantine/core'; +import { InternalSchemaElement, getPathDisplayName } from '@medplum/core'; import { OperationOutcome } from '@medplum/fhirtypes'; import { useMedplum } from '@medplum/react-hooks'; -import { IconCircleMinus, IconCirclePlus } from '@tabler/icons-react'; -import { MouseEvent, useEffect, useState } from 'react'; -import { ElementsInput } from '../ElementsInput/ElementsInput'; -import { FormSection } from '../FormSection/FormSection'; -import { ElementDefinitionTypeInput, ResourcePropertyInput } from '../ResourcePropertyInput/ResourcePropertyInput'; +import { MouseEvent, useContext, useEffect, useState } from 'react'; +import { ElementsContext } from '../ElementsInput/ElementsInput.utils'; +import { ResourcePropertyInput } from '../ResourcePropertyInput/ResourcePropertyInput'; +import { SliceInput } from '../SliceInput/SliceInput'; +import { SupportedSliceDefinition } from '../SliceInput/SliceInput.utils'; +import { ArrayAddButton } from '../buttons/ArrayAddButton'; +import { ArrayRemoveButton } from '../buttons/ArrayRemoveButton'; import { killEvent } from '../utils/dom'; import classes from './ResourceArrayInput.module.css'; -import { - SupportedSliceDefinition, - assignValuesIntoSlices, - isSupportedSliceDefinition, -} from './ResourceArrayInput.utils'; +import { assignValuesIntoSlices, prepareSlices } from './ResourceArrayInput.utils'; export interface ResourceArrayInputProps { readonly property: InternalSchemaElement; readonly name: string; + readonly path: string; readonly defaultValue?: any[]; readonly indent?: boolean; readonly outcome: OperationOutcome | undefined; @@ -38,65 +31,26 @@ export function ResourceArrayInput(props: ResourceArrayInputProps): JSX.Element const [slices, setSlices] = useState([]); // props.defaultValue should NOT be used after this; prefer the defaultValue state const [defaultValue] = useState(() => (Array.isArray(props.defaultValue) ? props.defaultValue : [])); - const [slicedValues, setSlicedValues] = useState([[]]); + const [slicedValues, setSlicedValues] = useState(() => [defaultValue]); + const ctx = useContext(ElementsContext); const propertyTypeCode = property.type[0]?.code; useEffect(() => { - if (!property.slicing) { - const emptySlices: SupportedSliceDefinition[] = []; - setSlices(emptySlices); - const results = assignValuesIntoSlices(defaultValue, emptySlices, property.slicing); - setSlicedValues(results); - setLoading(false); - return; - } - - const supportedSlices: SupportedSliceDefinition[] = []; - const profileUrls: (string | undefined)[] = []; - const promises: Promise[] = []; - for (const slice of property.slicing.slices) { - if (!isSupportedSliceDefinition(slice)) { - continue; - } - - const sliceType = slice.type[0]; - let profileUrl: string | undefined; - if (isEmpty(slice.elements)) { - if (sliceType.profile) { - profileUrl = sliceType.profile[0]; - } - } - - // important to keep these three arrays the same length; - supportedSlices.push(slice); - profileUrls.push(profileUrl); - if (profileUrl) { - promises.push(medplum.requestProfileSchema(profileUrl)); - } else { - promises.push(Promise.resolve()); - } - } - - Promise.all(promises) - .then(() => { - for (let i = 0; i < supportedSlices.length; i++) { - const slice = supportedSlices[i]; - const profileUrl = profileUrls[i]; - if (profileUrl) { - const typeSchema = tryGetProfile(profileUrl); - slice.typeSchema = typeSchema; - } - } - setSlices(supportedSlices); - const results = assignValuesIntoSlices(defaultValue, supportedSlices, property.slicing); - setSlicedValues(results); + prepareSlices({ + medplum, + property, + }) + .then((slices) => { + setSlices(slices); + const slicedValues = assignValuesIntoSlices(defaultValue, slices, property.slicing, ctx.profileUrl); + setSlicedValues(slicedValues); setLoading(false); }) .catch((reason) => { console.error(reason); setLoading(false); }); - }, [medplum, property.slicing, propertyTypeCode, defaultValue]); + }, [medplum, property, defaultValue, ctx.profileUrl, setSlicedValues, props.path]); function setValuesWrapper(newValues: any[], sliceIndex: number): void { const newSlicedValues = [...slicedValues]; @@ -127,6 +81,7 @@ export function ResourceArrayInput(props: ResourceArrayInputProps): JSX.Element { @@ -145,6 +100,7 @@ export function ResourceArrayInput(props: ResourceArrayInputProps): JSX.Element arrayElement={true} property={props.property} name={props.name + '.' + valueIndex} + path={props.path} defaultValue={value} onChange={(newValue: any) => { const newNonSliceValues = [...nonSliceValues]; @@ -155,7 +111,7 @@ export function ResourceArrayInput(props: ResourceArrayInputProps): JSX.Element outcome={props.outcome} /> - { @@ -169,7 +125,7 @@ export function ResourceArrayInput(props: ResourceArrayInputProps): JSX.Element ))} {showNonSliceValues && slicedValues.flat().length < property.max && ( - { killEvent(e); @@ -184,152 +140,3 @@ export function ResourceArrayInput(props: ResourceArrayInputProps): JSX.Element ); } - -interface SliceInputProps { - readonly slice: SupportedSliceDefinition; - readonly property: InternalSchemaElement; - readonly defaultValue: any[]; - readonly onChange: (newValue: any[]) => void; - readonly outcome?: OperationOutcome; - readonly testId?: string; -} - -function SliceInput(props: SliceInputProps): JSX.Element | null { - const { slice, property } = props; - const [values, setValues] = useState(() => { - return props.defaultValue.map((v) => v ?? {}); - }); - - function setValuesWrapper(newValues: any[]): void { - setValues(newValues); - if (props.onChange) { - props.onChange(newValues); - } - } - - const required = slice.min > 0; - - // this is a bit of a hack targeted at nested extensions; indentation would ideally be controlled elsewhere - // e.g. USCorePatientProfile -> USCoreEthnicityExtension -> {ombCategory, detailed, text} - const indentedStack = isEmpty(slice.elements); - const propertyDisplayName = getPropertyDisplayName(slice.name); - return ( - - - {values.map((value, valueIndex) => { - return ( - -
- - {!isEmpty(slice.elements) ? ( - { - const newValues = [...values]; - newValues[valueIndex] = newValue; - setValuesWrapper(newValues); - }} - testId={props.testId && `${props.testId}-elements-${valueIndex}`} - /> - ) : ( - { - const newValues = [...values]; - newValues[valueIndex] = newValue; - setValuesWrapper(newValues); - }} - outcome={undefined} - min={slice.min} - max={slice.max} - binding={undefined} - path={slice.path} - /> - )} - -
- {values.length > slice.min && ( - { - killEvent(e); - const newValues = [...values]; - newValues.splice(valueIndex, 1); - setValuesWrapper(newValues); - }} - /> - )} -
- ); - })} - {values.length < slice.max && ( - - { - killEvent(e); - const newValues = [...values, undefined]; - setValuesWrapper(newValues); - }} - testId={props.testId && `${props.testId}-add`} - /> - - )} -
-
- ); -} - -interface ButtonProps { - readonly propertyDisplayName?: string; - readonly onClick: React.MouseEventHandler; - readonly testId?: string; -} - -function AddButton({ propertyDisplayName, onClick, testId }: ButtonProps): JSX.Element { - const text = propertyDisplayName ? `Add ${propertyDisplayName}` : 'Add'; - - return propertyDisplayName ? ( - - ) : ( - - - - ); -} - -function RemoveButton({ propertyDisplayName, onClick, testId }: ButtonProps): JSX.Element { - return ( - - - - ); -} diff --git a/packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.test.ts b/packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.test.ts new file mode 100644 index 0000000000..568e1be7e4 --- /dev/null +++ b/packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.test.ts @@ -0,0 +1,221 @@ +import { HTTP_HL7_ORG, InternalTypeSchema, isProfileLoaded, loadDataType, tryGetProfile } from '@medplum/core'; +import { assignValuesIntoSlices, prepareSlices } from './ResourceArrayInput.utils'; +import { MockClient, USCoreStructureDefinitionList } from '@medplum/mock'; +import { StructureDefinition } from '@medplum/fhirtypes'; +import { buildElementsContext } from '../ElementsInput/ElementsInput.utils'; + +const medplum = new MockClient(); + +describe('assignValuesIntoSlices', () => { + describe('US Core Patient', () => { + const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-patient`; + const profilesToLoad = [ + profileUrl, + `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-race`, + `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-ethnicity`, + `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-birthsex`, + `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-genderIdentity`, + ]; + const patientSD = USCoreStructureDefinitionList.find((sd) => sd.url === profileUrl) as StructureDefinition; + let patientSchema: InternalTypeSchema; + + beforeAll(() => { + expect(patientSD).toBeDefined(); + for (const url of profilesToLoad) { + const sd = USCoreStructureDefinitionList.find((sd) => sd.url === url); + if (!sd) { + fail(`could not find structure definition for ${url}`); + } + loadDataType(sd, sd.url); + } + expect(isProfileLoaded(profileUrl)).toBe(true); + patientSchema = tryGetProfile(profileUrl) as InternalTypeSchema; + expect(patientSchema).toBeDefined(); + }); + + test('Patient.extension (race, ethnicity, birthsex, genderIdentity)', async () => { + const patient = { + id: 'homer-simpson', + extension: [ + { + extension: [ + { + url: 'text', + valueString: 'Some text', + }, + ], + url: 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-race', + }, + { + extension: [ + { + url: 'text', + valueString: 'Some text', + }, + ], + url: 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity', + }, + { + url: 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex', + valueCode: 'F', + }, + { + url: 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity', + valueCodeableConcept: { + coding: [ + { + system: 'http://terminology.hl7.org/CodeSystem/v3-NullFlavor', + code: 'ASKU', + display: 'asked but unknown', + }, + ], + text: 'asked but unknown', + }, + }, + ], + }; + + const property = patientSchema.elements['extension']; + + const elementsContext = buildElementsContext({ + parentContext: undefined, + elements: patientSchema.elements, + parentPath: 'Patient', + parentType: 'Patient', + profileUrl, + }); + + const slices = await prepareSlices({ + medplum, + property, + }); + + expect(slices.length).toBe(4); + const slicedValues = assignValuesIntoSlices( + patient.extension, + slices, + property.slicing, + elementsContext.profileUrl + ); + expect(slicedValues.map((sliceValues) => sliceValues.length)).toEqual([1, 1, 1, 1, 0]); + }); + }); + + describe('US Core Blood Pressure', () => { + const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-blood-pressure`; + const bpSD = USCoreStructureDefinitionList.find((sd) => sd.url === profileUrl) as StructureDefinition; + let bpSchema: InternalTypeSchema; + + beforeAll(() => { + expect(bpSD).toBeDefined(); + loadDataType(bpSD, bpSD.url); + expect(isProfileLoaded(profileUrl)).toBe(true); + bpSchema = tryGetProfile(profileUrl) as InternalTypeSchema; + expect(bpSchema).toBeDefined(); + }); + + test('Observation.category (VSCat)', async () => { + const resource = { + category: [ + { + coding: [ + { + system: 'http://terminology.hl7.org/CodeSystem/observation-category', + code: 'vital-signs', + }, + ], + }, + ], + }; + + const property = bpSchema.elements['category']; + + const elementsContext = buildElementsContext({ + parentContext: undefined, + elements: bpSchema.elements, + parentPath: 'Observation', + parentType: 'Observation', + profileUrl, + }); + + const slices = await prepareSlices({ + medplum, + property, + }); + + expect(slices.length).toBe(1); + const slicedValues = assignValuesIntoSlices( + resource.category, + slices, + property.slicing, + elementsContext.profileUrl + ); + expect(slicedValues.map((sliceValues) => sliceValues.length)).toEqual([1, 0]); + }); + + test('Observation.component (systolic and diastolic)', async () => { + const resource = { + component: [ + { + code: { + coding: [ + { + system: 'http://loinc.org', + code: '8480-6', + }, + ], + text: 'Systolic blood pressure', + }, + valueQuantity: { + value: 109, + unit: 'mmHg', + system: 'http://unitsofmeasure.org', + code: 'mm[Hg]', + }, + }, + { + code: { + coding: [ + { + system: 'http://loinc.org', + code: '8462-4', + }, + ], + text: 'Diastolic blood pressure', + }, + valueQuantity: { + value: 49, + unit: 'mmHg', + system: 'http://unitsofmeasure.org', + code: 'mm[Hg]', + }, + }, + ], + }; + + const property = bpSchema.elements['component']; + + const elementsContext = buildElementsContext({ + parentContext: undefined, + elements: bpSchema.elements, + parentPath: 'Observation', + parentType: 'Observation', + profileUrl, + }); + + const slices = await prepareSlices({ + medplum, + property, + }); + const slicedValues = assignValuesIntoSlices( + resource.component, + slices, + property.slicing, + elementsContext.profileUrl + ); + + expect(slices.length).toBe(2); + expect(slicedValues.map((sliceValues) => sliceValues.length)).toEqual([1, 1, 0]); + }); + }); +}); diff --git a/packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.ts b/packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.ts index 2b3ddde527..4026bcf38b 100644 --- a/packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.ts +++ b/packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.ts @@ -1,41 +1,38 @@ import { - InternalTypeSchema, - SliceDefinition, + InternalSchemaElement, + MedplumClient, SliceDiscriminator, SlicingRules, TypedValue, arrayify, - getElementDefinitionFromElements, - getTypedPropertyValueWithSchema, + getNestedProperty, isPopulated, matchDiscriminant, + tryGetProfile, } from '@medplum/core'; +import { SupportedSliceDefinition, isSupportedSliceDefinition } from '../SliceInput/SliceInput.utils'; function isDiscriminatorComponentMatch( typedValue: TypedValue, discriminator: SliceDiscriminator, - slice: SupportedSliceDefinition + slice: SupportedSliceDefinition, + profileUrl: string | undefined ): boolean { - for (const elementList of [slice.elements, slice.typeSchema?.elements]) { - let nestedProp: TypedValue | TypedValue[] | undefined; - if (isPopulated(elementList)) { - const ed = getElementDefinitionFromElements(elementList, discriminator.path); - if (ed) { - nestedProp = getTypedPropertyValueWithSchema(typedValue.value, discriminator.path, ed); - } - } + const nestedProp = getNestedProperty(typedValue, discriminator.path, { profileUrl }); - if (nestedProp) { - return arrayify(nestedProp)?.some((v: any) => matchDiscriminant(v, discriminator, slice, elementList)) ?? false; - } + if (nestedProp) { + const elementList = slice.typeSchema?.elements ?? slice.elements; + return arrayify(nestedProp)?.some((v: any) => matchDiscriminant(v, discriminator, slice, elementList)) ?? false; } + console.assert(false, 'getNestedProperty[%s] in isDiscriminatorComponentMatch missed', discriminator.path); return false; } function getValueSliceName( value: any, slices: SupportedSliceDefinition[], - discriminators: SliceDiscriminator[] + discriminators: SliceDiscriminator[], + profileUrl?: string ): string | undefined { if (!value) { return undefined; @@ -46,7 +43,11 @@ function getValueSliceName( value, type: slice.typeSchema?.name ?? slice.type[0].code, }; - if (discriminators.every((d) => isDiscriminatorComponentMatch(typedValue, d, slice))) { + if ( + discriminators.every((d) => + isDiscriminatorComponentMatch(typedValue, d, slice, slice.typeSchema?.url ?? profileUrl) + ) + ) { return slice.name; } } @@ -56,9 +57,10 @@ function getValueSliceName( export function assignValuesIntoSlices( values: any[], slices: SupportedSliceDefinition[], - slicing: SlicingRules | undefined + slicing: SlicingRules | undefined, + profileUrl: string | undefined ): any[][] { - if (!slicing || slicing.slices.length === 0) { + if (!isPopulated(slicing?.slices)) { return [values]; } @@ -69,29 +71,83 @@ export function assignValuesIntoSlices( } for (const value of values) { - const sliceName = getValueSliceName(value, slices, slicing.discriminator); + const sliceName = getValueSliceName(value, slices, slicing.discriminator, profileUrl); + let sliceIndex = sliceName ? slices.findIndex((slice) => slice.name === sliceName) : -1; + // -1 can come from either findIndex or the ternary else if (sliceIndex === -1) { - sliceIndex = slices.length; // values not matched to a slice go in the last entry for non-slice + sliceIndex = slices.length; } slicedValues[sliceIndex].push(value); } - // for slices without existing values, add a placeholder empty value - for (let i = 0; i < slices.length; i++) { - if (slicedValues[i].length === 0) { - slicedValues[i].push(undefined); + // add placeholder empty values + for (let sliceIndex = 0; sliceIndex < slices.length; sliceIndex++) { + const slice = slices[sliceIndex]; + const sliceValues = slicedValues[sliceIndex]; + + if (sliceValues.length < slice.min) { + while (sliceValues.length < slice.min) { + sliceValues.push(undefined); + } + } else if (sliceValues.length === 0) { + sliceValues.push(undefined); } } return slicedValues; } -export type SupportedSliceDefinition = SliceDefinition & { - type: NonNullable; - typeSchema?: InternalTypeSchema; -}; +export async function prepareSlices({ + medplum, + property, +}: { + medplum: MedplumClient; + property: InternalSchemaElement; +}): Promise { + return new Promise((resolve, reject) => { + if (!property.slicing) { + resolve([]); + return; + } + + const supportedSlices: SupportedSliceDefinition[] = []; + const profileUrls: (string | undefined)[] = []; + const promises: Promise[] = []; + for (const slice of property.slicing.slices) { + if (!isSupportedSliceDefinition(slice)) { + console.debug('Unsupported slice definition', slice); + continue; + } + + let profileUrl: string | undefined; + // If elements are not defined for the slice, look for a profile + if (!isPopulated(slice.elements)) { + profileUrl = slice.type[0]?.profile?.[0]; + } + + // important to keep these three arrays the same length; + supportedSlices.push(slice); + profileUrls.push(profileUrl); + if (profileUrl) { + promises.push(medplum.requestProfileSchema(profileUrl)); + } else { + promises.push(Promise.resolve()); + } + } -export function isSupportedSliceDefinition(slice: SliceDefinition): slice is SupportedSliceDefinition { - return slice.type !== undefined && slice.type.length > 0; + Promise.all(promises) + .then(() => { + for (let i = 0; i < supportedSlices.length; i++) { + const slice = supportedSlices[i]; + const profileUrl = profileUrls[i]; + if (profileUrl) { + const typeSchema = tryGetProfile(profileUrl); + slice.typeSchema = typeSchema; + } + } + resolve(supportedSlices); + }) + .catch(reject); + }); } diff --git a/packages/react/src/ResourceForm/ResourceForm.tsx b/packages/react/src/ResourceForm/ResourceForm.tsx index f70f86d592..95e2811d9c 100644 --- a/packages/react/src/ResourceForm/ResourceForm.tsx +++ b/packages/react/src/ResourceForm/ResourceForm.tsx @@ -73,6 +73,7 @@ export function ResourceForm(props: ResourceFormProps): JSX.Element {
( ( ( ( ( /> ); + +const defaultValue: Extension[] = [ + { + url: 'https://example.com', + valueString: 'foo', + }, +]; +const property: InternalSchemaElement = { + path: 'extension', + description: '', + min: 0, + max: 10, + type: [{ code: 'Extension' }], + isArray: false, +}; +export const ExtensionInput = (): JSX.Element => { + const onChange = useCallback((newValue: any): void => { + console.log('onChange', newValue); + }, []); + + return ( + + + + ); +}; diff --git a/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.test.tsx b/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.test.tsx index 5fae94ffaa..37243786fb 100644 --- a/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.test.tsx +++ b/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.test.tsx @@ -30,7 +30,8 @@ const baseProperty: Omit = { path: '', }; -const defaultProps: Pick = { +const defaultProps: Pick = { + path: 'Resource.path', defaultValue: undefined, outcome: undefined, onChange: undefined, @@ -283,14 +284,15 @@ describe('ResourcePropertyInput', () => { onChange, }); - const el = screen.getByDisplayValue('{"url":"https://example.com","valueString":"foo"}'); + expect(screen.getByDisplayValue('https://example.com')).toBeInTheDocument(); + const el = screen.getByDisplayValue('foo'); expect(el).toBeInTheDocument(); await act(async () => { - fireEvent.change(el, { target: { value: '{"url":"https://example.com","valueString":"bar"}' } }); + fireEvent.change(el, { target: { value: 'new value' } }); }); - expect(onChange).toHaveBeenCalledWith([{ url: 'https://example.com', valueString: 'bar' }]); + expect(onChange).toHaveBeenCalledWith([{ url: 'https://example.com', valueString: 'new value' }]); }); test('HumanName property', async () => { @@ -622,6 +624,7 @@ describe('ResourcePropertyInput', () => { await setup({ ...defaultProps, name: 'secret', + path: property.path, property, onChange, }); diff --git a/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.tsx b/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.tsx index a1338c976a..520ebeb0ac 100644 --- a/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.tsx +++ b/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.tsx @@ -31,6 +31,8 @@ import { ComplexTypeInputProps } from './ResourcePropertyInput.utils'; export interface ResourcePropertyInputProps { readonly property: InternalSchemaElement; readonly name: string; + /** The path identifies the element and is expressed as a "."-separated list of ancestor elements, beginning with the name of the resource or extension. */ + readonly path: string; readonly defaultPropertyType?: string | undefined; readonly defaultValue: any; readonly arrayElement?: boolean | undefined; @@ -58,6 +60,7 @@ export function ResourcePropertyInput(props: ResourcePropertyInputProps): JSX.El ); } @@ -109,6 +112,7 @@ export function ElementDefinitionInputSelector(props: ElementDefinitionSelectorP { setSelectedType( propertyTypes.find( @@ -141,17 +145,13 @@ export function ElementDefinitionInputSelector(props: ElementDefinitionSelectorP } // Avoiding optional props on lower-level components like to make it more difficult to misuse -export type ElementDefinitionTypeInputProps = { - readonly name: ResourcePropertyInputProps['name']; - readonly path: string; - readonly defaultValue: ResourcePropertyInputProps['defaultValue']; - readonly onChange: ResourcePropertyInputProps['onChange']; - readonly outcome: ResourcePropertyInputProps['outcome']; +export interface ElementDefinitionTypeInputProps + extends Pick { readonly elementDefinitionType: ElementDefinitionType; readonly min: number; readonly max: number; readonly binding: ElementDefinitionBinding | undefined; -}; +} export function ElementDefinitionTypeInput(props: ElementDefinitionTypeInputProps): JSX.Element { const { name, defaultValue, onChange, outcome, binding, path } = props; @@ -326,6 +326,7 @@ export function ElementDefinitionTypeInput(props: ElementDefinitionTypeInputProp return ( { name: string; + /** The path identifies the element and is expressed as a "."-separated list of ancestor elements, beginning with the name of the resource or extension. */ path: string; defaultValue?: ValueType; onChange: ((value: ValueType, propName?: string) => void) | undefined; diff --git a/packages/react/src/SliceInput/SliceInput.tsx b/packages/react/src/SliceInput/SliceInput.tsx new file mode 100644 index 0000000000..eaa149b278 --- /dev/null +++ b/packages/react/src/SliceInput/SliceInput.tsx @@ -0,0 +1,130 @@ +import { Group, Stack } from '@mantine/core'; +import { InternalSchemaElement, getPropertyDisplayName, isEmpty, isPopulated } from '@medplum/core'; +import { OperationOutcome } from '@medplum/fhirtypes'; +import { useContext, useMemo, useState } from 'react'; +import { ElementsContext, ElementsContextType, buildElementsContext } from '../ElementsInput/ElementsInput.utils'; +import { FormSection } from '../FormSection/FormSection'; +import { ElementDefinitionTypeInput } from '../ResourcePropertyInput/ResourcePropertyInput'; +import { ArrayAddButton } from '../buttons/ArrayAddButton'; +import { ArrayRemoveButton } from '../buttons/ArrayRemoveButton'; +import { killEvent } from '../utils/dom'; +import classes from '../ResourceArrayInput/ResourceArrayInput.module.css'; +import { SupportedSliceDefinition } from './SliceInput.utils'; + +export interface SliceInputProps { + readonly path: string; + readonly slice: SupportedSliceDefinition; + readonly property: InternalSchemaElement; + readonly defaultValue: any[]; + readonly onChange: (newValue: any[]) => void; + readonly outcome?: OperationOutcome; + readonly testId?: string; +} + +function maybeWrapWithContext(contextValue: ElementsContextType | undefined, contents: JSX.Element): JSX.Element { + if (contextValue) { + return {contents}; + } + + return contents; +} + +export function SliceInput(props: SliceInputProps): JSX.Element | null { + const { slice, property } = props; + const [values, setValues] = useState(() => { + return props.defaultValue.map((v) => v ?? {}); + }); + + const sliceType = slice.typeSchema?.type ?? slice.type[0].code; + const sliceElements = slice.typeSchema?.elements ?? slice.elements; + + const parentElementsContextValue = useContext(ElementsContext); + + const contextValue = useMemo(() => { + if (isPopulated(sliceElements)) { + return buildElementsContext({ + parentContext: parentElementsContextValue, + elements: sliceElements, + parentPath: props.path, + parentType: sliceType, + }); + } + console.assert(false, 'Expected sliceElements to always be populated', props.path); + return undefined; + }, [parentElementsContextValue, props.path, sliceElements, sliceType]); + + function setValuesWrapper(newValues: any[]): void { + setValues(newValues); + if (props.onChange) { + props.onChange(newValues); + } + } + + const required = slice.min > 0; + + // this is a bit of a hack targeted at nested extensions; indentation would ideally be controlled elsewhere + // e.g. USCorePatientProfile -> USCoreEthnicityExtension -> {ombCategory, detailed, text} + const indentedStack = isEmpty(slice.elements); + const propertyDisplayName = getPropertyDisplayName(slice.name); + return maybeWrapWithContext( + contextValue, + + + {values.map((value, valueIndex) => { + return ( + +
+ { + const newValues = [...values]; + newValues[valueIndex] = newValue; + setValuesWrapper(newValues); + }} + outcome={props.outcome} + min={slice.min} + max={slice.max} + binding={slice.binding} + path={props.path} + /> +
+ {values.length > slice.min && ( + { + killEvent(e); + const newValues = [...values]; + newValues.splice(valueIndex, 1); + setValuesWrapper(newValues); + }} + /> + )} +
+ ); + })} + {values.length < slice.max && ( + + { + killEvent(e); + const newValues = [...values, undefined]; + setValuesWrapper(newValues); + }} + testId={props.testId && `${props.testId}-add`} + /> + + )} +
+
+ ); +} diff --git a/packages/react/src/SliceInput/SliceInput.utils.ts b/packages/react/src/SliceInput/SliceInput.utils.ts new file mode 100644 index 0000000000..beedaa124c --- /dev/null +++ b/packages/react/src/SliceInput/SliceInput.utils.ts @@ -0,0 +1,10 @@ +import { SliceDefinition, InternalTypeSchema } from '@medplum/core'; + +export type SupportedSliceDefinition = SliceDefinition & { + type: NonNullable; + typeSchema?: InternalTypeSchema; +}; + +export function isSupportedSliceDefinition(slice: SliceDefinition): slice is SupportedSliceDefinition { + return slice.type !== undefined && slice.type.length > 0; +} diff --git a/packages/react/src/buttons/ArrayAddButton.tsx b/packages/react/src/buttons/ArrayAddButton.tsx new file mode 100644 index 0000000000..254818d9b6 --- /dev/null +++ b/packages/react/src/buttons/ArrayAddButton.tsx @@ -0,0 +1,30 @@ +import { Button, ActionIcon } from '@mantine/core'; +import { IconCirclePlus } from '@tabler/icons-react'; + +export interface ArrayAddButtonProps { + readonly propertyDisplayName?: string; + readonly onClick: React.MouseEventHandler; + readonly testId?: string; +} + +export function ArrayAddButton({ propertyDisplayName, onClick, testId }: ArrayAddButtonProps): JSX.Element { + const text = propertyDisplayName ? `Add ${propertyDisplayName}` : 'Add'; + + return propertyDisplayName ? ( + + ) : ( + + + + ); +} diff --git a/packages/react/src/buttons/ArrayRemoveButton.tsx b/packages/react/src/buttons/ArrayRemoveButton.tsx new file mode 100644 index 0000000000..bc78478711 --- /dev/null +++ b/packages/react/src/buttons/ArrayRemoveButton.tsx @@ -0,0 +1,22 @@ +import { ActionIcon } from '@mantine/core'; +import { IconCircleMinus } from '@tabler/icons-react'; + +export interface ArrayRemoveButtonProps { + readonly propertyDisplayName?: string; + readonly onClick: React.MouseEventHandler; + readonly testId?: string; +} + +export function ArrayRemoveButton({ propertyDisplayName, onClick, testId }: ArrayRemoveButtonProps): JSX.Element { + return ( + + + + ); +} From f807a639c4a8a02ef48615b0a355ec70c2f9927d Mon Sep 17 00:00:00 2001 From: ksmith94 <102421938+ksmith94@users.noreply.github.com> Date: Mon, 5 Feb 2024 07:01:18 -0500 Subject: [PATCH 08/81] Document how to switch to super admin project in projects (#3873) * Document how to switch to super admin project in projects * Add link and update to profile selector --- .../docs/docs/access/project-switcher.png | Bin 0 -> 237983 bytes packages/docs/docs/access/projects.md | 19 ++++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 packages/docs/docs/access/project-switcher.png diff --git a/packages/docs/docs/access/project-switcher.png b/packages/docs/docs/access/project-switcher.png new file mode 100644 index 0000000000000000000000000000000000000000..fab8007e19cba88d10feb2b600f9420fcf9b3a80 GIT binary patch literal 237983 zcmdqJWn5JI+Axd=QVK|_NSA<=AYF=~4Y?h!}wKFuz8o>I%>)?JwM+~!gtA+hRZj-nyq zy6azEOwJ?pg86Zj%@ca@*Jy3|iWIb${1k6>RD%7D3EC&Gs3Uk0WNNz%zm~!$wBO~9 z(^cgs3euD2+>RI(6o*`+LrX`N=f+Xm7OU{$DYCw|ITq46d&s4KF;!tTyH8F3E5<{9 zfY|4S+jx~Ka&gOss`PG(9G|g*Lt1@?eA1V(=(^Stgl` z_9Q#z)*qc`C1Z-G`#jpOwHbi1{PI-V;e?f|D(1Nf#Vqz6f;Y6oMw|5$FHltpx6>ZT zLv5%tsx-{XzH+lw@flxglW(sIvU%TT!71{co}xo|wP$N9Vm`*To8dh#vmf||aZsd^ zs;OcsZwx1bys4e_8siJE{e#)h5%ZsCDag7P;+1D<7|~1$oVUmcRa;eZ8n4;P-*@R8 zG<-6cVzN!eeU|GmRjMKp(`r!3<`#~OPwk3gwfDT4KZlv_QIoR{BT=JZFdg{xF!3B& zRGs~$Pv6N5QizJ%1Zv)CS0NsUsi zUiKk#CiN;~cJ5SopKqYl+{n>mJd#*%YNjNwuenJVs;+1J=37vgLYGyS_*N?^L3&B? z(tClJ!Wi;&GjcBh9M9&G3-^c()8x0G-!76YVKoSc0gSvyZd?lNsD^`3lIckWIXS<5 zMb>fZk5;~LJYDcY*~QI)+_N9Y)Geam`iPl+np3U(gVVYLr*D5eYx?Ct?v(5_IZlm> zlKegw2j8*K62U0L3bdf@yu2OQ`m0!L1_Dm^OfD#hOe7m%XW8n7j|=WOLmja zHFP__#$JJeLUM5TH4Hn9!wX)O@Z2}c`(QZ-?-os#H4+|rwXCU=%c7sE?_4Zcj4kFg zax9@OaRFs7Umg-3x;@lWB0luhiN#5~Hp)rEDcR|0NUUVHIB7_9$ZYxY?$|PDIbsm< zj_&af>CVTu9Dg}p9Ud))>dvMM+!-*C(oZTf%WKfseml>5>9$mU)nkkCpmF1Ip$7>p z_j~TflqZ*iDr(J34NJl1hDZ&?`+;wKlK1Yfxv^_^8AOyPl~x-$z!2V9dc0>fB{VlR zuU@*Zir$LEHu8Goy$%}f8_zY4c&RS^+$G)h*hyKrzBD<|wd=ZTw^OtX`Jq~Xk~MIC zJEFDz^~1}f+fy=X=UmR&o_ln`MRHN%f`s(7vocH**#1dmWPW z>MNtfyXrf!xv+H3K1c@2u<)EzMnlX&Kk6rVHS`BjxhLs*jJFaA4JGXrn#Hq&XSFFU zSp8JLN?(zBC-zQt^t#{eO$_}_Ez_@CiEJ+FGO3n5>vZ09S*Z)%RQKQ3(taX zpz*V*ov1tSbrbn&_)GW;1%?G$^{BNYgWeJu}S9C%`*EHMPUC zH~7=`Z27OD!M~(mFD0{bjBx z2Kyi{SIhHz2F#!7o0LqrChjn`(GlOl+yPk-?1czPUQIXOT1=e=FW&{->nM_| z(}S;=jy8Pt|2eLK>h`l==wa^>>1n$QteO{YXwDqkSX&f0|mmY1Xp)F4u++Dvr>FwzIaeY4E=&<*k!nwJ?j~w5!7Xk|Bb&>zL_G_I~TNjJQcxTkBVQS!vbX@92CIFG#aC zK}`9!ys?MN``UNj)9^(09#A9+HoJbYz}d?|S;@X)J24Zzi2P}JQ>(*1#Igz~cUqi?xOn=6nC9#4~Jb81L&mD#xkRkItv9E>lL5~+In zB0r$ds43$@6PBKZ%F2?ARx@+7<+MH!Ci!VZf>tJ3URAP)KsbHyEU2NMk+(LS9@%O3 z+Jr>oz8|PhsMf2MER%+~5y=CYf^5$-()a^DMT))Yoku!4B*MToB?unl_oYQ|@lRpNYk=!s;zjFupG_>=uw|Db=4Drffqt*p(P`PWFd6JN@@E`x2 zx?{k%NkT$8>1br?WvZjCU0}*YkQ}{Bgx{u z=QLZV zp(3hm;jwRRspz-|Vtpep`)B%A?{_TzQOknTrs{?<`_~7L$tdZ$)Si%>B0bqf?3J+P z^ViLj`2UMBzye&X^n82I{<|^OWYm-l2_GZex=*&Xzs^Axg+Ke(^~nx*A&m5CJMU(0 z#2Cx?K?4w{@7dC`72ynN~D~PLX7_n zdg%c)N&Wu}O<@nnPK+dLIU1Mdo%R_eWlvtUCu#}gk1U8~ZqIQhYDKlvED!g=vjwX_B)+3gP>JMZbO`W!xOpJZ4% zG12he$f-I|TXj{UKc_>H*D&`{EA4|k1DAdn>GdIyWJ#TH=P*daQfKIVuUg`M#o+6QRI(nmkVua$tcfyhV5uXw48H{x}aF1AP`@?z8!lM@(2c5x^+TfN6)3N zLTZkcra7P5H!HzFN*2JK*h=y;Bx1LgiUA8k_@5ND`WVt;2B`W#STrnxik?ftgS>y^ z{Z3PvxK1Km+zG*bLg0l(vp#lF$zgzYsA>Rg%|-RLyM4*`0VIP93hCGpJn1Yp-0$nF zu(?e(lF%eBHEUDqojKn(T$uN``w~JYh0LOEPVSvVxFT;C75%%*VXZh_M!t&71?RUX z1<8*D(q7T0?f9=t1^}w6)W9J{L$sK*$yRN0pN%nA^ZK>OtS0O&bvwJua?|IkwK2u%VBm(s88$hn-+&Afr<1nuh@L3^Fqp0^;DSpJ}Lh(iCO{WTk%T;eKd}D zz$y=_0xSt;Ji&$f>2QPWJSfBesuXz5asv4DrY7j9SEs7QD|yoPD9>|xRo(tm2<~We zg+~oIL=MWhPO>ajhyT9)r&|0sG^9TTXxTY@&>2@R0J~o|u6rIDB}Y=|Ove48r9W;m zXxX55%y??i)g7B}a>xGTWPj57i_|Av&^;wsz-l&p!vV}`rB|0)J=PGY7jpZs`qw`| zUP;>F5QVPR09Vc`{hQ7Gop-{*Sl_Ylo4>}g8$|B2f)5A!x%2`*vE5zabu7rOl7y{~ zO6F_!*Yqa;sgf5R!qC;XFk^Wz@ePlYkU##4M4kwP@7eR>_=Az{<#zm{xn!z>`?Q+M za0x)#7pud5qi0S~KPcsjiur34WnAP5^nVWhg%swf%D_d%4jg)qS@!ktK=-SB7<+0K zt|SN*tV+DyWLKn0JSVxbFNIvIKDpdKMv%1}_1RJ`C^dcP!d_+j9y_uz$QY{>U>-D= z3XAVS;PLN5SF;V;n7CBd$RSr;%}*-S{-kw@Omblrt%;;f_bv)kRd25{WaHI%i05?8 zLR)KZ3g`q_`y0=EG0(|+)-H4_vxEj)GZa@I2OYSrIXU{qlyT+W4fKISi);vfHhj$9 zCspkF#&hF0+hFO&nm+2k&G4VmctJbtGzIM~PfyR9nws{onuo}aSDp7kAaD2Liuh%h z;^N|O*mLu8lQAS=-AwOBxiBErV9kl`MOS5GJy4`!B-|DGeq%Yv^NY?>LBm?EX-l*B zQOe3*mw##;Z5Y}|N#9inCpMwYK?TV#k{udTh= z!Otv0M(M@$?kg82gMdG%3ht_l_gwO7a%D!xv+rp)sV>WkufvZL*O&xYuBbfkpgk$1 z7d69}sNGy#Vhdg5`%_52o)a>i_(5Om?R`I0v^d~cPK4EwGPCmOCPxNd;#n`Fh9bXV zeU!ta3=Om3^&W>Ok*dcM;NuLL!4tS74W9Pay-&Yt2IDRzQ$$msIV$cvQM0EuGD?&S zkKFLzspGhV<-0YJ+^3JUl|E=|xEKiyQTXEJg_tV;>gFqxQ` z(Cw^Qz(~TV>EETH$VQZRvO>(%Y(~*$jlP za9=&Kne$5R-o1O$s~c_TWHuQkP~+aqvbT~2RgPqou}Wd%9xxBRlJk1mvK1x+X@sw( zfkSOgh+<)k2y2S6z3;$<{#g-|%5NKO)>HS@6=-EoxavnWs>^ry(M*@De2BpZKNM)_ z`!eN49w9O>N^OmpwPfGCgiMpP3kc3;q@;i65*Dr3Hmt|a?rrBBu*kgJ!-rE(QVa_o??jA&VNlRkO3RtC7a4I2)5iD!zy? zbca7)GOfBb!5YH~V~JsbSzTWL>3qrjlAWccWy0agttTHh>EA5^GX8u*hD@W09uJ7O z0yWWg110mZ2dKl+B$AD`i+oQv!uBJ(RRr-M=q|au1@Ay zFZRUmkIY}SZVALzq{%`1BkScZf2=Dvx7~VO9I=8nO{X?WU4jQlm$g2REK&{9NT`@Z z>dJ!a+v(|$AwwEVIBt*v`%ncrlzj$GoCZ{S$!^H%YO^orn#?jKhqU>g(4I!T3BTp4J`tzL=V~Bv1!#wAb2j4rU3n zEMk>m!l!#=kRuoVGH~GV+VGS`+p9yB_p7aP4j3xv_-B7it$1kF)fZEPgG>pcp?q|* zsU1%~+z8qX-iS9$$Y4y(41MH+YAx6Ma`-JFlpf+gn|!bp1G7RFIm;D8NbSKd_oL1w zcqvpH#lh=cb8fafUM@|gN%Fxe82|)%R_p->hf3(3D*vy}$nK8frWfPfiAe6qks+D`@u`u#2*9&guHvFohcI?Y?)=a zs%KYTTDp>2(tdBFK}6*M)Am)oWpPIeoo|A7g=VN*nN;{)Q`wyI)$mODc6W^qwYBFr z6BDwL81k%PHUms_Dl>0VXq#hI!KMdkKl3M|trC=WtXUosicAAGCjBLXh^@i*EiKu* zvffMRs?L`Ajm=d?vs&ymX<%;zPk!(>48oTZ8AL@zF+WcCpu0wbR4an;83D>K<`R^c zRQCzK+uI0*g$chY8t~wXvs^9!bWXl?rw1+Nx0|O6^}M``Ly&C&{)XSpO{?DG=YNca zkH2_o*fA4>P%T30Z))g8!~y`QHA>$<9lEuL5L3N!RJSpDv3{aKdT(`dWIbdlE@Yu% z^5BD0@1dOkOcivCs%x)@&9RAX>}2vZ2Tc}j?V?AKElkPEjW!C0SBjEHI6P#IMH5;7pTooh%EA ze6&BVP&b1^Fgxfj2am3XJj|aE9x*{QgyH<&!omkt_FtCT_Wu%!*r~ExBon^2JD%ax zzMH|?wG#nmUjnfygz=osyzM?G0v?|Y^I{QL?>$g%y)Vly>pFHxb}|I=RYauV`%hZ zJ^Pxt;uS+|jD`GlCfatg;cF=wTXw|PAng!zmFBE7@#ljfS(LmM5x42k7VjMD?tS%J zm(OQzU;KJKk=MA~;^jst7Di~`!f@YIZ^~bN*4~9sl3X8ynkyD$8}nuZO0Ak6!@P!I z{~8DC$pG=KlgWX2#l|%jM1_n4ac@4i0~)S3<$3`>=sL%DAEJ7~JU9COSiMWO_mr6= z^z(lI+Dcb^lu(jjCm;r95rLeMLo3dqt#$Y+s<1AI77`-6e3*O5BQ)-)%o7n;bJhn5uq|#)P~`#j`L=~%&RA^lX8wJ1q{`kw zzB55&YI{#L-m=N(c8L|PeKw(^s*7Fq-Ipv08{6i?+DgvBz(zA7l@S(IaAyAQz4#EH zCK~M9OVCNfuCAvUDw6NG(b8Xs5@c%GO5=>fm`KGQ+_!;`t}m@$%fP}4rx<9`|OeqbpO_W^Mt5tT%I*Ftt2|F*4NFWW+ zGp9ae8H!Y-AuC#`Id{Iu-5vQ6svv8bUtPDjWbI+nrKngWYqznA%U*3-=G{o(w8FPE zui(fvyJXw2UoNPI+|<*JTSL}$BC^UGDmbzvBC{^Bu_gGqwBdV+?b*sr+xIy=SD~1& z1PgDiNI76?tu@-^g>2`&gL&C5T^3OnNff3k8cua; z@A!%|=5k}x7K^9*U*|)iyhVnnNjD`BP>-J!`|f6fv{|{)AoTKtE<;{7-!*j z^9wiTeFPt}=~X;nEFDo{wyR$L<^T(Ea+zqpZd`5&Yb!hg2h6tsM#)iZ2ASMBE6`FY6qX@4sK*GpbPe+hpL2{Ox`$LS{M{cvelJoTc7v)+Oc{f zkqksNMa^Cm7P-z2a8K9GOvf*<+?cH!iZEP+Q(h47`P?oAlPaBc^w&V=2}7wFi+0x^ zKsu);YZVj;fHfdVIM{~u(&?lth_>SKE4K4xV_Dv!YvuucmY5FWHXWuEM&$EZ9nIc= zNjrKpZ+3`B)wiwa&HgfEO+GA4_HA2BKvyWb^++@1{1dGGi7(F_<7_{C4$92$MXMFP{2D4$1);Q(sdeNAC$M6^s?&ACp|i8-doVT_ZqUJQ z{r>iL2zD~iHwc~28LQ~l#1GRPZ~K+L&~>DWONCBg1b+pO6SfOST6t1bxAa?AmYqYH z2OPe~&e5qdK>{X4wt`ni*spRNZn3J0JHaaC@V>;&aQLQKYE6rgqq6;I>t=&qmw2^9 z@CbYTT-bsXLPP_%`N~lX^c$fpn4sgDA$W_LqP*o(i1{wVyX_Z;|>C- z&$5<3!vE}WT5+~K(lJ*T)PEj%WJe~XJpmh8SxQ@8>3Vbn)uf)<7f2lL-5CIh#BZ07SJ-t!6n)maD+Bi~#@K$k9}zkrp7l8V zB+^MOoci*jMk1V&$`=#|cfCFnAN=Uor|Ak22EkUNWWO(!+1)q0vLfOX8L*F5+56Vq zjs#T+H-h%@p|5<|6zfz7vx+0_7{RMbtB&=!Jy}c%s%5XVOsH&M=CKB9IZ=jiKf_GjYme9$BiYRjk!F9sD zN7uNZo19?#qZG3-#C_}Tj&7dllq46~W3-#r$C5H1=<*i`W@;l&I1ZbgBlKxt;s zVA(}U{w7}#sC`rhB0(n_3iv#_$q2tyvxmsNEX+sV1pBZhJanX=;)8sAuLzT`g1+KOg5Z>APlv^m z6Ata}W`P)Wj{?l0@TRJ>?V|(AhTdcKgr6&0IZo71Lk@@`BBk)7?_X#I$$e1nb94TY zaKN9;#(rk);O`5*A?=Vm1Sa;0Z!doo8V#JAYhxQBY;_vgPo}ouk})soaE{WC8j6i> z$c0-B9IzwxK5)4@fy~mD=PH}?W@p^$4TmNKIRd4174hHU`+(PXQR>m$HtrL)Ed!Q8 z+EGlsS)_vPaH|A)X$d%%IevFGu8i#o-8jI|!H6E2ep^Bssa9_zxswJ_g^j1FU9@qBx;N?%;I?X8JU?V~Dt$1;msf zZ`GjGPeNyrsExg;;%i&rAY77vn|RQ=R8=r?I0&D$aZtT_SU!}_nmu@6(Ym_ z+zstY5vBvXuS?k;4nCXPAFwFY%V1D#o0*^BB7aq+n?8s!LjLD)@NY}tMVuPs3p^|Q zxuR`NxwggSdq>fDOUwLG@Qw&#ZMnAIZev-&6|Rf8gEQU2uk_(ye)tRmd~hRyFrtXwhT)BZwq`@m3%x(;6ak-CZJHUz?f()o zob?xnU#)~Uxvr#?Ew9HxXR5`7vNDW0{1-p@DbbuN^7*9ECl;Q#&901FsG3V?FSXjH zz$6fHLp}}V4whS)`_)8z9Zr~dKqM#=)~ktw>-#feV6dv#R0jEdLlXH~qbMxeA)7-G z)_5gjAO@Z3*%!l9qcap4IG&=4)5irG;}80;KDVst0ejmwZ}E$NmvWFr?XNUDZCxL+ z^JNuqW8)D+2NoNGLZ=hNawWa}ZP5zx`wHK~bh(&|4C3>Qj*Mi9^wsbzK98 zxT{Q)GPf))>%~_;((aM1cSGG@-Mf2|=<9J}Ryr1$`>XHS zkE()M9!|&mjPb=U!89k|-9MNQ#!h8Lz+&v4Vcq~Qce?{7@<;bB<0!y0i-@LX@VzBo zzveG^WpiRoO6YUDhQm zYgF#nGrV??%d}Tsv5p&2*Qq1oJQ0x&uu$F0)j8YCKZ)fthZb=fTXOYAM5kk{(aG3=*-+60D(ypsB z27Gwbv2N9(GF#@ESC^!1+HK7q`Z=n=x!i2Bi3_TLY1tsU;|_-lFbFKp?0dRmrB`{) zD{5OSVh^mN3ALvQMdZzHmvpQtc#WF&cfMQ8Wn|&Fs^ITqyacbV+iZVkGrNW~_k5JK zyOI!GJnrEj3UbVrg@lA$Auk%KG#@alKQ%c5N`o7yw^NmFOV2*JMvLpf%2HpUZT9Hf zaKFzYhadJeCq4syS4N3|5BjrVIIli7i2Mb<)yh_%rC^CbxWXlR>;|0mCm#y_yfP!Kk_qSuR zyq7SoRI{CcT~SeY{Yh4dnDfaLLbY1=GE;hHHbRO#B&Pcdd*7WSa29A(hdIhH4Ngf> zgilpj@MmwTM^2?#%GOp?OjdZ-{RAQ2*|kl%5s%%YV+#?tlG>UGJDl=uxvs0ay(%|9 zI;+q!_y$xSYOlP05(&z#jpAR0m&(CaT;v)oA*===?%s)&+z~fPZ zFa}OSPaC3iXDWDM4Z5&a-|1RE!6b`YUMRTRj;x2on6#~a*q`?erC+ezFVlrc*>!%` z#nw-ze{RgGZIlU@RaBgU9mT88Sif(W^^Vc^8%GTe%J#*NtgiV0jGQ9O$SPW1(b5u;hJA;WN8~lC|MH{=vPk(hb z9yi6Qo!jz2m?}vA5#hesyLxUKN>wHy%4S}g3*wBSTKRksFOjXXjSP>IOIV1CMR(u?As5+cbR*HSX*tG`;o|7=IelDrd(ZB$Z zE!k)JG`(J^$V*4t*G<}iwf{7o{<1D`+<=%}!&5())T+FeRI=QP>4n6-C|jHnZNuwK zgrZvf>Gwn-K{~kog|?{1GJV^wI>&(*{T0X2D&$A^CAEm;a&wt_$K$D)*Jw&t}F9tn%*Q6krGWu6rl*2+8+G>B`cdNlM2Li08_ zs}>)pXy-Ng96@_-wAG(RB;NDmRRt@!9SF@txqiof#^{q=c!FVuG}JZ$vE;Y$X<)BKUSG^UkF2};JrjoL7g`i&?O^rrPgca-MT9_bqk1kW$dBX#D~{dW76eqPcn*xRD8D3z2*5ifBLu za_&fArhRj$)9Ku`^(lNiad3=|M)kn~wiQ=*($ zx0Yqgkb(57w-+PLin3m%_sai@4IZoaihmn-(Q^~2I8nCm0)iA| zmVM<-Mxb=ZaLH#ZkmqHh)n$&3saqu!Ktqj#;g372@*3T#sfs)eU^QN6nTob!NIXn# z%%aCToK^O0^Es{h%si}$KiW20P?Lpj8Ny;z{TDbFOo$`m!Q5g&!~D>(jn$HX$Z_v`L!`Va5;^8^K}9qNjrK(@wv}@79QUf!j&7B$OCXHVBTe z#i}^E2wlz^*I)$)j9PE<_xXp!BBBk}QT5_=5EKhr_5;r9h41XKHu3v`MjlV#ettk% zgO%6bYd`m;Wm}wO>pEoO`a9;Rg)DvOYs>L-gJF53(DOV9FvPLN94P_49|pgmS#k?N zO)>KV%ZTvd*jPju5t}r zv7q&hi|>+g8Gp5L7nHETe!XdSCMa-Vevdg(am_0&%dweJFgAhREYi;~!l|2G53p-3 zSNpordf9R+kF+z!G4EYZmN32+Hf0p9Gc#TD_9FyY((bC>&VKqc`Bq+$Np!0Q5U)JP zid@2F*G5iHh2^SZ-PU_fcxj@IRXDg z=~kw~+mi3)xjSM_bW35D?9^VgOV$%gtY2H#_086f6-88%!0XAJzR;hGAJ`1#X|Iq! zQQZ`T^p!H!vxBMWSnnb6#nBifVbOc-ut)-0qOD}=`l)&BK-T6>HROzFXwaMuq$#+o zVl)jzr|Sy%e*C)wd$@*j6#aO$FPKAH_eh}*sPbuF!%lFvHDZS0#7}btU%b7`Z|ttR z8zt{?SU9@5MF|>L$iwp!oN3~n@O!!?PjHKjS$*C^eu``kutWaRY;V>{%H!0m z)2QB}!&@8yYw)m80_vOtuVsKzUjQFwE@+%TyT1Hp$?rUi?#v9+Ro#0(8x`9YQ>_9O z_v#k*9^nUw`yHn4JCX-N$a>c};%*@jc`^R={xJ|LNVN|oZ&d-pVJ1ZW5X^vcjC#Sy z>!yHh&Sr)9EM*~Z$WaDSh+V`uUe(4%X?B(WU|uf-&`Q5FRlklP*ToN0zN~bNZHtW; z0r^ot`$H&@TbScuRE(^R$epB{0+gtdx!_eg03!U8{=WRoi3abo?sF?0{)~rz7 zR^qhJiuh(84-}(tAvvlmmV>?8K{vr$&ox$)Bk)>je6y6UGG@OlU<2MS`(kem#pyik z8fj0bd}&FixUf)xg~NB#T+;E{K#@*V5-SHA5T4>`LPVE=o#B4{?NkTy#jl_Wq4>_; zg9$y=-M6^wcq!-38o$k#U3Q`B>J#3mX9u-{@tmo!)XzOwl}-26uT}{)S?;U2(GRZX zWtMG{>!AT+o(_GnKnmOrV!WpUx@~o+=KRs$gJlu+viHu(EdLzOFrcR49pZDlsM+~1 z*)>cTbZ<;Ek+u>{+OA~1BJEr<)q6lqQxsjZgv(=qDpXQl!2Y`A_}y{!*-ruOG=M0^ zWoP-tgM6&G_Yp`)TwBBP7tBvRoMp(-lGLM4lU4!|9cN;d%^BLmRvD}6H;^&=*};;* zbD=i@h>l+XA1w5BS^7?-VD)TfyvnNgx<#y5eNp~_6FxmU1z#$^?j_p=E)YY&K`U%T z7V~8CmVMEIH(F1r45DtVM*Wy%Ak zkN7xEj*_btbKcfu23l}RYV0psjmL1x^Q@H}glvoa%HWjDhAZo>8@XCTez5MRt&Vpf zuXpXbZrK4vaMG7?XTAfqnX}E+eUWz1F`D~%x3NZ2eTqu{2(?rN5?25 z5d1L^8gH=FnEhrp>PMIuJHP`(m>OO&qS2_QZfEFYZK+?- zL21QRcJZQQA;Y2$J>o*j{;aIUdF)iMLy97j5sd3<+wh2#a_Eh(#CQ450@0q84}JRV ze%t1#i>i(DZCjZWDOXAGmEgmhDz70yM@LWF)pzyBC+Zt}12oFBJr0}Qx|;0z8r_aA zJ4P7A?c-RBwSwNpD=VvpzYtfbtW|(22!=;Nnh*A}v!@n9inWRIW&j+6485lY4d!Mh z1NTyHwhPfKISd0v(q~Uz(8^kg@@!2~b#!Of+>PnMB1yW_^}jy-GV>i5tz7siIKI zm(?j3VzC0FSxg9QJoE3%)Bb61hRq1;Z+o37lHI9D}5 zU4uCFB648&=-T{x=g=g^_aU>NcWKGxv6TES_k$R01>j_YXf@^@qVKZwAt?D+8#_rS4uGe)vuKaOpnyN?M%|I#* zQL61L(|RXKK$H4G(GRFRuxMJJ^2Sr8=@uX{rWuTf_e&dy>9GZbm8`15PphlOw#H4D z)K6VhtlL=K`bD+tX=__|_F>>$(~8~H=jf}e6Y+D|_6fg`Km@C5lHf6^H8TT9-b;4W ztaP{S(Vz2+i{Na+5m`C%~#w3$Q|`ADDZ z{&nd?+nK3Bzs<^Q>`tMOnuU>o?gG;cfJo62Cf2Z-II|~yUtXL83SgH*mprpgmQ(8j z`<<$l`roaJr=@z{^R%oE09i$1MrY#_nCLmETn9SO`M<***RBX3AYwX-U>CPpFIz$l zQB`(62>Jn zOU|3vP8Zpw?RT5BN&_hs@VEN5?An#zWf97(@7nvAm0?-g|4}d)<*Aw2G=8FHD9U>l zh+SQhvg}fW`m!$HV+Km$>{eE`+bXPF0{PW)K9B{l@u^%;tSIxJ&kyb}kB?c)Fh_qX zC~dlok5^tf^Hvz+PJP}t%4D7*nz~)X-j*AEE!6sj=1D?P*3YSp>_dy2#Y(r3X&{01 zd&dDNt$Wl=MZ*1$FUHC85Ex(m#raguQ$pv?3arIA;noN}S+;WQOys4eQt zi>RX`D8k&7aiQ(8S15RR_mC^`bGxHiLx2t^!sny~)@L+xDk{X@WMt&?n;(uFq{-E4 z1h;&&L6lY$M@ip~BME!SudjT$FynK3QKm}eK=JB#VFsXR5nCq;jDS-;vqBj3 zF9r31inILN31UrAMgOQiwE^m??oCPaPVKz}f+nj`YuUdD zotBzL$?#fgXmBPmZ|jJB$}Lakq=mFOW#mrI&iR=o#KNk#lE)RU+F>?WFcDLv8;=d$ zE5kVwrcpAT&+eIY5Lzf~yY{?a3Nym|5kd!5K+N#-Hp_75@|;eUlfP$KNR5y=1d&iM z?fx@5q@&K{WWEEiQFlI4p!)k@>D;+(gt5Ma=pn}5Kpu@UGuMyJfr-$>W+*3o%;w|%MgxN!t0u=!s!LcY-AA`j?_ zy~yZo2KUV|Z`5o|rN^kE@#53kx4Rxy7frEZsY1 zr*W6!Z=b)Bwg;d0q78e=4YZ72%dR#%=~mUkDc?>*N_a9;{%D7wW~%?I8=^4jFePPq z`5hwV%~iFlN*3I7NC~R5RX~#zy(&rl35%%#*0rSim5VMbd4Jqa5JvxwBQmB@;6L#3 zzj`z$%B5v7)z`InQZoCKv(H8Xn|<=W@yfrN^WTc|uq_sPKCz7jsmJ-HB_-V(-qRn$ zZZs9K{n5I1iu8+K&i>W=L3I~Gq@;QOcl3SSJWtQZq0+Y%`RE#;YE?rwTJf4*?1H~b z8hOos&o*}Hk>39TEN>ovQ+s9DM3=_Rw_}uIS zm6*cnKx^Bz?13i<9v1ktG!=V~5ExKLK*dN0hqL{mRl{cZ6K}RJN!@Zd|TRdZE4fOO?wN*(1krws@rPgbYbYYj01mhE`>~3ptdWFm0p0FmLKq#hBez-(`mcuJa)`t3obHJ`hEI{&Uy&$i1x)#CL1Hg%JyF9RHzFUh504llM;NnC zWEuW}t`{+CCG+xi6~cQm9FZ>yPyd|ou!d{*DpAs3cy!#j?a#lvqgYxo|5dnzhJmh{ zlXl>w{eNMtRBzR$UcT5m^l@+iIK%oeSxLlv-TH}goT7;dB>!3Lp)Lfd%GZbMI{)0j-)ZUJVA?v8%qe2A)$QJiN4?Oto-Gih9c!69 zZU>v=$>D)EC{fLxsP^<9Ni2y-pz-!uvhcPg)L#WiAlh)@??_M3f@h4jfH2abZPrZUng8L+qSflq`%d#)ccmo3U< zWo0$(3_ahDT)p@9FVn{;r$jifuux#x$$4u;x|fMZOoeerK>w z%39Ae&&)md+%vIra&l6L=Th>i>`qCU`}sM(SaLZ~Q2SVTPlITg=gQVuspP2wm%Ey- z*9j%FQVNy(b=JbIh>KN!L|t9P!)NQ~mr2|jo}4^=*fUg3ZueLJPY?LL<2oQvKDffLQN{zdG-Q}W@z@Ok=UwbOKi)mTFa=8M_1>|Bn44>_OJ;KGCKfC!E z%MT_}FH@inDuAZW=)s6T*k+&a64R@ue`H-ebQ@2A$fe0x47k z!CmOrG<5&r&HwM&_PuzXD3GgD|2H5F@X)^}GK7Zz$tp^6L}C@(cC$M}GVY1Ft@4SP?d29LxX;@gMtu*N{MknUyOP-!9X3mTjg4N-5;$nIb(mpcOQaajiM>?gbJIDqh;Y=A3S$25=;3V z5H)+e*!CUDRtN`V($`|v;)woi-J4ZXXZ%|L`QHr@m(AYXf2I-8;hbM5V2(A?uLXkI zjIb7QZVgn_-s2ob_Ob3UcptoKcIsSy_D8N>GJxd3GE(q=&-vH@&q*)@{!IGBDCP8G zUu;8wF(2u#&&PZu$4=7%1m4|rn?H+qSD}(l-c#17WKXxlR!6bB^Vq+f=*}Hxj*Kth zZngt*t>M+|gOIO(RK)~>oBfSd7~}4!wa8voPog;J!Mr`BvrYQ^as4>}I&e*lyL?{E zzMp6=@Xv0LTrgC!fiT!*YdUI%86)KKJ0}QYnZ6N%l4JhEi?BTO^&+`0z{|h9PXml( z5$fsF{N)8BuTX9Ww?Y;pNn67H)Tn~bZ;0%UV5D9>K+HrT#((YR^JK~0WX)RHaM6oA ze}*4Y6A3N4+}^hRUR$+fpIOHxF&?IW^KKSz-veB;lTN4i@N>CeM)bJ`z${e~cNczn z5-K1Hi%vS%m8QA4)RsCR|6NIvYmiVlFi2ZV>$!07$?xEgVPuq>Bn#sB1^b?VliZci zyzgJJ5%A)XV4|nA*1x-+WU4J*D~?*Dw&GH7-l;S9Ii&x2lgR)xa+L8K&K$f*y?mJ( zC;iO5pb+{2UJcJF$PHd!Ul-RMd+~4g^A2nO5iUl41LzR6(-B9{op&*J>Vc5xu3vUzz9gSV;;- zr6OQb3)s@a;(4s!+8^!EEpZ^BX{9Uxom^mKmyi&Y3zVkDse(p|a+kw1HQGT`l46-Uyad&cE=KH20M z-#o=Zi{kHDLjQl(lzNAd68oq<+fd=B5$LZHx}$GDjdWd2iKF;`VZi@kQmZBBKtrs) zIQ-Yq`d&=F+G9%iblzaML-AL_ay4AiR{YJ?-k`~U*9isy#vR;5SpJH2#V>m-!V|-E zt02Z`uO7OgBo551W=cxC3_=++7*w+rKIBF{Y^^*k(LHiV-6I&yhp;B=lvgO@r#x{p zOmv^$^%VYf)_2Oj`{J|wTNAv6=bL=VFHqmi=EZM^mnM0NQ~@ULjDG}KMY;l=L?w3n zUpOAZ42HhAdbF;e9s6Z0x4h0y8DxJRL+y~7PjEg|Z0+PI(+R~OqgS~0hsWmUT}eJ5 zRwoc)K2$gr!8w)6MOu@1NX|or0wgK1+q1s!g_3^r}H1|UZ<`k-@LuIRNJou)2W(T z18t#VxL?R1!VD-~9J5b>c?EVrch!Dp%SJN%7SQub+fFA|G*s`b9{wF@SiQ$ihX4xW zz}zFDQj_y}4kFGa{}pQiY)>&qUjmdY#d$rL8(={_s%Zsm4`=@ti1Z= z=UMxl^nFfTUd`j#{8m|B9@Ma{>`{rs*w%Pi<^}*5Hbv%39;&x~u|^o_sZTf$=ez>C z&?{^Gc9C&IWRL{Mtv!a8p>KSE?n zchp&fK2h;*UpBu@c$AVL^SKVoxjiHd5W$6Kl0EKgDq(Le6nzu#*L5z*;@c2tnJ`VH z$ynEFS6TmD@HKKf2~}~)F9c)m0c5_0mN!=w=MW)%66i<=pzUz4-&Q!ATYDSm_^%Pz zj*+Ud);=m#5yr)y$hIcEW^uFT?@i|uU`UjQ!vHZsNc9jS5QY{VjC%xmz0r`ju%1Wg!KbIYF@d4vkfPL6m`G|JIHDYZrdPAPhSf(A)_m5I?2>XaO|hyY7Fn zwmWa!Y|mCsy1u{M8+z_SkCJNkBdv(X#ea2fVk6g^Xuq+3Lrvn|dTrHKmCm;;g#T#H z6mj1CK>GT^Nn`#?YvTAUXhn9Cyb5o2ho&QMH%g%$MBCAA4nx+c#k=#|uQR7k z98684J+hy@%*w7Rcn#ww)17hMm~vYc0ts+vtvjHnP%C%m;c&R1Gj=jo$Vf{(CM=<% z?MB|{;z){ZHD=oN=wxLP6dvM0w0F3gKWIyAp557*d(Y8*nogTIun1zIS`YD}24%ub zpRSHrrgY}9Wv`^Ls%+a@b?5PCliNYvJ-U+Q_qpuaI|m;d~&_M8T?jQ)+wy z<*#1{m0QbGy|=BhGzTm42MBU%+MDEB?SBov0W?vUmKJa0yNygzt|p3_ODS|ihiubxT)X;Ww6W^5h4kz8P<4p5dMm$JnSZRHIbjPN;J@NHW zY4O$pAbw>X%7UagET<11q8%OtME7<2L1kU8e^FeJHyB$~O90u`v*d+~0N!|T(^MgW z=HVTPEht#jx~&tod4$4 z5HI{)VRjOQG1IvzG04$!UsfsOt|~1|26~p*mfSRyRDNL9W-_;~#^-*J^D2|0v^1p@ z2qBBkYp-XVX7DG@lsV~?*&Z<_`*p9KnHu-uaHTE`DmFT(3EZ}`{ltcS6|&CdjTnPITz~ZPD~o!G3kQS~ zanjKDnA4eSRr3Ogegi7;flscB_^qpfHGOB#BMR5*#>6vU4nVA1O%835Il=<*vIOLk z7cTx6KNy+t=rT=tzEz%S2H_PGn{QQsOk)~K+enDnazxC}VvDbBO?h|l^6@1QYqPtg zkyf+v(pWi|D3#ysqtEK$P>nXkY#zjwPRoNnhmA5T-?H4Q+H`k7hiSZ*YKgZgD&U_S zgs+oo^Lm@G$RPCsrTT8PhaFwoERwK_x5`9V@fJMP3ewyVHp$~-Tg>A(UH)85ULw$s z&A5O}TvP4-ei75%+V*@14qpzlD5vst*__xGOEIK7yK5*=BsIa4rLY607q_1z?fsD< zHE#hGq${8kbR4J6_saX^lwa|7s~fmng3`WX^%<7#8zReduzca6a$Hn)uPY}NIqH4T z6mN!AF+|2X47Im2aMamhb0^|5qt6Ar|MWxU3=fAyWm+ zMkTW`0kfKENX-o;m*bpj zt^K(QUGVH#OYRVOL=TqFPwQ7GFQ*yDM^syo=m=tUSt~ z=a@+$YUOXr>+2xzNy+L}TESzKZ+f3^yaG`T17cEfYw1-EIpnlAVd$9kBuzZ?@#1)J)O2bHX;{|4mJ!H~m5FF&iSIv}&Hu8j zNV(Km{}!m32&8SXcvI9euc031oFS0!;^Z_;Uhb2HbS7b(+oRE7QvUDn6drV*^#J=Ftwa>%v~j3av-MrjC|zDw@c5+3$P z&gX*`qiff0`m`qhwcl1`&yngt8WX^N%xLH47PmG7Q@)iB-WGY=aYx*J(h5;N7J3`B zLMVLxER@%+&CU7nPY?N`9)?~CzgBnTFC-}N*Ih)wzKMIPR-LWcNt1qB5rL!)#i z*nD$}sG}U{lNBwF*kmzCEIj@$#e(W>mDjHf)aKMBh<#XFY6sL=#T||kb#Y1}9t@t9|2AbS50tu^8}(mUbBqOYw*QzLBq zHV%tDDlyoo_jD(ExVAS2J=PJi3YzfVDW0&+BWIJ#_sp887Z@24>q+&+EFA4R`k|Dm z@|Utki)`DEP?boiXI3d%ckwn}QKB#{;e)+)}e}e0Gypoe=9C3KJ7?o!3l)YiH zZt9=M^qy>AICZ@xyDu?pwR_eVzt$jIDJA&USlmUZmZ;ULK95V==YGTR6hSI?@i!`H zcjXizQ!P#+MQJ8aWuu0Uy1 zOG0l1*%*W03FY^k=I{^v&jI8737O~2b4eh0ijTJyG~QJpOOPSMAB)KocbcV5>FH!Z&{342Cb$-ranDmeNTAHu94 zKi?k)E9l&S2X$RsY0F|Rb#8ud^L9J~FRM@)`Wexs-r-bGoB#Nr8r$(oKoGh6mGmxy znAa04O(>^D;8p(UXsT(eyfN!1#PydF{Q9@^U)y@Z;((Jx1M7jBApPJ8R>mf`Wi8h; ztPY2$(%@+9-nh%rQEI&~X_lRJvD3){XUl zZ~bh+!9=qsvj0pB3RMX9%zW8wY1MJmKd@lns{&f{^!9|vY2Ka1Vou*JgPy_7`4{sm zT)gY3o5Y-|>kRE3970|>=@e-6LDWF;0S=~t%TCId;BQOfSy!PnZxKtKZSDuldKh2Q zye~CBhTPRyjyq3E>)saoRRBZg_A6B93<_3&*kN)A4@-_x=dznzEk1l4%QMBIGTA@= zW?Jj8esk=VZ36ErdHKAFa+DA0-J*RfHa1qNxz*x;HzxtCA`$tPWS<+%{pWz8Tqd(V zUM~)xA+k_7UCUJ2O&&Ohb$0#%HhSX1?%BjpbA{A)SGrrNLar|CDpiZP3I52In3|k! z+F=;KPXR^=otq~~SHTGW6UgHu38Q#xkYZEXXvOk->)yyP4hYGte9F4LX6Q`)#JuTN zDFpYFb2yi{gircXK){o1r8}k^U@Kk-k{5a*FXh7rd(PnrIQ-`NWa4dAtAHtN+L-$j z&rX8<-t|L%7KE94ZO!QC=r8eJo@m!1T|zKn_)1Sa4Sxz)A(?Ab$I9%Wsf-wJL)Jix z@by$e7PMRg%-zmHPG`lFrNsNtYQ%u>3y4$`k=0)!RTHl_;zLA=G_A0jp-be;93vj# zh!tzjcL#Rzi>_Uh&C1exHQ-;;Xutnl4x&T6{!&UnxSqnjXM89(*Vy(T72 z79^D{;sAY>X-YVGn1RV24unxjI$OV);^J{TyM_AnM}d-jH4u+OevnikN%8qFNbh^~ z@~X}~^`}GWg>sKY0@NRd(JpW&i@8iAr(OCXTR79L?nmGNeL}qv%0;yxO*3M8J?{}J z6Cg3bJ)Yk8#h~L73SYlrgJ0tTMMpo6c2`amwx+x{`tA8O1{0fFlQin}u-J)fv0|Rz z;>6uNSXd^9Cy@a@<Z*DG$i2O<7*SCR zG#`-B8s6+nF}|cz=$Bud??uU?-hbRlSSlTzmrpz1)>A&qsRFu?YnQO?`=)x!E^38=x+AMhTP^$ zAJyS@d2TiZ_tpujOxcWTGoL6CujQuhzWhg74~v|yneUugGqL@`D(`PIcFdJ5a{xBI z1n2l!c~gdZ%w}@EN6hr}tJ334OH--)~^YDX3LK!RYWXk}$K&YDp)+SoKO(MOhi#jnn<^ zrdrsyaxt4K+kDr)L_s$=36>cees+r3j*qIKDZ@iR)i&dgXzSjKK@N8OT5VjPM8I9t zZfhskK_*Z;_9H8AmW^o$$UAI(tvn^RrZu*$U9A;e`HrnDR?*NN@Ik)`C-Xq-jh-XC z*feQ-7}m;$w(GYA{a8n*1P67V#;v!!x<0DcBRAvsh0JzcO@BvTVT`aW=B*~nMhaYe@eT4;^)XQ zJS@2#*yVHY-CuZG^68b;JIxHt%yK#^whT!mesDi-~R zc~lpCpT-b>|e= zm)9PxwV_Vg170cgFsj=a>*Hb?T-VoRhKgu8T3TA^A$3$fzdsS_<+?2;B6%yV%MkLl+rDQ z?Zlnr%+55T&HkQ!m z&R;=ya!|LyKb%g&JU%`_%GSv#f~tMedT5{EUnj)3kX#N>3n3kK zOGxu?{C>R}{5{~^jYF9siu+1mTaMEyWBJW%Gj=tu`x}%sPoby3aXE-|Rs3*LKJ7JFuYT}g6Va}aAj-{~&!bGadiLXn z@_Y7q0rCCdVu{Ycu+ZK4=&=}tL^#9Muq4BV?CqM3ivEqQgZpv%iq-U0#EU>o?cH!~8?I7Q1yb6! zkV2~Qnxch$B|8UuLD6PNiiP9OO$8SBHRc{huS= zr^UGqKENZsUd8pQ)>8wW1J9voj=Cni?vS=E47(;~K{sL5h!Ko)ivIFBp2Dg8he9Xs z<*yHB#f{=$QwxRB2#Hz=o#t2Wr|EboHL26P+N}?pJ1kFDeeVyCIjvRGKpl9wtAU(C zI|fS?k|3tJkjMP;cv#%SXXx(xrEnc@qP2)?=xdINWY4#af;EQIQZTq&VzD=+Vz@oa zPb{2Jf-oEYq@%rxX5TJR{ zuLXH&>aW!F^vTutKk>XuEuLv_G7r&4Gh56hnEHqk`e==gUvTQA>E@?KYR=ld>R03} z(fo$41#ZYWv?6AXiK6t!ebuM)K><9B8F+ETu_yQR}e1J)uAB)r$FZW$SoVUO`q zv4EH^R=;&Fioaz@pG|$`Uh0h+m(Uj)k|O~(%AqWCd4<<*5Jl9Htn_gUfFYnsMYq#{ zn4aLMY^kC9Jj!N<$%{!oQcc0(&BWx)@om;6u*?u5%7L{-sShp#Lw+~ zkg{KhbtTt)o}2dXnEKM^zH#<(?CswNXAd;nF5%tD;&ZErv>3*7s3AO3w$@8+E0#p# z^60Q?vnZ}M&cZVAdAE+pV|&dOnX9Yz7cnIgaTiRU(193-2+2Ri)puo;G%$i_#q*if zK57;n3MoF2Ih&L(igThBuv9N1z0*H)Hlr-D4&IB!zv#UL*i-gPNa1zkn80BCb}-=& z=5Pk1=c54{y`eDAvnGjTC2RzV`imYMz3iHSzC`$wXDy6J2Qxr~Q`6jO3L4*uC{{upYn#$3wPiTNQ2zFblqTT033r7UH%XpwV=Ciol_UvRsPR ziw$Ze8CCJv*$Yg7S(DR!q2^INy0Gcky{UyKBh+8~|CKFDYLM(e;tIWhZ+iK)Zd2NT zDgfn$n{fXft~y+NKMjj$8p2I!gx?fbp?bCJRKOVptJV5jk9j;hzYbG(+_}6cmHO4Q z0i=?bpWlrfFK^GWIA#~ni5i6KMo#y%aOx$=7>HKCgt$>KUyBqwLCP;Xq`1F(8Fi5w zyj&tFyh0um(dt$U5?Z5~p-VFLTCa-f1|0hWha7cii)Bfha*}AHNPC!otwxxoh0W1c z_gf5~Mm+F649*~dLGbp{Ms?FlgfN%Kb+ECLq=BREXR2Cq6i3c zW7HC4LMQx2WwMp*_9Qp>DG8>ZSo-^vvu5MPfXg~LPo}0}L=@LtOD!jKM=Y#z>Y&gk zTkE80Pxv5MylkTSHGukgVn&XL3WVxF@Vi_I2+u@Mq)mH#Rg z7>9zk3tm(6ZvwUUZXYbBDR3>I(0QiXC}zSmPcKOb(n})KA!V?U8^!EOWyoam(%^eN zK2Z;U%~@2oQUZ8nB&*OlHcrqW-)?T;&CN{ltqGU!$myDyI}&z-pW)D8{7VNs_(TT+ z8S8-w*jE*7aZAfXyc&8{-_KS}HyiZ=Eulk9xq!7Z8oZ#B?d=uS{I{HuBqW~bw$ zQJOh0cfin{OyITXl7uoe;5K?-DbbxIvj)8S8@X^X%f&)O}MsZt~#_THV=gAK5VZrcv@ z-d!hS;0BtKtY7JTCl~!j-W=h3oBeB&^?KY)2{W`G*7C!$=SGlT_JaJy0 zQ(w6mloLgbBgPQjk05D{fT1du*B#63E5`9oA9H@)<7AvzDBqea?m3b#;3gjzaXdC# zt&wR*#|>q>4(%#i`JQ8`S`=2;0qKf%$(&{yx-Yk%F1pLCg?%-tuypX8pJSXUCY2XMMX_}Z7~$COo*eDoK6y8 zR;4Rr*|qyo`g$4#uPs^G;GCC->Wlh(8ZsT|mr_zz=m3~rLXuYFkdX>$*9q9QI_RJP zOODTI(^-1&3}r5_mMo+-l9Gl^o90GfBz)p~Ya9y~wE%=f7F-4)PP z5HZs~Fbee+#+`ijcHKFNC*!|GmvClXhVgFdxQ~liNQqea2gD^HJn$Bz`5lKC`$zBy zf@@Cc^f7<`i{uxAgZHm-3>oF|TH3eSZ`~g)aG-Kj`OZClKs2N=?bTc>a|j^4PUNqr z71(sSSGCCh>AcG{WG393^D#1OZCJe?T~94piQ7UjH7RLZMUH!SIfHb;bh-D!4P4(21uqC!SE}3zHdg@av@wa_+&bs)X(jmB9#CFD1n+O98yEGO<3mPGz zmuEuLxII=K#{|Ua3D8=<6@xQeKA1@2vI#xu*c5mVb%mAd5+mN1)9VJMdG_)VBG1rO z;lVuFidWdBhYAptLRPq;fO`bNNsJLseUru9W2yDrn5>HZ8n7X8Wn>cjO>EPxVm9Xp zgvaH`aXT}-2-`mb3W0Y6wR@{dM^72TG1#QlGH7O4Xzd;Xm zxnb_5TWsG)2b0l}`-0rnx+{QN>0sB65U!|MsA=j4zR*^9AZ6noV*u`p-nHv}31Uo} z4itosbR#uaGJfdjQ`Tw3F=*LvivQICM$eFW?Y(pP7^xis)Y8gLi(GVEnh#=o9JU=} z8qB+#w>=Ou$|#SKbus`JOI`F%<(t~i>Bn=se*LYl0ck&~RKyh1+2F;*t5_m7Kqr5b zJ$At}<`~xFP0_h^MFg^ zJlk>r=>p;3rU>MEYnNrvbOztL*1Btd6#B3IF4m^*5IXiob`u^F1e4(EOh|se%=qEE zYF{1Y&ApH(!LOpzA=|ZlR%+Gz0qpmc!T0y&?*~{WdmLrv`0~SUS5G0M`fAN=N~4#S z;pB4P6k%@I0p%24Sr4kLvwc?06J$uyEkdsOax#7^{yyA#xoeaw#v7e9;v9dSmKl7@ z1@6JHCWX(@{n-MjQ3j2(^UT0>G4eG>QZziQk3x@PIkv_!vj06H(Dl*Q5MC*`vA z3b}rcmX--8bA4apf%IbD*$Kc%%V>#{05_x&10VFUjw!0{wjwJ<&e?G}7@#sQv@nB=kE_j4efPRo`PF?IqBV_vdo$$rmB0h3j+EjSgOV5qGC@E_^sMV*pNA!MnUjp3btFqR;!Ugc@iKehgQ~r`H+US zQo7CkBx>zs#xnck4(?K9fy=xzpR1K_y*;RjyhMApWC3Ww;jBg0J!WSw2Z>tW>Ki+2 zHzedlP4vGlK4GI>7o@%VErL$$o}fI;+jIVnD+_-DW0j6ZHNvi)ee6A`eAM32<-#VS z$2mpoW=^l*H=D{hK2MA8kydoQ3(s05k`-5NaQc|Q-^4~|=N0uMF-%j@Cvn)7-oA~= zE2~?S%3>%shsD+1{+EY3&Ah!cM_U_$ZA<+6akE|DXI`LdZH|kBEDnLFACT2~ROPbr zB?TU=#?hA{ z{OBK|3>H`48qDbGw>IgEy;(9TMVrc1!uo;rY7T=&oZzVQqC*f+8SOdRuar<9 z+qC|+anJ7)_rq}R`McUw`I~JFd<$rI~}EM~t%$GLSdi*A2>N)iyy}9%^phVnN z)N?@hez+v=h#xMC%(vJ=(t~`sh~v z(qI$e*>+|VVr1kab-vj(&@Ir$P&Q118xHx{A^|6J)X~^4VhesflyJY8VM7oJ^=Q|c z4IAU&2D1F4=O}0)zx!qbn-6QYrJk7Qa_GQOWmXNzq$lLKq@rc|0pqx1A-89zjBSdz z*ZgM}q{z*j(Zc;G^mctu0%+wTlox5scjSCH2Jh<)nk(l|^d5fRsOayXUk zAYTr|n^uU9L4C8BXGkXm4G|Z>Eq;MPCM5 zA;q`(v_JfGwvw>xD#wgT7o@89mx#vz4P@wz`xSIAnC0?uDmLS_5AqTFrhQ!1(N&hj zuciQGc+RQW`PaU!@QbKkvZ{5SoxPg@^}%Mb2DnVSOPlp~n@80lTI!AQB}?FL+DRib zP%9RP98&jce&+)ghZxc~^oO9*jWJjet?o_)G4CVEM1@$_D%*yB@l;0p`Z6b4iBLqe zC6bfNk4j^qm)!WPpNIu<#7TmU5R#0;Jdw5ZQeNq_2WA8u1YRglQA`jWAPGNhPGPV? z&&>)L>^*A=Ot_4tY#fK%p+-Ib1?iP((%vZl{ zo}ol;j=>_lRfbo{rd%|}cfAJus+Nf)-0oR5%ouZCvTOfN>O6(bBCqARvo=)|BcMsm zLYjBmqW(}q;$X{gHf!o&ZnW|#F!a_^pn>CH2L_P5Mq6aqHP_@?9}?;t4tMQPCX=x%j-x@g|Ybdng1!O;J}0>A|!v!kd;bhr5%z>e>!cl+2p=x@-| zLwmQ#VInZuS!ftVeKgxF4jUo5V?Ur<6w{{wJKPRN`T0Zd`)ho35NK zt9^dKcm8_+cFRFV`im_$(}*iJUZ`903f>WeGgl8()km=%GvtQm+;@EEsa8*Cx~YoT zwf{$B;0R_sJd^rsW0*mf>hh6503F6aEsW&S*#U#%l)XoH4h8l#Q}Q;68rd*G$j$EH zSev<8q2)~%_4fdnzi~+R&X03ke;0>d_D;C1`q0gn9>lEvV5m0FWPK(`Nt`S&m`a8u z5W(AB?m&*0W%g{?6kC4(-_2B^2dp8cZ$NDZw zD8I|{cAC%zx3=c;=Zj8mH3R&_B4|fs_*L?|D=f3=->VvgH~!{YD8oa?ju4ox8TSdU z4|yF(2KuXgl`6ZbfWqTKNCRH}kp^7|$e3SiXxDwa9^WLr0MDDN7%S7hW7cJ)iK6O% zCOLC5y}++BGJlul)|xHx9`uuWy1HdH379us??A{%;Ones zMPFTMoks(Rxi{miYe5L_dOu+)?nFXjueW@;FD{Pfb5}rI(FySh7-Bh;#Oc5__@d@{noo^T!V~ zfAAUlb>a6WZC5^X$T#<+(ggibep;yy4Bz49JG%LU_@S)jjrIuc>HuV1Dc#6G{3cF+ z24E-91ER!ldt-$sx=X2|Arps`Mm49MgcVQhWHkk+`NILqe%h?^-O32P} zY%mqasQ7KGT~<3*{j8lCPF216^4&ri~jD2OYeDLTQ-3RXpb#ABjh*DFLa3%nF ze$RoIYHfq-YA?LFrLIwXnV@ykmMAyh5eB2^^2xO8>v3jP)G8MS z0~n`_)FB_!94-@P7uH#%xs9lsUgf82*s9-b`^uUVQ4w97QJ!`r$~aCrsPR_F_33*K z#t)+wf!D6kP7V%QIC2c;3GdZ1PVWj^-L37Q^;HOnbSQb+SYy7*lfJ$$Y(k{X3>bu` z&Gt5g|J)*0-0X-~MfBh3w&PTl5`~v^J62{~4rS$&cs6vjS2#}w3rj&LBE3R*5~pW6 z!`}qN1-t;Z{^+NTO`wdG%areZNH*48Vu+E;=v_&Ss$SSY#I6@nzDU)YQmT8X^V2$k zyEq`qH4?vhESHb}?%KW6IsI3~XnZ=^bL;9z^$|H)fpGihJbJvIsSy+4r&eNusP@#5 zi3Ca&h6PMR%~Z>;%=k^@pYVS5QL5gYZw-70gb+H^<;mrV1|NDc$gy4^$bH0Hh5@@O z+ufJ!F`D-m76054_31*XRO)uV*UY4`?fX2|qO!B=vBvn{Jkc%nOPCG{x->tXgR`@M z5c8sCL+R!vMmyKRVXOd)i7JRMU`{<>i_VEAKpeI`xrxcqT9MtZY9^t3mQ*Q8WK%MS zFr#uU7b04>E(h!Ng3!|98n`BIq^)?2pD@wpc*r@=3VQ+O4PJAv>uZBqEbfT)PW>pxrC^gyHf~ z8O$!x)(gu+lI7kfc~-46`o$Vgi!|q0(X{EsPPyquW$K{Ux4AXZR&RCVS>RM(wTm&86|tXo$N1_LZW4Wd+2_-!Wp} zcHEUBC|JBMdWr*rVD=d4s(_lFO+@;pS%Z--B(-vq3IvlJgE=Nr!>o@x(I38G=<;GSdns=0z!Ey*+vMXq#v zUOQW$F-laH(XN1gAFKvTj=)e`+k)y&S+YX8i*%!nJD!48YM-g0_q>ngTq7%;HNymq zkNWDGIlG5m^0?V-&rB8yM(h=(th7EzeYp?x*+0n}DSyzRgPhO!|WF+4cS^Kb-BCUH7qFjUTS654Z~>>cYzWTC#f+ndB1R zQAhpMeo;2~)ZZ96pnK6(_r1<#+QJ_U%c=!JjlA14T~X<67WC3oMW;y_!gGVHs@bB+ z_I7d(x^XAMkLnb_T^i&0r(`C@_S$879%~Y*Kv{9ei&9Yd?M`Ga%-0YUE$)tVHH(Ya z@P5ZEcjw;R3rgSh?*&_VKsmgOHI2Ax11C5jPW|d;!rT7HM*nU)*rYyCb#t$oVL%ngBujyYfkxx9 zGNp;ON2^Zh-V`co-KN-Y%1ryVI@Peh;zeGN3p-m#ZH{#GyZ3O<)s7{jWLC98MA?I~ z%TrHk%gTmBZ;Lh6?y8#=zdixGGWh#0hCg!q!tT3N@o8=^dOJRkTF>B#9k)uw>21p@ zs0F5NFZz}21_TvHt;gMP)ZtIj5JaqgfAmDAEX(3^V;0G99+b7N&kuL(x+-^IEK}1V z*G{K}z3j-WFd642@{qhTowKnCtb63#LJ0)t-9TgXqzIx5Wvi)?BzuDXzCTQOv?UUI zsvs1f!P*(XK#io6f%i2w`!_50C{on@)h`gGNZq?GqJZ;uvPMl~maVlPoj{+FOfu5{ z%WBA1?S0L><=6I<1}+j*m-vWBafs$i@lECCzAW)N5Fn;zRpPH4Pf8qDC#ZhOIN=Z& zN*GLZux%NhBx;Pyacer*MkH|AY1HlKQ=ixG7&!?z#@|N#vKDE^8G=h>^s5-z-YKu= z7{oX0?ALwqfGsc4kLNE6}muHM-oQlx+P{Vq_9t^VPL48&_Bj%~!q-*+nC z9Jxr>2juFZP3Fykfkev)j`Fy}1aZeGw}^8`pQ~1-HVaI&7fUk+)P!yqhvU=y!@SPR zxmk*FW?p+OJZa_!i^FR}NSLsZ^#v)csn|3WeJJ*cv?6u&PP-mP zs%NIGq0rUE$t?1SZ_}YLj85zc<^9oO)q3JSBP+qzis%xkRSk3uYtB2Zn7v{lYt1*I zTVYWm)oFy>%P3b+ykR{tWe_XXVzE`y-_!!o9Twv*b~|FKlE+CSmOb-?4$aRABigIw zqY)q7SI~JX7J$BaF1(#7a4eA3$}*9)TF+srshA|naiIM)j4sVxQNyTD^+0~Z zF#)F|)1^ZAQFHU`gv2DIpgVtO|DB!D=8EvQ_2*ex!6QV0rt*xU-)~v6uOP*D;0d-6 z>Psit1NEW?$Xm850KVlZKbuW@I(#zvouU4T8-2O!NXtCYIk}~DdCO1Gn^9uIc(Ez_$MndYG{lOmLOh03a?^m5br&Y>3!R~X z?KWFuUkGE)juMLYx4!O9F`V%t(Y6zaJW-sPi`I`}JMlP&LM2;9l7Ce{SFiiN!597E z%rVp@?_8(w{sP`dywkdRyq1A2om)aqc4Qx)MX~p1Fibsr1^OVe4}s5*8g7tx6!`Sg z`FW!+A2FU4eQ7LtRJ#S5YQ9R}KSOe3dqU6lc|^_wg+xDwsGo8x$_O8+mQxNOPG;0I znFF}-u-5QsaLwrj^3n|slD3P3#$xl=Oz;;WR+ zIdxcS-CkJC9?S1$IKE-VNvXDBj?V{;U4uimi+I|V6Q2O&6Q8j&2C->RRU2$Ymqr?} zd9<~C`AS2BO@`ZSvPh_un*}uon2Xd`JDq4|sAGxeYb+rYAE=YDj9Q=dBVgrI#7a4a z<*cYj#|!YGz6L9Cd$eMj58?%F|JjLZ--PGU*MycFDDo3!UgR3JEcE%hEi~SF6GT#{ zmu^-mPTe>PvrU<01h4#+%7lN%q2P3B>^#e_->+kUz_y8%%y1I)7OD@GxoitHsoV=# zA;438syb}541-SRuDNDoil3q`)=Xx>7YZa2axI#Pt)5#7Z$Z~$E*L!zE2Ba%MHCTh zK);n(p2Pdvc{aWLg@*7F?3Y~gvo54fA1HV{AZW@+si}odFPr`Z;4$xo%Qm8<8ry*q z842mMejYfFIHSRH{UcF#JRoil?4g^R0?5mDg(Tj)wSEwN%;|#>GVi0$aYEMLsLdqo z_R3(PM*Q7dwhQf{!~w{|+bS0(_y`xBtm>VA+=GZRY!NPlrgU7`c3{2gLPc8>L*RZp z<}x{5-r}K4TU6K8mlQm|3?I?uz^MI1H+65Nj$zO!o`$1cqFM*WI`yt$-c5P7TJJ;g z6a3Jm2|V^ZwuB=CoXcs|{z?xwB)NEIi)aAuB!3e^?QEv=^VE23q8qceeP0}%=_*p+ zFj*H}r*7~NKfr~KA-~yH+XPn`f{&kt#l^R6c=%!+4x>rBBfLiK?kF7YJDw`EmnaaN zDy@IW@;XLO_m3}5#hFY6_B_WCp#*f_&SN{qfoaKLYPq$^XVhActZMhB_?}azn0#9+ z6$5BdhxP)2SUot744u$BQ-pr)~KQxRLB z>&-9}K@80~4n{Y4VGlpk-}Uji(V-csNpgqVBgedq$hY*ULQmTTnm!;|By87554v)r zr%+n-mr7A32=6pw@0m8~=5U-t+RK*Hj4$=H=DJKf3n>!Dlmww;m&S|RS0n)G+6zFX zV0D*pZ@!ald175zB=ks+FTep{_yQ{HQbg@)Z=2!o1;=A~T$iZ!d-A%bi4z>hlItq` zR;G&$8mVKfs5HkYU+xhFgVxp{64(IhTGds6QEIMuso#DOZ(Cik^>U*g52%v|jk|(| z95>|~P}tBFAW5` z&cS9&Uf!hxy=Py}A`x=Dsy?(YWetc8%cDn&xH)Uj+$RdrODvV&HDx1!n zO!QQjuc^C&$ieLz7{9?8caR2xcobjSpOQ8F=6!lkAuA-a&e5pIRxT=sMAmPliib~e z{6qBw2{n!LT|?Ar;L3+)lIwbf;dd;PP6G-_uCpbg^TuYbCa6&jJ8>(|Se}lH+(upo zSq!`E@^ID~itm=7rG{$uL;5eEWf}y*`;C`?3fv^lBbalM-)Q}Qso{vl0;{Ek@MZ?^ z-Q(caDG6^fv`9`>smU=>=9x$TckHxKV8W~akFu|hi*noE7X(ERX;6`{KtMu~kQPN$ zN`;}jJEUWP0YMN6K_rG0C5EB91(EI;V(63{YKDg2#&hny=iJ}<-sABvfJlsdAv?frPTg3hSk#jGZIquuDMapRL$6?;posW(z2UuRPyxN^Hjc^oEv zEmr6rECcuovG=X>d6Nv&+NpDqY3B=1Ru~KmkJ4b3{b0zir-8jN7#cAtJ0f|c}ylp?BrQZ3$ z-FeOA$c_JqG?S7DrsADb3$Dg!YYRbj841b@PT@`UjX@6+)x-(jD%u;E6Xah_0>uhQ zAn)ZHs59@Fv`lbJB2y3+6zAxu)o4MU-Rv#87ySQ1Caz7n~A;pJT zd(V(v)?F>#-7{ptrmFEcrw(mRDs!(zY{`zjX1ICYq%&@g@D=q%TuH%v$57Bf9fyk9 z!Fi3@=X&$2ebl#9tEZ|~3!CjqAT$sfgosyi%CK$WrMdQmNxzAMNUjvQd4DznA+HYE z?Yj$&`S~yH(Q*TqAbp$1J}Su-6P{UqxZFrCOgXmUbCgax72=i4nq-LN=*Rmw!7rz> z*@+u1N(R5?u}3WW1Q-rm6|38|ci|9e)^3m>1~nCWFQ1x^_dANKR@WC=Gjm=2rd3je0|Yr^+a@7*d23;790FD*K}Nj zyL!XltdQoqR_~GXbdgMD6k^fyOI4|UOxC02VIU(4UcJKOjB*gTH7m_va=2gRS>}-@ z%7R$5;gMjnn%pCdPF0C6<+bW)TNvAw{$%th=hNiFLXxJwdx3i|O{Xzco2US;B^N}Z zq)o5YsiR{0oS?yXmWl2r`n!kA3=|C^j0=9O*ZIE+Bo)lRSoc(0QI5zk8`&VPQ zDlWrCL8hY>JJ+R87uvpmEV%Hy%1YuCFoLz?ko!9OPin}4ej9qE^x^4uO4AIIQVBaL zDL<8D!I<&G&(*YZ;7=dnuh-RIBBRXYC6*`sF+OnQv-0k~#qkq_c!-^3#1AWdk(0W= z#8r|Xk4kzHws3jL#L%-i#OjMCxFR*w3zP}lYxmaH%xu4Xg4D`7S5M^g;>xcp!Z)r( z?2-*qYD78)WO?khEVX_^9PX;5GS9S`DYiMPaLcnrr--ME>1Tcfb<+@RBg)hY8 zs~k&f7IW_~cyadMRa#jn)2TA%aJ3FCCbEex#$Kj>tL#{~+I29gM@M<<?CAi4q z&bHT#(`H?cwg8|n@9xJV$j6h`qr%-qS5pKJ4^>OL_`AiXp z5mvV$``4YGi_>#P?v)@bUmSfbt!y;8w6dv zBxPHG5T_aWlvIVtO0}K*_=a{mH&yRz&cY_0^cM5GRs*Zh22FU|2Q&GIDbMAs*$olm zhJ6lGi;ViKdgGFzp5vj?7S zLB_t%dQmjL;~-H%%u?IKNbT0ec_vH(8K@+jsE0LHL`A$Y4Hb&f~RRZDaY8 zIUAVf!S0kw_Ie@tM)M(&ZIlpi-hr@nS*-|=SmCCkF6^74dt)|lK?i;mxb6JN>-vN} z#`gl`KB)E`t?S4QIPT4gc7CUv2 zz?0#}^PV+{x!4mVaH=Hk+$LfU6UV=atN(I6B zK8rj{JC7h3Kn*XBHmI}i5MSxe=g0->x?Ej#K%c7+70t*Z(7RY!l6uHr4`AvEeOFDQ z`z2hd{kArDoYP+0rG2JSTHbp9A?mxz7bW#tbQPc2gHa%QzK$I0rjS!Kj|R)3QKU`@ z#r>pkxkRz5O50dTt8xhP_~R^bcgCJYE}P5ewb9Az46xIFFzPEkS)Kg@`c}jwnfcMN z)-qQ(MMnBdIGk{-@(CMbtlR3L;3i@|galj`ZO&1_xL(y*Wp%M#C;oX!z;?;Hfpnt? zZS%s`Lc!yb&o)Jk*)1)^^lLHGiKFh>nMn{)rTxK}g8Ai@$d=_J@g|Mc(mU6K5w>UG z_)bSWzU(`Mr+I2!9Y#h>w$GB@xvx5M&iu9?hWS6M-tdt~Y1X-5xGF;+4S!%woukJQIj zWm`c^mBtrT+TcbAcYh`fU?}O=T@P-!AR*qO!_{azk=0VE%8=UZW`}KUdddrh28{~- z3_$yr_a@67S6$(=ZT$mA1{0(4$M+Oh|!>?bkZD@6{={ zD?^r#F&g81bX*RaBFUZW;onItzl@W$;}|OBe6yTJGF24ZwR^@%^cJqLzUVbEPN_n} zLRRKi@_`_Q=Wy9$&jU5Q2{Yx4pDxmnFF5oZEICfv9!+|-9R(`Ao53(WXYoPf5+mQy zRP+5ya1(KZ?hfg@e8Ed9S4CO&%ib2r9btT=R|w6II@OV`clkU{njt`kz#Tdjb@eF* zUhEhLb*4Q`Nu9s5sy|dIifMVj+n}-i1^r0hwo(CCY5x2usQTU$U77C~@3|k^b3n(< z9}Te(<53Sis!2AVBlg?VNKIYm=G94b#uZEz%S^gEGp2S1i3yZhmv25^ixX96+B$xG zIIeV6fAqx-?Ox1vCv=b^wyf`dDNgvX#;$fm?eVdWDt2r*cPMRm(;RYFS;M9i-m$xC z%sZGcU$H5~xt$d`>FgY%TchYdWH0X3JJ05l{7Pp(MkPR%ld5sou%qk%I(7fw;xY+6 zqR*Eb0|lz+)1(i7Tcsk~9yxCQX~@KK z6`j?fKtHVwqkw$q-qj~@2(I4iVU^xu1_%jcolM~}iIUO>cbc~;g!Bb9PAG4Q^>d%; zZ*Xyo5f>BvL3KVw!z`j%iLuJFjKRGiRMF(C?68jdIE&lw;0oHMcyc3&g!f|(PqO)7 zV5)+#hx2&~*6F%?qtZzQi5wXB@1Fc-MAQ`$CgL2PiMUR={rIZ~-&{lfUE&2YEkCvi z$@=W=+5u}p&~4J?iZ@Cq4GGwda~!)nbn{m3Xiv`~gq4e%wIv!!-+MLet#^poQLg7& z(MKt%DWyj-v_HQpOoReVH&3Xfa5xRSLY&@`c^TGnGdo}~1Uf0Qt+YK5Bd)^`G8P(+~p;(UNz2Gi~(PYXzv?@EL@HxhKiOSAMILoj>Wv5Ev!X zKS?D()~s*aVk1X4Q5!*Xoq7Gj>XMjBpT2wl=6(M$k)J8fM1n2hwuXiVkHwcIm>{1~ zy5)F0P)L=PBJ6Iku6cMWj2BPL>VAr`)PE{^|N2Q{n_RU}*Lr*KdGTLNL1+s9`b8!x zztE62=oCJssikH9ck0e96c&nVjdNw7T zG%gI~8~z_l`{(cb`+LE-Xicqdh7EtWc`%&mjAvW*=H!z?PCltLU+I@W=_O?n!tR-- z>^q5<_}K`~#GKEi8?UZaIV(kUw3hhG)4;sNbU#P6rDwaR@{%S`zRj}woZVG0t5Xbhs)kqSR9Z?^l zZ$@41dI~e_yaeW=Q^pScp~UdN7coa7|7yI8WQ^+L%30nEu+?i_!w7P_ht5MI)-~~N zIoO0=atXjsz|8Ua#*trs?GwAzy4{6BJ-#``PQ90+E|R$0DCJWxRYj8f$5z_^={UQ$ z!(_j*ChjYYlQ*4(H9SW2%zfMMf93py=KVwgGG1A_=NGc(Ry?cPyY5>uwGY6(#7djfkjPd@v;fagCclE3u0{{j)% z0j;UE^_}+i_TACu+qhMU!{lh0oJgMe10aCO}?rly+(%uTPPMT`$ ze@5s3{TiU<0t~{lBphzI%_-_do0+=?^Tnkd3lQ3 z%eJtjm*jfI;&+STsFk|!-!I-qRlm?0<-JV!4b-;Y0?fH}>%8QzEZWaVp+i7)WxiGT zf%t}0QeIwj`Zc?}dvuH)O16TRqMn!TyvlW%tQ;D%1S-#`#<Y14V_z( z>i!cowG1|H)@zqAP?Ib8loN_|o_}RnfB8F!xA;ouwSawDvj*??;x(zN1IsDuh}VR3ORxWXcP zSyFw8tNxUo?L(SdPFbJ`H*C_<+FI38Z%yy=D5-+@N8G2+pKs`Io}-iw7_6Zo)d!sp$sD-sMIz7 z_xBCrKz-&%eG65s2+S{-)0mc^{0%U_)pfCg$bVo75)X(^rt=jS7yB%@vmnST*4FBF z3xeGf&U?dAqG#q`l&WR%;+Y88B)<+U!k984kVv9eYnSl|kP?AauNS{M->oVR7X1-JBR`kk(ERDXO7tIyrAR2;>8?W&@5t_?B zwF|f1fzK3o+V{lp&2MfkLad%Wd-keYTRA`?n;?Wyd^QVFiL3_)<(j3m!oqun80r1< z&)*BGV91yLz&ZbZ7oR+ph%_Ym!U>&|O0l(OZ})p+PjTe}PG-qc$IG2uFrK`SNK_*e7uQsdLM37)UC+9d~m;F34QR(z| zVk)L|am&@3{bHqauY{^_U%+^r93cyNm9`f0CVLpA#@^w}gzfjM|C(|k@iW=-#HxMb zYxHteADiz{1PV259;=~Vd5k1w2(Q4J*tDwN{TranIOo7`O%*4;GmLEz*X;b)4#6v* zzB#{$F`lG=eWElqHND4)!}RWs^qixZ*YFnnXoCPk4uOts9x$S8AXa@!e{Tb3nPk%*2=_anGRfL-# zFaA0~=7mOdLyyb#OQ0nEOON^uS0%UzH>xh%A+AmVv3`~=g1pf!k>-}XkuWujvfq@4 zZwaWW)-8A<(LvH5@gmpN9#-d?7#*7c6;}S?6p>U18ti$-~_VDfnLMoG;f*M6eC@sc%ldL;B0hq|AJ=8jlc<;4Vj4 zXQX}vDTO=4J?P$}<*^mRZ}l0kb4K&+vC*CT>e=U2$?z$epS1mY96Q;oFgnnQ&*|D7 zs>`aJrA>c@dc7PpJHH4}4INEplSQt`^?|!R8o0Y(-4zwn*)Ec`VQqcL{2n0jBguFF z-$C+!?M73%goQ<>nT18`p4&?;m}G7y$|KCSc$NB^(?u?k741g14~h8Scl!!+%o0;{q=?OB~Ap)y%Y5l*MV3N*sPh90xc~c0j$eewp3?pd)@PkzDfe;{gdJE0rfC zKv{2ib%eGfQ85fSh}~VDA*Y{C?@}3YyV%U(qsWcp$;-1SDLq< zwQJu1)2z8CM}P-%>N(CqGN&sVq&n-GJVv(jb7O8(!lLnK?=iBf zCb3{<0E?)ObL`rW?i6Emv6b<`&Uz~NCu}8z{;hH3mw>>IfJ>{~a3hP|@{@Tzq7qFL z8K9-Bs;9Xvayd(~!(6Mo(QnfP{-v`%TL|gdTEekM+x~1n0apuXp=k=p*L0IOm`P2= z3p=|pi9t`L9D1w=>BugU?$oo2O|~ER$Z+3`JL(X|+oK-tnCVfvJwA$DWIdiEjt3IhR6&S9wCk zk4oXU=Thb{y=e+<>-7AV0ji$&=D9s4N$x~ZWQ86XAyxCP%~f7b-ZpHDzGM8OE7`a? z{HF0>uAcEs)BCw0lf+-T4$iA}$3r)6aw#K!JXu@yu2P<&mo zzE0^FyNR_q&Y>HYy9Z?%Kjnvn#0Y9Dj=vg8bAfi3dZ5ozVWZZG3|}&Hvr~$%eLX#w z3b%ecn5|Y|EOnmbf}rzioa;t2YgMiIPAyqmR>|1qxEsq@gHTc91g|64%@&TFXtZ^Z zh^p04{&%ES1?pgB{K#t1qn1u*uP3Ta{IFNtHF-P-x?=HoaypVjB^J2fWkP#L-QD={ z-S`PhiX4y>Y8$9I!gj66!TDo!4yNel-b*vy!D6v<+n$(h$?2ESyB<4hxz7vOS&y3& z4+6{fS1W??`?K4l?|c&t-b)udTBp)rsB5)Qpp;9k!0+!M`yPkV@*iXQq={~Ido z0ErZO!kTM8izm->%FnMzYkOSu_KR)%yW0D@uCChkZ1(w8NCLbXiNGwRd$TxmR43%U z3-E!tOr1l-uCMrxZ=v#G6$aYrF~^&D2cdnCc)Xi9w>J4LRV7FJPL$(h9igJ4q5-4F z+JntejHiHpc@211elM(wmr*Tln{%G_4V4bLvR-`Hs&kaT z8&tCsH1Q)nM^WpI*HvPQ8Z1g74Ak{nmf{-_p0pb zTDIx}nN#5!z1w&&E^0xqBcDLr{U2|+ZYl=vl!$Lz++R??3w@e)y^NTe;}Md*WnDe{ z_B8gvG$6W`tGPnmhVP`Aa|lnkE;a{Q2uu~e+`(ZTcrEW9$>@+sAs`gUv2NUOAqE!C82Do4nLel<-2 zSdSWosg+eLoZRj%eLA@{mwGW6^j5)6ZLvFKoy~4niJkexw2s0wur*~v-}_5AQ{Vc* zYzKu_xnlcO+P{&BTRslHY(&egZApDwzK_v;`HubGmf2<<3HNKca?eAz0<&J0l=ZyW zx<=xrG23sXCx^vFL!jjn2%_b@i>W#G>;o_50P&E1ckzAOjO+d2^JDH9QEpX+WnC?; z_!u#$n=Q{DH28mvNdL|2VbvS(hxB+7@&xKDT6UXXZc>c_dMb~qfXj*tp#wGj z0_i$JnJSgVH@?jFR!4KQY^Asm9?V~!-OY^wrpgNN9+|$%Nv=zMwCF`YbbQtAX%@L z72xrj^{ki$3OSLUJHTtwaZf?~u<$-efM2?6_++s35qGiWkiI7f5H7W*e&H463n=XI zLFe(6?%=U1cX+4Q(I&;WQSX{VbPbjrT(j{bWiIDQyyMVM3o73jn zzh;{M0?`Gdg+}c5l4XlD%gVmq*Ha>F>F1 z{Ww`8>w|2qim5Y!^p`JJR+q+fmQBPhj^=ER8h%tI{c5)N2J1u)bM(j+ zSw9I+5)kQT<<3$;8rz-trj#V>xm=dmj~0vY%Rpk_tP_=0&_kWQwxGGA3Y%8*Fcqlg zp6oL7I-;LxP|T3KZMm4Q+dyn3C~&XEg&>@w>%Q+>EQ@(w>mf@lukW<@;erAj1gFOb zTZ{c1bL#w7!-ivC$539IF_E0uJod|p;$CB!j}GE79;|a7*7YY5up?gBBms(3v*~ij zRGBy}59-|$(T3kOdMhBLPH1%OVSO;u9C7G-J)xwi=n{n4NqlJ_N8tM7cRUsY*?hp) z-bPn*oJrrXm64ID?>su(r*bmcS(}_Y0pg(olhEC@Z)EM_2j9e9`U9{#Wt~v2$phKH z%r*Trjrz`(w5(M9dYWb`c;MB`;6Y^~`P2+<;^XpDxg<18FZ4`+ta#qD{RY~E(6wNw zk9(lebZH&G%Z{B^8qP}pt&rP1@iXAq1RK|$h!`}Sw;zXYOC2aF)pJp0n}ePqLlLu{ z&s_=tcZPEHeCgVPw~g7oMI0BEa@_J#75Vfb;?*EhPq!OW-&pNVdGlgIKwG24I{uP? zb=<;0j!tE>>@d^zG#&S8-U1% z^wX(4y^q?_B<21Ly{p24Oxqz0SMZs8?-zb7elSTE%syL<8@A9BKOuJmigfov2CN`V zo~vc^ZGh#rDR}KEZcg};#e&QGTnb$J9ZWjn%(UGWZrW6vQen^~ok-2H9`Ki}!#_&D z9W8erwWU*TCLS~=*2i;=OEU^PwH=^q4&ii*7jQ-S`8^^BYu~i}3e1%xCw4oL%+o-l9o#Wg>M_h}U)+sM zN0U$+hV6iWeh25Isr~A?R*282G!Uso0u0Vk7F!ys`%#$k0{iS zQsv-nQ8fof_8`)_AFAf+!d`sc4P;H=Ve(jm?P89PY;1;(cEh1Z(q3EA+E4amboRo_ z4smEXGqZuYDmWb8u2QqtpFZlf)h*o)h~D*v$fziHm?=cHdeWc1%|v`(jr(4uNhjl` z&M{U;n`Qy{MNMFIDBeG?1yKUxn}tI|>$&m>!)Y|U^VZ*T|W7at#Cj#mjkj&31t9Teq2+fTTy zo$pIeB|aU$z>`=yg7#N{E-JEdUytBz(mV`A&Cqi`L7E+JDIE9pp-^r#iQ<4w`5QCc zGY#9F6R0usoE88xgx>v2G4(f2AyG|2fN6=t9Dea4nkn!WQ`p^z5qj=bDw8qg=T39f zt`w%$+Fjg$5Q(#T^XTd+6^aLg zam3j;xn(DkkhYJVA-&XBV^7~OLgWAgDHHe$96QtSK+R#zor?F(%r=FQxB5=bfGNQn z{zh(x>MBU)Jmnp9;(k#yw7EXOWl5!L=Ojwjn+p}a0~8<@0FOMUZw$o`VjyEtaLw#r z&E%={d?q7Pm1sKYB7mG=r*tN?u5?^Xy5sINFVonlA${jvW@aYuUh=O-@{`w%;wqXY zl&pN7C8R1KQbF^I?1~8Wz*WWmfYE0lBVy&-nm51}`Y6(OG{497UCGfSjFixlj7b6x z8o_KQyT`#sA8I{NMj&W+LR$Y6)ew6M<)6pfE8w98xtF`8H9St?iX7-mOhn zSA8jK{QN^^Es*bCXt~4m>j($*uIl)^lqiwsUV>Riyo!4#8reYhM`<;vnfbOTDDN&K z@9c5s81(DY7h_&mjVa^t5501|`1x++z4_|JBaV|17$`f|n5+>e4hG^I_MERmB* zi#=}w*)+N71+X~n$#nUhq%c*+m_!E5)q(JX}CLdTOv#l9?FRR zlT7LFzH`?t*q&r27D8g(_soFyCE zh!nB3{MRt`zX67y3lmm_uLPA_8Dv`)Fpiaz7NUfU9T_|E^KK-pe8(~NOSRVCwHCCi8&QdKE z$U>Yz(Yj-Gx)JqHN}@!DWfD+GAk7)itqc>nNI&|X!gtnKJ_N-8YrO-(E0rWcXXjwi z|DG=K--p&ezekcCJ~0NR!d7Z0I-u2}auX#KQS|hI(d7M2S^{+cH%dP*C{qSOKvDL` z?8hP|)*DdS;$vALB-lEUP}H!A{&7C}-y38XGXUUV$9`WZxnOr)Y&Kl-ySW$<(%TN& z+JMV2q9P18n4i6aPc)MXjAle6d->O^`XG;=hfhdIsH~+mia-y%?Dwb7kGTGZjHIrf zG;?t2^mkIV(8MV6yr@BkoOu>fmGB zWL{r%gcO4j?VYsEX}(R>KUdJu-&;N|1Br0TfmuBxMncuXZUZy<`vvuuV1UV{KS2h+ zv~}#L^oN|vF5op|OlGeImq49orxJ(HC0LaAij^n_lvXn=_GU^$3-0YZuu}>M-{END zffcgGDSx24){$$_Huuvn3w^xh)4MIMk^b3A{tp{kBsP4g%BwazQttj?><;mM!gf`! zmgAHI4r$&PbNQP5yeI#c97`8#exqYmT9_)VQ2B=P&8fG~*eM#XZ35>4jv`LD6`MZr zn4M~Ve%<6((k_%Fdds6d)Xx#R~b9o zRVf(f%O!?=>gj!QKOne!euoI%2ybor*0-5irRlo8PyBxcI6$!#06)9t{tv~?X~tmL zbCv9}Nc887-VrDM8%l9c@${`cps8TV-?`sbe`S~|BB1dqQBB2@85dNB2ax8cp(l<9mu!OLzTqhos^Qp*SJSV6@E zczco8R*(epWVs_Dd0M~iE-;%LUoCEQp8gf?VNOC#O^ssgej7@y3z?miweh5ikp9~x`za)cE#+Z|En|E@p&mCyp5ij<=1Ga`pL zmxZ|0+@mtg`XR0;Z_~U=>|UJd{wHW-2bGv-CW{O5Yay;k!Dh6?BXahsVw#kIfWaHy zg|e8uCzh)6psw7J@vXVz7oYfZ>tnc|KYy-U4pC7l*=t}|wV?l2oK!~ z#a?WMosKk!oBfjC*PSe8*5apwt-I}xT!%h!7#R^2<^xDn7CVJx{QJ%Ox4Sk;!|LiJ z!PIX{tX58rP!0m0Ni^G)V~5|wTaaSy2H{&@IR1C@c7_?pC7IkiSO#B$pfAwEgot|t z8*(7-RWsP(e)jbL`vd=P&Uo@CO{ajLPzFgpKs|zZLvN1H5mgjF_7=o-?R#V5TuJcX zFt$_PO1S5!zHDwGigM#krnu4ZI^`>WVIg?8kF+MxzvCT$LPTXHB~u!j7`!{Lc6i*k zGIq}iu#AE(zN->fDhkASvew(R;su>XIjgD%vHOi~@;&5D0)Ue3I@J&9{E9vP4h+nv z3GQa>Wg#Hh`F@LB8UdBfD`T!u1GjuhMu zz2p85a->9tao&>f--1`X9h%n0HTmBxz+x;W&bC14td-)RnY!={&Ev^HHjMuur3 z=i4-N)lCfGb@hu6`(qD<=+P?tx6|gYJ(#i-d}(P=4ro!D#)qR{0k?l|=}XF6?Cgc4 z^z4eUC1oZrb#dKklxJ;dTKco@BoFT)HdxRCSqkXXkCvl>sbwn{BpZUy(ORg*+$$Ec zW8W7AjH%hF=6mWw*^`Ih`~Ya#^b3>3zX7>_KO1~e*m?3>w{H^{U*q+uC!ShNU9%w=D^P*dhdNaP41m~u zmP08x?5kgsxT2NlPfvBr`bqR)Cw+zXv0wy(V(EVR{58N^s3&3)L#5-Pe{*BcpIr9t zBM=p95$tyPskXMzekb~uD=M&IK*jj{0yR?Do_maK%a_Xvr-B@xF4Q&&kYO*q7{ zG}qNweRTTYT9ybGEPoWvq+F`qLq zeRj`oK3dwdnPsncjwYY3@gqgcp_O6dO9>LnY8%-~*oh0X_;@psw71RhMiVadr|ut$}_{r^E?6rf?u z)#pk|ahK>RhlnAwSvhxeNXQi8=RSh>hm&5_S4cwC4v!gfUQSi!GZvLc_31!Un?Krw zn8oajScDvYRycWWxv-td=GKS4KWqiV|3PR04y#PjRR6Z#=i+@4*D&QxS+^wyF#0u; zJgcs3sIRaJOXvlSR=v<1yVqQQ*x5I)eOD&+YM4B%l8CY~Q{sw^rjXtO$v=4@Gat;y z2cLthqYY6n9ZIi(k)P2I7{nCf#3Om~62-fr?8(cITKNZDMCZ3gQm%$|;=5AZz|jPR zGIS3)0OGc>J0Z|moujVOe`U0X_18^$rc~nT(>cOGsa5ucRdp|;DZ@9cR2=Ip=&?aF zIa0jP^9PG0*Bi47ZZeuI+GpRy#XB-j5WW(7awSW|VigQ=6Pj1m~Llj=Zh0+ z)P6bbIKjq(h+2R#{N^tMWYa^~r%%^7Znw2Pf%|+7Caq(C1YgNw(=%W3VmXt6)$Fwo z@dw#K!#TDqW+Lp>8@V};klB35^>QiS?`E1-!{vL87Ol(up(pgZ!{beQw zXXJ;fGI$?6m>+M2E+|A5e)Pz_J6Jt0u4>fZZ>-~~rDx-*Y&||$4VAIRU;1@n5bQSH zEiSHN?9zbAjP?~#YosWER(_MQgAXO#iVq9fFcOB3{a_%Bh4Jp^%wGoQjkZ^NUXq1V zw)c3DdQ3bhk#4gX+~*y`Ng=vxC?`%0#Rcp2;`Qbdm0@d*;v%Nd74z?YgzLs;TcwZ( zj+1A{G*>@j`ck_>=!eSWbr^itp#mIsSM{@?&vWvHHVbh93be?Qb*3yQc%D}0UHSqb z<`}zy`p@{yKQW8Hq9GKp@bETLN()};3rw`H1L=Y2yso6I?{fZe_FKC%n3@C&fEzrm zZ9;2iLU+Y(U&(sBT!EWD5b^X#9nZX$SL29LwIqDsw)Ua3btTp0qpFnAb6UY*tChW^ z;kEtDk(K_?fg8)aZw-ob?Q!T4YoRYu*wt`XFT;7ROrq^+-}@F+t7m_?Ed{hoI1&*1 zNGLshx{_I3(($ogC$laOAsc6IVM;iyv~Lx>s}!iVAMwm0U6CD{YR@RzxTTo_@)9AC z8wp}2@p6e3*&TMlDt3bEktd)yjH^NqhK=z-9Nug-SDKd(XS9Zl)tzZn+Jl(x%wEb| z8K;Uu$1<<&0mYKWIJy}*}$t%32P??;M*k3x#{ygtWTsW8h(o#xFs$um# zS#v5SBJ13EN6Gt&3nYk3CN_>!{sf<@6dD-6>V5+?uV(2tRu*QkHV%5=6b~a*UfZ`v zSD>hw<@-ElCtZ5^<`-QjffkUCcH=?fS`>BF;2J=smxdl>x^ z&YVt7gS6%D??Q!7HErD)qPbv$>!SPkYTKNVKe)U-U@!xn(j%20=}3FggLmaDlue~UI<1Sd?r z*Ct2q@UZwC{5j^6*lqZ;Xw`T*0IP6b8yFTO95eC1lV(UF54X53cl4#se@rj{HkVG( zRL;Eqw8T>llczYLGO1GpDyq=4v8n9!WD07lkH4P@KQtKK4_k-M<2Hj#`VIrKhSfVe zzVmzSo>7dPQlQH&(L1y_f8g)A>*yycWaQVb{gR?hMK2O!JSs$s{%U=b-yH5#JYT$W zfL!%Fkj`9LFHt_T33)Reg+vsRVBkz*)wohdW#yNOQ-=*EwCE8dzRAqikVQs<*Fkl= z2VZ;oBXNn&%CU7Fmz6m$OEfVZ z{(u<_?mv6t@xj=uvxpws6#_Dfr z8`+74w8Y}|>Bcmx9P9(!W~aDw$$k5|LA^H&AB(FO{?O<7e}tVTI#^Fn_`3^~OMrt29p-e@>uTfOoGRKt#4Ls!s4&MAy_U*KU&|NcPw#MRF z3-*;Y(E9?CW)=crs{&d-cxParupm?rmWDw*nJ8mB7;vet#Ffz)2uKnLSUO3j}QX*Vbueh;N>n`R))_ zl+{f3H>3h4fh#P|^5|n6D*9AjvBG}%!lbDcYk$BOiHAwW5-7qzFUJ{Lr~YXYtw9x- z4unx&*|8M2nu=>W>rZn7CpTTVDr-%Aw1;vsh}0E5GOTs0aIU*6kWrfZ6-UHQX49L} z2f3WU@19klM6N>P3F&tkU$OA1L{NI@li${6P}+cyt7Sb@OAbbVNufuH-*gPR@TT|E z=_Pi=${zDfW^Ueav8aqy6rNR<#hYs-M^6(YHC)M;4_jMx&~qzqEz<}h1B;eP6}XFM zXCc{dH;QXLkwpHGN!4W2B55B)tGo3;NBPYKg^f^6^V77YoZs)(F7ZTQ*#06pJ> z>FsKD`ZIwg7wU@3*^-ug>sNf3*WXuIBXCRm8JHtnRCRGDEy^;{r){2LkPqpBOu~ry z4h$73+|DO`@O^q*ykZcM0C8qGH<-ZO!Gv3|^%BT+?RHdX=G7%<=;1&$qhd>rjCtkCvk^jt2vF_XO zs#ulVnaTMipxsC$mFv7Ing+J*xrVOda3OBzomXF+-jpQ?MP}@p)`OelMGyLQipJoP zeZ)gYJ4x1)>*t9n2h8@*TiYBbl#ckhKxXfcT1mNnksDS$rwZ}uJXhD22hW3I?h3OL zofg06S{8YBe|;`(mfxxIf}OzZAVs|SN}};g!nqUBtf|cJnm2;dbW#IX(gBTW! zPtV_1sh$4Twj7S>&AC2$*n3iw3$wuIt~tnGslZg16>BUjBoN2hcib;_Fl(Rh1>zO% zw3-iXY0&oGG~NnO2fKq`X%dJxd2-=lm=yj2sZF(Sk@ct$8t4NoTRyNk^*m)A65Sr@ z_257cZcc}aTLY0nj=RIKS=tZ)Sk&^8A>r@5sml!?!3^hopw9;6^CxN2I&a&DTON`6 z11irKRI-56o1I%?7X;-5TX+d`X}dF20LMg_0JSa8gBEVRQo3$!W`~O6#8;!gL2GOO zI*|ANBN6Q8D-9|oMx0gqo;kdxvQGQZG?-KK1=VYI5K(xySEo$uw%6;vbzc%HWV<)| zF)O2Rww80$YIwPbmin@j)$^Q}9BglcDF$TGT+wsr{Rc)fj~+~|xHXWuvq`L=!y#ap z3y>(gjkul4|Jwu(F1g z5x_(w_DA=1CtAkqVZHb#N)uV}xjm;Lqo|?TD4kWNxs@vUNB6h79*!Q-QBw~Vbc(@M zXr0Gg(?r4VtP^WC<1vHhSmT~!j>bJU+i`8You#{;2h&U?i=j;lBI~b#gf#BQiWb;w)g}2<1ky4%{;#)C1GG2WzLoLj0 z2e%`vAOVL?OZPv|Q-X*J>I#lwsud9(Ja#N^0*Z1JixEf8$t?3>~hA zTUwx0hD=BX(5xgVbfppfh(WMMLH4j;z%J6Ai8l%;zrbO0i zOHQ;>{JLMg1?_o=VI!2!byctG>jbKPQ_-~Zb6;+pMKPvT3qWe$Ts|(jj+RWfsoqUJ zoMt+<6|Mm0G02t{7rqx_6L;b`7q-1>4skMOyJTWJ%$jr21|d()Vl3m>7^;U#$1Qn* z|8>OzUwn#Wf_8o?bI_jwJ3C)bVi|YW^S}kpYZBtFq!zPNG2texe{%g_6649fo_%{b z*5Y*`m-EiU8=w#b6h?B@yP8$huG`*>r^pn#r6wCR0@d&9gGQ;BP+XMVn$p^ zah&IcUg$SgTP*`nyRBU`wwQEz?O|=TsF0(dS?L$_+l^rzg3joQ>@hjjGXC`-F;@S+ z*2eR{!%A2(K9Hsqv0qqf4FNj2a&FPWW;dj{W={1UqT=54V4eZ5gTml#f^d>s)cMCp z`*1F+SY&&mjy?Z@40qLxu_ECxSpBgOu|3nJ9;4n!^9AWsLUQ&+}Pg*u5Zrl-h8zJvED zO=>T_03(XM#@HX|muv(d8@4#R#mrIi>Hn7af_z_)+!{6k_Y~?^E1Bt+L5_^knIlOV@c7>g)G#C+Sk8@yJ3w^WI0oI; zLhA={hIck8o!B%D!47@GMJ*NI?x&UA$_z@kdbSn!#uh(nSldM~y3SC*K@$shbyQd= zv!WaoW0Ig|p_wf*AzY<-KYaP@$ze-^i|EJeFX7?(ndyUJMHLZpu&JLhd{R$M4JNop z&~2ZZ!CE1;Z*UQ&N!=8~oOdBa@+;!D$PJ>*IemKS*8z?ePc({CdswW_Q*c;%Y_e4$ zACfbN@YAp<4?4G(*F(HhHU%793Y_2_LaU=+>T!d`S`RC9J^xi%U^XG#N`~l7E@fAA zI9zH1f?580>Jf_%_n>I>m3s!n|-eG;j6-%4Lr)+l$#sF+XO zB1Y=ej2&7njFj2?3ZuSEedN?IhTBxT`bM?;6DlKB^%#Vm3VqIK<4bd8s^mDe@*9b( zWNR*PogM)?zElTAO#WWYmw|Zsc%gn_o8|Kh!vlk!t|!-r6{eoTsKrWq)%p0EiQ$>+&$Zz|AR3%x7f>2w_I#Xx9r+FM9tRb|I^@9k$tEYfdYx;doYmsYIh z)eF!3GA%3$?dX{=1iEYf0poH{tmrfXl8FQg&GvFeq2D^)lD-gZ#aMmwZ<#nPU$z)09f7Xo)Wp0v@2$S_Yr{w5{$HEYYw$?4Rs z?m2*NSDmQJ8!u>~;#dX^s_ms&<@yDv$HFLQ}7{#;sRtK_e zc03Ckobu&=-@zts7?6_pSOciGO-qxF(HB#_6k>P^c7@Vq;Y-wVek+%Kr~R?!fS~#w z!HJYwc)gy=swwO$sv7FP)bk|>U~QK}#t_g>3oN_^*TAjSkywR_;%pzN9p(BnRF!5VPk2;~P9ylW3 zx&++;_Y?b(4?T8zym2wU8}9($IbYO40m z-!o`_phNpW(>xi^D*bQcBNPsF@RmI7UUcyENw1-iPART(-@D-x;nnF@XYWe zgj>}IP^=ad%S*oas*+fgQ@)5`riN6M>lYtdEfWE=X}ri`<&-_zUC}cmbsGMYHX1t zFVzz&Kk`(+$+sSRElywE9rvN$`4j!-8&*r$fvuEjGfnK^-sX3jw|87g)nUU3nz`7$ zK@`mp074|76If7D3~T7VAH}CUtJi7VPQJ{!&E!_o6sLMxWR0K9JE110kuIesvV1iU zR7xYPG^?#7vU^}>v}AeS@pSyUp@YzxMWj|GcLko4>eUY0I>v4Y>g&7<2I2C?&6(CX&t0O|njII-s2l2axHXrZl;~S*3(ICP#=$ukGpYIa zCcGUqKaM!WaB0*YV^-GP_Q&1PU<0v}$Q!B+cm$ZlMb;4ml;!T@uJK|AQ-O{T8~PH| zKOcY^<__)!v)$-dkUuqyyW~BW-~VXQg}OWmkRK#VsEHq+rmGbbQkDhs%>c5ij<(84 z$w<0zPSDk`De^M@#If`l>jd_~MAT^Ib`7?SDs84Nd=M&*1zavjDWCE zV-rsR$9ouL{uWS=C3H@j?=lyAPa=OuZ8rCdZ0^qo*(iCQoFwR3L&)x+uZl-eeyh)R z(&SkQ#Nip86Hs&6ETnoDu53H_VUyCn0I#a@=%4(Wk|;&!*9Xtg_GQQZZdyEODp3LsQ`rk-_W=pkmG3UB@wyUTCPOJtsTsS5e-0d~|eyp+a7CWu$6Y{7gXY zEd@gwA|LJmmI$7M24+9Xiw$ev;Jq5}HLK|CeceJMM&K!~8)~+h2rChxWzm;+8!Ac`&vEg z7B!gs>W_7>w_-|6T1MjyeXTH3?h`eh_SE&@JtCI_uE|CgRJ4h-J?t@@#O!ZuK*>5D z6%J&}I}fJD;3^>?0(YDC5Ji zu7Tyex1Dw}mXcd;-bJrzW$3Smdl7GKvy8o5&rCeAllo}G6`whC-@NIPR)!Z82!>CP z)&llF?`A$%l9ysSN zYm2pwv6=?jHrmV}P1`=bDOlsF2PwjM=f#WyJC7t|4^1=W%*uSHs^P5fUi|?Pg~Xj0i0l~3`bEU zgCOtO^D7=p+s`NKAmi@$L>$%Ah+Wm0c3~)qUwcN6aHT&j6A697Q8sU^nyb|{*^?kR zIo}BrzGBr~Lu{Y;^}3Dt%>qwA&N6~{p?nlsn9^(yZBw&12zD3&pYL?7L6Ss$wEuF)9^iO{zY-c+=5^iR2{c7j`2>Codh4swM& zM{P_{Qm!s%bAM0DWfCdK#EIsz6$l8PW|O#`XYO$j2jOYGj6zqt--M?{t=MbchxfLl zG!p`vs+-}Z?+CFYmG(o|Rh|gDrG6D(r&fD3k!Q%@z28w$7)}{L)>UmcQv06O(L`DM zmwrKjDl79Gzye0~;@>VsJ_jWb_0NSbanSCm5gJXsX?w2%2AC&NhWZCJv&TlyCw+m8 zqUxtj8Cw1Ck2uxnDK6xPrBa^fHl>Fp=gSpAF{uG}G5)F_jfrh&)YOGEd^@wtV z>{c3NZ)Do*B8b<^Altg5uBuV_R6P+Q*HVY6tHh_Dr7{3jSrSz#^#Ky7F`IA$!yT<@ zn3}HYM2j@4#Io-y+V2E6Zd0dFpn`3_>E3>0xVlu_0|A||V*#0ZcDL;OXmaonH36AYXL#pZR9dwJL1U2|E+=2%$n?Tww{EG-xh? zW5ti;7i3R0qxbD+vj=wupOno@i?W?2a2Wd7%A8u>IDa$$G&kk`Ij)`58d+Kf@nRUT zlt7O-in^ByTo2kC^X9hu9QHIHaAeirtQ5)A=0pRUHs62&8uN*gDRGwzR_>-yNR zZ4fw$zSAT+GXEJaps8w!DM`Ot`k<3cA)m~dj5s#Q#(D8KO51C0DqNqGQSn?yJhDN1 zEP*aml7uW5lk|Xr%mmZnx=bp{ug=&{sZPd=X^?>+l{7Twq1&L=$*I#$r~?UPSbvj8 zy!{|$NW6hp9(GzuYQs`Bi*ph_<|HR^c_KfouQ7I0Jd`y zx9;v+igXyrju7>11)Ubmghuh9eDfoevZXFZeM*i)<1l94Wrjv-Ci)ElQ9pabBAwy*6v2{CDvV#$>Dml=u6(SYLC7Z&y zRnc02BA{G$87e%|1}X{yMn@HBE**gC#tyj#x9QDZE~};tqy+U;$`L)XUZhR8MVa6T z*ez5?S1Jhn>Z}+IBji`N=os5LKT=I;>y9Db@sya@-&iuF7P}fPN+?XtwihD(dCG%e z#zfWyKKsbUNoqPI=jP;P_Clr`dk|9<^%?G{rD?(ZC;ORjdyDEk`b}^IQD8$g6aice z*4|7!!QLYCMKdBzNZrXx5pXYkb*h`Z#WRn|_NyKCo{fgE+gHfs>HAm;BACK8G^%~! zi)yQlO>N@vNuQ@Ui^jbb9pn8JU93t$3B$LK@*h*^U*))}Uaqdm7#)ot!c7af3I9J5 z$-~(Ts>uD!VBaw%)vTNJgjH8>8#TVt8;N}bn$Af)eI!^34l&?|^GV2b1f@522`aLh z#7mja!b@{sgLqueYBbG5UW!$nNQ2n0!fGHpNXnQ3!)qRP=)+bDP242g>^ z%t;xR*z=m!z~dw5Mf8WE z5);{hg=iN905MzER3=MyHzp_bT`ii!(x+5jH0CNq+WkC%)B4OK8Pa~zS?PeQ2 zwn+8-TDxaiojhCn_0X!0vQ`0bf#}D6&VWG-d-9|N*_c$AF8NTBLuf@<3IJ)rXTEoy z(6b!xUNP!P|EUCU#H=4;-V-BuE|7$cKNCJuM6>cCU8&~no~lkE9%>IXB1R*R^^=BV zRrjPiE)ic@JZIK6(;=^as^d3AIf7d0Wygt9CMA*#+eg*PP7uT;ezf@z(S|=F&{I<0 zw-zt2bXii_|KOLXDM{m^uD=K5sxE_XcC?Hxk8_aaaS1th-f0;IK*S7GDB17$Fk7y1 zxpWM3MJ3ip{qv1j7O5on4SE!k@YyMnvyUr_q2}1^`W8@3BzhkJ)0cxjqbUK#^K`vi zm>=UOOi|CE{JAW}uA$Gz)v~l&qy_cGYxC^(R!59zY4u+C<5mn$$XH}aLH-oGISpW^ zrfHWAo8ddB@Nio2na$KJPs-QOWeKo1?0unDto*5vSH5WZBu}80R{$Ua(*Y1()45RP~FB@)Ng_E9vz8Gm^gcX1&$YuHW+c0@PIXTO%iHaaG zdlym1@*{1)B!4P|A$3M_$MvZu>A`Y-(F`wLZM)J=*>ikU|Aki)clv#7EXjs4G+^dF ze_C^?Y1v38AFwH=CFW4&=b??Cm(LI-Dt*L?OZ%o{go3=*%HL}$A($ENdV2B3-gX{K z7l2!TBMziz5&O)YhiOXqwrB)eEv*?rPV>o30l>j2zN2O)jJwgEFLJ1c5PFo;@qW3# zjNbn7z$Bx-wZ^oOeweO=<#LDu?nMC_)P}l1HlLO+nbq;dTo(_%%YNI-dL1YR}M-$ z;LE%8E4}yeCx6VEyU=|2;wP2yb7DJ6AeW9kR{N}>X}tupmXyGm-XCK^O zVxe*7a%)!~{k8}V?&dHfTKZSTTluG#&>x!WFuP6n8J?<$g8W0Goz&ZXG<1m$u$?!O z>!-hi4y1c8Zb+|kQ?)3BLl|!RdG&FNMD?tx~$Wp!zihjf$=f z3+VRYVO0_Li^ek?!w)INzO&~-56{ao8j;m6t4u+^eqsp((7;qRxjkbhnX=%xON_qv zTE!lKu&SYI0GY0a07$8D>pj<^fm7S{Li)iJ_lbn!w66-UN$fIcV&Co3rrfIbS-snL zXq>a;=z?kRKh>now|XLOlL~gRU_3!!2v$Z+eo0z3s0p0dU%u0*uc3L(HDUDBIiZGc zh{$kzG9B=pp3`?D=z4$b%Bwel9(b!PL)Du6yoMCE`7k(HPL_#bQ}-`$#AAHMDWOI7 zVH{Meygu0z_?i&q{REp`7xm=Cz;^i#hCaUp?Ccxap?4nkp|opB@m#HKGJNR-8AOU} zzF`&{{TjuxAx;aBv;1{yfv)=bRqeo&&Rm{IoWcfx%}ZEti?ZDcu^TNB3>MWHU6*ui zr^Pc@wx@&_l4FX)UT~51q4I#y2YNT&Qust9Hdn+iWA5@V{5VN=^4!OdK-#BaP&ZUN zZ@vXDwOgXuY)PGYw&a?HIJ(dp$_kj$^&(E>N#gGM?O@F`F;ZO2XMvt?A;XL4ex>fF zUng=AK9ptlWYdfFmCm^m%}ux)^m@0+q1@?*S-ieEy{yfLmjSup(4<&5*Q6zK{cd^5 zG>v|+hQw{I;lu*4lYNbpIQDQK@PVpfTO^MAKtiH{7}j?mpW}4^Lheb^aXybkE@o^t zSL-gnzKGAvm(Xe3zPYBi45Yv?X0@>z^qYT^@}0RJoDNMN7&Qe4HHmK2I3Mh5Z!Bw~o6wzu! zP51G^F`H~N^?I%BqoM?nCyxlYYWEi>>+3~5*(Y@R55?IHPUM+rK{z}kMFEMhTgGUE z!_sBiDF+YSBl(1fnlHe%wKncPdHpft=YkE~%Sg{v_qB-Cux6@OUH?YC5ocmQRoH`M z+q8i}#ZuSX0LS_HJ=}fRpgE;F*We;JnPVthjm@bayZC|MCXPJ&u!Di?d$!yOP)g7e zuv{IYtgl|WBQ+Duf&=ig_>tolz>ZzjUz>Oe3X#BOs?k zOh`2f_2@*ovRXjyrYn>NW$pQJ=b8_r!&KEm>b?5H{(cqrs&S9)Lq`CHe_$zatp}bx zs>3KjVrxivaM*5U5igRtNbLAv_QxZnMXP!WPPs#QMV$ydy*R+Kr znbVw=2}?h4of1S0|KtncEUAfd0gqM!r?WG(detsgLX1q7-P3CX@Bifm0Me9QJ~yq8 zH(B}NcOfO2t~)y0UpmdZ{~JW7I&VW%On*?8 zBL!*)a&pIqUqqxd)KgXMe&}U@{ihTbN%zah0%ktYPmnc!vDA5*?cFN}^r=Zg{*!3o<cQFdL}rhi7vpOoVT)JM*SGi8|czx8=BJxm1oO~J3&b_ zDjG2>>YDmA3frf=Ch&>Fu~7j-_W7MJ9Z<`(yKwZ^Q{qU|fENwAoeA`@AZmtI&Z~Y!}e7G>5e*w6c6>4YcQu~nv@=!G>b z9%p%gf?g`Pw+Uv84dAN?s$bXWbxX2?I}T>zz0&Z8J^8^5MbPz<7G@jp-a>S{z)D=< zq6Jo1$A$Di09V9VaMGuYvnD@VE=D$=+9sKI8?={}34hkXRV)G+4_FPFDJU_c zmMYZag(pF|ra!nRwm00gmcz@CZ;b@!ByV5_wWF|7Fl+acCXc=riLclCHtbG+zZNOf znDXW0!YFW;VxgtlBgY>NP&QS9tkvBC3m6iXWIFkrw+nVz0Cc^+wK^J!KbZh@L-UUK zTkgm9Ut1+enOu}AA$}k~m6ssQ->M;Or)Gb48}~xb(5y+#2Qh3+?mgvCx|*9A=~Tqe zk^CYc#dSz~FT%m6iFjw%YbQkF=<`J<6$W9aPf7EF{7l$o>-s z6o3^uHU=eO#MzBntdd^j%$rV=wnh;{^@VO=y0R#~u!1y!*yGJq#?J_1>_sNd zaHR^;#FMi4^upwt$z1O5rp)4jT`wk$x$k}_*h#mI+Wl%?*Zy#E?q9{O;1TtOHG)D> zh4LqZR?dSrllJo2rJwEGNs6RHCyo#jf7af%s>*JAryiS7n`6uL%f14;(OdD*t0y37 zckVtyu-41PrlhuCmn6s@8X#AoPBbm6{&6_Ny8~8V<)%i10eQ#d&2ez)2@0}j{2h;a zt-?<)hWtd_@NLH@EO^<4&Rd;cp(3v|=YcPj{mDbtG@^@NlvQNf2p zJ59z9747>dk;iwlp}e1<$WZaE71@XKnv$9X@s@zdc;lw`LW(=P8H(})S^hZ&fr4%y z3^J$QuP>Y04bZaanbbFmF%kf#Z%ThrN?h%}VUPC*TiO+b=jkMH-OOKVQ4M+-b$_e% zl{j?tQ@UymI|EC3WJ)2Ks@DXU&>DB=idaltvBtm|HavG~DP-J3{Qm z?3no4$_;8l+HPqx0OJ<#HI)M)IKP>p33djk)mcFzgAwiVj+2+Yhe?$wY9^=U9pk&x z9X;X9=rQGAVKC`pj0;_CMVI}pinjgg)&O)Ckoue)A%=aOW8@!q-HF2;kF5) zpH*h@2NkqOy926+=+%YYeSDsa6=lE`K&|i;GA7@HX?uYn(_2;FF9`a~MhLRscYMg9 zppbfhq$?-m!YR^oE={|*0i`5N-N1l*ut(Ul*gpc-4t=3LCr-jFetn9QEZFms)O z#pweBH0G~%)psNIK=lu%TKY6$Xq~fMMUHewOJo$J;F!M~XFywn7%H~68Pcd!ci$Mj zJ}CcOoM`K*rEYP_aN}@nZal2X6i0)irwJD0GJ7h;*{XsGcde- z;|3NK38TEoRTYlj0+cZEGa9nJE6Ky+7uD*(`Mo}kA>_`-Y?fq(QMD)9>bYvywbkQh zDI!(~01#*~2w>b0Zc4qT^ zzsc`iE12L|tULl0noF#ICR4sniLcfKpaEoN&YsIz=BCA+Wu+VcL`3!>9h5ll0qF z{kM|VvBADMIX(H9&Rj@%s!)Nii)$i23z?9AHYk%e=vw-t(s9-r!jVo>ZigPK^@SWv zhdhy}A41;g{6$YAdFjkIHTK98oYSQ>lB8;7$$FZD8K!jb02rd&$I0SyUcV@cSU>(m z6H48G8?YL|#_2CYNOnGdN!CDTfLP5R)R^K}yO|i?FE#PSe zC<@~RekaGAegt{A{_DmSHPCz9O5b|3q0OUdcJPsG+KE?c%+WY`yqK63-0HCRL{tFuS$``HZWoi&5LoM_6u;K-h$7ni4D z3k(PN;8#J$e&jn*{0oRx^Gmr^d)kCLhnUx&s;F|lB^ z7(O`?g;F!qv$HA{y*xg-@vOjGXYcgl7w(|f4Bmtqd#jwfVUh#H^bBl;RdtpFC`HPj zSrY&AKc=5Ru?I4~GlkDI-@iq-JQ}3wE$P2=Oq&mmB>5rB}6~0zHmHJrfb9kVq5RrUUMkya{y zfS;hZ<~r_~+6}EKTQpVp$fFz}0Y4_e|`r z?LY9r1MwmJ^o>>>H8oR!j)4O)3ZBw#AHydAwT+F718((r)L0CN`C-71`*(oRzd!Jw zN8+w@dbWstLVjfa@)t0+bfsoh6?9;13&~0X(D-kK_&2)PJy0I?dWyQ#NBK)WlyIfN z#?+jV6i)c(ne(Sh`l}ILd;ta-mLHeYe$*LL{gR8SBaX^(*}OAU(wY1d?5ZPBpQ&r8 z3-0grOZ-~MbEKml|F+-#S1F=tyrh*?XzcroXv{8+hO+WoeTFn6U0oHG3q)md&(zbv z?hvg_XhnV@o`!aKlq}#&f~&^}{m)VL8C9twk zR4Wy4f4<~@9WFd=tLh3B#YIgu+v2dWu(9D1zBQRAPeSu2o)ORR`S(*0@~}n^v6jFQ zs^|y0K;So|LB($GKik6f^QG>pNyffDbKYX{vr*Z9AF^M5Ynkyfpw!GPOG8KJ!mCih z4_p^DiDiVgOWGdLyq0?!21|dLFR(D{{1Vb_?_W+D+Wc`!P1~^ohE`gGQftmHOt!50 zniuc>ywoTz-5?92%qMfycJ!wq-3OENPjl_hFR#^(fY^eDeF#=sdiN~N!kz(YHHDIr zmWF1oX}vV+^!EbEGNRC|te~l@@X^w=b3g^ zQ@s>*#!=d!OdG;4uR_X}r%Fmrf4TwuHDqp>{|<})^D_U_P^DiXB%8TJn;+mHuU1rA z+D;sv4^=Cvz8hhe}0L7{hs|F zksWNVe+_6Zq$LgozVd$u=?feKM*mw-{o_Z4xxfsQrM!v!2cG8tx;M$Y zVCJ;BeE8oY)4x9T%lT9!*~NuL-;#fga&!=3SzU5`kZu?IZwHEh_`uJf0{?sumvnV? zjdA1e+j9NdqODk=uB#qvubzGAx-tQ?iGhdwzeOCbt>3g>?x@mtmW3n(wt3mBDl9b4PK zkW+j;%yJ_r6uZA>XYwd&GxKL|%s>2IRtut1k;gt8fl)bPlB-b#0d4$IX4gYajmMXK_1b*qG`td>eT7h5v zgcR|#xRo$TUG)ze>g>wfB~WJ71R)37W zvbC-JqB0t8jo}1~Wa|(XuNQ&X=l^w?|J6;h>JpdUeY~1aOs+o|FhIk)N7}dVzV!pL zTAk!jfnrxsuu&Q*;)unHo@{LF&2Sfuw#RbYmGSD^PQ5&?xU|yyv37Sj0~s*^c}2ne zZS%Grth;pa^R+06C9X3-F6);HsZ*Aku{hZe;4vuuvbNh4B`y^$6doy&^bcC-{5%uo zXKQw%>^%u!1|qLpZJU5>PTKg$dRo-_yS-am zvqZ_v%&J%cw2gbSA_Lldv#WBj&yH_%fUL zif9hHTXgL?UKPX?JVZfIzdb>^S5Ir`hC4f9kFJ!Ox#Rt&G|AUn{&{7qx{HF;rKPRY=8J)Tw!O6u{ z+LxK=n@nPdh*$ze`#-$JE&24@C^sPi=>nW;?!g2pOHYNqD>k#eDTK)>8uvXUDWK_A zl;5~UVS&Dta9_8>aPjA%0!~G~kqFIZ;ENKDdfV77uPttn2IE)2DyM03q2bnp%f;_p zxVEvLE;r+N4DDA=4P=*B*q}x*yDLfk#2x?40rjP19J#5h%#0S+^7X;@*(jG73qN5L zaj1nKe~Zh?Bto(B0~h56p9^-ggha%BS!=}+eqYITVxrb-bj)*G{_}X0#NoH#jnXpo z5ujF~dwe~E7}ilmQMfG&+EKZp8tYpm2+C|fOFTm%si^)d9POrRHW8bz0kqpEGN_M^(CN~8=EC%_U$zA zu&QA=?xxEbZ5?-WZCWt&35|^}Dg!+zf;(`#Ec>H4O0junmdZ(Sx3N3?JrPzLbq9rm@{ z(|@hyHcr@s+Z%6*ynhru<%#ArKT{^ip;&=D@r`2eYw-0(m&h_1?N6K(%R{^qAzMop z8o<~kYT~3vq~p&2f&=`1;%+@;%?bE&9L;%i^;A=J8g5XJ_NqqiaMb>`xy_4V2fv*< zhqMXa#|4gZaXw56paExj(R`<46zH5`+pasX`c>knN<(HWz1ME|B46!c6k?>`tfSG9 z9yB7!;`GT+B5aL6-XR1hZ+0-x9m$ot#dn7I^E(^)0gv&CWWMI?+U->RqZk$)G@QSq zNUw@(>`{GhaI2e*o~vtYnkktq7NS#TE-&QUJ?81kMdHWwD$G??7w#h?BaizRuO1K5)H@OwOGH7* z{4gU6Zq5Al6$1*weF{SBmO$2ndl)5d77c+H@-`^UgxRZ{JtomR3-~Fo&t2y7=#_Bl1So?uVoyKU=M-nBmw@;RK zyuOd9v~D;>x`VJojELkC>RGL!5zb|~B0neuCylu^3tggsLr4n~9KoA{92VWEvrwJz zZISoJrns-ck?U}BzoS!=6-dJh>k8a*yGw2afqKs%h#I9nAZ?Fq~1|` z|8(#1-g3vd6QCl%@71}!v+^Xi7!+|^eBQzgsl*$NND)tBM%y?TEkMtnV$_7kfcp60 zk;&d=8np)#{XF!rYCS{4p88Fv%U3maCsN!l&p_|T7x5rCRdYu0dzo8()wDTsFU;_I z!Lx#0J;$zHsn?zfYZVKY3^n6$xKA^gx98D{RP&6C{k>ycLlb;s!sMAjpTKd2;Htw( z4Ju+8Y|LO&mM&&`X!p4>NWiKcz2{g=QC9cC4t2EW?$%s%t73*oJ%fXEyE{o@5?JL2 zn$*QoETVdbNLmvAX>|j9T2-ZHj&WW|@_d?*gNidTU1H}V*H(+Hp+CNgc6J1EA^YNB zG+{JXQ~7P&D!FUDcgaFBPm!H=U+Nz@_Ly~r&ovPAJ+VKiqBS6U_=S|EvL3fNR@Ufw zZp_b+wj1|DCce@U%EgHG^&VhZ4IeSA>I;OUzZTZdlc}E8T#GIttxVq>kvIy?zj1U- zdT(|6c@lzgcoI%d*j3G`g*PujBM`Gu!tFds%M!9Ice=YUgUOyo(H{Hfo9yZ#`wA80 zf|DUEJ>?P;Ub2;)*J)3_Jc=0O@$u;wj4H!2d$bv2b#K{B*-50y5 znY3z5L-pMU=is{He;r=5@@5)YO5fg%9_tgvf38iTe!qEk%(t@e+c>@BVRV zEoo02irp(Y{w)7_8f0*8AC3J0)rsB+pBGb(nFzR@Fo-N9`lI(A6OJ{~U( ziRxz=+SyEsL#&B4Ob{mvv?(lt(glZKneGmq6Fp!>JT@{iTFsC$n}h|_A<7jcuw!)w z)!*hjQ!IcE=2aXmkB+T_qBn|Xpio~I`8^};6`(-8eNbTGw{RATE3B_pE>irk81Ti{ zD|;-T-cemW7M*;JQlebr^{~TiXbX$YMy*8Cgy#l?HrjVBBJ^WQTFYd(a)@CHCv%3$6;mttYX2R^8)b!v|53Jw3cfC6E=-Nd!2<_nyxB zv9~s$P~y=7vr7_i>zb?Q`}g6Llsm1asfh+GF%!VBE$$LP7hKn96ZyNsA>E%)n1*er zF3soYZMO9n-p4iPuNl0q^jDy}gZ!(w!s|>`vwUm{WGk6>E4JwO!#M8`Jx{kg8JBBsFPO>{i+O=QBkG~JR zs9S89xtkC}x^)XLR8p0g_bwy>S^o%eIG%18C%jTnwwf6w0Ez2+OdR9Qaj^RR z(hSSr!H|!Uk_w=US;qUe&(muA4`UtO>EM|uK8-)5=Fn>@`ZuS2sM!@tla5{TVcM#x zK#olfSb8*(>m$zLFNDw}8ZaF*@o(sohom->zN*85ZiZeYpKT|B$-oln${6j%uapF+ z1jpIab`OC8htMI8()?9Z8;M5Yvl@!FIFmnOY|&vaAAH8kx8xJ~IG0wT(u`<4TWect zX4vWnvtqJPA2`?2wdn_ah~zo?{$+-UmB6N0PRX$= zr>opHh6YFBb_|gY?F}&}hehK*;_Iuzl$)jIX3k#=MmHSe3Y-@d?De7P-W_&+XP|TT zQ1Mk1Evyhom5n7tR@TuKHVeG9`7EvKL7r=)Cl(vx4f(nKB$4%Ok`E}Od#SA#nU-qF z9#cyz5ugIX3ahKzbBu%ZFCq)`d54KFmSnPC@`{-dJxz2vn(#{WPiLl+_NU(LPmJwQ zsT?nO7VhKwXLTRSpT4_ayOB)m=eNP4Cj01kZmRmYfVcWX2ct{D4@!yXnP~bKwiXG6 zhJt*}zN`O+0&o?-m~P|?e7(!4U+wZ1SVZ)%xqt)87ia|=6F}Q@TI{`oum?d`1*X%k z7~#0mN%Due_ZRrfC(h^%!cY^HHnODb;K3_`7B}p_rPnR4JdIh|3~jASm3Q(s4+`OD?-;wD+Pas{5EdlHtA*LLwv zW~WxI<0koEBVF=TE;wV#>GGe<2CEdV8rErx6P)_pngI8eKn1c_nbJ(_Z@E`yTcYAg zDCo5e8+Qb;Ir2&D1Oxfm-OOM81B=DH;fPe*15a&gJc~Un?bcx z#ZAKs0TV*zT2VOikqE+Jwom7_z_o(a>X0bLJK+?C8iO_C2R?%soe)hpTL&Tr4$j`|*}ZwtoCWXy_t5 zfBt;iqh!F*8v~6E?bqm~3qF6rWc+cce*ckQ-l*g{O{=E=m#TPSht2}iwhI{vFQ{)B zyx%lwkvSC}NZ5gtKhAfkj~~8gF*dpy!4=CbS6l6BidwH_e|<2AntW%G=JSkBm*F6tIf3Nn1B(LqL zdAY325dG*r@PS)KyNvgW*oPcfKYx2ux1_zChF4EZ#Jzpk#A}>n8`SZ-78d6mPBtKU z`uW*v1ytXMREwIgMTwae#@45$J@a0m;8Z>;szAD$pZa~ZG5Px8W?SK`6)t*LIt6b~0ILEHEEx*cMUkMNR&;bURRS=X@}Q?f0(JxIp-GNp>m3Mhk2j*l8Vw!({^?iZ$fGHxd8uQKtbUR-sPBx6v| zS99Ad(5cnPuGtFK8z`-MrNzP;GL$#BDwo$^txito*rb%OJBRu@GvX)zwlGEXDYwm= zg9DE;0a4vzlBXxzpRwU)yJ{U?9B92{{Ou4*JDrjucn3Favg2%!DOWS_HBXO1)kK21!{aw zdQZ{eHjrAG-uhcdzpv>c*t z>jJk>yL(7@d~6?Qcia1wYQ+^PDgV1mOB~<(I8yH5Eh)WvveV(fUVMmUSqQDW-!blfEWhKUnQ>&dBX3TTh&wp=L46ljHZo+jGiM;; zYRQ3!tZu7)%k%q3Z7m~Q=t)z&B)wYw0W=T|Cs|WS6Cbb82L3V(bb#BdSNY;IY~1WYPR?o!B7e-`)M!gClvXKVv`$9O=Am?G%wShE>$ruZ0$#l`|c z#jt?S(_Pj!0^VZTnXsW-3E3Ju^wt_yUA#EZBi%30)$H%uoN3B8EI?C)$vNPGUi{o8 zh|VwCmq*unxy9?*<$4s~1#6facip#*SGg|N&V-13=V;=9NK)}Zu}&{= zh+nru>s=EUUzaeB<K_ zJH@t3>XdDTJ#Ek7@ET|BqayQJo+Ph#QbYQ6{l*n(#b5HWYNmAzB@6W22xn6LN` zU2_r(*OAz3mFVQowAp41xBJ65>k{5G?~I1&B1B>)O3lLCq?0wMN+1oKD{UMoL%-Tx z>4Y0dPQ|(+!+Ha}B3Lc(wLWe3=OGYso$T6`^aBG1$ga9EDU~rTxSF7LmHs<=(`rZ< zRPaVpllOWrO}P%-DdMJX#n!x;%dl6n z{hd5ZF!t5+Y)6g-n349riE-8MHpjr(!md9UE&_P(HY@?EC_8^bQ5 zSW7YXO6eangZ?TY-O?fD7`e`@pF0(q9<|RMB z`qj&ccK~mSl8o^*xnNnL-kn2QrowMdJNwb%qxah|ki_RWrnk}2tTZ(~eKSu}(j0#w zBenJrCRXfa?7A1&^!Wt>N$leX4J}G1L|yKDLM7s#_fWu7RpQ;Q%hvqx0P<+%`jc$B4>tE8sQNU!Z-j z46Fr3U=migvoUEn-{tI=(E+f(PXG~eYDfh3AZNDoM;5u(|U(cEqLyIAHlSj?Pi^|qV ztn>{X7cO+V1hg;Mo_(lsR_Ky{09*QgdYb1v9S}K3+z0X$O~?{P_~dgSX_uHCiY-Rd z#u&&P>U;p%-U-+X0T-3AchgqMLXM3G%l;ToSrB&b-X|etjZP!FnxT*|cc61~0x_oW$Bz+}(+2cJ>BeB_VY700CwF1kQtc=}nw6z3_I-s<=0zfDCL?G%DqZ4&2 zD1*gLO+9xW=&e(pf)AKH42+xf#o8f;z)sO>O5|qOY0K&u%cY)dIjDxNzx` zc|ge8*T*Xzao`tDhKl%a+u7i_dr2#l30yP10=)S375~ z4y}?DN_`QDGw1OA{#I7np?gFo(Akx-Ydr}SW=^=*`MtpZ3*K@PtpbSO^KckL;;!zSp0=zb(ig#UioJc!`;rH_@mHIC-$&a|Ru;r30h+i<1KWkiz{g)sJ@d z2W{$s`_|D_E3@7~)>)90cHK`Xl8fhUyI^AqVW(q}N=we!-{hBsgPf9%g*9B(k_tXS z9)(C{-_YUn&`guey|tF9?Z12Gm9X~CnO9e?TqyAQ#DJ3!LjJ9XJ6z@uWjNWZmlH~7PrvZHXaqhG0~_yt>a?@iXNujK z|76iu(1hvop=faB@$xY^%eDt!2h+I&=q6XH?-#jJ{G^bVn_|OmjPk{MOFT>DYpZy| zr=)G@bxk*%8J(|r%M(z==YZzhb*1A!)~vu^-?Dfeh#%#tO4xlq0O!*KEY{*A^sdkh zu-D?oo5_XV;8M$t{+wAhgacBocfbKGNoptbCBBz+g6!_{U{?$dG`HpGkvOT5mX%Es z_6787yh|mI4M$f)A10xBw8F*=7$IgT?rq-$#h=0q6~$Hd=wa2@c0{F$4IJ2DW41pn zgVr}MnW&QcOx9+1*wZZfR;wn$#F;zG7H1o`jDuFx8UUTWkZmX$o+xl{r4%ua{fsPLaPfwLXqa;<#+>4Qe5ojo5mS zi3*FziX|9ubiGRMx1bjmzoPJ3l7NOay7HxgN8G=)L5=n!B0O~}hUXE>;T-JCmoIEK zdu~3Z3RT5dxVP&(ei&zKs1<14!J9(11@uHdZ>8|84ZhUcQ`I+`b89WBaxjAZLezDHz^WEV!`0 zGNo=c4Mey`0ePjVLhtIC!Xov2jVim9+5Wq_d>#iMLzv$U=4*C<_+^T(B|)^31OBhI z=Y#OzS!`Tz!lUWFr$UT6*bN^g z3fOT0$y)U0=B8$!P>;3jZ4?5%%R{vD~T7QNNIdXXyGF)u*b4jTQ zHeFqo9%1X5H=uxd!UaH#3hTjlpd-Z-Ev*mEZwz9yRJ{z2b#-+!iDtmOpmv^Q4tQIv-js6qQnlB|GzeAi_#q1wrhb|= zGaGY+>`4{0;=fkBPz_Xx-rEQKsFrjYY3UDd$Y^=>UUY-@Gu{Y>nC?h5E_`l^H@She z09TBwz-cT681l4?)g+5Us_`;-SWhT5#~6I%k4W0@rb=t#OA^#My1UYR;r4CsuA6~` zzaOC1YNUg#GuYb(GLP6_@#pN0ouxm>38IBDkc%j zK`i2&k(P1ufIomvYMTIJG1~x_n6+Yhy@dk+ceLUih==4+IlRl+PlWgm4nZGzg*4#4 z8d`@pyVtJqC^_@a%}s|!-Zw*>{(~(o#j_y+RH+?Wpf5h|{m{Gj?@bG%Nc_;(xwxLf z?Z>%53J?S0q0k7<8_BY9`l1T~VJJC9@u!u4OUzrh2{DiFLR`?m`D1*@2^c}O0wY=J zK=a*4(;(cgzyU(pTdDN%LEC7NTcEj^kjr8;ns2>!-(YD7mSlwlTev(``9k$BNtStf z?@bb~ZLKoOMMtlD0~LCHxZT2}C8*8bE1B?1Rl9=>iC)e+_ip_kd+!<6WV*EtD*`GG zA|N0rRf>XuQl*0k(ovA!L?HCgLkAHR0R^P@pi)E#q4#2;_ZkSHNrw=6uiwSY?Ad$f znR#Z|zrOc49)HCovx&bO90&%|lQXsysn*s#8h)ng2W}q3PJbuo5 z96myVA|oSX0>={cL(nWFs4?Unn3f-dMYZH3(_v}}$xDD_#N$^>b8Q%e)K0$oyh~QB z!^a*CP+i%8x3^M|X12EL{q(NM>KZLA=W+h7vle^Dq*jkC)by;UW#=;pF|!X~p_kmHVqLtfkr=$kPp$Q2=yEP^yn- zzAsiN3WQaroh~7=@=8{hzmp_*u6Kyml{qJcDq|INjkc#(D1 z49HD;&Ul2Rk?!s$4uB3-2v3kJe?Uem00Y?eu~lsluI34dP4~|vZcL{cY~nC%!iH9L zywcKE-sry?v%nBm{*17|>Xja6yi6&8+A`^%;JktEh|$Y`GlNCR-!dH~mk{94vh zeB5?U#bP+P&{j0^bZ$JO)*17S>Iz!Z1?OCL7(*Um?|j5Mj~buJA}A`^C3~3DXXv={ zck1+~&-egBsFEbqSWO6FI}m>+BOKleKuf|OH^Z~DRxQ(!Y7>qhm z)D*`;mhQkIi*4rqq7w8c!FiaDAE?~5580twzv=Sre1AjDt40g`@$IGKbSrgF2H4Lx zH;Z`t>T7FXi@$B>Vo{8~TqT%Y*Dh+asQhcN$-iClWZ0J;RS6u-zrFhO zyvHuM^DVp`q7#G60GhDJpxz#flvl2=81J6ruFAVYLBRqN%JBKVtYFC5#@4q=w0bpp zNI?PvMS!{M3o0AX4^O*)SLv{g383?6Y;77rZ2@Go0vf*yB=}_t1ZQ>EWiud(oex6i zl$HCRJi&>&EkB_KwCVj#jNK~8ZyJG$&V4(%&+o+Zm1Z&pO%C?=huX=>$iiFus^8C| zy3C}B`|i>EW}x4G{P;1c$^$3tx_B#ohXL^gGQ+_B$9?kKWzJD{E>r>AUvZo{0#z%M z=T8y@?VyXao>wP5?dB10!2XYsvGQNZR?8-kiF-Zd$ z226{s^e!*@wM!Uq(IO`9ns|74iRrbq_cM~;DFB$fRm{06G$-zDKhlUBEGzF;2@qbl z6o)^gB2GyLVTjw_n|r)u8IKnh+Tx#l*IU`xaEZrNt4IrIc29iLd3O^~GxVQHj!&`{ z{&w!aUR2meJQ}$~%`VBO-llpUih!tk-w3V4=Z5mx*Rqw9+OB+# zwWrzztBEdi$~a%$HmiOLN4~wiJw;sy^c(e&D#EA{) z)f;$r0@`QSv&Y`uN!DG(!2h=#BO!!OMZ(;`hDg@~Vl&J)ZZvL$i9YJjZr`00^Dc)& zuzgB(?)+hQRiE+7<2z;^!#eBVZvPH@{>xzU*AM(!@MMB6%gZ_(M@`4l83G1vpD^Ib zEdxqJ!@C+e`e-!j)DKXUx{IOCw3s#|W=;ln7&KATn@SX)33S5rU}3mH7ElxdHuVN* z+E#Lj+r3{4QkZ5Cgqcp%zs=k<12ZcpRs9lMf|Df0s()*u#>x%|itblF&i&wAW z3yy%yZzS+A!?vtRBCgT*UJ}I>gGDdJwKTpWWGu1nS2>P?j{_AOOtCVs?2Mq%G|KPH zS%~Q;j*%_;qwN4b=#Vy`(1%Z#Ig9OWEeRd~Cl(1g%IX3}@q*SwVIsL7-gQX^2fwo3 zTbKVNockTj@SmZ%5nh@?RQU0Z+~4142659X5_+z8@7}dDWocxwf*SZ8%yAwC+{)cO zeGU*gM*_W^gnocn8Mq3$v?*lE@RDNCuOEb5y9okKm)*%%&A5_LXnLMiAA6jHug_<7 zAe+6#A|QZT?@GZAsjZg}%JfS~o$}*glVn(lDlj`4;fv!|%IPo1LJLBQAh81F9fk1l z@WcX8moVT<*#_3|IME0BlieiPhL9SXLJYoes{i^BDv^3xIyt_v!Ill zAcTzvR+-}%iEP#9Hz|M|Y;0}S%saUzeF1l~EuHWgwg?oYUz`tu;iQ3i%jW{ugd(Q? z%f#W&cG?*G1tl;PqX{TrjNjh@P;?TtW!UmD>;_uAF45D|lRxnsuRUK-qn4(?W7d3G zr_3=ef}mfh^W%8D-$2n~7w)s2)hlCSR&bN|k``(~RVoIg7A?Snw(Wz$E&DO9z7Mp7 z$Sz-Y?weT<@&jU&vdKV?AyX|a=ra4`#)aZhvAui%y2Mf4aBc!SGx7Sb$mE#Sx#Lnp z210*j)kM_s>BZfepLX3MQiG{jzIr^CyLa#T<8#erYcsVpLO+i=`IB(vUrisG=S}%r zS%1OMT~vO?(8UXS%B#oQSz_l1qe$eya4gVYPvSQ)pe^LKoKjm?7f3inNH~4eQO%#V zbZmBj0yY^)V>xvT8`ZJmu8WBv$sTOgIrH^r7skLeA8EN>bokn$v#NT%Be=Sf;0MO` zWdw=L|BOKX(-{4abKX%;0baKo?4SLz(#x7@Ix!njTER6nHM7AWtU+^2TKH@x@Gzak z;Qe}xGDF~J^?d_l8UBku1_x$v_`d`HuS@rD|NMWq`rr5BAH4d1baSQH=fD}sf2Fel zCvVSitMW^B)XTrbNPpfvlVX0Lh+$^geUlcRJ>25X)x;vN(5)42;po$I7r67Ya_tH4 zaLOs7OLF?BxP=vep(g*$IsX2{0Px^Lc)|l79{?=r%e9tx&|&j5I^Sk$P>K6ysxF0- zO~xjISB&uNZ(HUc*nwZa5=Mo;Dg)py(vHLfgXpTHCzBf1lb{KLB~`cYR%p|yfBsAV za{uQhW&ONJ03!c9&nvKOLHnO-1)M(l4_<0)!xJxacchNf%YY$xyT$sd+)sJbKM$;kD8M*RORHt z0_eQ&LCamTE7u2o9cP%;<1`H6zU+!7Cladx!G^w>-#4wD{Y3FIcr>>v9o8s!j2FGu zd=`h?XJ(HJb~s)B)T5%RG{u<^wL&>kb0evoY@HG**wV21IZ93)){oHgN9<1z1_gRR4_I&lCN}o^c=4Rs0>P# zL3S=sa=uHd*oxg*pC8efe}$44C>atxGL1+(QP>S0oYhFlYT|!2=c4rAjMoJeyFDwF{UmO&A_*i#FewtB&#p{hB?1BGS@ z`=C!XPW7Pty&=fB*Y=%=naPj!?f>rP{>NARl+I6LB*&F3C%gR**V-Smy*@68I4%vl z%8ibQMG(|MG1cmB=fFukZM!{G_~?xE&Xn5o*YCdYhKK3Sb2l0-6(9lu0SBBHF*@{ zfX?Hxn}DP$`!|wvG0V-}I@HJBeTjgdIas@Q(p$XG7G`%Qa6>JPk|F!+JF`{EXOoq1 zUl*VG!D1*&$(zcr=uJ(_k?g=BfnA9E(Zn2N>sy`TR1U^23IH#{!xBkCiqVMeVVhCU zEgYFX;#p_kGV%gtOJbQs(^qNZ@YvWJQ_ay7I9Kea z1ZIYmH=?`SJ&wYgZ-l0Yk1E4&Np63jSl`1X(wl7zZlJCND5Fe4k4okvG41)@&n$v^ zSfU&1^+Fj2JKk-=J7W{nkOHkxE*`JhOK~DcR62Q%MGFH(Eksu0+*8d_Q7D9NBWU|m zi1ro&dGX=OEWVT1OQN6qE*IXLHRV(AOssJy;R@M9#a2l&b*|SU^dP8>&RXOo&Q!mX?}E9Qq2UkfiTLU|8|JrKr!}gu5XZ~f3B&Ea?S5}?)jTmGL#64* z^#`rRgh|!QtJr+dfp1p5~pk88gt+K3xPmASAEMmTJpkX(z%jp7H2jja{2iU0w8~FNfWU zi=POWyg`?R&%&?8;?9XD+f3p$F#(sn^*cL-(%Gk6g4N>N|G5~Dqz z$<3MZR7GS@Eum&u-d8r+V{z1xk?|{(XQ>>qx9&3bB=)#-6}sp0eZyR8X1y6}rz16; zHMC1hOcDcD$M|_Lo0yZ(4GiMU-?ec6F}n(5BUp7|aA=*=c6t;Q3}no(X9t~63$UI*<_MbJNZ@cklv6gav-f|TD>npITuCf8ar z+GSXDoG_wKr{LbwaLMSKzD#M6O!bQN%zk3T1T48%;P9^fLsk`^uJcS65$Y|?mxsUM zt0OVFj|6li&d{6^+r!!+-gKmyfwp9|Ybs~zN$7>xITHk<>fh4D!^Mb~NAjFXgdhj? zZLGmgF)Flb*ZgTu7)FJ;;;-5=taL|ZneBWonpU{Xt*KN$e5d(5D~e&`!(at~wB!!~M>894hh^ta#FUv-f44O<=Ie zh)|q*IAH9)TwRf-ilkG)zP_3xs1roRr2`rC(aDuN#|(4(^vBKqTdex0AB1HPc&)B4 ztnEyyCW}sG=SD{v-@Tp|O(AC!6OnO9(*P|PO%SkT-WqWU=>LLAs@RNeEgLKWlp{y% zr+1si?VcEc{?~+;Gai2qz;8)Jit{1`4Sl|z2bECkFeq}-0H=)3-*BWf%lMk6_SsaP zqjMwK)Tyf2RrRLUq9f(h1L5IFV%0JG)|8-Fn2oiW!>y2SlLLTd} z>KT980{9m<#9fbJ6vB-9>AUh({@Yp^Zg+gyuI33?EPsaF2nm)j-;B9w&|EoFe8kLZ z=$t-zWKL<6!T2`F`UtdcUG^-o>JqK<_T0p|Zn{|Xv+A5eRuwokYyvlf-b}|-5Pq2F zC^`l!fn`@V~M=+P>toAnd=*?ETr_$}<$-`-O zaKomMmUb7GtD;H7$5Dnyv9;&h%PNc}E*TebX4Bc+%A-YCZu0=xMdwVuPe%P_SR{Dx zqu%V*0Wgz?XBA1?dxFU8I80+uy>J}(RIz6goU^{|K#h$`P?VIUHupGt*cyW6OMSF6 z@kJ`*0s7ggj76QUQ$vx>BUO8e21V8==lNs`+K;gmv)6nV9iJ4>_za_LN*{#KMHy6V z1rgq8s7;J6FrS#T(NA0$D#BnGn+88?$h+sTC`Q`vvazrTZM@Mewi)K4_u~0}O)~?= zN$t8tn|bjaEmmOzKXc*Dn;^%{SNLave^u!F9F-(F2Iwm$h9BGl9>E{7%$MEUcMs_MlWJg z0NvZ$L$@`J@QWbfaHAl<+JJ8tTWT^YnE>NW8D=9(L$s5cjJ|u_#2uUESH@krUv4oy z3^4Ad7e}7jLFr^R$p+X}ftdBeByn1-bC!PQ2lcINi}dl7FOovA{Ze#VgZl+$E>nV^ zerbUI^K<==VEMPmI%VwK&)JE@8P4E;xim7e+KZ9OoKAVzJ3G>7(zvYDnlO{Adm~7KO_(AXaT2Pir}js0>3pGG!0kx3?}JV_htstZxUYo;FRh@Z1tFAMcAk<9yIsh7C3ZPkqd1q*C|et8XUs{_INu2VQzEHefeH} zx=vETR2-}mi&v)b$*ntQMV3@c@B$~=4nuG`@7i&IP8Up1rmy@wOCt*KC|ieo4qg0) zcpTm0*M@Q}3+~^)AMZnR8B=Y5MrC!bRR>}S_g;x=nodyr@(CvF!*nmba{pC9 z2nZwOB;$D6VY2KS_po<|ef3JUbxW)mlSGPL2MZmy8%f>G2LqtCIajfQP=N%RPSM$N zpPY)s-So$J?~+usa(V0yksqW6bq9;eWTDptnA-}>w#vsRI?~)#Ixe=w^F^n%XvIT_ z>F9+`Q_N#p_5`2~g9!rA$==LA^*(=Qio^5>+Tk?}7+2h#m4^)~_k(JT9%Vkxd)|94 z$4abBJkhkv0k#ltKXHq6XLkR}yhV+uPp&MNTroABNDMM>YiyuII; z_pwK%eu_%G&8A@5E5BngZL_kn@@{YOnn|(+oP1{L8t4-!KfZmx8vZP9!`9Iz*=*wY zC=K4r4F}YfaS1hTcbu0`buo;5{LwH8(zVrYYrEzw^+Qg;x>JFfO(1@1Ir8{n7AWRa zztH0{;?gi0zM*I*y%F|=nrBvc2UzqrM&!W^ExOPwtI%phDM&{rnLifxBRuW6kI-8& zHJ2G+1y_YZQgltkXQ8Y1L!MN z`DTR>;j~zGKpl0WDJsh`qBj>2i@J#F*e)cIcn3OH6RcN;Nu11KD;kcz92dp8!93f1Z}jlk(h{4G=BC+OehxK0^XD-J z{V5V!SV7w;1&ZgX@*j6|Dn4C*m_h(z!92shUc49ljm1hAy(5@7B#h&W55Mj$z60@4 z3;ujE09h8jmr3W)Rlyc8bV`b(knKVKXn|c#YY`8&zP?^K1MuPG&I9r>x?O3iz2|v%*A_W z^7N_c8Va}CS2CKxVc$=E`zpt-Z9)@w^*p|$D{e@NFIOn-nBN&meC~Edfr&xR*Rd>3 zWYkS$C1bp;F!NpQd)QtDOBxl=xbC#6z!j2~+9vL1;FqWTL(^N5#r7s?IGmj0c+$*< zca`(q(2h>S>kPz?Q9t!x{)dZm8s9NX@_AJ2a!*H1w3DV|)wJ?e`3lW8nTSIcX6D9s z%M+_>&skWnJU;Wp)0HjL&;$9gzMHsu5qUo#zH)gx@Wzh!FriMFM^e-c9{F+?M_*lU zj|LUHnsCkWy1m764KJb{?XR`fb7HqJyGg}Qu_zr?SGy;5et?W(oBdNH3I1^sC0;5H zIsX>X+8hChbw@rwD7u_C<9A#Sxi7S~U}(I(y{AShpfT0n9`2Z=_W}nOT=ymDBSXdG zZr0q=p2Sa^XE;u8M6x9(0IrmA7b0POu|+F~tnJNJQc`yzk;MjIvql79Hajm3%8%?V z_gro$RTH7SWyt=BYczjnAsDG9)_07p6(jWG!~sCk*1nIGoBGV6Y=y%2MYm%HPo^L(dD{oAVHg1F!D^M1ql|LiewjQT=< z_Pf$0hl9?+?h5tN9NphciS$izDufy*6`cj({u#T>jJR9(e zEGbIV=B>=wt2cG|mIsO=IkYqlJ$%CVXK*bk#Yy&id z#kzHwN{V|;e$vkm$+W6d=C>V^45D(9Sfats{a{0|rMk`yXoCI`)3)6<&HB-EU02=@ z7JW`&n0-Uol`P_Fc<~xh#KGB%O*SRn+vSGl+WWl{k| z`)}YqD;>?q;AnZ^%~KTvijymtd9h*?PJ=wcHij{`wiGBhVs|vp-CBBslwb>vL5BC_ z#Y_~0&`KERRKYAp)6>diRm|DXU6%B_hvHT|IEXfHX+@7Vy$7)k_nHb$&%8xAr#2SN zlP7Vu%>Kvo1&r^P-4-sj48>iA_X1Ib8!?Q`ukqa_ybIHB+2%TBzOKQ#s0uFg?iW_B}UiV#1C;HMHYo^FHz1 z|L%GGF5~CZKq9(Vj;n;iw6?tPejWI~NQp}R&1X|@o5Yhgdg9S^?)D&qQ&kW&j^D=) zsdb+bu5sy?ckkEMse8n$cDG$~i)QSS9LQuWG5K+=CC`Hn z4)&DnjxmDk{a$`o{Xmg1K;YnG+sS0xA>{8&b`K^OO z#4Sg*7WSl{sx)}y@Ty}*>TS^3d5?1*GpbLRZAr->YM#*aA~}dtp)eAhz(|vN#-v{W zJ+E)vyrA9T!H_yiQ1^`K;T~4 zi@*lc!3MZ|*|SsBt_>DltySeqElbn7eg$l%EyV`i=^6#5>br{#aF$-8=g`sgs7Q{h z4DTLwIsuqH?6l;t#a^pIy*XebzrVvV$9Y;sM+K@|S(cH%-lz zR&5^WLYDxvgjb`BCF{M`R*$~%fSsCntoLw{ST=LwSI3)k;R_OOa??k}+S3R5a?i8x zzlmQyR@PW;JaBEHM^)<>9X$~}yS0==%^o7fU_><8B>9w>X(Zn}14AH0M|yL&xqVz{ z+9k@vbY{8C=hnf66w3(Ne2XR?4!ONT(Vc~wG}jKO8h~hSTAR^{`;5QXFSgISWwcYF z6YS$TS^wVO>7e{1?Lm_&r-=t>@7Sa?eaO>2>vr7YA4ZLatE|v%)yzzrvo>7^-LvhNA|=gk8f_ z&AmBk#)O@fjBXlc#|quwa={JM<9~dm_qBFJ!JT8xzGh@NX$<{kb;|S^zt4Irw@ieY z)iXzsLsZhv7amkYKPC!OTo)^McFv!lS83DFcIrEY)vy+jgW-QqE^IIG?x!RFWPq?T zGrtI{>u=D4=J8yxE{61fhzr@EP(yrP$MyA{Uq?L|udXVOd-k+KRTmw>TQDM{Ic-@5 zlRmrY*wUWeKiC{(NkDxBA>g$W^sCKTO9z~jCW2K{hCIkw7$;lysqqD$!t$BesK4U zqOJImunB$<`L*^XVr4;mQ>fcR!g>4kYzx;+woL<@%6(mQte`yT%cEczMz!@UQt2ZU z0M~Zd6_~=g?m`D|HgGIi`0_}h!x^mC1Lc}Ryg*!0s%sUEq!$xUvuWU_KrzG~MFrBH zZniMCGl3jhpBV@^MeKLE%s02Xoj158B;>1;c(o|G46VGcvkoc5`Hm!h`m}M*kB0H9 z8o;8;%qEvFA2GEb>fAX&i`=C7>9%{>Qf0g4PYBBCh$R3!pFXTj&$SWCQDCAm+(DY1aI5VMsqwcW$`iE={ zY})FwVls$Xf{fbh7s`6t8?TwNq^xT$z$)M+;xIkg{;QNSNl!v51j8mGtIxg$8Z)u zi1OxB7SU{oVSk|odyn{`5})KoZq(&Rn4-kD!4Y;ovD^WnFA{9qShJ;a(-fnWfOv~@ zd;QV&-i|wM8#FXjV#cLr_>9^T46|u3NGnANIFu(qXNr< zqI!G!w~$Y4GNfrJJZa;*(0X|@6uw-0{aNV9qHPLrnRaXM!LSxJ!0x(hc6HN@w6k!H z;`#H+h4&|EgaBkm2%3Up4hi2VbhZQ0x22`F#A6@?bh9Dk!Ck*vMXhWBsagOlhqVJg zpBc`Q_`Tn5kX?dXd)&sTNde&B6q9=F=$lO0FiucLioiM9-q0`2jpWwS1QKA4uiu|R zFc{JI569*7gScU^2>NbrLW~568$1koK_3x`x=P{QeN8R%-h3V;P6sKL4oOM}8t*8A z{FxFZ)Xdj&Co0Gg=#Ta27&T$-YPd60>QZmKbH3BGaDnL7%(~DeSZ!~GvkjK=e)d~{ z>pJZk%qf6a-cZ$HyKxFe{6#tIaWM~pQ6b80|Nebl1LUAj(#Pmr30_H0m0Dv%hlK})HZhq2B{rc#9U zAsKF7 zDhr?J)RgxR_Dze53`Hyk#F0?zibdq2Ef zb}pC#&@$X@<-@S)$icxu+a5^Jp*S0chn)mORL-YRzs!ODAC0?zzItII_6{O_HtfcUa0__QA=Gm`e8?m4QQ6);8;RI zYw?Xy)+=c#rmYyJO!=AuqG9>yxniA(SDLIQ@60q=2Wcxd8!uu%0n^fya_BwTiY^eM zlpCs6RwQ4q*xxF0{6h2;l`=E-IV#Pppq}h00aZqcvjr&1+*GT@Z(bZM=rA4b8yfA#Cmv3{#x_zHUXfE5A>LB4pk2tnPv~~IVHZ?eH zOYpV(_5AAuZAKvN8lbj;9eSH;-mk)HU4+FC(oOb9zyp92j`%dI?$|9XyGJGmv>&S& zh%V=Mf6=QE2OZ--BdTFB_!nQiOqkQmxe{yAgE}(nZ17{&2|etz^I&CWmV23!DB>H! z8A<*(b49|QOKN_%Q4=0179g54+|}=Lum5ly9k_E``&%>Tn|97VuF?ciDtoqPBIG6D zp<+%beAzu+^lK-Ru2Ff=bd;tP*Ufr0uSCcSPBrS*uOH*!`eqYVS7-*Qa#&f>6qOE- zyrsib=V0JEyQKcsdznMGw%scXT>iP%vl#7E6 zO=x4DYVm|cs+OAcqn*#OFOe`ZK%Gp#`HKeS_Cm|jq863cn{#Et>ztSpvlHQ+Rl(4j*^BKf6af&$G}Bl%ylQVVXu% z6Zv4eneW%%A5--^V;obHrd9UzNl4u65f3v9i+p!oaClhkjq~Tvt1;e~X@{F!n7aSx zM(H;=Kb0Y@?{-3D$d=945NggMVW+Zvw^aqZ8zd6*1VZyzoZ%&P&%frn5Xc;_oX%(D zhWD#uo>UAd<;^OVfVHddrx!IF_%B*cQ%;>b?pNjFQ9SWX5I`(e2OX=bq$k@mzXUfo zP91k4KMQUpJK1{uj5aO<>~BE*#XpB3{~crzJv(kh9%CxF|Bqp~fB&&4!8zv5%f?rK zZ!!O@#Q4Ye{O}l#dyz{UZ;_t-k58)ta-x{b|1IzT3N`<{O#kmL?;wmM_+MH8|F_it ze&+u2)&ILo{r~Uf-S*vFJX0OSj+@w)CXxSiK9$r1sYY=6CKD{rRo}(irLU->xUb07 zpx>b7v-N|^k1k2gJ(2QQO1ebGkou5J?jD&}htv2^KzI7ZW2K#>gP*oqffxqwXqg=4 z;a1rnoS}x~GX@6=x5l9pAO*iw$E(3KFmD;{Kgh=Xy^R3#!HLdYV$Q)MiPQUUoSxP* zhCJjs>>n=WpZxdnX)ujXV~o)$_@8|Hw+sE{%iCbG+$M$bUy|tm=yLw;XZ+Y1z|>_T zQB&xD<9v+#;;XN+&wJSDS5~dNUx=pMql^~}E^N3+(^)d$WdGkV87ZVv)bIoO?~B6r zQ^`Clj3-|zPR6%9kY9s5Vo8oT|Cf*aUw?2T6fcTNZh@K!wTx8PDZM+kru14npW}-@V4SAfUiQrT9RGSfYzM)YK7mx6cn&TghVXAu1{=qz06nhU|(lh_JIuHG1;| zXJ@FjNCkS4Myl}-1Pd6S65n@S?9Cg}FMgGvm91+qwsFfSuX%X1#8uUO{p(&TP&Zf< z5KguvUD#^uvfyeI)VQM<2Xfp3E)eoQ#mNA}-d zDJQ;a%QCdS?(E4;Jl`KfH(W9J#a0%QzA#kQLa1L*P++#x+~>G;K+@uC=&SP_?~JP3 z(v-uOEN;>3YgAl3jN=!{s7W8uad#Q2C!jd1a&WUs`>f(3!5ygy$4%t$x&?d zo^sCt23Mc2#&i0hYWbj-8Wz5sd;57U%fzI`NdjCy_FE4v{cB~;6H=KiH2EO+mp*pX zo+sVH4J$5IU}0U&Rmrvwlz?W_JeG{AMvrWhrMc3Gs#(YU;?^}a<6S>L7qi;s_fxwb zMY^jsMxHl2_^tZJg(MD{mb|y~QqyRoH?9gcWxzutFwnA_9ohnaza9QyA$A9 zY}_xNqniFoFX}7@I?SyCtlTz2YK9Wo?q`eW71?l=9E>WjZn43OOlJ1io!&pMrGX&~ ztI93hN=lVE7_Iq7!do8=Ibj`iPv5>J~ZW1T&v+&v@M z+I$fXZ)LB_)%|ph6Sj)i6~e4gY$F_klx5n_c_3jfBVgr_==V_ZkJkJ!P;gM~EOoeUNk%iCvjr!6a!PMcMUW9ofFay|yg z)lB9EEXI^c956Yx=0gu;n_62V`;C0slehYOt1KI2UI-pABR7Uw6kogtHZ!(zBaz+rgGRSDZbsVtn$;SAU-+lf!Ryt4U>{$_XWn zkH}x6W+Gk<5#L7WDOi1e1;kY1Y{N#)*yr z9A~^>!~^=uj?Il2PQ8-gwM37kZUbo54xuaDduJlE~5WF+x>202Thi}xo+yqxnygH!Xfo88xK0`t; zZiDwxqUpIz@YK{yaxq1S*w#$!T0>m}Mp@U^HS;{9^(Afp6qOz*Mu2nb^?A08g;Bc( zHs*zh+}!l`OIsQpTWj1kgL_0g8g3#kHcN{b>kx-_ms>Nj6~l9%zMl!*mnI{RNL1d` zD8c4itf=XfS-suIjfkW{hbyC^gRRG0^2xI;7=S#kY`!4l*N``(RcxwZ+t|R-rU!7J zMg9m6R#ujlQY2gB2NuPts2TXC-keTDl_@(Nw@&dl?DnwdW@7-{i))SQR`lLd*>{(9 zVwBY~<_;>%!AJSbWcyw3oNDDQiRH$L@MX_1G|*q0r^wR8$b82gj>KeITCT>QPud&t zCrlNLe^SJ?dy9St+sA;;w=6u3ORB@i}t7S2hY3A_t_>97zDp@xQ!jqD0@xC_rw<}9a zSIHiT+5ypNv~~sO-MeyxiGy0@uDGXoWqYQ1ne7$sEt-L7YL1So_0OV!*kv0#iU=`gb`lyBul9PISA6pLO`t9!( zQ7KQ2Qzlo~_-=Rhgq{tg+9M&e{xV?sJs@$>S#^82TVgaUDbE3>AFyewyfK-P3TYcE z$sGwLRI;mDGwf0*({*ErLUgM1G~U3PC( z%zA)uNalND28k#b=(LjKleJg0YW=PHK5ozf zZL^=wXN<0f@hNTAkEdcP9P>G&x$+n3gqcBT=96T>WGX}kc>=k@SOE?i|od6$fM`Fp=gD1kb?ik`>%`JbKYCkS3h)TP)R5wTkPMtK}tM2rf07C#T<>grS1ff+Z#)mlfPig|q7>|R%AdTcR z-(=ilt5oK==&QD(_t#sHJbMjrQ_SWSnvipYr7osAUKB0wcpbjJHP2I zk_1NmN7o>6la>q7Q z_W)ZB3|`dtwQVckSe%}WJb;cCkdx9uIO;v1Yd8{G(Z*Nt8y;Zn@;C*v;S*F+y5Gb% z0mM^mA!XZvmI7>Z&y!R6jPIH!)S8Tjso&7$<(n&uwCft7sgsvoXRq2FXnl@5+Oei# zg2jtp5R|({K4BA0$@yWp+U@~|bc_aOX2mCdZ%q{Gxhk4L_%bPae9fnPbv1Zu_{iJF z_2{;Dx>C{|MP0toXs#q-g_4}4sov_UG>|y8|EN4+(kn>~02i(^z4^)cI*(Ilc~;*T zZ$7q0<;1v-97)3J>(Lu|6V=hdO0%UN8}qPX-p)Aij3oR0*w5hy|7r}_hUmz;wT}~2 z4rPrAiu7=MS=n@a{N6l@msFhXh(;g`XYEAth%K>&IxIeZ+L=YPG84mXlfg&)8R)dR zj=+NZGh=S)ajD3aTlTOzPE$))ZDJL4b>k-|CrdH?Ihe7{3-fn7?$Jj0k}&mOxJYTR zYW?UP=?;#4Wz+vGiF@0l*sUJM(3y<0Ms6cQpv#4pdaLceyxf5K=v7^JotUMJwo8a+ zQMa*EWmD-`+fnR`>=R}^Bw0I#0WPiTWL{+xH4cUfmTWCfj;zjMh zWCFrD!~w5LCQ~+>jrgLt56RvpEizYs-~zj?e?j}TMVEQdcHZnr9^VcU1Q)DeGvKhN zfvm#NWS)>P=v7z|PhRoarHU5onwg-Pg4u!Uq~SE<1t3S+^j9al<{t5Nv(?bTu}? z+->dd#z!dGSuE#mqq>W#H0Ol*BaQSXcJk$cFxiiM=CpA)+ z@ju6WyGCI}MEn!U{8B+=Pf%^a^dYma@2XCP*BzgGe(en*v=KPEA6=bcmt4I<7Y&9F zU%gEj_R)bAsx3ZKcj6G^HZ0fAS1vLkzHE&}e@x)B=}eK!kf@uW6R<@?&SV_2b_Nx9 zRIkU2?u}=yeqWDU42v+fN7qr6M@3AGPneZwe!^lzktP%AoYfVor6c8L7hGGJ)Heo< zdV(54ifRr>e>7w%gJfk*u$C~YWa;%v1@gfb*W#vff?x$j^zd7jSqtsMd_~!~hnr=K zw+O1td5O;5t03<@p+3Orn&ZuqO(dX{?KeJ3Rl_s&!~{ed!&$M$p9^#JYPt-z7A))^ zbNryY>(&XqNJJDaV5?guT_|jyBDP8jS{K| zI+%a_PRwgNS82y5egU2EWQIn3eVwU;nwpf6cCe(V!Ln~q>u3X#dkQrNt%Bm|pp`H$@fd*s-${Gf`JEzO?+lfdgA@%PzpSpy=EnlGIzsSGIxN8l16j zgCDA1q!$`r2v?yMDLq&rXDy@nH2gMwZKGB`-=Lc|q5Z{0!zI^QHN~md(nf(m;?Y<&ygp=akd_yNKZ&f_5vbVT!?_OQ zl4-pqgh9qIFgK{>0*kMnajmZx$~v*_C&jlCwbUi8kEwWXjj{{(4^T^UFSubzN2|f! zuSMxZIrEL_`ZqJ+0`ogwC>@Db9Rj(B*?)J4F^sE+q5X5HOcJ zJuX^hYiSu7EwxM4C0Jl8Bb_>gdxo_hvWaGA9{V{S9-g+LEZjrHW3MgR5{0&S{sX$t z8ZSt$H4@)9;+gmAT2aBeD3I)!9p3B0qN~i2**X;?>%7b@_^*BUr?hrvBRb3)+LK^z zSwPg|RagJr00nvCUR=v));+B(P34@jRUTJphYEm7%%&6RaV~S-{huDGm%EA>eidZr z`KE7IoIRS`RchM!4J6O6&l71OH4W4@mr6ROqxz4o>)zCc*AdJlvmETM!&FIaM`k>h z?ueM+iV1=#F+LDTI&pmH&ML=MJXCY_u1%4p{dVHJ>o%i1W(ap9%h%eN{g=VDL*b_J z*uJ24a#rPc4o0e%I)n%>-qp}8wphHzCAOinU;c$KZNt!+tBSDbn11KA(kHmuRuX{| z!;ihRC+0dbE#8uwuH3p$uBF6<79Pcw zjMzNy*ckr*sC&<_CbxBKSOu{nDk4f%K@bp7s&o`l=_nlpMCny}2azrU(tDR8E%X*3 zDj+?P8VDuyP!k~Z5cnp0ueH}c?{clP_xbnz@VdMrnB;k$Ip=+kG43(!k;Q6qjcLM7 zsY-&yJQ>d91+0m026Ejt*>`tOBLfu&Ee~Ba*FCV)+0af>!qmNe)X}L_d*k+iA+V*XPG=veE8E@8xSh* zn?+ML*iv0-%D@>ew*L+{7xh{h#3=$qsdJ|Lu+at~u?B6o!wxe6oyimI=IFgW^I`9a zcU?R_Z^+Mw@4l`d&JA&0ni6tRwR#Y+SuW z;V|lq-O|*^i4%)$D&Z1Sj)&UMcen-7H*ocb5#%BkcQmQdSFdSmVC#tLWjLULoj{6rO7S-2h~c<+#35}8JbzR44HI2@~*Dk<-F zwE~Uzs44qS;+k6>lCH^fu)*o-YiX(P+@i#qWE4axwWEB}z`$-R z!KBTCTZ{v>jZ_Ty)^@3hndjE`RK0@B-qya~$2IJcE7fiD7RAxv5X8|NE8x~9Phm?# zV2gD$>RwYlfsZ*Y;#X_>Hu{d|Cf-;9Q2iS$>`VDD&LZmPMM_eyes#IQ?f5!Hv1 zfIMwK#0Im!ZNc3>;X|@cS3t|vFn@ybN#k?Ao#NweR$G0SVBU|uTTf8`H`qQ{5_go- z6Te&`==d;o?7~Z@S%b;^PHo~iy0e%R-no3yMRpEa`I2o^I;VdF(20Fh%dQnjxas(a zn}$yc71!VUY+~33Pbtkwv@8Ukv)W001D&^B6Q}b~(@2$>)rl$Y84)!_#jxi1?dZn9TTD!!tIp(5S1y!48;UdK zkPb|^Xm9##T6A*i{hYk4VpXe!=6JcA{lnG=g#LRgs|HIN& z`$;KVjzWOSIR_*HK)8jN&B1DR(GWr@IgLHRH>9ZGQkHy5zI5^w^}YAH z|Lw9=O7A5NPD!%QKii5$Sv57K9)DeQuTY_S*Lu6ic)6>Uk&B56e8y4ewZ;)ole`m~lkmHLCA**G z>-FRADg5~%fTZo_0x40{x(;L2LL*&o|7Ys?@7Mo-yxE_rV^#hmLBdL~J?ww^!C!T= z*TC=AlmFeS`SZ2>2Z5UR9w;{oCZsU2|79O@ByqU$tjqncr4E1bj*`cG*zX)^AjBQ4 zf4`Xj%?Afx^wfXz^S?b||G)d9K8Y89b*F#*n&d3iGkI}kSDP>q;M)zpa{7nUd}quw zS)661r+5sEUi_Eq?C9C(s6Iv&+_{aP{C~sL{`%_QiICJ%yLY!o+VlO>M>_Y&)R49)B7d_!XHH9d_*DRIjb!)xY@yci#AH zCJA&-85JyRnGY7;6x4I~_v5ox{apT|`A6+!KX6pI%*++Z;QOonj6DgwLC4nPTE@SA zhx~VvPEKL+slsx_u)P=g{pvm#-JhIZ5}sA2mF89?gzXprj9=kP_WacyVmL7%w&(Dx zxBmSy`kZAdan{e=FMB29U#rJeVM%&{PLw&goz1Det?f=y)V*IE?aDG>9cc8P>a_an z(FbKJ(yLrrT(;0{RezmXhF^Y!JC5L($X4v|`_)JP?X|aMq(>T~1Rkp@y6z5Pt=t*W zU;eZz4fwxe=APRz{qmpxv5HmceM)jNu-yQ^d1l<5f+>!ZK0@8>R+aOcal;%03cBd! z<>aylp=cqB?3`gdR$+)$NT_u`%vK;L*CZ%RPF|7CwnkV)L}v>t7u{dc1mB2R=LeK$ zqLHDodIw=T=l84n$64d^p49wmMbt0v&hMX8()CKvhtqLBxmyuuqH>2jbsDUFf$Hi7 zs@hC#V%Gd5K=$dOjs!)mQn#Yzp;|eQ@X*s1>|!8I9!$c?065~TNDs5}?jvgz`JOEgKa!%6JF-Z# z$i3iS_k>)ATeH54)PnLhn-R?PowkmoB)$7MoPMN}ZA{ob_4|s=XuU!XVz<4x+IsjU z*8_JyC^rl)!sszamTJS`8AE#Vb{NPQg^61q%D%Nmv2-9f3hl_K$jG?`yD$#ugee$ZY%@{7Ev z)nItp>jCZB{Y*Ubg9mN))f(QNRo4=DJY!wR>{E_wR|*PPSL;@-46$Lt=vY8YlRUP? zFTy&~aLU`>h5W!1AUNVCMn=Q@==3Vs8oWwgMQliruZR$~_!5T5UD5(MMHXl?{8g?9 zKvh2|(1U0bwc=Lpm-3|R+g$I5g{wl~)E;g(OaJW57b6RH`fRu4(er=aKYy?K)Z|_@ z|NJH}ny+RQ{yDdkf_Dz})l%k48Bm>dX;2#Jzm619$O<6;Y=k#{fgrWz-=~4OLrgzu z>b$?c-97}FRYJa0&%9S^Uq&QQbS$TNIZ8E$((*_D{1IxYSz{bJ9TU#KBVyaZS8ZOx z0HF%n6b`48iA#Y_&8|i;IcYciP=f70v98B{5Iz2iK7HV0E&^V}Qn++!QXyNR^D4;I zkdjdx=N3`eM2opoGP7dhEr+Y58uwY$tn0VV((eUU49=l|DS`>2NG&Uc@bSa>YIV4@ ze^P!=s#PxquTfJDCzgdg%VMWq?`_G^nlAlux5p2{-!JOj^Mck+{7svrvnc(aAEETJ zGeGrgsT584Ys@(P?3w;zT{*Y};r#A?fsNIUO2KR~U&ass>e#BjBk-esLApqmH3lR1;EJw*UO# z{c9tmx?+0u`b{EX%MRzUg}@sf`uJkqo(sE;M$Q(DO7VRft=6D`C>NEND->Lv(kgOt zmoKy&W0+p%0=OwtK*!eu&;5WP$Eh?7cbQsXr9MorD_!t54N42f?09TMXi_!mA+Y_30QJC}x8%(& zzW}~Sl{!bb^GxZRR(YebQT#&sIfR(XdxZ&p*kG-cNY7&Xl7MBE)CL1U>S~%QZtK%-7d!&Q zE_c>9suRQh%Q!91?gWgul?15PA&Se!#>TR4ZUMx>u%^K9XWA$Oh;%i*P^5M1ye4rq zgQ39w=Rc3vyu}l&2P2Stu4dw2MofaqZU2j>%|@REq!nD4Z1)z0Wp0#@JhvafZv{-IS(}-1HOju{WR|n%}Ba zskVOi*iX>0S|N!T<+0oCT;@b%p{;o9th%b`VCdIhtkyX$Z9Mntb<$2ER-X!&?EW`Xd_Jh8VO-H<^p2Rj(YLAD^>4ZQ9j$L#M?Q2>O~lsxYnU*-_w4*B*al{h>1oC5 zx9SOgL7rW6wf_>o0{uz8dz zpCGQY#VC5m_tb`cb>APS${qLPoS0ib5DU2!p zxoD^~K}mDc%jKo;?~Ca7LgwH0vmNUBpT(g?!cgO}Cn6Js>-IBqJsP`=`-DLh3tjs0 zEC^Lpeh{_X?p7<~GzR68xOq zWFc{dnkxYxymf0-6aDyNL1Px%a{C_0Li`Oxco+EYz{90~e>ov0W~Fp99A|8T8B(?{ z(&j(X&SXk(_x-lR$)6bD=<9yV{rfNedDFh}?j6(Hxu4&9OCEYc-F8+lB?v4%HPL8< zgwhDS-M-C|T|;~?Opu=#GOIKnjGjopVNxltN9*=-dfYSE$nF9{W+}epaqa~x@UTCX zK`PRZ>WVmF?FguzSH*o7k|6KJpNqKM+5GGwwi#U9G_244K+`R)x0?(uhCbD}z3>t9 zJDUceoek28J=`Tcuc7O?OYY+tdP#a&dcPg8J{`;eTH_H@_+!ie#hk=T9}x4}zH5>) zseXQ>!V%iFXPXK|>*soosAK0q<@+MOqi&?CwWhVgY~Z`ctnKgtUT*1X&)iasJ{EB5=jxh6GB1{pW450%aHM-UK|t2gJ9-?Xla=w?(S3$>*7 zHpN@$8DXs7R{s5=^z`ZKk_TKb+1R^x9!d>{yX*-tmxM8NosoU*#PTKV5F_YYbl2RM z-L|<>ntAUoU)XDiO0^Yz@Up?p5oC#1i9F`GZLv8ek9X|X{=)e~^{%zqBg6sfKIKkI z^{Y~tuJpJ%i1yu%m&)lVg~diiF{OD5ebYn*h+fOzNdYOHQI?9CtF zRC4X)hRGxH86Za6Z+g`N`!GfSWgvKSZ`0&Cu{Qpo$?=}&!L2wK;+YiSx`1%_7}nx7SqLX zMka0P2i9s+(Zo|TOE%smF+CoLFI0W85N!^7g&W#UrFEv7L*uSi&*Q|y`v^tLkJ1G-QQOy1H60OO+V8QA-3u}L~TCdQGjIKuyt7amfuzX#v z*i5S0am;(374Z0pU+uQgLg00~JN&rH1<_!c_OjQT!X~n2W4+$jQ1p_2xM!rdo!EQG zO&qSc6%dq?G+=>f2&6Nig#2^;sy+jtqeffn59zYZoRNEYc$ihx za%iHDnMD8|py;?7ZFV#kRN331iY%h0V-D==yY02DH_Sk}1ka9fMbHa%>2fcH1(*aT z-dihV#0;yLJT0--UT9sEn@Hs#lo+ulg2KCZbNq2?rKjxumP zjN%4lv2kXMUsktRg-u@vc4;6hiTt!hjW!7*W7u`pZiOA4R=zXj5hSN5WPo;83t%C% zraenotKz6XU{__g6T`zob4~iKu!YPGWAR;HAqgF_c$-sSw90?9!;Zm&{;^{MGDbD{ zMk#(_XQ*b3YdHP5rzNa*x0CPh2oY85Z!QtM*~4`JA0R|q+s&1H zb9FNN=?0c`{MU`UJ>GIXUv0p|r0)880ow0Pi5=3I(C7g2Ma=M6m7|;xrpsWx>7$TS znmXfPNr_G0;@2i62?h0pw{|i*0M_~3vUq2>mSbEo+W_N8D=^X+Ae4N=*7O-u?cDlv zfoom>%PM+V?>cg!r$v7W^~E>Y@yEi80^^t+44*ftZ&akjo6*cXA3KKxHLAe2h-~a#{o6}jqU||}r2(SJ z>V1A@Nqz#cgPe(-W&k;(;m;R=uGc%^dV5rv#aY1CTdn3LcFGi3>v=U~+dvpHkI(g* z$LTtx-sL6Z01m@aU(Bj**J=|pv0p^DNKsptnre)j@6+EDW@p12R)8d~~z`ka^< zvOt1|PQ*z*EaA`>a(E-=h;8ha$p5Wx`uCI9C;!IVnP+up@^WNRlT0s=9#*!*y>BnL zAJn`Yt0VK?q@hSKhrTFEEc#SDC)fRfG z3nB6wsW}Qr&2x3j5*}%K&Y%-)n3LFl-LZON!^DQ1GHxz#u4C11>{8;@rXDZ7_qAV z5eCFGmzgM82S^^$>r0$=D?|C6p`7WJ<91Z420`IweL0~}MXQGuiuD5g|#B&uk=C?_IdJ$R98@eAR{2-=qSUrvf6VxgspM7;krjxin`}|FHwO zJ7WisK|aIJ8?SfZl@gSuPN@yKf61e61d_s3k1c7HOH-dl9hw~NzSWv&^y^I4{gM~> z_XqVKEsxKMlNHxqshzpR;Wu6|ZV#H6vkq(&BAD(ZR!pjU@#jPvfu6z7GL$-=H6Xn6 zNDZw8bt)=KcLxAT!u>Y^MT+%yv$AGC(;vFl?4PwK54p;@e)iImKS+Gf>3`qs%?vUO z{2Ho3D)NY=8xQ~=HETYG@6&95sbWuSJO!mag1gdtJ~p}SfR_+ zH_EyBn&^ju59zKiy_e>(Ykq4Y2w|0ZwO2SC8J%p2*y?V^$pV?NHjpvS0Zi#0hs1@6J%Nw29#ARoFgAnoCb5S=X^4#L7VQ9($PN z?m=Jv#W4KU88UCj`KIl0tnFic*R{>+mAb|~b!w-4ilukfy23StQ{)f4l3NYEHJ$rn zi^^>K<{qWMzU-?0G@{nOgGrf&Eqi*Z_-JulvIwdH*G0hNWnS=gRZ}0J*SkwAi18X| z3NnzA*Q~ZmeFJsEGDpGEjH{jR6MHQKU-TKayONJYa(^L26~+`+gbH9Y9l#O8l#B`&IY#&-!AGwBo0(@m$Xo{mY;%zB~0U!|ap{ z;tbc|xO*la@pUe{>^okD!({ddr|i^T(# za!VeKtg3GX->RNo$VO{(7> zT|XiCtH}d%()_slM!i$`zN7<4~GtDH+VJ1!c=L2->E+se*Fe`>wAtqKV|En~$gt&HBIV}d4d zx}LAgrx-nMfStJf_-pdDCyyVud2Wxfo;lN6py;l!P5=OBUH#e*+RTixV^Jw*C@88S zVs0BH24>`2dlrqz5)%Xi?T!_?v4MO<&t=m8;DX3x6*!M%K>Q(nZcGYy!^R82{$tO_})gJQnoX0d`o;03E_VJ{JC8xXLZ|n1M7vJG{-@$Vkp+v&Q(Af_hkF!nnuXw^%T;YQk=)xo`%uc2Gl^lLiDr|g?UtQw_$x=tqq*SFG zf#jrcg$+ktv{9dV8$yEUlB!}V>9j8xHB(^2f?`}|IlQM(=d{2~^Y&Lb_phFV+f;4s z?GZo$9H}_%SRW8@E?<(4>JrxxB}jE0>zG{Jj1y<5>0d3ev47N}on6Dnai!oPz}Lqn z@^G$)ZJS4@VUc!amwiVh2uY%y>Hlfsweo}3W&nIWau z#CLINx$o7YnuhM}>DF)r@7yL*KkhoOaT`cgweo)jIc_YCcBUEJBxkoUngaT)kyopk z(jha`;SyV7{Q-6<7C&VhH1u4v_t!hQu{BcxQ>`DbPs1~igB4~f*Le(sOkcST7HVzP z(>TE)V%wwk0;Vgq-eB)oKOo?Am~!(gFkXm*t(6g@rNL1Ey!nFKQXlNoqc;g#D^cDD z__R!)RYvdqo`4Sn!{tT5w!qSr zu{4M|_$F$<6%H6neW6PIw98|WWHA=;jHc#EjC3DR8x$l&KV@L25aO1HUi&hqa(}an z_2bd&>tLWNL8+Y-#rW=ny$5QtsYTfNIU)wVBNh1K_Rp8z3iuf6+g93NcJ?eFSBTL) zpw<{#P>1L`f4@|bgZz#AqpMw6Uayt1iG>%rAt?x2{5>&|*w zZdo!u{Nop@hF<`S(P5HaYWg7}P!<27Z5MKi?WM6M>p?HCZu>Ma6nD0{G2^vKgF{oZ z)-2Dn!UU!wyMm0DRx}g+?^Y5_QnfN)KJ!APmyD!&AGr3swKO%&GtEX-S&g8+uDm~a z&Qe*EWAR`?$vYAPJJ1HRXmk8U>fMfynypY35g}Z4&%n6bZMoRH5uj!rBr}!RouM=v zudfv)ZuIGa$w|?@`K&TmysJ%{%3_+ z$)Njqgl*W?gN|)eH^i}~x_loiM0#RtF(~#%m0ZP`7!D1Td;fW515^vOzGdUAxRfcMl|l^FYh&cz{?@TZjvkPx}vLI^;}L6coVxkO2rDK zz*`buj_`;3#9SvS$9xmKAiKM)3mxYX^?Yv{bKD#=DFK!LRzUV zbGM-lfIVFQD&}43Nu2%a|!O4 z)svG}z$%vhzQ_ury7@qvbj6v`3!jeW#T}Q$yxp5Uv{_U(C}k80^L?c}*d?2l#e}&7 zZm41O%92bH8yeAVBtIy9N?Am4#FEwd1LGRX%SGyERy=CD3@aP717sK z;&;!|WL-7SbPZV_BR`X2wK{ zh;s+1!fn`uy1SV?ekV3XGj28I8esZTn~%W;h)OhJYOHQ*#W)Oj5n2e|X&212#UW z#Zql@m+D>fU?mAgR*D1S8QPzY@U-}7T0SS1Ui<^XJIHNnWlteifV23|vgaWtZ*6(_vmG)A-RzjMTyh z1GNHU)h0II*WBAMdk%EJkQ@A|$vsXQcFr)thK;Lej{ET_M1!JsXTSV&&CSor3wxW zbqt7Ju+Y?u|41tc%t1t0MOG^MX0qe#LZ}x7$Z6aKilUeJio}<%s}HOQL)4j^64k$V zL&#|b`B9U*dj7ISw!T&bdLa-$ei^?r{g-RV&?`~f+idlj{w%Qx`GBQ}k@#<>>0jab=BBHC-H?OIc# zDLL+j{uG&lD}eKwm^pWmS$ucS$nA$Zr%sSME6{ZO-2Yy6!1cO=qVF+BmAS=eUc}2+ z`QzsIb`v?c%hiH5myT6DTETN4_67KQVqBI|w!xU-sKn5QiYl0(sTgpo+_T|T8Q1sy zMuX*)2lQWTZko3i4PPENunD+%WTw!N3YgNZx8wtj7ISvri{Qy_h(j-en4lpo9UXHn zg|NcGUfdygCnly_%gW@t;#>WAYsq0DJ^eKI1%7*P_vIXeT#87O)E#L3K?mLrADX2= zH8H9*b62}wl|?>riXrET;KxRk`Z*_o3&KUPv78aY*203N_+){)F9W_s!ztOTEFZ}> zHXAE(C{tX&e0h7H=84x2zXblFm!or|SqzLcG?ADkh6BRE(=yPriZrOlG#d8ru2;L` zqMuXKLFPo=u}av=IcNIm##Ga$2~hh)8Ay?Jg4Wg=O*>G4Ilw%XT0G=Cu8+Q9Zzd{- zN!%cQzUk^ov4o2_8ZWxu6cB$F>2vvYzUmThuQZxlvF~gzeCvI_zR2{iGa_rk}U0;QkbmoJxf^-Z1bZ<2*F4c21rzJaBu|J z569F4HX9ho>%?~^e0u>*wRV1KFN~vT(;hC=8sJ6j#%Osug8*{u#Uh@1CoAg!mJY*c zRJ9Ebi(fXCex1t$YSMBsXV=Y29($Q2GKlxy0;`BZID^>w%P5l)O^$N}0R*A1G}XWc zQL6o=rbMAYo=}yekR;0#McJ2AOT)izwYX42f6t#Jr0)a?i8TE_f6$~!-mO!GQu#civNpKAzem#fnN4M7 z?gpL_3&!!~fy11Apc&AHkyw*7Elc(NyFV;m$sDd1I-`-p#8`f*Z zW3~NS!s?14#A>(bb54$I=hf~b`zAgE#PlTG9xkkct;0Rk8Biz5)6S@VLvX_KQ7Y6p z4}uxdx`0QAM3mg+!HQt$;h}ZiIB8H?)X_Y7$*MhfBMYQ{wF0Wov0{GG$jXgK5kFDb zhpM(>0@cbrjJM{6gry`ic-HaP{IH$x_?42H9WjuF?ldA%X=8I;e8T~=`lz88T_ZEj z6XTV#)rQ8#91!;&6xb0fH9nBpOAzY9+9s&u!tz!no>+0+ zsi!f(RNsB=`nJ${{>eoSuG(iHX%JV&egAodn;ozM%+^oS6BbqK+s=eTaBP^svyLMr zw&kMxLs45=<}pi*Io;8C`1J(_{a%v=MML2|FLN-@=PFTe>f4-3sPL|>WwcVsMMPFq zG*x*Y2${%$8M_h8e>V5_pj6Tf$n=^E)O=EM2Hq1kw~A?&mpQsMSU)c(kLb0Q#bjUB zQ%?eXbllu*Eb66p(RQjkwzLyGJu~3JjRE=V9TjGM&1am<^_~?V&DWp=91PYEUlEB= zM|VT~{rsM}xYU>q%+QNC4Si@Jr{P=8kEOF|{Ng$PGh?6yDZyNgJFjbrTO(GhdadGz`*=O+V;V)m2M643pDmEZTMy7D zyx^{)`kO-3{S|irN9%|`-?LITU+qg>U1>Ek*iF6*pCnCibeDloif{ubQe_vP(a3av zMty^)=0Q6t6hDSq&Ro$LN)r0!Uv1HcqR)tNKG#>VzQTIwuEXMu_y0(X&(Jt>wc#KU zvD(zeMebRM4npeodqr=)7Q^N&K{#K`s}OIc6_T-8mUNV zmEd&1rBnkkq$4aKnXw+nWd<vhR5M&PPR}EQTxm(n5>+q#qOUhuR}V5QG%RqX z;G3q2jJ$m0EQdxXI?Y2p7ScxakvdF=fs(&IjOev^-kG@e^dJ@^WO`@25o+Vy+O;qC z7;C*cKR*=6Si>5wmK(M*`vjl*7WHAkBDvU;bD?}Av8!;N-<1FL-%*5rjD$po0&)JD zZ$PTXjLw-IL%*ODyhXgGZ>M6Pa;f@LE;A0!q)5Hi;4tLH6$QaDW?aK|D)~I{w9=?^ zSeRni>Q_;UF&iq^g-)M5ITq#SAi8ZauQk%>Nf3P!R76@h@Q%otN)iQP za|@umpBB`ULnMo-VJ|gdBfJr6Jxj!j^mvzK-C`Q8-6BMPL6=!2L2c5X{zQ(M3e!vt zTwLuKsMWtcQH!Y7&s9wEkV@;$${L*vmT5hdM3)Obobsl)YB8{|&pu;*tXc;N&s2-9 zSF_5A}WqUX1wxw8aP+r`Ik%Wc$+-zazySuq9qL|PBSq@0+$%@E4i2GRmjlje_91JYOX zHXgn%5-slxIdjCl&!trj8uW5oR_vnF~og2Xz3d zZ^6CXlY#>*YejtX?jGN)(c=9i9*7kef9)42<;=7;B;Yw%tLq2qE zyNK1eI`D!*cp6v^wwnyVHKtXTMXB)ZJ*iQP0v^Gq`{9l+Mkaq}lW}IAa~1w3W_){;$Hj5>$T{&4Yt!y1fMM@WXN=4bSgLL{K=uk zyMI_=N1bK9XntDe*}SgXrHhwTOQ@BvEEjXv)zw*0BP**N9j!VSwVl4{2=SR=?$x{=-9{ z$2uT^J-Zrhr<;9_(q^<~xK@p}*TGq_)E1Cxt_uS_z{#&Y%~+^q0XzBe<8QMP zVlWHP*cg9L<`!*#TnN{-yG;RlsmR-BJ>(&dQ_W{YxNc>}`uWcyI?(Zm$k~NQ8bAYQ_*LUkF`rS)mh+W{(hm-+Z3|UFb?)3cY_n z)bf+2GqA4wTzSUIU7QkOGHX$P^obhb*%ZI4DSy$4{ZZ=5K1qM@zImL3&f3XvFB|?T`cZwMTd8lcH8BIw00_sunf9nL%TO|_l))-1ot}HP zc6Mcj`mph~_Dvfa<7l5@m6V9N8RKFdUn;)jmD<5`X)J6V6}FKyFK329gC*twQG0pr51U!A4@ z4(BxiiJaNzQYc^l`Dz1%Algd;=>Y(1$|9PA;{(8Q$S{yC4t=M? z`IB=~&hjVw;@}TMle^^2)+#cBkk~R2=wk7Bl1ZQb&HL+fv_OUYq;3CXh~dII)%wU{ zWn_sJlxY08^#GTpWY`*}Yloz-cDumY>lFW`x!|EOac<$$nc1A|T)EK2fpcOeL^sFT z`iCZ%Yx=oPexjTEAyqctKSO4}{Ctf4*dtGaKv)VB;CkfcQ~Fy@Rqre#)mCY zwieY&c!8_Bq_wHI4N4TL9Bq<-bQJoR4&je8{1<;~VM^Bu?CL7Kr7%}OW9!Ab!LVeL z9PqwU+t!QYoW=Z5dEMH+bZ6I4CTVcFC0BF#2MDY(*5NSyq3`JGD&U7WqyjI*02GNA zX|y?fRK%58Ai3NN!*;v-K*45@2nh}@jq%ys0dSQefb@G|-rn0a*7cz3n-k1{boXFv zOaQ&d0~)65B2JcJDttGNASkG(=iY{GH31kKt~=_R z@2Qc@_riKooh@SyaC#9%^)l>ugF7%kY^Wj&1YodLSNwhJPc=`&87|Y%upb#a0r07# zPf3C^T;iZc46%#YSm>MmlACJhedo=KU)0KT z{08#$7VK+JZmQQI%it$Ex~=Qv8#q`V`s)WthW{BCJEoH*gE2}rUB52x$qXuE|g zEk^7^2wHo($NsaCZlL z!r+YN5_UG*U*(w?y}|Kgob`*6Gp%;%RW4)9ZjT-(A#3}1otK%PUkxt2bhmac(ZX=z zt4}7CmtzWtdf}%d1&fjdafZvFKks?Y4FmXV`v$koBi;a&YTtf4uC0ke;x6c3V;bDI zQIZgKiDH{|e)lay>H~Y$4U{RW;!Dvy6qwuf)M{5*nvuXjQ#0|+cWW6ObsfGG;_+{0 zju6jt)La$YS>HxL6aKWganD?6`XjF)y3uosq2ffju^OL}11MkTuIaxNVf5UH5E&>j zXd1Qhrj670svUJ2v5Jg{Fd%7xDtr?!$|*d)0-7b=gPm3hR0hp8;JBzi`~4i?{aBVZ zfS04Ty_iP9_1vWQ{{J|JO8#*S-JxMAw$e)lXLco%xvP`f2dkn;!Za`oYe;sshMQMl zk2BKB#d0~jbJx!AzXZKVyvFGo$AYWteIK=)!m81S%BX4!ru%amWG+=&<=!!0Bn-XZ zOu806sH$WyQ%8g=SWzja zSg%|WF3Ln;O%`Q+#(RA0eHHLH?D(ivzEW~BBzA}VGHe?b&g|N%=sqsyJeHdE9`~i# zwQrNjupgaTk*h$k4S4CaCe89TmB_(5Mbr8rpkO=ECbQzv<~QF!=FHRHda+zO28~)m z?Ao;`Xg4oa+9oTqg5PFP6>Z6}V6yO%72-_%@gI7H=U%-NcB54N9G5(vA$K5v|JgF1 z$q4V;!Z)wq(9AwS2%!mM*Kr#b@k3CAP;A=v`eo+9O7k6!f_?KHH9RdK14})3>nOTk zg@SUyx`BV&2In;$Drg9#LuFPY0<>ELH%d)t0rhy4a%}-=m>XWPCjgQt8r*cPxvm5z z?T159=>&A7IDpopq}q3N8Vi7_qQ2K|c2PjR$FAM(Y@Ggbr+YotB3+ZDwq=a(Q<25n zL@=13)~8&e9(XX9WMdh>Q;raik{~W9ImMHGj03S`HVvDxfnvsw3sN z8^4NSRB0U-lM6`;FuP~Z=Pr*ksWc|dIVa-DQl%2?egd*~eUH|r zodW`U{#h~~9dhU;>8)-0>Tp@VbTwS#;C9_r95CG)kJ4phtJ2}XR+**fZaQyxk4MlR z;G77Fd(#fM4AyO>IlYytfL<{j7ngV_)Whgo0e70%4R>Oi=l$niI#Kj4PAr2ZJAN|y zoyo4}X4)buPJm81EQ}r{2k4k(hrR9vZ;Ab(1XN?e^t^M5?IJGNQwOo`*>W-8QRGyyxL7OOBTidF{Vs+lFEXi0M>n` z)x2erhGl7X7!I^Ww8>VZ51_A7F>9zfK?92tb&{HKwW!Lo6=BG?>qqiQgh5M{nC;YK z!2;lloXFpa98HZ05#yj)dQ~L5-^LXFj2@rVD5e8;DJx}dv>D)*B%hyT_mqBp>KUwU z)T`6cNu!$MHLajQSuvVPK zud({J^?4;4!1A`2d(<9vQEv=bAlrAiSw}i5u+sGck3tu+DA3&7;yXdhqcOK=L+Qfe?C5xz(eOvhDNwTF^=5i+_r!9ZLVtK8t zjjT7rbA)c1KYqEQ7Ej%A?$ATw$oO<^jy6Y~Gf5N|R-2-J_ zO;5B+*9Emo7X@k0bKi2bFXu)dYLP4R(gU9F_0)IO@ zHw5@r#?W_fH`)KU;g`REQ1i6Gk;|YqeAad;SVm;@d1{7kbsr*NyGG^YCwE8OT}l|d zF*eQ2-qAe_e}Nk#ynBUSx>C4h!(Z3~h+~2pD>AVks5l;T>;?Gy%OHz&v#my|GJPgg zDuHlfq@>h1-l2Z390<#1)h~E$4roa0@0ukpxK8?0&(va4qkxPO<;ZU{E~*hfh-0CMN%_s1xMTiBl*;J@CVy#as zmnN%q1RRURy{)PNUtS&_kjl83>(5Y!<6iGV>w|e1di=@7g;vIhZHa44c==aIe8^+; zc*D#bZSm%Al$;mq-R9$-KWI$7=53TZo=}!34pr&$UEC}E*m*zcprSv@oA!2SH}zda zPR1nuR2m0bf%r{*(sLWe=*r+!&Hr|9m1KKnk4&nmD$PScZ)*i@(==8tKd+%LCI3@E z;^FItuZ~Q`kqvVp2itvKky3NC742vb6Mbw;cYykGL%W|3;TrNgury9Wy{jx%7;&FV zXxN*w$Kr(+cHW6@v`v?}%Dpf8xLhDy7Dm4tNc-7jySQ$loH+So$#UK$^vq`qsyDct z{|NZ_lL< zJL^QkCAO|^Ru-H?V9S%IwIe5o@66X^lD$7yCV;RHF`pIEu(h$ELW_Iu4Qc#;?7d}N zlk z+xcYeKhB$Jm9`({F5PtDoPT`6E1N|r4R9=0))%S9_qP{g)Wv8B=^Z;IDL#I>-DeHH z`3yV3X(+y@Wn+%+H6E2uPL&3UAHLapn$%EAklUH;-U+9EBBS7( z9)4o8HpI*|H-jFk(d4oJUM_LFNPAlio_10vw-+@ic;mi|% zi=i$R>4b^d#nkonX+OB?Ui zJgMbtJf^Z*u9Xho#}y+x>E>K1Ty0QnWvS4V;V$F?PtdWdIGsQpp+jwv>c{vQR3)`6 z#T{Fv2}sFA3aDvO6S>$Jn2JK~jVtU+217{$>HZ;!y?p0ipd|h{+yCzM zQb@y81qXQEELZ4`C5)?v@V#3`MNU{ogsXZCHoN@Nu%N2Q_|?shZ3J_L)v%~w=GN2t zornImwCscD5IGuZGws80h~mOvhG{{&X7{b`59LtoYUqz2k5jyQrY*Eo_xw%u@_XZ* z=C5r`R(o>gWx02z_J=DwV1Rcb&x z9XipAxvB)3np_!CI)1&%pS-F&Pj4aJcQ1MQDu681T1Gsq6<(DYwr0OM2-8;QRid-1 zM<0zW*;W~|vn|<-q=*~-g7WZTRJRF5pO%xYu>EF>%<)NB|cBR zt~5?qY9+GgeXaW$nT)mhJ|yHwsPmpD+&D^KI@ObF8>9MnIdRoTNtt(PJZGv8s1~dVFidtpBvg?C>QsrLmqusgA9~1QbHXtVHSQT~$jw)I3ORyJ~BtR~8Jm8W4Pw z;{PFLzHwQz)*v)q&%408^O{DK>|NrY%7fu=vUbG~&*T!}`|5^9mIqBN>=#9>#?gYf zJr_l}k*MNG4HRBuJ;R>?@^rr z0A&QGO801Q#B3Ox&N#FSdTQJ{UTOI{ZveOPglx0-A^v=bpK|&8e@NKxfCXi=CBjhN zz$X_M1e!ZQNNup;}J}?%X0@zYT(@^ZaOJ_iuTEqtNO%GRo z_99EHZF_cXYr|)<#YkX+tTfLY+Rb*PaR1%5{dGC2czC4{`-{E56zUvhQBMThg`p>e zHXcXUnq`5SyQloAW}^7fL7hj@-i!s@&-Q*eJuP!V{`AjgS2bfhDQW184?bKv>VIQzS2A79mdAJZ zmtQE^f8|WhNEs6KGgW|JZ@%O+LQviRa8Jk839}ri$Xko6%0mB*XL9Hm44;HFXmiK<$30_r%nNQ*(&o}?Xs{8Fn7!|#~eog7Wx0QD#Cs`jO?n&C^ zfacbk=V6Uw60Le29bbQz08Hc_mhROCbzt^f;?-4PRrB*#f1NORe-V)3uJSegn}XN*-5RYlO~LgskCZa*)1TAAnr`0Fu-mCEsnGe!&|-wfsq zwJLx8tgOWVq;$XcF8NRGs$bsofr)k$1~mowtMdU_*A3kFU@|#VdMxD2xfr2a{KBp6 zMZNL82Rf#Khd$n7=$!=i+JkQf;S-j2^uJ!;9=N_ZhMS##jQ{`bc1|QXdGh3?%a>WB zqcz2WwnDGSmnAscI`9+_b}Et0MGdkWP~;RXWITScUg3?#fs?j=O*RGebOc|NPFVWm zf7>sF5gFg|{~N&VPoE|8HG!0r)FZvE-~glp`c?KN1f5BPx}Qe#k0q7sFXN@zFDxx} zeij&+%twz=-x{!V<St=3F+)WBFHEqOK~$QWNacG2d)a9&&C59GFb=V zt%lfT6bo>GYS-QZgu3xkX(O9M?HDToP|jg2H*2`|>0i6<&wuehUQpGQd{F4+~nsP$Iyfu&|%9e5KJ)UC{k3h0;4-jke#*i3?1#Y{ntJl}W7smy)2I@{<^ac?uyrO>R!UZ`4n zaM_a3k2h0Yefp&#v#2NsqL#4Ck(0qgSns*=g|@kLEy$=Er_7p#9Jr2fDFB2pe*IzW z)ZdTi{C0819OR&*t2vO8^GOb+4-hJP2j!Dn*7YAcdxENYMc5&HYr8MKsw5SVe`jqy zgUMn`htkZ4G_^y{QgZ8+K99&fl;^7bQT3ek>qvll*Rhdtd~gNnmC=4=0^%!Lm)Dk> z`vI4b{{6SI{DoBHjg!ys53|E_fU&+b@o|TV!!yOzELEwZ>WvR1hpqzf$98nx#rt* zK>04(qN3ZrQxnHrDCK_ln#!>C+aw;31g6+h=IxQ1^zsHB8vNMDw&^51 zbaqxnUa(Wb(B=Nhv)R|Fo<8-YcO(OkD->{nDlBj3e)9G`-;$EX{zW%d4Nw?K-l3k^M!>%J_E!%-}fzi4P23rpM3__z-(~1i|_-S+P>f+pB3Y61Qzd>Tg}2L zF@**Oe9Y<3!VW%wSVLF1G%^;BXr*QWK(CHlRk}U$uuGAT{K~&z0`@LSCK(a7P6Snk zwY%T@xha5O>~dJXF)p?WloJMxt$dWMjTQQRRh;VNP(!`9#`qK?2tqEtSwNp$RicB_-Y15?ePxf zP+If#Ef4eCjI-@84khOb4M;}hY1}+Jdg+nuZ2_<@Ib--f$4nU{JW!~J2i*jtr7SJC zDArJh*>?p3{Uc1|sbl$gMdyYN6OyFtaqS-49lTHP3|jQ3MAKD5COkCDjVh{50_Nqx z+#KJIhecoJb*gjJlL~O8MRVunVT)BK?Ux~a*{aGhQNi}1yBcSC(5?cxXL7JWUJ(WnbqdwuvlPzQ z0E=l(F6q!coXu2{<5xTSQ;uqbh5YhmqwGtkqwm<691+J&bC7Ly>KCib$KJ{Ab=1~> zboTNADY+%lv>qxT<5EGMI%gkvK*YCpVVo_5S-rqAE%s;6X{8XGyT3Q*YZqRK%>vc8 z7_#e@O0EHCr?`TRd(36Eg(e7t#n|t_NAp|W>4(Y-3XBJmo;w!|3gOP}trG*S@`L9? zg*K^L>?u-m)i_lG>DuyDp;X+uw;SM9Dk)+F)lXYQjModpd+ro^xgF1NXmbkj@-_x) zTE6{${rYu#K)I?Q_+DYNiVZwR;d;Eg$&Jcx6OJSfH$A%1gD_RU)&&o$O(t4}5cF!z z4V(Kuzc6~xEx`>N$SE2eFnl(DBsz+{6^wA)V;h|tgRfflPc7w9vjHKqZBg9;+mV;< z(3q}j)2{M6SeVyrka#TP!h*D--o#bgkq|})qXNf;J}Kf0qP^mnj~~x%o>L!vQ<+eX zpZSK5y3$L%3{SeQBo4rzkw|oX_HTjD-^b>e!35Ov9C?fd1cLp(VnD17bB%RJRI$vR z52$N2JB}iN1ci6ox8c4N(b7sMi_tr)d10t~P46BaY%SWrW_=P~R%>0uA{b1D$k*R{ zpO!%kaLQ{454l?8t2cbrqAZt(jfMNyM(>B}xK1&en+IB|jx9*UvSyzpvV6k#^$@cU z^OdoZW1_g%8O&gcv+ypeZP#U0*K;=Ds*f&0%fyddv~O5rXRdtqK*;KThgSS+a1yvT z1+$%N0^>~ zl93eZ$MZ2WWhG0?B|iFgS&h6OLQYVWI>%6(nF-z!mLVn+>{YWIAgP1N8>7v8JtL4T z3j9+G>~cy1W7noPtM6cAU3dMWk1c6#&=1wZ9jykNd?NX-=JoqJ={m_hS{N5YJe7CA z0Uz4&?kmM?(cSVRgWp*IXZ2;ZH;2pHMQ!n|U!DMlI(K}n;Qmj$7`BgZ)F%7cO0=9x z%VT`}x94=@HP8@(rcT6;IqQ&l&HaYXyYAepY>k~`7E1K6iy})j9^Y%~7i!kkwCC~>VS6=IqtLqhr!LD9jOH#`SJY-M%@-K)t>ce{Ivv|08{79F1s zCCu^whwPzUW@gfC_HEkX^j0Wk`i%lL{(%!^V7l6TcR#MEGuW8jBh-juV|!Sx)A{>N zwCuy&n4MQ0Yj+j~5gD|iufO|5^G#+?{iqs=cxPQ`Gh;%NL~oS6C@c@7yMS1};eu^Y zG#-F&ez^H0r*6UJR#XmWjon9w+gLHyJ5Tbe7dLc3XO{5has??fM~=_A>RmUQfqp41 zV~qx+ZxHQgeKcptd)bIK$xWRNi}w{1MClvYlZLL9dqo-C8GQZ$;j-LUnU85iI$aaT zQ(?IZE3{}ZPRkYnV(bswRT`W>9iz<*s z88foLOJjwquq-nzL$1uyu{>y987*#g(t#~Kj%@`UR04e4`81OqPATfXJ}DHXES1$- z(NIhcuF!0O+FRkA%!(Q_$Db-4ucXo)EcUgv5)g{(JnEw(AU(|EMzw>v%2RsZ4BB+ z6^d}{<^J}#{s(D#C}E+9&&7rrR*G^FbcpA(PqZp&`H;i5+mjR0KK+>qhUby-FO;i? z5kyfXdzPQc?A2ul9OAn+&ll||m(0fB-U&-I!QGIRBQnIy6TaQ9QUgM?6a3C&Uvx4S zO_Krmvh)L7&11)ADt$U19#J(jON~RpeRBE6a*4EU#;rHnwG%V9iTrV*4)tURscQbRG_EE6jV zXOCGL-@V)(XV>agvP;eM*-1+Pk#`+&fO?)D3m|zGmZciZ0FkJ+5M%b08nxA2U0B4s zXhy44pU-Xc?@RgC=5FwC+)E+ea0blOdo-TCp4{R`3AEm}B1z|&pm+}7(l=xA>*^mMkQ zWPBLEl;Zdc%`x+}egPXHd%$l>8b1*hcvQ?EezusMkfNPC@V6-JqCkBU)LW2j{}NRs|7%yxD~!a7EK3aSH=4>B*WYl9ace>7QD&x0EylY<@HhtM-M`&ZMCMWyvzW^^_8Mzv}cZ?d!Z()ZrU>Mgxx zRTi58!Nf5=%!yavGeeJsxJ{IbwV<~~FNIpyQheQZ>A}QzKVx}N`Lu-vrjB;9(WDFD z_p-Jg^@QiIR0lSG3B``GGSn^9kJ)ZvHg8$UZq*dW3EJJC=hR|znCEVFk`uWnpA-xF ztSBFVliIh}3OSd4;!*wbBVHu8$**2z?T1AkvsFL()(`E2=AHrcT4(3z+9-0f4?&AH z2Xe*}djS!{4l#V6K>apFjS~?+Njck}T@HZBPDu8JPhaRn<_3XMjgAR`&u+}*=v(Qx z&C5H`ZkF@{=XBP?x0Tz0>O&`b`1u8f&jRB(CB6N@QGBe2PJWKcqkASx9i81s-IIWL zVHar0@KiR`HoY_7Jy)9-Y1cleV6a*ScJPkxiJAk0bXpntj=Q;eru#Yi#@KOpp?#P- z+y+Y_Ut$V;u*x~F$qxl`YW_ceJfc)(WM%#S&L1Z0>gxKa`n~92E*3n{XIf0kpUSk+ zy+$B(+?cqVDBETt|D`5Nl=c#>)!T1SqnY$vdk&@t3YHU(r)iqY%la!-9ij~hzR0Q{ z-l87+6!0mc`j%sr-KLuPB`S6(b_IguZ28ogQ4*;?&lv(BQnJ0fO(}I~&UCGfZo}p; zo)f(*S_)3?WDDo3X9@P)rj4$N*o0I>UhO8S%DQz20l=psgF>e9*TJDnflt++p22zV z(Z*E9jB(R)MIqQa<`x89r_a-cbbzy{grc;uWUY8l_M=|qyyQFB(Xn^m`zL*`70sb8 zh=-fV&wNpM_|A%t@&ja9=5szZ-xpi>s!*P8t)A|I_bI#!23pVsymyh(NdHV#o@SS2 zU<>dGB^{3hBKDuoC;|2+;c4~GI|V*|+v99nG;YqG{-4a5dE_OXP)!{#d%mu6_MN*B z%5$(20t^Adz4D@YeG?uJA_pQ%vOmN)9%Vk`7ourwG5;Teh2(Yp$frN>_+PT&466Kmmji`hEJ#V?RmHzqRJb}c|?d==~ z?Xi6Rp_@p$V0OlwMBg?pS`LZMI8Nx!YNUHpDd4wPbhcqrga8&*Mu&0oo%u}S^!jkOEoxrMW(3OZv4jrKVofKro@v58F6`b>y{$9{40p2W+)OSWf!kbGvyf7ArU zLNPG-&afw(=h!4;bYD0m!C--DX)sGEh_UxDHdXEUwo@YI!easg0;{?H4=VmciMBvu zs+!$6Xz!!(i0A3^7f3fD*`sje+7qpqzAU=yUboD&s8`VX=uyd)n_!w6KxIoKMcsD& z%uXmYARn3UyY#g-QIF+bq1Fn^l5t%20-aQ2Do@Yz_ow1YfIMv2yOpQ*2b_ZeYBzTJ zNgQfpC6Hsa}8Y5e>*XB*h9@aiykUjPo_@z*U zJf65U&NPJYdHmU<%?H+`x=}zKYXg&y)=s@Q6x444O0!joMZXNEq;`*hW`m0AfJF(B zIp~}%l3ING@wUGM->x>T{3)mH#fW32n%WonhOfp5A)%1v!sI$FzJ~Z!9SjIE$tm;#SAel4<=;U0Gpm#)nL3sWTcks(YSE?Z5eW8&Gq)l&ieizhBw&7=H z9`f;H?@Q6N3MY>OqiCQ@qgCys4XV&3?QEsGzi+er4J99-vC&4JDErencBOEl;%vAY ztGMe{6z~EmI?6zm)}sIZ%^?-zwD3=F)THv1Meg5!@SxS?*uc#)-BF_zmy_X|Fv9467|}@e-HNgoCd)? zM?B+t_up;n8wRhGCac3w*j4QK;G9|FuL4Syxp1b1YtCt)Ai1k)_ibU|K*)JHDr|7E zQR}@RFRu!(oZN4kHKHV6mWOR4DKE3`EQNb?^h}&c;?~!%jT83}O&1lHANZR;|Hp@u zq`cI1ClrjeTGVRw{n%Y^x3|OA)k$5tbm=ARR%8HC!EX{NA2fl>yz70B?x)WUXD%z? zeTI)S&$d9C39W$F^vwI%Ou!=Q%2D-m68mjSvlZcWMh+b-1b%vTvn9QuVC`%rDKL1M zs}u*4!6}fG+I?Mia!WsrGJK;IxH=UgaLoK0xcHxn8Y+?}$+moad}1Q~)NXfrP%35$ zva)um(K)|uqYU=D`Rl?{Vg|qW(X`?3qCJ259e?%TFrPQ3Ls*bjF1=r%c3XT;`1%OC zN_3C{$F)B)Z&nBHuK>oM4Xp|9bQPZubB3)v#=^ z7M{<|zx>Nr|LWfU@x8DM_`Jf5P(H~|ucJRMPev6i?kszU|DxdW#}EJMt5#p|p*PlP zHHdzGXZ-79r?CMBu}Ve7Z{@asblr3KAa?Kd`5c0ucMQLOcCY6^wMi#?Oy}JH@ZOE< zTZe~^%pym=*4RBF0{#k=tycDA?DWXPCb zDd*($aOYhyqrjj_bfC=PlM257;khbsDw)4+q^3;}Gfl3ZdAH(!&v}e_VW{R)&aQPX zEGhiM42Ve&tp4>le*XR|sS>ft2Wvd2A3PUOg6z(^5BL!W+pD*M&}~jOvRq~YoBY7{ z3mz>q@^zvEAk&_LHX=|m*b{m0@eeN~vys3ne0^N>K|=}C4K{1PUBHdDk{`N-@8bbQ zdX*+=%^%QK;CrC-yReVtc3-M?TWd+g6piW1K?~ZL+fuweO1`Xh*0u(gQ`X}C(|-Eb zttv@R5Dqe5Sh0b?%$ujr({HTibsbP&(W`WO?}qEZ?ud!uGNBT6b#(yxqFk|7CDn7> z&cn>h+$g3-DR6~D>jqGYC@vE>9I&deX;i(0V$EiIzFURHc;4dX^G@;PiR1)|G!PFD->Z!Z`? z!Rf=5?lub5*VnLd7{DESVqGSYO5EeHJV&>zu15T5i_~QqenF-veO(-x`y*bWBkHE* zKyF!?-Rjq(FHW$C3Y#qJShY*EmzkS+cy5C7mA}Q+=%u*qF*~gT=X)-#Ei#RC?fgFn z)A)TCo+QjpkbHmc622G&8n@y*y{U|AJI*7eHgh~rbl^6+F6fRytF=ndl6mvao&3TvkAgAj+M4RQIjRIo zTu$bN|Lt0!;^~0c=)<@PdKv8Y#E=y}6vCahO2gOG<1j##*jnHrEpU90G3S6di63z~ zQ~1G#&l$%@A+*AdPkMTK=hD=r+dTD-GeCbh722MyU5AW`$#_lZ6nnd?EHoMDhhBE? z{^JJz)K^IsBr4w-*O!GzS9*KM?WDZxg!35wU?t=S-#fgLL51dz2J7BkoHu} zO~ax;M{}~^8=qvu;I-y^%r{chKBxD&y!C#|%*_0yYZDyFIl~@-ksd;z_GWf5FMcqk zw>K!pl$H_n-g+pCfJl$5a4^ts4SN3PMtuG_5CC;KHlyJ3lc;9Qq9 zz#Xyf){YBf&unUrs9iALcX!sdq9xjLJz*VYHwraIr6sQ$DuwX>E=B?Ay)Gj=MXnr|Bb#i&G12C&bH+YZ(H= z0Z6N7^@8vLM}_?lV`q`trHOL$BDwv2n&`3@pUud%Vtrm|$I4VwofB6l-olt3R2^@2 z4KrFEQew?yD1o~l!Kr_praf+i`q85n7w^u9MW6R>aa{owGDlsnu8q-#GRKMhg6r2eX*{C&UYV#9$ z1|^;|7GW_mF{zw+@Zf<}{y~eWK6&o^60eg^l~MGV6XwCjY-;Kkhvwe62RpinD?g5_ zn5eH{t%R`y?^brU+s5_)HVrcO`p7kpI5!@Ru$}NgPT4nZM(v&R*j+chXH(yjUn5{8ob@PFjjHq65{fltzrKnpZzqp%fv4$WK@v<)CxWY89DKDd~T+VyGy=!d<(T^N>F? z(JdaoMNGc?Df#}1qSFaC`7C|fvM1EqiWy9!$d~z}1w51GqP9wOJUjY#sx0$r zo(D2W*s!0pylG?&n(QNhEc39nemG?1B*Sqx$#+FD>;-{`W7)C7!;DDL^p+m6+cMF@uG<9_%d-ovp&2VUKN#ExIQ@HhW7DW>d$32 z-4fE>5_eu6Gj5n!60j=9u8m3PMU)-mva>`{-_hHfi#qwQ{FyOB1~Ag4G5P6fW|T^x zbBs@P;_DrZjbAw!qwE1%0eFB1hd?4iAKh7$=|SrkLDuvn3-&A>gZrggXB9*ilR(f!@Hi^=ko=sA2N_J)(2bSheZAtq7SS_n8#IYJ?cT1y*HPjZLr;IrNCo7gV*tA ziOZ}}t(_TOi%<1EEz24j>Su>pj!h((N*fN*fP49noffHIK13WF3D?lf1QUL$1FzP= zu!XVrc;{i~0W5VQY@0^GrxGc=c}^QQlojJ1c-A)&x}&*K#*$ESat*EL_Kh$`|00># zPYu=JHC$w}-x@5SPOASVe&!W&=?q7ff7?=(1Tvc2wg&sTecxCw@aFy!((d_f%<^a? z&hlnVEJ*{Ad53)Pkom!mDRO3ua?Tw%c^e`vvLII=gyG^#R;?qPFfhJq@dMH}K!^?o z43}Y4;0KEzHX<}y9nQBAsMF~@`YAhs@?Jh-#&Bj_V&XHXX=DJdSWS9ses2g6 zh?75i{(QuRarXzef{q`G(`ZwTT}9u_J!)XUBT9(!DJL?pUTBy_VOTesl+=GJtE!J-@sa zIg21pjvqMKqQA79(c=J|eO#~6)O6ZvDfTg>=mKBtQs;e{B-cW~Lwk`H=m| zfyKIAfeoZ}Rcy7?hLD(eBvSdLV6#*a1r5cDvrwIR8B-z+-AEZ|dx7nVLO>YXdqX)8O0U&DJ*PRX<6hLr<9I797Y$js~M};p^6}hPz9YC zjfGJ}z@c{wQ0(uTGO-!ICqqP4cj>rh*Pf}3;Rdi+)5a;T|a1<1D z^Ph=}jy8ua(2LtYtZ%5dkDm~{bxS?b*{n+-=P{X3SPW3sIc?(}f0csH7`g3~WAa+Q zZ1n6(C0(s80F0@FK%dca(DpJJ$CB`NiSvppMwzsVfoADo+%ZL$KGA!j$fV7t=)Ar> zXi2q(H>1BU%z)zmPX)`rN0qIQlUj-V!-qv4E_WR)K>{Opps32>lan3VMc#FLn$lpn zw^?-W8Z_HM)H~VS+!U&rn}q zE{YlSfVVxOIu~K^fE&_GIoMR^J#6W3b9oM;m8(KKoa6=cIeHUPXWWSVPszO~X9Z#x zuV(sJz_B2}^!<;NoYW*kAhQr<_{2l}G^kq6Uv2mcN#_5aW{Q^#KX)mT4pNslmZGT) zk+fJecnTaCu@$OQl%L;4h1sS%N0Z+4%Ar!n+ABzxA1p7dyr~8@U~zMTk)o-8fR)Yk zh5CUs0QHT5ImPJO7r)FEdT3Xx;ms~MlMg`wQ!?>;ARS^hPsGLvu1-snur$b42_oWy zIJLn7h^=!tDu%BJGm3ume7TQ&9d+SQH6#Qw1Z!$~C<`WyZe|;NF{3opU8=zy%ypDa za2Ke)_cc{F74J~ls{w~>5BQwhm27p`JeKzSKg?c8E|7qXy)K48mlL?PvkOhF09)cb z*2mxxV(ELK?QZiXcg+p3-HYAJ#_dz%#FA<`G)r}!KX)D#&pjH=fFNlN37Eg46lE15 zWnu!LA8@iF)^N1wc_Co(3vYg;Z6+QJ^4G0Ag2{43;BbM~ zI}SxQx7B}>MY*HOS0ZBZYANT_(pzb4t62i%ziz#c)N?>CSY-DNWa*Bg20#6HJlyXE zSse?Xi7G0x>oqau8HA!0GAi6*gEKSs z529$=f4F-;s3zSf_Mefb)Uhr?a|MI!!mzrOiV3oX9{mxEO<05SP41S54sv{V`Y{G=`u6}{n+MylzL0>>5 zR{ZMBatzS9s~nv(NL7emepQ~4TT~i`f*Q3#dgD-+Tn5=1bG7)(9Bju!7#Tk9^`V_^ z$saZ0XlJ)8HiV)S{+a0`XT9q+c`3GnkOgF8GRQ72-Hc0u`*@cetH>jJPuNRoyUh%} zJ%2l56C79o`gT86bfb~S{&3gssk|5mLmc-ehpg=kw$9Xn|NCH9?PEFT~seT0aC#N$(xV!dv&e zkzy4I#M{QPXZ0-wAuE++&Y5~}<>uwZ5_}Y0gF8A6m4mS|h`VW2Ixeynq*L43Z=B!T zfZZ;%*rtZ+V30+^1%RjfaMW-=@&6glwzIRz?-#3Ue!Sv$W^c(G+p3JjHRK)Rjz574 zlz>dosxA=!JEK|rI=Vd$7XZ%nJ(DS*^uIj%|rR$F@DIA#lfP9TCl3w>F zv!DjRp0mu<#&Mj!K+m@BBzE|<+Wt6G+89&g4o+i{h3q%KiIbASFknW#$mE!f$E2fl z>C6REp2ScG88>;I-yP7;daDfH=rGo69NJ`5g{^xB%k3%yu?sBC3P{1oYafYk;UzG(S9jS9{FB*!5W2{0X8 z$8IYV_G9moGh*{$HC5X992_`MtgUiw;&xU>{PL`K;10RTKCiduc*(9vID({9bZzad zWB7NdO2ZSE#RA{CI%#^UTq7}%$XaeYc!@9SCT6)hEx9thWcY`6qxDHrJ+Xg0NnIzR zOuxh;ImCvom0mDv(LGbiU|J_M%w8Iw!Pt&G<_f*#$)xNVXTwf`E+Nb%jw^%)J&Yl# zs~IV2@Qr3`dU-#w=w^$`NoOrL9YX+9=&s7@ox{3!37vWvUmLX>ZZ%dc)a>H4*cWj< zQiqvJ|88x#uK4xQ^>W=7SnVRFznkPjDGltFKjh^dX>SZQo%w6zUqj; z0O`o$y2T&~Iu_uo!X9L?B-*`rkpxxn;3rOSN+-J=B(S7kW?&e2o%*$yZP)NUw;`qO z?TE4~M)l*VtF@!M?pzWIiROtVMNL53rG-;Km%FYmZL=An*?VhaEHo(k#lE0szS+~j z#UZU{+Avd?q4$1`L_lZdFYKY8U7FzMym~0l2pVWD=oLEV?aV_NqRH2Wf^?zi{}-^vAsYXe?3Wa z%cd_sxeumZ{-NLi%Mh$Q*#+$}Lv~7YWr{L6I#lvIKyM^Q92;0PE6hbj2m4>nJM3! z`P|@pA%M7^1{|rmyoVlDD>gAH(Su1`+0r(@Kno>{wr<;NTbN~zO^6L>*4Xitq<$PR zzAd}*xPts|f^!eT57b5L0^^7YsD5OsJ6>t4c6gz*Ch&{tQ265k_>Ac{$KNXblByS< z3>Qs|TLfpdWD}`urwKiA-($;M+){1*C8cY$Ge)#pgc(GbzR$V8JT7@s;Ap#PbOTk) zu#yRb)3OQZK%GvSPCDXUfY$~WsJB%~Tp#fLFDV{S_`gQvM&w3npZIG>$r4|25Tl(` zu@KcSyU8p2rqY~uBQ?FkP=b-3|7s{^s3sRwls=(?Y-ueg4z?Grca}n5-W(OQ$$pr= zJZ^}Q37vgL#>75MXD{+gMuO0P@7&*nZA$wDH3T(8n(u4Z$8qBau4F4rPjtwwf14Ul zvN(I0|A`gJRrSDhN9kHdBEi43lLlz>%a5c zd^7!bkL5qoB}(s!-m^ ze`8R=X>3Ap|EF7O|EM-+e#BSB(#u)?p=(${U2D{-*3(T|A2qL6MyWOKYhhY3c!*w{QK|Z;}BMnTjaPhd?I+M%=m!{Gd%2lq9^*Ec6m&8{~yP_ZcK z=)EWfAPykO6Y=KF2^Ro=`(Uj}O>l(tJTlZ@0)kV0Rp0JPCv)jY5d(0c?Ep5nY&@Hh z-6R_PJ;`DG>?;i<@56s}0sqejp(jujU4K|XO5wTHEq%{=gcs3@mk%&3v5H9hI{htR zuI6C12#)_abI;42Z(g}7U=xS?Cr6V;8m{#MB(8RNc7V*1`r^UtJoy=)KdjB) zf5mFg@8rz#K`E2)o7`FwmRIS_3k(K`ZytnhYby&s)aw~ zh~V%tVj!i*wJLj;;n%fed*)?3MR925}wngJm8Uz&lC<=%a6TXXk&tk4f!B1Z{3a9Dje2|GMvN)o*;f zdV6{R%#aT`ST^CYq~&&u!x~?@0eS>8$;g)O=T*#8SImaU;)!1PLS?k5OCdtslLo|B z8++))HX!7Z1_WK6m)Ypz5rne<9*9(bWD;K{IKNgVp5Y0AnZM|qEo3Be0WwApprvZ_ z?T4%Dts?+atV+342u(|qIpC5m7kz_CIxvjidgKYfC4RA;sM!F(ZGYGIb73qatjdvb>*(QSHjA1uRn)jL2v3!~r;Zpo;)Zs>SWEr3<4xqy{9ms8Xk z0)*g((Cc4MUHA@QQio)A2UHvyjDvd6T(h)gYCa3UJmtaM#P=XcRgQMC0ee!!Syy1f zt!U*4eCXNZBh2v^AfU9Pr=F*?;Ielx3-qF964?EUE(uZt0|Ov`V|jpH1aR;j z$Wxb%7M9!zrXbO2EN#gkIu8z{!RG~$qn>*kbN1kg1dNw?3?LjAZj_C?!j^{$n3@Gq zc$KmE%~;!-g62yU6sV;iCFf}cgDua|ol!CJI>O z>~dXmBb=~@n$CKqX?v7GLs{j0O`bL09-{ zr8LOo6r%3b`EbSC0gA%R-X^-ux(sE~5KcY2y&a7g+ zJHrC$MCOIl>Gs>VhYu{N9o^@nP3`H_COi*k82H^_{kaENOd^vb!l>mEjY$Vz7c7+( z*%~dA81A}j>ABm$FpJlV?CR>$$#6SdZ{9g_7c&&o^sb=h!ib}>%fmCY&J%pI z$3RPF_~4Z=A30otcM=Y?+S+iNOBmYyi>#zxVPXd>#@_7=q&S4K{=Vr${RGcQVM~X7 zg`?Z{uK(!h)+(7)_0Dg5fNwXeBkN(#D8O2G33PvG!Ats%4thEQR>$gaQAXA7bU@+j z^_SXH8nDVdtr8g^^Sf%9IjT<_e`9zZ9;X} zJSjXo{0>H)8oLBVZ%iRj=(1z(QLtY~u>J zbAZVMHog2!7C|ju%VAShYCq1Q)9#eV(VnTgDt`}=%C#ZVi;$I({>PuFjkB+cxNe#C zZeO;+gW{Hf_q-5&^dmJWX7ymw`@%ks;+EVi_eBYy)^q4#D}B;URY)yK1DOL^2k_Jp z{FY`r#eT;XESXBS;c;4wGNl9MYc*xY9bi5Mh~8{_T((f_b;2*|GKK}J_X>P!Qrcoc z^_|&`Itb^LCk?&Z-}duV=tf8{CPG&$(q*b84%fV)5kQv!@g{lo;(`bs+Zn^g#-2Z# zqsc=9#Iri$MFy*50ZLdq`kt{)rjEgMya>c6NB5+eSlRu??D^a~MQ;~2puptlJ~ERp zpNr7Gch1*WhnMCwc$5QK$}H;gu7Gu!M^G{jd%I7fzKDa}E2?49Q_k0zV8I=!18Qi* zF`aU4eKRj1NtooMfY9%48ynGIG3>`MsD z^6*6j{P#FR(u%+x=dA5zf3$ds6YVJm~~Ma#b;a!)6fGyPAXcpeI0n z0XmX+J9g~KsPZ@DvLvglXQ8WB(j~hDE;c1EK~8dECrcM#KXD~L;#IO$TDDIUWO;b5 zDKLu0UO6*Wc^A+yUtu_Qt9BCIcQhg6&~eE})4|t;>^=pP`q2FlxtduMtCp>jRV?|s z&po5T=L^x>E}KtYCo&JXh!ZcodyLyYeLtx#;}9pAH2CTB`&Fv3t*Y@N?8>OXZdmOE z07ZMsG43v6p(0*vbw_(33k5}8PxR`_lt@>I7dH3GZoH0mB0r;aU`Qaf6TAtq?{YAW zf)o+Yrfa5O9J(eYzYoN7KmYPXi}4KIz2iK@>IR(dOc=O$^mR0k1Njny2q2L@m!v4kYft_Bb+|m&Xp#A5L^0r608}IW?tu@TNUwG4 zN%Pbjc9U@CznfY4w=nR#wif^`YcM{vS{W@v_P)pCwy&IVMr5aE8!hsw7OxEC#)@pQ zU4f#jkU%oAp1{kNU9<2DOjtWd<1Qxb*riuqU~h0;#M{|^np!GSwhYFWlYK;UCN(yp z{~&e6`*ecm(cY?(p-P>=b5g?3xYo8Q^!7)KLX8p&)$iu2+p3m8zENzwk>WbK${Ucn z4=63G*T*ZC-LlziLCv;DUX^m3gp#fNNXTLSUEH9^c)49FkY?;0cgO0gcMBRxPD)bR zsA>w5f8bNad>Ys~=SA*X-23dn&|MuVy7>{|43Oa~08$z7I#J4-IGT;}bbV=h_N1P2 z_#D_R71yj)}u&E%pMD^o;n|)Be{dDBNwUCYZ7XmsM?d<`r4!;&ATi1*BZ5ito70|y&3|sy||Sg--VwJ?!P8eDG9D8`_c>l_{4V$ z4l*fUsNS4Yc~}DUPknL_fHbnIF*TCtv449u?)*G55wj0MA3>36Kc=2|tT0#A+I?FA zl&OLa6Y5dX(A0ZNK5GDjy=cmQCN3)K>bg$STi8VH%Wou}o^27F{Dw{EAZ`PirjO2{ z7rzh`14HFJGv5(w>Sa-HBLUDd*{srW*$0H#ln4jXD;#I8@qnlcl7m)>5`0doa-&po z&KzO^;)%~b<&u?opS3wh5F=GCMYeRAi{0;n)9URX;fS-cQ8w$27I{qu*HolWM^LgF(;L zk8+)=fA3-34;ot2wL5QT#7(nxWw++Bm9jm&Hg?%n)e?w$zJgTZK6auT`Fc=yWPIxr zNFI4&Q4p>i*jiS7O)(7l<&Rp7L64&U-~4(9xse|L6OP%F4xCY+7yD&4)?G^aJ) z)k)qk2)_D?$^r_IVHe=HSoV)n{;eb%T&d4 z@%fFu2M1?Y0d07DG&Qf;q(eGEXJzxmxPuPv=&*kkE0UK(zv*TA2ur=&9YD zg!WD>FAV!alweO6q}4n;y>ouL>fj|vapuCsGH}%DREzQuBJpCQUFH*e@YouJb$KC+ znx^q{7w`_46Nvh$knyLcS9EKEy*5(P4+mxIkrD@0Kh@kaAY?I+@`$w8T{T`cw!W-l zy|XofbBUYtkIUy;{jo>+7FB8;TLRQ?RspIp6QbO&&z-aA{+KTA8%04CY-6tN0HR&+ z@;&r??F^%c>yiCjYc!jRrHxuFw#IpRXo^i|Nvk^sn-FmM_RE_xVSfA{K_~zBMY>;pC2wTC?y;5$o$+zpe) z;H)*aqEa^mRdeFC9B3!~<{ZCXKGXB8psUTTw(QOHI$t8c~&Yt{gn7^x(j- zm0!xQu?1)FPmTeZ?lEviyT=p#W})5_ksjP}ZW}G#KH%D6YPr-Kg9I2jDT%VQrC$H6 z8G1qbgjYKl0_=>VNc}gL_ODa-Uw?0skpQe_lCuNOt7Ae9*8pwWVM>>qH)?89M?6a@ zUkhlQuC%Zuwy)U$yIFLa{`J?UAO=i*x^Zim-3%px2e1RyM~8c*KzA&YQVpEHE^hCG zKqiywLL6T6bj*Ah>h)u7{WPqJR$RQOxze=3hs=6?`WwIHK+d@g7c_XEH7Gv$3p-=R ztDhmi+22=<1$_6H4)g5=#l^+J!Im6@9j#*;ts`wJO}g8f4f+oB*Y;DhGfxBC(x7sK1~DN8B&ErX2hXld?dNbI;_G2=Rr&RPb7 zY>-GY_?wmLO%NVQSLQDD?w^0umWvrm))2u;GV&0-zsKWu_vf=>Z@$(M1`02-!c?+C zXwoHDh0t-KQ|T5p>*P1aE1h6K@q7x9&jp7J_YE&!p$AzhiK8!}3ibyi#(JK(Z4euF z0#OZ~b8NuIzQs`0=mU#C7#;d21KFRrk?{DPfn^61N)?u`=DP20kMVTOwiy(L<|qQGZ-B(K;!(vUo$8TZ3DD!)sG%BnAzJMBz-_DddM1%)0fkVG=a9f{mkA#C z(*t?Bv4(?l_s=xOewC=*oUt~}`6t|_a7nOWtii%bUrZr6Q3?#l<*T1ctM?aScI$2= zd2E++=82y0XlVfNnhQ3Zjh}jp#%b48ZJUa372scsT~#_Ec0vjZ^ll`EyA%RuwOF%kiY-OJ$BvNYMqN<*U8cx_h1 zln>3A9mvh$*5JZhIFD(6maz)#gy%$F^^Vp7Fr8x%P09}}a#47JzapR|`2Vr@=J8ao zUH|w=iAYo=Lm8SWN|}-&4V0;{%|oWlbLJ^ZQW;88W)-rxd7fnmktuBBHV?6}&9m*h zK6Sd!{XBQ~sm?#YKYq_2=XD&8*Z%li*R`&-)_Yhl-Ftgo=jtpHmmU}6KYsi;RfwJL z-6XaVSMl8Zn?5Y=R{ds0E?H2{Rc*>T&Mma<+K|{jOKdA48kDThK6a>4j8(a4=sjzj zLF&<<=&3;$xNx93)QFrGky?_Dna`g5_HQPwn*=wSKg1Qyt&Ah1CJm@aQQKTW4X@JOTH4BUJv17@* z$MVNbn_hdfyif`(upX@BY7c+@e8{Vp;)86sz?shZmuoPYWCAU~c|0E&j`U(l3i1A^ z1o;pC0t4g)Q~7u^7;kT(NrBnNsdN%293VKdUub10NOKb!Zi9t(xTPK!Zd|*{`FUK? zYz-&wh~kD4%7#<%-7Q#->_LB_dJ*EX9=*LA^a2whPfqLR80c2%4d&Pxz;L!waair5 zwYM)Ge4rBPW>J9}bUrX_O&>C0-nUI4FKgQ9i#1F?&HKv@{rbF{(hul(lk zzY+^49|}30CjOpcD`w$K)1hW(^vT5NICKzt72Q@Cok)_VMUKT(IG2*r=c1D->TkC| zmD7|=@RouOxek4GOD-g=xix9%KTcAT-nPv+@eo5Zg)}9R%OXd<;(8E5x87%*YmT&LhuR-)~;5Ip9>&5g1_3MeK_bedNu-1g~|dJ_3}v5Dwvis^CO zcu(heTMD6U3Xay3bEm3S(Mn0_Mw>k2~7e1V#A)SmCLN$+xC!bN?eVYEDRit z{B}YrSW{hF&+`{Jw_ZP^al}+%A!p}m*y>hK57u`2Wo|mC)D2M_Ect7O=2=g^>4g7l z_ndWd);8=A}kgI?hXWtZ07|NjCxKoCOfRDy;~s1fuN-h zK|Bd2iJ>s4SPkyG5gls@=wyy5w3?L#?*>t!Pc3Z9w&kXT>QnYBHhBBpDNOi+&kZX` zZ`!K@U6^E2`pSIO6!}nGf&4o)lxN0gMFVG;Vwg-~16YQ}Y4wX^pZG{~>AAHgNb1m2 z5%e-_MsECc_v6`U0!TpFWM6xI=Q>utPczIt%J?zu-KACXJynBU`ZC{MDD&mJtTPwy z3jD;RRE&%KxFo&QV?NPB7$di&sO=6;QX3R|Ag|Rb_+BtolX^~U_dUxp*5G}`_S&yw zsmGT|?mwg?3XSaenqtnz19Y~&(xbxr6N<%;g60{>)BJq{KK|%uKl$E}97N-lxuP_a zUw`y}<$$^n6PWk11pRW7{IB1nsg42|cy821uk|Mn_#-|=#bKz#j*1-pWz&eiP`^M? z1Tt?}szm>Thkkes3L$A%>7N|3ADuo{4?tn-qX>SefAG*WL9jVqF28*8_W}1uKNTa= z0#q;B%Y^wS4}BCi$C;Tk48IN?e=iF>*MZj{;8D!4^pivO!-tlJ&7n7-$MX-)!ioZ{ z5z*B$k?|iq^vi7!Cn~D+|G`=KzXSZEmHaQ?`@aMHS48^BSN}gez^ut$6#$>7-@m_Y zZGEocY?4!lK}eopX#A-&XPQF!%uV=lcY=zbm!9phNxY|p2O!?s)@A<7(|#3A4DN=IvtqZmZ*}d*-byFpq?#jW-fF9P*XK4Y+;GG_wQoWx&ATI4N)iIPQWF+(-ypYSZ z55ri~&@?%wZS@vK3rbyiw(;5#0t4u?9fFW|LlqN3xi3X6YdkvRy#0g`fE>CN)+L+7 zf&}Mj2E*{(Gp_bREUwcRG0<087;CPQS5RoyJbLQXDNU9G|KM>dIB4iE4nWbK7dUk! zL_-(KR&1OMlR~P%u~4rH6X*g7ei;S`>bx?8dmA_fNrYfRu9IP{onF z?f>9bWT2?H5~$MZBPTR@4rwZ>o56snlc!GA!PX4xA2@594t26py8bnFfbefW2t)4) zU=i)%Gi&b)hys(X4QM9VPiD2Px(ODYg~}QuANxqtzQcekH@vxR)cGI0q-~T#PJzq7 z8_5P*iYjtwU~rjT7jP63L1)4T&fk4mw@p2uftYPODl0VdQjdqX1o%WsxmG$ZK#|#) zfxYxP%e)KgFS2S>!X57by?jHY>Z*$p?cGkr_`EsfA3fN8a(FP6)`zVRb{#OGVmvpU z1KX(sh;K-nk&Uu`+*7>91Q6Hi7DZBL?xJ~lm$BFo4b%`&GjIqyqyYLOXL&_mT!cFB z&Jsdzsf!dAdLeb@8_D=LPB@T5caqMYlMH|H;-MW2wCc zQ7#F>vr_E!;CnJN4Y1uh{Y8n6E)a(&k(uEzP^lYE+elPH-2rbj;Bk`|_MKAGhw<}y zPD07QpEO#%q!==Wg?m8PSk&i5$vz941*dqxvGNInJ&*(>nBUYxOi)c2;O=Ogt$Ym$$JrruaKwBZ_%mZt*w3NRaa zP`vfBNCZtdKW!WzaT|&8^+$9=-%Q)y+fJpMzx|)oT@Q~22JSOzlKmX;XKFTc0%iIF52TZhTQUIu>K2*2g1L5|+#iimB1~uW7_*$%n;M zaNJfjg>wCq>{_S&RL`iATAr@_bGqj5%j)y^Jxr>^y*ax5`p#pA59cP`h!KyIsp_x8 z*JeDb=%*am|K~ZG^%c8cl2(0;(-7f!{+Dd>fB%36DG$l-Y5tkTSF5~V<9^C>@Pq14 z(|T4tPncu=lJEQcqM;-xyb2dar+)dvnUzR5ZnZ%7Bzs=A2;`=1Z&@VtdZ?&RPyg)m0(m{56Iv}}1Qao< z{iokgaw;;Xn8Sg`rs+vEH(aBec*|yW95fvrx=6Al6UJ9(k=wt$E5ooMCim15gqCu} z=xLqR?hAZ>-X<07G|N8v8~0`*aY?e$A=Gc1_qu?iwAO0jOeNDI%o@LN`w2rPD!mmcg)gTras9OnlS_!KS|j*S{jsNaAOQ39qWjHy=1KLbY1 ze~<_sK{mCZ-I?U?W*^y1rmnVPl+K^y-`Rb|qc(aHz>DNP2w>A<8qA?|LEp6R z>QCPDP^?(unJ}Zd^0o4Sg-_$n4da={+FkXy)mIU!!;h{_osD$Ac^X(&J}IVjnb?= zx8uOkeLTvnjT;Gr-V~}a@8dS6M;1sQ{L6#EGjWHb$D1?{4>Yg|g-wHgKA4A8>phGY zk1<5;S30dyrS(vSyi-|w+UWqssm+SaY5K8vIhvelJwCH|vD4Hkb&rLU0L42F>kuwY z^4%={+);*C`)DYEtx?g}z3b~C_FU(Po=(ki+KhcBl4FLu4ScZrlzQfF#%G>REwaq2fO#eC^7Vpc1re07CY`e zc)D<`+@$;3u?T%gYBB(p?gD16g+s4fjvJt!3_YT}ysgBglFbglfXsn`@D@e^bpwx6 z#`6n8>x7Gz-Wp)D62g@ARNm;-3k16!n1OE54K`0xc6oBbDcE;1vQT9J!{C-f@7uSB zO&g;RuS;0C>Ri=!2aM1KI@m_{Fue1#^J?AV8FkOz62Z9)lVc z06cLsEeQ(L&O3yq0H{uM-&j?8eR;>|F}Wg_6kYr2EAtx8QvssHcDoeSj7sf-)f@;j z@LO|hceG>%g)=b8`85kpCEa#ynp53x0z}5`@)}ga?{U8+d{nnA8YUsG`XekOS>WEg zj3>mK0kk;fGLR(GT5*WCs20K5+~iOfReNWqa0Ok!vt%?Da3g1>i6`0NS(3W3VYsDY zj6`+cpK*3_wdK$(uQ_M%FO18!Xa~?HtxR%E!r_%=? z(Fu&;Pn6s7x3xT`{mQG3-|B{_PjB^P->sdIQA}$TG4^JWtteIh=}eRi$>!kqLPJWLYgbX?Q;h=(6K3Lc20K+)wg^MU?e!MdAwvM6x>5t1NY zp-%-*Zh>%@kQ^%;a<)DT5AA*z(nkiDF8$$o=}@Z9;BALD74Nk>y(meYJ!T!3ISMyf zjx)P^>3zS=SwrsILF|0%VQtL{JBIt*`j(0Rw&Z_a z0f7Oeuf4_3u1PkHNZ_f9lPkB8`7V^_!WiuB)V^VE-gk>k%Abl?K<)2fieg3S=$V`J zs1OR1V?=NL?t$Zy34sY-;Y@sHsgNa0X!w;yq3>fHKtynsz8iIX z9!|3@ojGbn8RboPR4ZNImvYLPUVckF2qp4q(l~ZtIFdQ~(XXfBZH|{!QS9eb!K6Xz zx)7J&fDk6AcjM0^46~(ILJrAcn+PO}Qd7FS0z<}Wt=jG zdq>^|h9NkiWVq?Ho7D6aZ|1|bRWDYRKLzG5n#VO`Zu6o<>P>3V84erAA|1Vreojfz5gZldwQ|b;2Vlx z*HIIwMHeT$KnSW3Gthex>xh<|K=CUczo{ec=3FO4PjuCq7Rc3R(>2$s7FZ5&7JNI?yhk-E@x#=04|>nL_m;Lo$T z%|DF*?7Y)Xr`|L9>BNwIxPd>=ZrtPlzw zhqWGVAjzEZwrZvAR0=$gk@3W8du+4?(QsRT^(v&$C=HHPPEs50!9l?? z`Q&W9m8Z4$(|cXeW*bAF=G>oTmEB`))m*^LYw{fEE)7X+%No;gM=WTjY7m<^jv2B=(V0M1Jec)ItO9-;R)cR7TmCtmzU2^Ep(t3 z*)0b6Buv7bi_-v8`L7GbL(?ca10bn zkcYYVb(G+eH@((r)Jf!4o{XBaN9gSk$a(u`2CL#JcbkTLB_gC&nJ5wA!}s&_s&>)K zx9%sEFTYeVuH0uk|D`F}B)OOxdKEZjpNb3~W+~|ChB5NRHmZW7eSu5-I9|uq$05(YPdg z>RH+<0lI-X#)vib3^&bclyt?Wvnt#7LcsGbjPvl3=a!zBB-?yUc}k(G)95F8GVL}` z6^}(ab9YCN<+O=D4pilC&B2n}k=lN_{tE$1np1))x@>GhRM zTnTq!_Bgt16$lF*X;lZAY_wV*v_hQ9ea9vh!2&`X5%JEq*MiKTx=1Dfw`Hdwy=yc_ z2?RCrsdNCOm)#5_?35~K5|192gx;eezmiTP(*EP9Wuve4ya%sNqUaHNfkUzonzZlq z(p_HP8T-ujz7RAO%DZ!*k&*0Wdpmg)bxesyU1va2CitOxrcpmUl4N+>DWQ5Om+Wh? z-N2E)?6!@x=MAYZAln8AnG6!X_efobZ;qF1A!xKPS%F>iz*VT~x(g}xed9=o5%)|J z+u1BzOZ#2naS$AS zu8?Ry*~uk|9xw0r?1RKBUyvg8c9xSZFE?gRIYn!7(<~b}FbqBCmkT-YN-0{xm2HRJZw*-mZ>X$e!L?@pJqxjaa%+?o%aA$#EIuT?^awkZy2 zMpz$*ynUB8h19pB%__QM8`-0x)7Dq<0rT9#Jd6#3-|zb$!K?7Sfh(-oD7!1)-rA^x zo2RJYx#_A0`5TgY1EtsS@()$mZkdMUo8S$k?J6*mg~y!GA9 zL!1hyjvV>G;*sh?7DXL7s?ADfuec(R(g|g+8CrV1`cObS(D9J|p#{?ZN^+bTz{X0^ z7XTGV!$8N<&)K1LANssoYBor(q-10kdN)feM=Mt*1dvPP)Rn6Yyl&JBIPBR6{G>1G zW}$n&CKDXKfJ3Efg}HlRo-NDp96wVS+?e;VLmF-uA9NLc&lyT z!r$(@KS+G!Sp{grBG#^cdxb8fOHk{2E^}U=P@z4nB&l-olDQVI&H~1XP0xX5`3jpZ zA$+xPW7^%))xwSAbb?iCTjt`EP9SYeLT~R*s|ByDDCH>#RpyY3L9PC@spa8lO$aw( z)d5(yBp>NGm7WN2$s2a08*Er4alYB-D&nYh)poWk4`d~;zJeAAPb%!Z9U9R@PT`&$ z5W;$ER|T+GBCXYcIEVPYMDc700+|pCxyKJ-ICAOx7DV3c@-N>DO$CP%?$L&*sQ&Rg zBJvO0!`@zRfCbDbA+FYnEcb1NqAhf7Bbrd`PA~$BPaq|ei|z8|KRlNKE@zG+c%!}v z2m>ui$tgy=l&i}AzFZy95EFe&x#FY|zTNl3yGL)eUN%zh!DzGTn4gc|=UQ*Kt{qiY z>63qgcEa0iZ2><@65E7r#3*w+T&H`|a2|is<0Z!qPRVts682>X~{EL1v2X|);<1R_PDQD}%9u`bY5$q=O2 zbiB1K3umsZX)LyJZ74d4m`x>|+0bumW9(k1cPM-`yx(ogW+I zbF-xFE{3E+28>7{)kJUy=vFAjC4XQAPBatjE~mkI&A)#Y|CV1JPbEQ1CdNUXv+so1 zgx_4fQ`_{a?V19cLKdVQT|sgpulgqucuxEo+5|Ig3`(zVgu8}AG;9jIZLqev6d#^) zorC5F|5Y|=(o?Op#j?`UcTl$$CYrn|C@TD!1wVU>H3$(dAk{8BYMmmR{Wm`FNrxh% z636e1;29sMH4r!=K$*H#bvx7Bz!|8Iipn)MT8eDagHNh{PA(xjcV!v}QRa^e0!Bw| z7{6BS@$uOaD4&^?XSyhr3=OGXX}a95GBBiS8VR>5+cEW=gRIxKK^S0?)H|2x^c!Sf zYSV=_2rp?U@JoD?0q0THr6DbFQ zC8E@0uhZcns}VW~aD*Eu`GIO10i}4WMZqK={d}TY7TTi*^K8A}hXsoA^rf&On7P(S5oemdL!ofv{_;rpb*vFzKwBPncixH!kTk>oY(1Hl1AQV=<5;ed|c53$vp_5Wa-dtEm19az!BfZTR4s# z+fM3NTYvUyzLwG|qQ7a5TLcWDz%YW}GIA|BSv{w|$8nH~emj?Xwpm53$&)8pPQ9_y zo{?#*bf4nCmI6S6A}p1U|Jw%qi*NL!uc{qLwr<~PWvYZyHb*C8DB?S*cafWlt{3{Z z7MkYX(;!~iq7E;+P|WyhP9hN8vH-)Nn&Du;i@70!Z9Gz`Pr?h!A{4LHd!IwaID+H^ z$=15k^n8%+jYaXwZGP;U0i=S+y{Zt~iBK!O&+}V!e;L7CpSVXYVgwM*V85dwsgnR-6leTZ;F1O$no)&F$m1lPtal>)obS8IrECtCvQO-t~|St z&nxU#9xaEUmfP3rg^|`QG@%O6O`$1&%y^MfO4CJyQW>9&Iqm4!yi{h&qX;bdmX<)? zFNOTFF|$zEDs`IE^WVjCLVEBWXviB5y9g7D3;kf1_!_8k1= zn@L}QyM>nW71gVG9khW;YY_P-o=I_8n=V7K5s{!?gSt2cQZ+;f8S!d@_VkN;k;YJB zO+{Qr2PPFGvTB@Zd#X+xM;%%taeCgm>VP3d9mnN0$dPj&74|=tUoLQ6nN41UqAg7? zKo~o(1yz>#1LuZ!vTn+>1K2fTMHzxhFeW8`oMWz-jN&n;ej?#T8NrW{_fbAGX%gLW zrDY3hzt;6NcyucU^5W@0qxGO~oUco=kWPgCx+_<%kwnmXx znVU9I&-*^zL2R*dtjZQW%O@gP#@#U{6k9lVCSrlIAl;h{V=%ZJ6AWFU{~$g>#Uj$3 zdEz~05!%0bHJKDy9G}q9Bhb2H;;9n4ab*Cs*fsg~lZd{-B65T?5Nd|K0P%z2f>N9` zgXN>mg!h=TWT5Q7@z9F7#p^9WcWVQv7aG&eQM8$FPQOcb`+V(K-rWSR*HEn34K7@8 zw#>uLyEVEg9%#eJPP*M5=|Mlhuwsg}X9ID-^43F7BnIY^G@;}0DD7P(BY@p&-csLU zk_CB3!TZD>?;ad*2sDoixkmcattzz+Hp>dtp~4p{@24On4L&x3i3PLa|Zxq6{kM zlvbf*X3W?s+)4N;3^YWn!o$NSl%r($1;nf1HZcQ*U~*Y9NfX8AgX3|dO0gQGspaK~ zuT&qeD<`V-bC7UmVouxL4s!=%?u<%v^z}rOf-8?`KTh1?m+dz|t)rp^)gVo_V;cpU zx53!P= zGo>}I#NLE*b5o0I9+<(SQ!f4E-zwapM*#R~=nTL9Nx`dsoW`_|_gjuBJf_cqG*s2Z1nrT5;Mov05m4Wq1uW8N zN=Q1ABsGq^v-k%8#GvEf{^+l9L`;?<6O=k2kdr6?bWHQe01_PmAkcic{O*l^`eUyA z>aXZYU+KLayGIXSXcK3Zg~?hkXEYBxyG^DI9d_~B1~lmY$qy@x{ZyVgtBResw` z4pu6U8*29@zr3wKd9yz{RMyAfcc-*=Q2wN)_oJ$xbqOJJ7oTzX>+ksGuUMxLt}l*L z=i-l^>L=eDDuyu8%|Ej1C;#7%F2;%o_}yHWBPYp!y$txipq}0Iz~)}7?B4PZem8gT zab^e38gEjWD8VkJWG$w4Y%bG|4cBvd}`r_QtKQY;(ASUojW5hy4BuS6}laIdGU*uxBPzY@t8YbG-bN zTTU}FJNwvU2``v*i;vR(>k+@dzn{Lf zY9~(;CZyS+qmwlqo$B@BW0q+yO`39qirY-Pmg@>{*!;pd1@GS3YuD_Dc7J56J*k=h z9F-*BA$uVvnAfD8bx1>t*;U9T`IP2jLBT>uP@D?kATG%!_K{N>?bL4!=KXVH1$v5= zQtcr_@4{T6BriB^&3Yk`)%rrthZ5q5c!ki%c?xbLz@nw`e5UY`25gz%-v|Exe083M zwABR_##$nF?M2Gl#jBb~s&t(8X>WY@0=P7{ema+Z270^`4OAyyym&E!<;52W@;QFl zYu}5@K}0{Z`bp4;Ob>}<(h=oa??7FXm6WUwvb$U`HqL80@kUO>A%1BRClM&XNW1Zy zN9;x)DTP^}K%PBShAJt3gUUre`hbh{n?Rq=9Lp#-;;_EUWL=JVRec1X%hb|hclAws zd>#M&CqmE3pnH&Zb}Q3QaH1b?cF*H*q;#Ln-kcZ{IIAH3{`dM|*z@eGm#4Q1_pY(HGQWF&p#dcMI*>0YWn87gXfGW% zbs7JN%{Q0}Jke0HoW3zVDChZpR<^{W@{=V@?|qRwICQtxZ43R+aQ*+-L{_=vwC${E zTDKZ3=;45uRWuvc@rBW?s?nVj8Lm)@iC9^Fr&PztC7CGEo;$<6c};#RTVJawF}X$~ z(`a|tUUGuSOw_1!@Pnd#nWl`Q^E2@iT^+VX&BRe{4|AI>J;u$3ozra8>cI6_x9t?P z>bvWa61evw8KsAs{t~t8VNGv6Qm>NG^{l$}MIole>T>&*rynn75IZ6^2gR9A8$C=2 zO>dh&>3r2(A+9bpGDO2nIN!l;i=N-L9gdFMdzif=vl5_#+JYbX-SK)f0w=do+zC{% z1n1|mvxa0GdsFOMeOu%Iu(nBJB;~lAINF)*Rv@CmdGq!C9H!YkHiI`*EHjCe?FXGM zr?e~ zLrQt_9|CI=p3U^dlyA^j7v6?IcUz+@X=U`vW8B;m1|cpQ6WNR&;rzUn_#8`9 z3I0UOl2aSFIFC7p*M;sy=j~^GkLAt{2R##?D*uxW^dH;cf2W!Ms#$L|l2W(^s&J>W zq7L{kdMiG3T$v3fjxn}k*49de;8M$Y`_^W0e($BNbRGfZBEySddKTj*X#A*4`Qh@| zv4a_C6vx_Na^5VDvWz2GVg}%U($=A@5ZAf7 z;E}%uWaJi$!l{~frpMtRoG@cbNLte?O4^|zmjuilBqT|eYe*{VXs4v7kd$Se)!D#> z6E^U?=AYYpse1+EZXQxkoi=RSC=Z~VtUmM%M_ih7pZ1AtkITZGn{e1Rso}6N9Ow6K zIPkxA-GAMVfBk?)Gg@q$Z+t_zIYcbt=n2D(qA+IioW%+IQqhgwi{JjV&V`=))Y9si z0K1%Fvg*}*aZ@tKISc&)@h;kPf{A~10~Rt>-1{|sqHMBeEeq3y2Evon44q4lde=UG zX)~W~YhEAhiy#%vz+bE!)br&DsHD{65`&e?=#i z4Xqn0gR+8YsOmRWJp4oAKGV^1goE}IXZ+TMIK(va6;EI^5?%)%zgb3Tn37F+pH;X| zW1|gEN$d`sm0N%)xcQ3y!ckXk(*?DFDSGuuHQvJVzy_e_mzdaC-4a+9@vvyWC2@FD zflhFUg!xC6!QX$6S&>zn%vU*C3x9C>`Cj!nmmA)X<&2wa;u_@JmkHLT_X>+AK2a6d z$8?J>ez_WVDo5=D#qfp{h;I<%Bx#*tNppj~!ZwcQn}(5X^kW&WD~C(e*#_+6BPUo4 zZ^!F;QxH?>IF(f)Ft6*`|H={jpXqh_(HqB2%Lh#}(Yp*=o|$fH&hUNCm2@j@t1+^e z&h^UF#@wdDOnc}IXcjnh4IC-C>RJd?miC6n`_@K7`HUwBhMCRtxgM3LRt>IQy>uv& zGE#JO&57#^vvD7!(d~Uzk*TGZe7fWwyZfH2SeWe2;v5&0{rgw;gP)RWB5#p$wN|jD zA%494KxgE5uYJ2hLpvx>*7Yn$^PX~dMKST^mWT49)yuap6e=*dN4CUe!Cm{wZ{b?UXa5G-sqFtMna`O#NLNGNZZIsx1 zJWLQTw5C%kjCqO<7c#r|9JlQ{l{+zh>G5^NeYdtDdSX5;+>Q* zeOmv4hriQ}LVUBt**>TD1(z*b_X2)1Q)pGxC_d8l^?|nK_PYxvYZi;$Vt+VBy0iev zv;E#u&#SR2Sq|m;)m|qI6(dhKNUzMlOz*}f3^Gi026Lh^QJxMfL?0G14z|saesL2Z!u8JlfHWN;s9i37u>LB zQpuW!4ofHAolzg`ao|3kr{s5sTi%T^C?ao+OX=?UN8ss1Jzr8jRqPQB6`2d`+oI0O zCkG7m%rvSM(CO3f*9d89yl=>IFCAU)^(@QJ>9$Jpb6g|^NnL=<%jKtCWmQ(+}K>nu1y)CH_k zPW{bozRku>zdJjdR2A8yauAAGcNStvz33QsC0|GkObuN<$5jYxy{<+&nS0rH&*JpG zjme}mLg!FIv)O39Ch^eG!-uzd<;o;UEG1)8FAA54atawn_Vj?x#sq4_FJ(2Qwn5Ko zI=n|C^hP6bA0N?k?{lr`6-{ze=TuXi>&?;jP#;->>})&9FmKYO0)-^gkU7L*uD}9s zy1CK2EsV$f0XQjkKtMT}b|Yq5hxxbF?4)X}Y&5T>YYB4iXwvAJce%e%=*)bypMBTC zy|vwac-oc?XdiYwcuw23+;)^x6a5^Opz-lKsmPlwnJ|8J0%x*qe2{5dI(?)&d*_X| z5~sO+YiF%CK!Tv+2ZMUeI(QiTsYUagq;3%(&`Oc76RHoHgmG{f#!kw~WPuA-o}q$d zD01r#(R&VKuz9gcDQYK5PQJ4HNfS1Z@RxDMNr8nE+A%U~o$}P{GKjLJIA?gv_nVH- zlZuJIfb#J6aWn^YLeh=sYIL^biew76w({Y{YG?a`_tbk*G_tUE$J&y2%g3}cF%cPR zyQfX*D)dNgO<@R1_NNQIWl{T9$~lzJJLbyJJ#5~FONQvet$t>+DY)CBJZRx-CDmd@ShI$`Tp@pgTc04ye>-y}x#DOuw zFOE(9_tQ(GO+tE9l36>^r`JSe(czSG4+`_cO&ftEsCq4lsJ+3YJuK zb=f%O-&V)uu{j`F@-ACsTL^k0a;T9#7yh2B1)LdKR_sfe%$5F){winjg#+L%Xx`cO z!nu!Nn4)oJtdY&?`yh*lQeQWQ9qH@vxU$qvpp*^q0lhjU0Q62g%!X3Y;5OV5T6Orh z8^4}~yUBon{ZWD{CKi2~;x|4OI{4DBc#bH9nD!RC z#Z?ZsMrUT8Q!jSSLnJ>n&rjGk8UVCa3B(XXKzq`_POJt@Yi$|@cYez=Sh-4D zFB@4z|FPr3eB&iPtG@TrqEEdl0PIh7q@ay|`xcPXax*|P`7C=bfN=6L(Auj(t#b<@ z6M^M$7Z?=M%`ikcjW95Qxtfr}L|RoRker#UnIpda#TAdCiRP9ltBv+`1ys7JBg*yj(Bs_#w@hd9;_BG!Ry&6U1V^fE|u%o&zitt5}@8> zfv6jc!-JGN8x-4@3sX4gr~#QqvJ9lB2WxhjS>o6?5M<>Fa6#Rj6cV97Llkvc+G2Rc zZqa&OXRIh=z9*L4>)~D65B|I=jqg%~kG?RP@VzY4V)Vqf>7~&lI+sJcnMgjn5H!*A z>GP|RqbEms9PCH3Cu=QdwQi4OzFO(ABe;tYJfeP4iz@qv=lVO@La+#XXcjLU!CrNd z3}{dx@VM0g5XJ%*^X4V+#yVyg=@jP%nCnq=L&Qn#VvVW7Ia#2{`aS2?)(Yjfw7@Vo zfVO(q%Bbk3@o2AAIhDmD9Jr~nrY{2iJq-VV+gVCAsO(_6T&Fw`UR*>$#OE6tQuDFG zckeLh_Cla_*x@2ACl_LY%l`;k1a9c0P-@J=S2;@#;D@$-v7pk0P}JrR)c8@2AXlJE zX4j!yu>K&1I6qs6_y_2pB>Q>>1vzi98;0od{`w+{v7YhlB`)T?<|ZK2A}NSNSIX1D zFKotsxDzO_ez9OWo9;As8*Cjchk!j5;B<y2F~or z0E5qfs3a33nB zqllfGqW0@!mc=V^RJ&cO@EK;^xi^8ia+2{5;^(LcOg5p*KpHY9q-f;X+?*r^!Ag9D z6Bi3^zfZaN`9I&FYzY_9cg>O;uRUbO&@ z`jxF^jG2Bp#s+=L+HR(#tuZE~m&D&Bl zm8h_5O(x}{n=1{m0OO}y^@}qAGZ%)H@z<^^GG(IE9|VC)D5H8$fbil=^X5f_R;-p19gpQiDt^$oum8 z@euH5o&&v2Gtg>OFGq*ZUT2&8us^Dz9kQ!+$JU&(>*Yk=>ji)Oc;T1`Ly>k#YyEmWkY z!@ZZa&STA;6WMyZdq8BS&&g_Pa0>?(0H=VpmaJi0#|u6rdyRo_(hY8pJ4oF&meBng zR1)|_m=18&`ys9!mZyWG3^Co4iCF_nO9}XZ@AIv+NjH)0ZtpT37vY;y}yc38Ps$)>=)=!pe<4L&?YSC1P1s+eDmtyZG0cGfzd!Z zt_P5y@}NFJIVBQtzU8qVsAxRXX$=y@2aAX{V-dXNuKOPpHo4aob5HmHFt#!bfohx? zV=SlK5~Fn39mXGAE^h)?Og{~~R@BXn@UOWPEf=Jz-uR6Hi(hU*9lt}u$SQmEa$>2T za&S9pwyxG^3cTRI;6Z`+44DD~s;VL23Z|B-Qfp9(g3+R8bsF2#6!Bl{N;c$ zG&f*x-}xkn5WG85BAm4a^NfcFAhl{g0@O_g+c8kZNx`e0cnNH&DrTv#7p7P#eRYT2 zbFa<9?(agBe@j$$H_R6ncf~QH68PmFio);!xUI%`!B)|%9q2f-5ywZUw#)awt z{zYWu!Sj_}F|FX^bsd!r-#=z! z3x}jRE_(xHe=u|ALG^xu=YuDpSsO8@5XPpNeLd~pSt2CA9>Tz#9;|F-p-;gD4z5qZ zJC|!D!lY4&^C%t*Q?p*`6-A^JErvcnp>P!#nulR{SBxtROiDNJ;%n-ggt28;E>!>v z{sH964ML|H{Khbfk2qA-bB|uz z^~`Na)vaw7bK75NGP!(>!{G=4V=gZ7;g3q9AARrrj$?i3=2Z-3mDe%^Lp{4uRc$Z< z(TA~Ti_XXtg>Z;UJRqy=fJqxe2eB#I+>G~yVGav2UtK=iOx3B+lZ<_1X4jn_uq)w$ z9UGKqJ6eNeS&*&B!=#6?rZ+xDjmz0x7C|5(JkcYc_ZgYJQ4M@-<2LMCA@KPPfQN1~ z|K(-}kW-QAJvJZ%ZyyAy26A!H+p%&Ah5N@Qw zPHMlg)KDLi@|T}W6@p4m&}Qi2L}!-Nx%(J4^`z_T6?^3lrz<8uSy@Q&I0eMcV7TQ8 zvv9+=D}!sZJ`kMwE+o;nqDzrT&qWM#1U@%1YbzZUf+z}@va!+g`F8k;8=4+KqFWq} z!q;2o4ew(*PXzY*2PYhmZf8O0T);N*HM-i|fWx>;H=XvE_xG{%_dr*>0#Z{NtTKC8 z{Rwh)7?pLU9C1H;2Af^>1-G`vz2bZa^h8o*Qv zzRMbL0aPYAWL!r!8qbIfE;ZXCTCH#1l6q#u=OYkvw$~O3mLUZ z$@a|Gb5QV3O5x63b_L~vWQqF*$!Px74d>&KpA>>8_G?sh7JIsq^}p6ycZ6xOsarJ^ z_2+HJnzIeaM3y1RL0X4$xJvVXcC+^ZTkg|Qv*Hq*M{-V#IpgWpkb`)<)$JZd6Qz`a zg64HmnZuVgXwWoO&uU=sGAPJC!o^~4q*9)t79o(sPsQK?voLHx5>y+=cFAVVCI*jh z3*GJnQnph_UD&UibT#}7Si`xS#?$%ozSJmmoVgr-wGx!u4NCWtA(<#9#T?@Q3JKqM zPs(TBVL?dET@j|;Al>OU#_joq^9wzX2Ghj>5@Mx|%eTK~8}vzb$;bR8-vp8=ImPl( z+YhNLMElJ=txH`L3V{L)A$tubz|MyQh`+&Lc+!C`Vys`VM1qfktl)`-?}B|{64kJ5EXV_R?tHlcmxat* zzpdY{t$`ho&PUSx4%0_WWCtx2D_X8IR~W$kY98Mv_qn-wGQ#L#F3{dT7r8j(!Ns8u z_bUv@9TOf%hjo1KdmOUQy)&AlufAnNMaZALpGK(OOkktNG5LSOWPaw zh@U+1J>%hn=cWhlC;GT9JN2zc;tJaB_Nd+eC&Xe}cF#=lJ1MFbsPgiQY<(xUDi~GQ zWhvwy_>->xZ%NdTlFk45y}&ml-udilO6*t}49FRsQ%h`S+IJ{Odk@%UUr*E0Ykco4 z_^4i&eCVy%)vFH_fh%$>ME}&UujkiA71*+k0$3E7?RadeyC&1o`h+En;Zic+^?q zCq~R{qc9I__kOsrg?E|BvYDgY!nB>$@LrV#uCS9^E4}4`l;NjPbIvB>%-FHF?nU`d zb}M@a&ihcayeO3uIb@Qk9`AqPb!35x+L;9Tymadny-S5z&CPFCZO8QuE4diXT6k$r zLAK2v$vOk@nEuRXgKW!|FKITDt;&)Soq4t_f(sWCzx$5;EcyAqexwr7joW>L*ji|2 z+I_3;Rq))z>~9OYxRqP>_A8RJix|t2$J;c{^6(55JngMrXw%ZXHI;I_yBhp`4ppvR z;JU#)Z)Qog@_t$S$X4!x!|#OJI@NMa*p57+54m)7UL)hG-L(|d*|y{lWxx7BfBt_r z=C?g^4Qyv($4;kOsMV=xcZNvv4tmXJvs`ezQRyE=YbSL`hPM-+oOc~S<$bO7*Xnni zvuHVOk~S%1U%fafWES?oU)uw(Dd@WVP=j|m{hf85lYEj1%4ssVr212bZLCX9@-yHB zhlS{w|K?6Ru1n(0Cz%*?i+-p7LS*js3N^QOKZkzZNlMB+$MW41O-HZxzCF>b54xk9 zQkI>Wsw4Hz=O&bhYUzl<6tDx4JANOe zLOzae9yqG-$<4?>KMAXzVxeiQGZNK=@d@vYwf&+oxnI;eJ*lt2Xl4)Wy%k=xa*(^5 zbM1$%^+7ek9NzV9rtZS7_W_g;=QHa*jO=sBq_5lBPMW3^i)WsvW+0A<#&}ACDO7(Vh3^!b8TTc=C!m3M-t4LDguH#tx@M7LNp&Xmk_334be#^@= z==$$t>cO6N1fuEN%~x(i>$a;>z-HN79Jj0G#iv!m5;@S+{8&1 z9!ny70XCYGJEUjCjjD70)j{`(Yh9=qai^8RX1VqhpXjvAUza(F&5}URGD!1iJSt6` z6l^H(%u>>RW>}N1G!6Qw8hrAmyh%LSXc@)W8|yA|S^-aXro#t?bRJE#zuLWpWww9x zsUFp{e3>p6djh*Ro6!a5?)#E-JzB!~T@sB&+kLad2}38ErB5%OTQX8zR4ZL_@Nm?M zYj{c58gFaZ@XAno@R;ng(f|>ot;sjv>ATAHA^jTDT$3x>l5XA z5GAY{B@v~mz4w@0kc+!@LAZNo!cBb2x=!#qk=o;#9llK>%anKMRmK9Za-4J&+Y-)B z;?o~?E&9FJ?DpQr(xhVQ0liX6P!JO4xsIXk-K@T~VqN|~GVI_N+ax|z#^y$)5Z}wB zi+#_mz504c?-X|yWEHwi2?Q@C+F@SIF611V*aN4JiScmcX{YN$tD})_N_{Rz}hxl*V28SM;S{letJYm0VLWtm#}3t+{9$(279?b43VAYRbtTZ-N2s{4PXs z20`b-a2#RhSa+J{i-eCC2Db&JeV&=&Te#_f8{yW_?e%fA9dAuKB_pjPL5L#^r_-%! zSv)%xXB6YcEpt3qb?|CTj8R>X+sdTmD`CqRO?3JSW=6DNEWXD;;XF1ap~IHGI{Sc- zOn+Va=XV#~)`+mL^bb8hA$Vr2g7)~cO*$h1$KV}C&itEs_~u16mgL~bcspLtf#l>o zu_=isjCuqq&W^<|J#`071D7WqL{qB{6|?MRJdCwW^>ClDd`gq1bYSZ9ioUUjy!u?7 zv-ge}*}X}p3+!>uE5lm@YD$R2*Bhtyk;Metdn|ooIm9+FQWtu?P2qglw7IH~1JUbB zcK(bI!Oe&yT|&mY&l5IRz(FA`Ef3R&!r1XFT!CHiu1Q&*U~m4Kp#->#pT$07SlxJY z!Ga`9&Ai>bH!P3Eb^Voal3MH|4WTa-3S06L%+8^FAptm4UQEO)Nw^caxj%k9K-^7t znOL;r|8e%_@lf{v{&*Uj+p7xpsaLlz$DMEFg`QY@-HKxpjBcVf zpRXnj>qtMK3KOFwZ}tm0z0xOEfBEDmRGh4cUu4eU5d%L3Cipq+Hc^SIl#GqdzHA_< zuTM-jv&gI2^rpBZm77uL|61(Q1^@71>ZpEZ>hSveVujyX^o_@#slu&*a4tmbYKd-` z*Vw_j;)N5MJvZ3GiisOG}@q}rld!kR0|*CX%Id`N}GB43KQ zQ;3z7l`2ptt@M8qu)VT`me6UN^rRhZ-aFvCoCju+gZVBgFcjALZN9=CIocTG`N0T5QG`#0D*GK>uXUF<$!$H=)M zsvHNVGBGUEAgZ|vw1kSh_((J!tMD94e2wd^Avw(5Yh_6#j|gESONJ_ZL73_Mc8>x8V@GR^##!s5R&pZ};4{MT3S>HF2{UEugIyG64eNXAq%RN${B1)YJ?S^FkbQ-E(qZW%yJ>S&WErOg^u6DM7-wxBcp~r1I>QIiDc2&Bps{xuxVx_u z*fP0CR%erUcIkRN#8js8bf{Tb1Z~gJ3hGxqZDy6ze30z$wOZp^O*xFkr9{lVNNB>g zX`hGrA?d#NA>_PB!mDbxGQE?YX{YnF6m~9qrCKZWTz)-V^j)wo|I~#vgeVM~$8=}- zmFrk9o70nSlSX{Uwt>6#L2hBqA#?4_HGonbY3M$^{{yif(6&;b29B@o+Q=Upy6CFQ zpg3l0KT}oiDBdU8yFkS(Xv&>M#)9j2!__POuKc}u-SW5XPhNk)X#wYg-tqq`MY8Ju zqP|w1**LbjD@e7x(c*GH816iDqQuL6DfEZL?0_YYcjNVTvp|{aZU@~m*mvhHfghGz zd|z0qoxTb1f-6zOJgF|D&S{c9;f|>CmQ05e_%;m8!z2!Z9EDnaAuLVSv;37Q=BJs9 zP5XJn>6$L9GXa75bgy+XpV6D63G2*jR8x}K<#prX!`Jn`&HjXQ(-Y7{o`bbXGPWRi zAuy}S)(nuIgLH%PtZVbqI4BQ{qE<(&J6K^8J}zV&<0CE=Ak#&ptiqi!K$a%+K>#fg zS2b7W%+d?lBm3Rpw2)8@R9+48!@3o0_58YYSE1y_srjo`csb12lK0!6o7}T&sC|gZ+}o@tr;6 zNZjDN{E$2Kl)%LzqxDzm$?K|fSwOUo?61%qfPG}@NgYqu0ma8P&ZdhvJ%;p5?(AN9 zub=qw%w?&d>jrUk9dLDYka4VjOKCwC6DjQu%gq2>27cW#<}WeBCUZll;i4wP0sUHl zi~gv#^eci>TSm@A^f3lirB;rtqIeM&TLiD2_77g}Mu0M<1qO#dLK-{D@ruf=AQrKz zgsAG*+g%)bkA}FT)?=Bto0`lm(OG|auTCg*_Haj*1fjjtdfn4PQZOUX8@4-`_?NuC zv=6jW-43q2>|x{9hr5k9FdVR5DI7L#KPY=K@}+x<%X%_0CGK&V-jfS_-(kVXv~xoV zKU_A~L^)m~Yh047xh1EY42C+S3;*67{%#Lh&yC?NxMp@X&f z&3@P1xIfk5W8^aLFG{*C01`vUddnBh0Bvuz;w$Bw+7!eI(;VNCWPTx&OagPOmfWJ1 z(TRQ;Nw^*@5Rtuwej>K4R_Cv-Z$`#>rOTK$_~*&34e>YZ<%oN(_GKUUT%Dgc zNk&g~KvQ*#gSOwH%=2w02$1%di^MZIvI5zHO+d-u|H1Om^72HGm&Safl@|P=?oi z*|d6oDX{uDl4+NRuBTCw4UvXcm+3 z3Kq)sJk?G&cv9%PhZ-~Z3@64AE0vkf^5a#XHUJsV#gRZb=V8D+o4xftH~~@5Zbzdf2*soW1Wr`Pfitws@7_VGw17cMN_!#SV5C7+C{eOBlw&>MqU(2_?V8tRKQKuC1VQ$X)D^6L67h%h0H)3j648KPdq}*|szO8K17-CVIdcv{ZHzhDm zuKSB5EsI^iVf^7FVy@JHo3>uixalccK>v1>awaEF))N@Qp~ukPo^RKoUpfwSsuhTX z`C3>$?>aa1z8jdg-kJ3HWbS-ePJNC zZ!_NCW3vg@cCN7ma8&}n*oGcqr3HS$`fGfW>~|e5r@J^<@IA$#+a?&uO0$%J zNS*PSnU_%EwVErHHr;SK`BlvuezcbwLB-5w?jwp`4r&RhU;Q-{uep{2(V_8!IGP?|HS zTpE8|TcZ=D&p3XJBmehD+|-|AXCPZW9d~Q!T@fOhNdWY&6+WD3dD5K3|5M2bnDo6~ zy6@Egmy?VgIlMOHS@o*ZIKV9BLgtN?n87s1%lbW`DhCtHX2^@11`yw_N8J3n4KH~I zW~=nNGekWe%uc2IJ}~j$-g~sm0`PBXoAe&f=h}-{ACgZE7B>=T@Lb^!X$L;VvS~Uv z@lywiSX9wc6#v|1ld*0qsTX$nX`yqzA_aPLjr+(e4UGA5(1``|21HG{kw&!h-14EU zBA(wavtHk@D$>IXkG$zl;0G78#AHa`XS!|3H_r`9AVd7>e~M2*dCbkI8Tmid-j_0= zTI^7{a8dFrWjNp(x?>wbf$G<1Y8*V!E)uP*NoJ%_jWb@`<7b~Fd6`dse$W@DplbZp zzr`Kd7L_(-{*CyVkS6{pC*&*Qx;l1I}*;0PVh%>wO!6dUP{+ z0u!2%4G9M;bciG|@K{&f*?8;bvlK%5Rjnbx)yAZ!I-FiWF?4HqKIRdF))ik9Y~o>~ zW+`GfM=EUbA1wff6j|%kAEKP&1RADJJ{;?9Uo95Z020(|v7JMrE5mDQRWQl=a7`!X zVRas(dZlhGc=R*7ZX!zNa}&Xdwbp5-;yc^vjUD+Zy^r^(ypp%%fw;BNE&>hep_AAt zA*kRui8!Ql4R+BR_q7<9@0AKQTx+ZR-9|k|aB+=?QdG8h#2e2cd5xvYX*fIcU||lI zS5Lr9ggKUmm+5uWPDY0Yitsa2M-7NSE39!-y=Kr}Cl6Ik?-HM&bQ8NhO&abU%+m?& zpf@~8s5rUQ#i}eHUgbmQwt6KA1`KNj+12NDp3584C}Am|!B$)PFXB0P& z>)WtJ!9JQ@NLt~R1%PWkKK6`D5xp28C!>c;E!P<=uU3gNMjDBN9mhNZ-!=mT&Ozd~ zv$$&0s(zkjZEK=sLLvXN^A|o~IR^+C2u?I1zWk0441Nss5kjm4v zO_Hmip*!Uv?@R)p+IECVyjlyXBanAY0!`NnKxv#at4)x;YjuXeKVA%$W^-|ay_fLk_GQJ88*vy+E;gAPIYZ5-tt0h9y zqsbKn>D>q+MEQ~#gqUFrSzOf4@%8ZAAU%X?It)!BKlHnb#SrPxwN@5R3MHm&E2VnX zY73Gh!D~&nGvHm!kUG#?ki)T>AVFfwT!USdO=7W7qxRkPiXBs5Sj*jGS9va-j32wp zh;Mjg8z?txzQb z_#XqAm}*LB9=^=4FSB2YQSR+&3CAhNc8}G5!ESHJXb86o-0?fz6NnTc(y~pf28`GE z#Io7SJ9Iy|7Vejsn8#AyIKb7dH?G#6y2)ox(J-QMf6ht9a~y9I$$PWV#WhW@^KDqm z>jlG;^-a9q^-jbKUOtU6O@aB}+{e-1T8KSkuVpT@K^*F9=3h~2uX=UQir7yNYJ zzh$4~Vxy&lZZZ#Uc+yio8pD#@&2v<_ssA>Os59x6uXI_M3hb|YqD<&BQg+0>dt!NA zY0lRJGx@4S@B4^LwEL34ML;V{g7nLGC1GqKo4^fI8qS*b&0!lH@K=pumziPL%8*wU zL|7>^KvBJdU9`ncY}*%9I{Z( zvo1};B&cQw-``p7Vym6G$sefC1yo{W#l6b6R|n)i62i_8W2!`1DOt5qCFuU1)t!dM=}Ctw4k4ZlJ|A+^J{l6UX%@7dz~O zoD0%#?+=1nKB_0dF4JZ;I~U=|D6vzZ8s4VZk9sLkM~Zf2Pg5CRCBT!Y0E6qqM)jZ$ zHix~Dk^SZ}BEn-uhKt^ticqG5J_!^}hC5csIqZ#|g!81Ma-ZQ93A z6gsf&`tOqyM5RPs>SWyR0?tF}sAncQO3WE~OHEtC3|9TPJmlhBx?ilfyFLR2{~&Hm z%vY(#dRG+=`vLMO?|O$^Vj&vWwvQY=?r?7@X#p0vAN+JHVJdLP%teKwWy}Cr;8F@q*U97}!38La ziGn9p2Eikd+nAsLox>4jL>wrscAPGD%#;v861W5vJ@{p*HC`?Ts-8uWcAo$lt>dt9 zyiv5{jLWhRi6oXH>(Q`2v~>2odFYR*+sRRTsEsW94Fjs#% z-Ix`y@m4kMbc)!ujA?;Jz?TF-Q^oR3kmAC&GyW-e>?=q(ipgh_{vBB}a<&vdHw=G6 zyZ)CAbZeC&^{3=2t@Py`YBb?u$H|}~?lok!`$MCV-Qu4)R%@+=Zkq&>WvTJ>!TlGs z=?xv_Wud z3O0z}*8F;EwWS5gcigyA?t*I~ z?|!u=(XvnXt|A|HB}$P`j_!Qu6$T>Cg%&M&5d>$|^yy$C9wWduSsg#e72Z8)-Mttb zqYw`Ok0%MIdKKD3-tFX?PD}5y?!0~*;dF>4|7tRRNvB%?G}9aw8{j09tO*YF@vO(tiwg_(Yy{|LqT>Sc9m#e8L^c&XSZNWDDJa z_J+twd#I$W`J9!{%U|(mxfMQ8ny5Hl9#sdr3{@>L>SpR$Ok{=Cwoj0FfQ^UWXQ3Rs zwkd77yV8F+AxLW2YHpv|JU*Q^w&crEhe6-4T-CWl0Ri-EzP&g6d}k*AC!;J?Z_v2bg-gAvdi= zkIFK)5Bgnzp_6lEti8?g?XVNhMdI`2*SNf&$$ZxFXii&5D?#f!agjvtAt*Lhe^WUlSb zGCfEmMO}d2St~m4w}h7^iPn=Ri{YT%y^t(ZC*{_bhmr4T^RJST*Z{l99Y#1+4`T7K zR6^J9(5jfRFJ0{6_9AM#_7Tv_EKf%s?opS8g!IV~ZxdG2exjAUU*CqdYbNtq_DG1+UT%C= z5i^fwL)o4Vn-+U-BGfo9k%HRocW7)#xWhHu=%v2K>It-1?hDG12?kPG0^9yM*LdS86+%Rm?`aWMabKBC?gu$_2i;OffC&lRIC5HX>XF4 zcr5F7nuIjDRK__W>q5hzf3hu!HW6rmk5_v@urA@tO?m=p-Q9r!nn2hD<9(P#FuFcj znr2#j)M{+Cj2S;s=~WsMfjyWI5Z)W0&NCQlGkE=NWu?00yIE#n+f-oA4=VScW{{c! z_P`$@4RCxx(x8H6cHd4Tiahz$t9Son$~3Zb!!LhK$gDK|rHtN$Vc!mzYYnVIJYFCJ z2)T;Vd<{{2!e}>^pJ4Z5w-MVOu2;R|Y*_6%1ys2U?^m|P3DUM|0>$~ZH2DlnjX1YU zuDKQMlAyfahk+fCSYW-$sZtMcfjV5eogck1QTgmaBs&iclYocvdb4`LkJ}@c8Ps~m ztNDvu7M3?Z;rj^-o(95NPfl5;*Jb09P|@$@{O}K=uF5-<@XjP($&OyV!&4}mV`TYt zE)NZxbV(R(E40~hD3wA7wD+D*k@ISPTBNTeT|-m)V2#Pn^>!0!z2k1h;g^$Rj`;nV z06B{)5AaM?+|7LmAiR0blbl*TKy^=SyacPoDn=Ohj47D4H{=E+EPQ(DKBC0R6_5RAQqq0 zYG&l}Z8}(^fE;;p)+uAMnt^<$^ME#k1Avv743;UK;(aD4t-*%LbT4v&5z0_yuJKmM z91n_86`~$@A3S(c_@2w%fsl-`T9O-58%9MP%#;TH(|+<1kOpSr;Hi!P)Py1rrvCR!MI88#`&7N^E}A13Z`>S#9dZU&=>8}?$eQgC9)RL z=Baiz$GV`Ws4Ax4_yalzd}F3QDK`xbBq}~HDW0kw(nzopC>pPNqC9}$L-^;(;{@#| znhJlU)IRRr@CtdkG2SbpesjWhxN{$}P%SZ2eR44^py)m)0}Va6-zV`beU{}$PCQor zlXF+PT{DIOXIe^h4KCC)R$>t=;TZwxsVNWaXWHK~;O8?781OORV?y9J9}~r15TnKn zZEI_65T+2wJD|g&GYI0chiHjiD`EU7Hgzx`0&?r{4?GyjsBPf&gkcBLun-I7$^C#@ zFtW>^_Q38Gx&d8pyL0Jv>l3T=%N9W(qZ(wRkh9u(@GE^Y!&{?lM2?=fk?MOg=Z5NO zXu$X9%C5aA>dOJ+*JkomL*9+#<`V9dJgbd{MQSKhwp^nm%{Xzn&$>Acw4d0e+@21C?u<0rb4^C{ zDHw&MoofeDqbi~GvFcutpHE!g=_6T(Vf=ZvAKp){h~)r{r!j0KyLN@jqZF$mdOvJ& zA%TBmX$Kn*y;xovXoF?fE_(Wi!P$fp$1E_w&6=niGm>BAHlDa9vMLP}6l;F2i9=I) zu3jI#vyo1qae7{>obJ%-OWWgR;{H8vi&S@(d#yWqG&Z)wc4@&=I}uj)3_d$m zxfqRf?h2Nfr3Vy^wO)m{ZoUB&rvCw6|JTOopTq$eYxSCsipMV@TkODz><$m9S&0a5 z?F<)bE<3k|M4&Bm^*3Ke%fwlM$V!zW{ z<^<$cXjq*~+^Kxu3cW3;!@VLYxV=q*O4yus@8#PXFV)dk7)(TX+U*x-p~zZwU7b4Z zVnJIkV741S1DMM~;B(4yjVq4Hh_Sw@9BN&}vNU4$UTd5DSE!;Vl{QpWBUZ9;>&L_F zGv^|O8Q*33{e(Ty8vXKgC15oSr3zqPawese06sY&{hqF{Va)hH-u7%O(~F?*Yquz& zKV5KHn4Kz+8D&^yPM`5_#_hL?-k6NlJ?|M@;jE%)v(iuEZW~@i5fO z@ba4`|HjPySFG!Q*u=*t0D)`c6El>~Cxc#dmjSpigTwh4bf3`EIERtXipKpj92?Xp z0Xpqnv)@GWe}DZ2xZ>lySu5;yJKDgszmfm@pRT)B%tiiQ?%CFh+1EG2Hsx_2&Mbr} z0*Ni)?74m11O$K2fBJX7@IQX*Kd%TkViOgT7K{GGv7my#Js#B~Mb(lTw1NZJyox{`JQHkKdpen+*FO zchP@2TIYfkKP6Dne9d_g@z4EJQ-mF?0V2|LGiL;kEw!YWtazO>tE_%9V7dRN{`B@A zoSxz(=8oPz;Gsn9F;mBc9&-gBS5Zb&$HYuhBfVx72YeMXPye~0_wGnXq zcZl;!b9)aW|62p`U+(db(a8RCHkVDE3{c{;idv~QrhkU~egl}p1AS1UK(mYv5LZQa zQHI42Slm9Q=|4e1rT9gUrRqMl2tVCM$2cgt?nIN&)E`Qw4{tW71?kE)cM%1KyunmrD(Bl8@(vU^9ujKuVtjqxOhQ;n5; z!rOLiFlcNH1fLhhd$hyhC7A~~_ZwFBavnxoWqOn4==*IROm1eHKM76w zm^TlaaZj_Ao=I4||eVuI1hG0Poj4va#~ zc~R=gf1=&vxe!)}`H2IXkUbN!7Q1%MToD3C?A|I{jMo zIfnLhy*S*2_KaWOjwCp8d{{ty-R9$Go?(xjK(fGRP`fuQ-hgpH0k`La9{=Gkx9g(x z@#pdmN#q~)n@>=7-!&7i61hqtvd5HvExz}KlTwiRx;rUOO35jIh*i+j9q=R@Q;nJ6 zh!R#e;OJ9^*c^M0ZmB7@$Savo>W|^6-)tMVsF1T=`&q zn3S|rcLgEfYtvR9sI}P2Na9!o_D-;n=aoFd%gfn`CD(eD`>n1p=5(u z!qbepd)y(Dr9hYf3f2+Vxcaz9(oysJ`bGf#mF~ZO-#`7OvMl?s^V^YO673J(?Nka| zRpL(VSIgNs1wPkL+KJxj9Vp+JTA$vFMRpDXuAb&B?Xi8%W#f}Bk5!cs8&iHIURWo5 zXmCrWX8YTy9qJf3*qi@KXb)Y9-I~eG**&}RUnktZyh#0Ruio)q)Dx2&y@~f(u(|eD z{-8NSYkS4r5#$szvV^D#ERmrzY~QIDJ6I}}o5}pYS%_O-C}w6K>o4oI+hutl0^pEFowIv&Vst{rV-nXq4tn%sv5kAEtKq_uTYj zoVg&`i~Wxu7b(r?o9AR3(KYJnH^T4bp=tJ3QSp>wXGoM~ z&!dGnIL_om=X7wbkR$ZArN0ui%x5y-cKFpRbET~Ivwt>v%qwDgxG2|V3PXzJg^H5- zby?3fwt2Zx=R=<_+sPnvT+5_RJ-5C6Z%tFy?86u;+^CRXl$E`LejUkx7DG>g6*k04fawsEHuZq&aV`ME}o&e znK#HT-cHPx<{ogJ@p;6yq;&CiA<_KKk%>4`vuLw@_$doK!Nx4EFtPg-b)JvDfUjNl z_-5*f;;9Gqrf2uYaa_IvF3Y^nHbAG$GrL*|9e+rj=Nc5n@3oe*_Ye)3!RZ)v&!)7b zF8KuTrJI`fQ>Bjo;9kEv{Ysm5Hl$~#wh`D3F&~$lX&R`R(Z*%$iO^}+RVmQe%EW#lq>iJA53YSPl?q)A{2+$jE<>us(-C+!&V z&aGSumW$_T*4tJi@O2X@#o)$S?Iuj?ssc-Ky!_D<3t}+6#v>1|2u{wxw5PHb3yly# z>K@l|zt7V#{(|DQK&m;aaEqPL9eY3hEI&gD?iopmnMN(^Xs>4WrB)WQnAG1v{Vf&> zsm&{KT`bb=aEnK6EDV^2^8Ib!&Ha{*uxqZ#=hu(@T+_c$Cx<~|>nc2<|hvG>MB)dz~o_ksE9QBquScW0>PqGCok zinU-LNe$l{ZDl$R;u8~@#Kav_4)fFK@_*Me{uvq7ysNtJX5}JlHAdlAL!rz z7k1yg-Pu$hQR4?=1sh+7Z1_sm+gu;BKL7Xem^oL6#Y_3<*5)IZS|^pj1mwXI8IwQA z0Z3A^P=>)-j^7A*tU@LF^fLc*R+yQ=%gAP6AS8X!hYo3H0XPR+-yb5iFh%{&MBH-2 zW}E$6vI@Y*YQ<xw;<7Z^b+b1(m*oDsZ4Y_i{#kX6dG7cR z`5s?3dBBt)N=`FG zTFlsPS+fe-uRYiI>PtGxFr5)A_GABfs>h1T_rfSm;;$6Mq;#ZiO46Tw{M{($+BS2- z(ycRzQ%A#xVo?|@{m!#@5(`R%pBrnAU^kF+2Vq@_AXBO5&rTEHn=b)H8b3~~YsE87t5Vh+zuJ0mBQ6`B#yrYAoM~rNQq53bs{(70{M!S3 z;+ZAM;^a;A8@)98U9aQlujGJ~Q5(VoY)q2cddoemZC$Lxm5 zCIY3v=~-vtv7MQ;M(V>IK%o5gzi!)V$^C|m`!s2prL%Ottr2d-Ux23!b2t2Oe3E4c z)k#`l%Oj|gv{fHxa;uj`lyr4Rr!p=)zoGx6mtZ}!i0eWi(Xm9s!N>{l_i z3!7KKmogV?wfhivBuCL~fIBLJZ0!5&$mjWH{gzQy)%*ep)N-luAqB2W+{}+%?;M+fC=qZO&$l9me7=CF^?XrQVcxQDtcOs zK7|2OPoL8F+f58pz9*PwOfu4^C9FYOSvg>+Y28orFa1l(Cz*>;CVdE@Eu}eK@Msr6 z-CuXdlsf}vR0P&ASn2o7uUFxCD*>$7%^TTD8LuzOf_CIzqw9agZNyxadVl`m@OXB0 z&gnhPDGwZSXgU2yONa2nn(`VHefV(8R2GGO%L>GAuer+Hc6|Mm zJqE#-OIYWf{}*d48AMsZ+W6>YK$Ghvhd6hM2`XQO__~d%nt<9@XbT3hKXsxE{&R5@zeV$w;Bc=Vc;%dbc@ICQ?v=%8IKn+xQy35QcDu# z(&c*=E&I%wT_asq_dygpSjZJ%f#(9yMt7VWp=S38)*kfmboYDf?mRCTyW61<_D8KB z!Fj>X2S7(ZS`p4$9HS$vO;$OlxZuCNJ(l&mc1S6@$H4yq^IA#C(xL}c)d$5=ZcCS= z8(hO8T`i|){Gi!OuKxf500cX}l6wN!F_^YG_#un=y0l<|dD;$A7H;S9>{2^FrN7l3kNCL&WR4H$T6~BEMsYy_F;P&e1elKvnRS!d31H=PSo>wu^pzG|J!8MgLB4Yq1_Ijwg$p zQ(}}wuw9$3TnXq-gzk!UK0Tl5aIcJR%B%Ki$>XmwZ{}}O{!11iluo^eWrPc^*K^Z` zNBpVs>=9D%Lg~};_hrQI(MrWAU6yhsiBn9W_+$iGddV!v&u84$2b^9%rs9aXJY0V!`IW{HjZ z#GR)HOX^ENA6#|8Ou!?~6J9tqnG;48T)y7BaPcZW6n(2TI^ax$}yv9o)sJFn))pY3A^taugpM;bMx{R z_vV-V2;Y-X<3M}AAoO&Y3w*^_YNBZj5NrJ4$y43ANqnw!EFXGHnu3Mf?WZC0ZMUTf zA2n=)5)f76dmp_lX$-^~AC?DdXv!A<6D?h4KY6%xPjlKw7}Lhe3R4*HJ2J~!FWDqY z-<=K<3I0mUI_%MBkMC>xCD9nToaCiajS17^5`2eLIK=7QRG9*xUT#cZSK4+EZ9N=XwSE1T?n<1Dp)b38UEaxPYg3aM}=s_s!Rir5b)~fnxvAvaj$Jc-+u@ z-L%2ml73}uhaSQN_~=c~7vhJmV?WTs8o!h1Zxo#YdN+mTXOg{1U2K2>mrJSrtO`u8 zftoTi(r@?}KD#308V<3qwqU+?B%NdRW6N<3-D7lPtVj=9G+gpT&TT_Mvfk3Keu-H@ zfQQPnuXlw-8)aQ>I$os+8SdV#^OVm4(E2csR~988{Qa!#AFix7rKx0NCcP zY#biob~y=eNBm2}g4hgxr%xhgO+4&XCK>-TV;#;<22@IQZyu>1 znpWFDeV`G-{d@pz6=KPwk)U*n#)iwnYlwDrEP?)eL^mfx;&g>|afvvdjcIvUisdTp zr0A_in{`9v$Y*m>HfIf}CC_;Z7+H9)-M$^;0_W!l*fJRY3u;kpK5>Le1)x{lclKmG z4^#*^^}yO76RZ{O!Ya7pERUHhA#ZC)>6r>C`?KZ9&p)~tiy3^4J@DR4k-p|Ot@NA0 z9FRMF9VOMc{Go8jwhI^NBefc;6(3riMlM@x+YfxP3(GmVPR-8ds64z8QuS@? z;BABzd8lvVc`aV9vtyjm02wuA>ozu20}yTw%h<7`abVEi)Ne71)3xc-7zk#p3xV=} zn4gTl*V4}g`ZCc-$g&=o%R9owUfj9e=!LBr^_n4O%aYSVb1jLUGhB{4^GhH_6WGt` z-PG0LBNia+QG7vH3m(N^-n`M|E_JMF2|+J{tdtv+`0M+19@|OG>~HQ*@6fX?0qIVo zgdAOif<*KmrHl#2UjgXc3cXLb`6wXs2W#z`G8h!>x%4EieWQZ-FLp6n_a5BOzvmbN z9|_4n2SqkP3=`_y^3+O;zpO{3T`UiAlK}p8p6Ax6(Deh4Y4*`qM0wy?C7cp}0t|Of zzz-w=nkCd1vTP_CA~a4st!1BXoB-Dc;h1kw$ zX`=vPnO60;U2SwsZNN0_nY$KyrG)-9p-?k{H-WZO;`}m6MHe`HTFqUzFq)Ajo)c1; zeq_QuNgt=&GUp;u|GBfU@Mf8{*d3Ef8_>Zne$0CP@)XRAPqU^ed>$@)kBV`s{4HAKK;Be3(<`vmd!8wVN!fuv{ znIYtvgnamlAIo~p9Ng<5)sv26FVXyD#9Kok|aUCzrDP9VeH@)cX=@j2biXP7WIfhFnB{|2%xLl_uoq zQqPf0b}`sQ|7f+FM#KyJh|Io>tUi3tba6Gt-k1yG>v(_8{-H(~FgVjX>*?FQ$RW+n z+d9>AO%|?lPq_m8J_=*sn$_Lb&O<&8+HIl{ip~HAAl$g1Ai6sN?i;to%FzTNfA)|v zUv?1M*|XSk9z;jNOaffbFC)0VB=XG%?By&yD6&d>U7$c-N!Wy82}^S<+OH%IVc%z5 z+g3Kw5>3tC>#s<70ZKQo7#gAG+ZY@|;y~(x>~nRhDR6+F)6`4cc1fUe%d5RUp}@P^ z-r_){_ygZYU^aQL+YiDc3NLa8kRHK$gqZRC&q^aXl}|IbnXxp2{xXv_bGiYK*rpo? zrbDq(b8Lst{o3|lx#EnNFTMRCo$uD+FPXyxDyYu5I3&}cL9~>xj;*}Qdl53pswEH& ztJ=(i`QJeQ$Vl?gQbKnNHs5J{DD652Hcg;P)#3Vixh(U^i^B<_QlQ+vzRg{r#m)N^ zGQlj*Y%fPheBHt!Ph>FQTK2&)w^>$Zy@Q{_-g4r-Baf?*sqpmWNHXzPDqr1fo#t-c zgvmG1W;u5ABs}SNx+sV<00tE4@x}Tj&;9E)Ce>w;g8J%hrkXY%pEC6M^rwSQj zSrRfHwksUUXHR7-z%7okc&weKriRbGlEms{y<@9EHP@wHwt$mk0;7eP`L@a32uR(< z8Isk(z84}U?`>C0MtNSfKRP`|?A)aSV!k5op~6AX9Q{T;o@^l_mg(*0yjfQ@`69rr zi=W+>`FxR)^gaBFA+xN1YWxBqCUeE%(#biv%fY+!u@JBtqU`G$NtswF?? z#-W|n-$`bzMh>B-fRFSRc=)f~;JE!%f99&2sov+orsqoYD1^U1lqkBy<~5x`i$x*K z(utdu5u9`Wy_XZotrHx*bn_t!sPVbOcan|2X4I5h?Vq0`vm1QY$Z&~Z7V1oU%Qw+| zjuG+9s$B+KrE^y$-Ags$vre5PynFl7^v;X~}{MUI^Y^SMW(kNQ+UDbJnP|M1% zuIJ0(!6w0A{#jYZLr2l*7~R?(?P>19K&OE&+#6qa;wx_?E0zr&?5; zZevfMSgl#pLZSZF@X*8}o98&^g)BGKcoUsi0uSoY)7a-ber}GY4_u)I$R}5*^@2=G zwib$G6lyvz@x~f8wDBzcaH=qa@?e{YnHIOaW2Sl9%RV-WN{x_->kPrCX@8XF{UR`E z_1agM0W(R`X!T-PWf~-HRw}#0*Drry7xsSPDI56p+aCIm7@%igKduz~uP6EMUVK)| zmOG`i(gy8$wz4w}tJmVf^WN`R;*}elSwCQ}UPJl`pl=*K=q^eJf`|-<&IFqSMt5CK z`><+pJ{7Q$mC-#19ZU9Tw_?<0PZW5=KMO55+)cPm+4S5s_grh9(K*T`BSQ5{d0npN zt<@gJu%YGwad+Inm4XcI5{Ic}U*@a1=PC=*4;t3uDH`bHw(Mm0m*?Z|<@tOOA(@H$ zV;U9SC5=KOBr>;R*lN1Y&bL=f;`dxFD|ej6%Q&?=UJkZSSCEIIrLPABPVdBxX7Np$ zdUqyO@YNC5s%_VRe9Tpa^Np@!9T1y^q+7Erc@a|_^(PE8^=76jY^ZXpVONL;OgvfKM4}*vgmoc zTe~Pga=bi)r66lJWUm(=n4Uq*QXun|B7XM0eMJ}__rk)jzfIOjmZm~D{W}-*&(dTU z(Byv6>T-HHI%HbXFLnvfr#b!DZA6w3>_K|YqHY*&wPi&TwdMZI z6ylV-38IR_HJg-ki1(AemEa?yeX&l)@y5eQJJ%~Y51i_oUzi&_hJTNPni-dWa*J6=0P?;P{eguA zxUU%7b>|BA&d#x{?aAdvWmMhj7$OP~BIhP@sJJin#BA4Z1iHL?N+K-hyv*$uuA9>gqKsyEov zA4Lj57Z`4l1dDdLMV3Cz7h!LsLTkhf@#3qGE1J45`tpyXuQ4D7F2C#;7&Zmm*4v;O5u zBBsTQZS*r%Q#qv7wwIva^!QA*Cc?u;_SbC7W`9eJ4P&@YJ5OdhL)PJ3(tHUWTp>De z5zW_3OM5EZ9T+k*Xwk^es$MKSUTu;j{ao>I&cCq2UERnOD!pTGH?D0`mv?u>m>4>C zm4l~gzs`I1W2ogQn^$|wTeXRpo`CDX5P>Wb`^>K;RvJWvdnKyxB&oKfG1YHb{lAHn z{>zg*Gb|WOaZiBiB@iBM7(dGLe><|>)4o6yt$7KypUxvy{cYJ5#V8h05+jiU>qb4Y z3ENIkE%2E7m>#GxAh^|sYwq4VNvhZCwWCFslCbx#-2X=lU{`?d;?me)`u97VkuvA- z3esXB<}^? z&uOd@E??1Jai!QMoguK3vt!=UP~&(Y3R0oPQd#j>X&>;zcZ;7M%te6bHYc>&)+;w6 z=cDBs26=am%<8`nOo6vNcw)&UA6sfOBqlTANBn*+^u29KzKgn$bXGwXV5o)GVW6W+ zAZrwLP+U%og*LrvM7>E^b@iCA7Iu_-J$g)?|AGtS^~}4)bqn@71(%W4qWK^>QPxC0 zhNXlEa7w}G-N5D**yc(fM4ossw~DHg1f4_4OWB&1_w}22^42_z{>#)+fwV31-P8UC zi3`&y<*u$Jh735^7~muF-ph9*ACRG8YIE;%3eYNK`L*zf zA#HVZV@Z3K)Z-${rAcTXV_XW@i)#;eqW$0EKE-UGAVUhpp1vA_8kf7?AWx5;J)+4) zj3fc8S6hrV8?;W& zql+#TtbT(!&9_B{5B_p3(o2^&i1q$DS28_$b=`-Q{p{*R1d-%1@j>D;UlY4W>(6p5 zF!a_V;xdKih(GQG_SMN#7K6Fd;UsM;Bxf+S?HIFw>3b?yJkkq0w==wlVkOf9vJ1{H zOY4I#8^gEN%_7tVr4@jw9*q{Y|Bt=*jA|}aa`aK}5|pZ`_Ur8El~0_OpHmvyU)E6`iekp6%I{i^&~%-emwCxpZ#C-W z+0v%@t9)!aGap3{We6-i!dr5S3DzY0XyHy9u=A-oUV&xzti^pvy&c*73{yCu+rT7x zqrtEaKey6)QVnHey4Mu4m{!2i!Ry=IJ=vA9MuV!6)DEY7A7ne;04= znM$nt<(`erN8LfzfArk`$~$?x-wa)ts&jYB@=o&=oiec}}JLVA>w z{$Pn0q&$N*SOnfZiwfj}NnZ;Xv5paMd;CTf z=i4_JTCcCU^7$c4NKQW4!EOGvRvnv@(pt#}!W{4Z?OeCphdlAA$uwniucHflw4uFLif>FPS!bF~D8&xHl(@GEt8@ z7xUr?fWJA=4PVL#p~Fh5WbW4*k`ruV*8pYJ^60mu)<}ivkNv)j4IVJ^;;j3kTpa~B zdt!P@@fz~#{9?|bdWv(ueu-)yg-Q1&(C>cpCkIYjn0J=dgkiWOW-@NdeRzOQBCzUSbCJ#{FfLwJX5 z9lsn6-8KM8ajf$6dZVjvl1-%0;J&&E9v^y-qF55hBj=1r)u(4y>6)NPZS7*Ja5^8< zXckPo0~&hW)}=P<;WJ%DNj`6CaPF#;xo3F^WN$kRxPMmfzjYD{)|>k7zM2f=^OR14 zt-e_K1cwz99~7#LMLoo9512SLd`(ZL?Zt;~U1bHiYgS{T1yv^~&zH5fp><(ILCHHG zzjk)bjf}YVztuE|l&Jcqb#u28V_{2q7n=H5-fntyETb5!4}o7Z#TU2XZjHXai0=|D z_|(oXITkh9RRDQr`I~M!1x_nYj?nd4EiSa{nLd3h@m%kiNw&Gqd+YY#>qjb8%C8%c z(Ib@nm1Po)4NTb#oyo5|N_mb>1tV5&mSXHg#A7=~7=4q1uUjfP7q?`n9pNy2 z$GGdw@QFM2#z2=L#l7bWy{l(fzDLC-9W}#Xah(J$Qj&yTiD&ikxFT{)BXft(G=P)jX*B-Tk z+<~0u>S9ejU_LkvHQ`6`6KtS9_G!{l17Al+@2;$q)&{BT_t*HbS^o8_`?+P+2%bsNM!s;@H?Uo|TTLr9 zTvfImv0JrH)PjA(YUvpG;5sEy8~2_fpcRkpXd|ii!g%VvnkuB7(2GR&>sRk&F@6F{<1hYIVw&!un+%&lhQ>owuI{qdcAQ z?W9^YZZ&B69(;I#PocbF_TFlb=BKAilydb3DqoVG>;oCBM8?n@$x|cMpEX}iXE5O` zKpWe_E}iqY`#vCSTD-*)5uMA$EiqR1n(s#w-BDSc7OMvZ^itmqF=`b(5e#I>gYK;V zKZ2Iud1!LC-*N=zx)LO94V+w9_Eda)@0k<)Ce5{}69sqnuhL^H>Lm74EG=cE-|S!1 zy_Aez&MSe?sM<>f%9SrVI<=RDhvLuGPD#}rxTJzD(dWuLZEz?_6&nGt&r42rqIot6 z5DEVHWE?XErZj)pd#+MrIm=Z=_}fc{k8_1-knGWV_cqm@_VRjW;oT~)P!>1WuLpR# z9Nm$qI&5KYb2!T2LcWz)^L-F37CRqmd98FDFd2I!;`NgcLx~3`=R>TWL0#Fu?omK5 zOa0Tf?(-i@U%KAtl5_vfva|Op2boG<%N};`uF5tGq|ra{xkTM$J;BMVw=6l&)vPBh zC*5f<`P6IRX6Vc9dMLLmnDe6fE03YLFE6zAZl5r52D&_?J`18MUMhMdes$pAm+*&~ z?(#-`FRKBoS6({2)VIs|MsZAco%B9~xqj)|)H2|hcdN8+xJ@eOQ^e3mdLqF8$@q4~ z+vLNCK@jS^w&)(#3~WjC}l^))L>KHYaKoYkm;wcq6b_@N18QQ8~R7w)5s zxlLrF>mAeu)*klN!Rux(Q|2nLvI`A-Pkt)LCz%<9i}f59%3ex(9?p8{eO(H!|Cp>o zuEA4QgP z*Z5a@5jkXoGtv_7MlMa39{FRdsC!2Xbwe)jcuG&W8qPbfPNKA=@5qj*M&7_wML9)R|{x%BPyW z1WQvaZM~{!9=~y)E@|qt#QqqWZbc1^NLC@o789A6pI$Uw%rG$sQ@civqKqeIXQ64Q z$Bt$$%L>=TZbZrt*yN~_NjD7J3}+OxkDSyT>Mgh5xQ?F$e}C~)JgE8pweh)XPl2qM zLhUc#KoA{eKjNdhan1sI;o!5^WGqdl2V*=3&y=OfL_=7G#5ti?HpM->jMoli);uh7 zcp=4dqNga`=N=}@%Evi3jSxe!?VvpGnA@;b%6R=^YVA?_ukuJ!vv~i8Ir4Jb=GgPu zj%%MY#f}axA5P{g6|>KLl>JiOK%*e->C*Ih`U(?I>b1FF&?v}O>Fw-Qusb55IrOFO z$T&T${WDVOA7{MwHbd}SzUQsX-pWBm)tY{vi0*t>UfX-#_tNZbBgw62H@;$g$ff|7 zSk~SZ1C}mUW6`%M8Yy|EzL)Ic`njx9!?vI_dgqI(nPOS%uiq~%^Fj&{k^hvIw`7YW z8zUfO*Rz-h2b+E($G>lC`bvt5|2xm9tQv-QWFG}9@3#1kKWsWhVAD}@Yvu>c31zN) zXEDoOspCgCaTEfdL)7RF2y{J9w{dJcp{2>^% zD6+;km78IMTPh*>_%|dj#eY@Z-0H6iFovU~?#1tlMLD;Z-TyXY+rrcMJ-8WhXfg^nS7QXT|vkZX5<`jq<}Z}E!M zacz4xA;%6mEAf97)_+o>!R|M=^sFC2m8Mn4-lw;F#E5vfei#0{B6>8H{)2Kelis@z zxi^?do%|VJY>3dkjEW-POkfn{pzbFYXeuAMMrqNUtnxkCq;9?EIlRO5o3GN0xqkyM znWJqZg!a?DgRA10&i(AbcV#+^GDZ?PJ{d-d8PWYNHoK2~?shj4BV1QXFUE;#n$B@| zCjXR&>0)!&?l)ictRLS~qQDex>Tk@K@hUbh(>ZCjGYi1xf7WDKian>39UEe*wQEMq zo4Tv~y-P7WL#vLp&bDntdHj;Z^;XRW**lD3>mz1`yGcI}>eJ~c#ma9nMYpA9aNVv7 zZV2j|l0SuQoMMXoj;eZ+oj!U-{C~DI8v_AGSXI@T`?iyrIencXam?R8 z#=Yk`EYNMK=%q=h0MxMV2x&92THv$4y8CX{i5AiQNB6K#hhh^TzgM8}LuxriOjLUb`3ADz%cYd0rnP*<-(%wVvV0gW*Qd^M0MK$y5?{{CC8k4tUGb$iHuW_|KQAWM*t(M67<4`~j@d=fa|$Uc zJ4DFLc99g99-bamz!ruz!lXZJ#Xlv=KX3l?PCw;S-TK?h$%InB%=*X6*9pwqrGOY8 zOi*{HNmXamh8edp276CCJ(}1IpY@;EaqYh5+frwUYo0}3yIC6I;Kp4X`y7Kieiq{} zR^H&e8EPXM`6;Ul=?z&z3U_@^37G8phF&)@%fvhqfU+A^ZRR5fCfgZ2Q#4a5l&H6Wplr+d7od_}*)VVF$cHrrSPt)Db5D)EE&QG4!x_jVZ|)3RY162RgMr&O z^%WHj0T!M;Y@`JD>C5y>xitvZ(xHTDW(-z3*OTzi_wzrZt?G$7pZcLw^Oq!$*ETLz zT@7i=U&==j*pFS5$D<2x!r*8b3Z! zpfc_oD(3aZKB|kk~KQV?j#`GOw<%ezQKdausYBNtM;7fLi-*ZSeSyD8;BO$sbm5!qV z$*XQGrReUa$ZyzOfW-(9F~!izkA5b40qV518^|a}bgAKJim#EP3k9zOay4#YY@ zMArcX1|%I@Px)c4u{qFxA(Pv$k}2knJ+Fd~&2a9(xqpYLe156U5_zU{c_n{C*qvk| zHr%>j`beef7)YQ)22ZIc1P1C`Vc1T$*CVr84Jr1iYClsz8#J@bX@gUbz4d@nCnyDE zK#C|MisJoRG&v11gwVJll%#5T0r!M$_fP)S6T}hTc z=IeG+C}eG!1>fDJg+~8pW`)W?WUGj`|>1Y)1kB>KYMv;$TKoQD%$jP#1AN z_}Ze@?IvCd$aP`|tXeQAHB)}M^|@Z7cEUQp{cn0i}7^GI2 z>0kysuCi%f`*BbKK%-)<>sAcj?0ug$D5Vv9X|=U_Eg}w#I-wE<(Z>78`KFa39OA~; zQ7XNh=RT!P$U30Hd$!ZRSDmg?i+kLmiNcQ}gi2C$m{yu!R&q zNr&m@A#9!f#LI)k7sW-sX_m$9W>i4~${Jo5{v_|10ok`+c)g-0gwOY$8+G!eT1Pu+ zs_SHg%Ie2{XVS1`edB&;QzEIsmqj^_F|2^rhP&OJc@X*`C!%F_zPQ62pYKl9&jq}2 z?SO*Ac5W_=wv)VqeFp!aQ=M;N;%cJ%C}+q~%qW66R5*}_hqW;>)O#MGHeWrHWK`_t z@Fr?uqILnff|*b=juO6?e6C%NLqsS;fPgu!g$WIqq35!u ze%(;Go3S}IbmCsdX;sYxhId`oLs$1POmrmf@^1>~Wd~S`c3nACxz)YYV8y9ht9eqE zWF`qVTp5`DdhYnB6~3=-_$i}3WqB^%fK}D!(@~_VC$_I{kcUC1iJ$zLf~~MjAB)8- zove|CInVK9cB#>9-NX%;+8;J}UrAz{dM3gFkU`tF?`*JjQZALREqU^xCd=+VYI(@= zf$4Gp(=o=_u4NS5!<9o*m2ln~xi}`#upQ`6=kfN);S*sk`LQ(3o-h`G3#wSCcIIY` zj#OFATp?OZe5z6bjpG;HaXnA(^lb4CCU5@8|ikGd?=-z@+$ppnWLT1>bZ z=W(|IAQvv|Ws7y}37Zq|35L_bk*eAbmX{(!nA(+2UfRb$(mf_WVaYF?gd0Y<7beP? zJ5vjx$PU;OMrR!S>no~@0fmiLLrJ0(NdlP@>a6D0d*dkj@#AgK@w#eJ8kz)qzJ#!9 zY&`+Q-ECuM+fuB{{JZVTp}xx~DL$_|&OsgkcO_|yah!S@z6J5Ep0d}kYTu!Z-#*Y& zet1d3FHmz&dGrK8FRDXTzjhYDM4dS5o5*oK5JlNi{lk??J3_pdrVUIf>Duujb716S z(+hrOC1%xy-&aU*Z>N33Rs9@BSn0kE!&>c#ueTd%IR^|PI*#k~*2uB|UdoZ3%_Y47_o*HU}`>@1?g z965Mzs6sLccGvc_aWxp|hFFjA#=!;XFnc5FfJ8&Ac&)(B{ai+~@n9s_%57y;O0x&C zu&Ed-!4UzHkG!_Y=NcRalFZ6-<$b6XP^6n!_?J{J!zl5@R15X<`1|#b_an=8ZpIM( z^MBOG7+l$fbfSZ_&NpOv-M2UJC(L*Tj3%T%EezCEIMrKIEe^EZ$i4oM=p4+zL{g*$ zVm3jyD~)p9$^)Qn2PUE(njeI$HI`!}W^{ zA+z5jL+84UZZ0&J9~5*0rEYWHq>*c$mFau-ZN@SGAxvzprsVrKjOjl0N5;aIOxkX6 zIA(D{?JaTM3r{M@y@R)VvU|cE2hGkkgmW0kYZ^R-_zsBzaBG#@`a>3(=?7C|kkF~} z_SW|O_iTddS(K#d*k1rW412t7dr^lcr}>c0mC{{wEav$n&+&oVCO?N;|5zKa=P7%- z9ilu?2lbB?oJqjZ#aLG7eblG(4HR6l_0am~)Pd5NMsd&S7pK*gtw;QGow8TG-_?2} zRY-Eo<99g`1Cdu#FR>X)JJI}e7w_*qtXAjaIdleFHfs#f&|V#KzQMjPU;BI+if$laDmD1w$8O}C4t%TOi(k7y z1vp@>sWJJL6vKuY+NcZ?%C)JL|GCXX!OsY1$1bS*<)fo)9PQ=l=NibRi@xN3M^kHk539G_?{JG*^bo$R9$ILO)vZW(pZ77)ZNx< zmAjyxt1*(-gOdSC7b`{6@3Tbk07`F+N*i!fUG`R^ zT|5oy!s32)yi&cSUEov(H#A@ADS&fRREpf8d${EsLpaTi4hmoJ=~E$boJT0J``2gG zk@q8El(}NV*|%%UPtH|?>~AYWtp>l03{c(&BUMzDXe3>D@IZjb`x>t$k@l-RR8r&b zv^Azz(`R2eDZ1jVAi>y(M2`AE9G7jr>YzjHk}}EC8mZBtA`v6zEk0i{lO#{7X2ND> z1vWXdHa4A)RDQ`A1P=H4(y+saivIGqvfO`4nayYl^$8|okN~tjCVN-i+uIkzxTO@; z4Dc}E+ySIzYvvR7$f?-~;kbBJF$Yl55kyd13O-!bB93%antgkoj?if!!HCaJpQ6I$ zbR&tv!>Z8bzzuApGGL*psY`QzK%pRaCaxV`$uKmJE{%+Pph=wSldxwIz3yB)N!3L* zsVxl{)p)tS+52%yzc!p-PFB^qz3G##(uvJjcz^!Sq>vvUyU@H)v7>7|uluY?=4)l~ z6Z5i1DLm_+Qd6DKwI+G^=g%OVbhDXCjWuinGf6_C1DnG5*m0f{gP4VS<~IKZll`X^ z@ag)x5pwX|6nF>Ih8+0=Kmao8J$r7VxV*8{7g0XXv^O;g6x`Y_c*1b+Mo zaOgYk__~kSYSMkeLK5FIQ7$FlMqbdjouFXtnSmX@vwPy&57LG-Or{szsF>qwnm4zqKKzz_8J83l9et|)TruvgvT1sWo+LXHSg^< zxQMy-a!Ts%MLr>&3H^`mv=2yRNW+xr)Y9ok%1fUo(FKqppP4y)|LjTq*n3LT3+FB0 zzdoK9q3ztiAw1OG59mFqP^xCM|BHtCzbzF#Gy^DO(3u?rcvka^d;;Pu+tjH2vnwV? z^%AmKGxBtkRp~bAC4FhRva2V*JHRPSJ9Y#kei;_ql#xpO)vt>A&;;&vr8+eGnlf-* zs!g6zfW0$Trc_pSqq#qCr^5I6k>X|bsETJP!;d#}bLuP{lLyaYsB5OAraFMXRpA1n zI^-E`wBl63xdf6ZS`9H6LB7|YlPgs_n@I)n&_5j z!;ES7p}wV=VvntaYPKp-S9cuiI=b(0GE z&n1?Eh^SeeYUrlYgFg;aFCM(+;CN|M5%~XiiT2yhVya-i9N!UDn_X#&EO-qy6t*RU zT0pOl0Yo_0R!`Fr^qao{bqDZTY2}ek;bwkFt(1Jm9l!tF`@{b~G@9?U*AlxpY&Gnr zBzzR}!TPG~?e7WU%s5%s-vXp2x0HKOPqE%ZL|yOq>J^lMy`(!uhXovcj_)odXBWg>V~O9l&i7&d1GO(rp@C~78%9Mnx#5M|n@8}DU<-`@&(E3cHS zvzwLkW|fuYShfKOalSIiZ+i{I7)XWzbWs*=WK-JO>#=%r68^A>8a)9SVlldyT`#N* zYUeXQ=CEhfPMNAWWJM93J5>0EZ4mTQhR^T(zT_AdczHcCDz$S3Jw?!%W(C6S_a6ej zWauV)tW#*m8zZ@&sWg?>QR*u|&GxD497%jys6-HmrFZubZl3-hiuqv-{li0w5)*Mq z0QzeBKI$dYZfgt7IS=`OpcyNZI*apH>Z~qNN$XU}yK4A5po2_p1@U?*>0?$PQh43t znq-hkb^m_u7Kq1DRYIjiLqa8iE6))b!ajfxVHX9t)4f+Z;_C$H^?C^l(^&2$*MIo= z|51;Bk%f8)^~n8)49}mbJD}(&ejn7}^A3<+q-kNDlhHQ zG}f^X=}B~%p1i>X$|$><=;I*$v{6B3CFv8Ov{~>m?qwbn6y@AJj$u2d6CFbu@GfvH zc4OKU81|2K6LbR0tso7sw|h>#h}jhN`B-&&fHU*g$}I9h``JKa8@{ zeNv4}pIIUE%Wr)()CWEkgjyJ=qq2M>X=v`My^p&}>-%lZFz=$5n_Uczy`weYnUVN% z-@%Mfddk9(fBjQ;$(HQtl45n7TT;Q`m016JPmh&PIR)XsEw}Q+D61#5r#XOVZOB?x z8%B#`vFY5eD^E1|MUxZ=zPJ$)&}1e3cRZX*d4Z3SOEjLPJWGbKgN&{OpIje3KbC7$ zSX9}b7YvW7w!3_r5Pag_niYaK_nzg$c{L5*?rhkDui;-4ny(-v-TOGePg9@0>7h!G zCVKe;0UN*0LkIEbdhm2?j-}N(cN3@lz1^n8^z}TPWiUvjI)2;^nR*-5Eu2^tW9J02 z*d0x&ksad9N?4*C=U|)6wJ2GG6R-#EL%A((zGZ{iGxW^ggxNxXk>L#$dEN~BDJl06 z|8s{)$z@y=fZHg%|7HA^+g>p^*?!$%H%G_4Zi#?LaN@IM>rqUoDrE{&dVNVAlwDn6 z57X*!FseIR$EK^Yb_I`W+|M04L1+Zccvv}oMniA@s-PCz>jDC~ASIx3U;?!7DFn4? zNCkSaZ*{!N7DVfYC=7ax@V-@1iT<#Q$-s@^4+hsShd&W~EZ0~1g`Aqs$2Zm0 zEo(hrU9o^^0h!?8le%H&SyWGfJQFPHZZn= zEXZ$e-_IreYmR9GFB@#%;a>%u$|{oO;1)jk76@HvvSS~zcwG$Rl~QCnhhG2s5MR2! z>XvHPB&IWYY%81RX6--W->|f*qrZgq;g)lAAx`&*s^!|&-JL_7JB|spTnd%Aiv@Mo zzBrgQG@o9jJ1@E;SZ%Gc!+P5=Zha*~5=QOU-1c>&CBfaENL`O5JhFuzSHBsJiFZs& zN~Cm!uN&?=gae7aC#pF&@&HB)L=6APCU??-12Wf^MoZEZBx)C^lf}d1(=AT0`Wfuh zIT>uRDt=1yz3rAFnGK$MAc|Vha)CaDt1%lK4d1O1-2<% zw9MXNNOh+DV|_C{&y9mk43@o|27SZA%afKHXMM6CQ=8O=D-4pU4Ws(Z!JP-7ipd|^ zoqSv37sU%b@cKwMZyAu|>7D}mLXMbad|bYnZcR1J7h20pSr!9pZ>=KCV?KkvX)ZYn z4lpR{i9YsIPJS~p4rCmt#2b_v63Q~id8z@ZsoRKiu3tf2Y(sH}>foK^vV?@ShG)o8 zfB&WxSBnY zH(=GV7)=*pnK+6m(OpZ|PVAiI{f?Sg5HRR!E3YJ=_=L<0Z(FIFh4DyM_lr9bg|D;n z$-3AmFA=*2(xe^L*J)$JuMLm>I>c_WS8if3LaEF&s}hag-sTDaUYE5wLHmne8-v-i z9S$egK>E;HN;}?b@`hCUeSC?aY+xV(mX^5aTpA{pm?1bwZ#tI%8~YSZ2fZP&#$5Zi z?_>*90fFAzi0VSyu5^$2O0hm}uCg#u+#q|?vyMAd<2V#X4yxiq3U)_IJW~2XxM24%U}nX9jIkx;6CUBmT`NyMZ(&*91Wu!?*FXE)5pt&&>cA>1pD@-={88f zYm3`Yby{0C>&NGa$PK)|yoHe3JK58Epp?^Lw1!v8!45B3umbu3PFfa+HfhW7dCuZ1 zrpBl6L@BM}jwFPxxSKbUMqwXxM|(pG%|~ zA-_{;Nvye-uy_@5QL`udr(LW5W)_<;b-PB<>ud*D+aPZ}W=@JKJR^xJu#+o|8)MqL zzRbzh;h%pL-BTcOeWd2wVnHM54I=G1cs-F+R6w=Hqps?+&~X?Xk&y{rMxdjFoKo#f z5(PlWsJeLLkvJgxsb52q#{!{L(Ma(!gg&;OUtS7ee4@ps0A4VtgY#Uy9F^SY0s6jx zW;RrDRazDSOE+!a`NeN<8n&J5J@>cu{<~8IDp-B1>W|r()mKQS-ll!HC<&58hk;Qo z$&)ayH34R_qwY<27x>t>^e<#z zz3=b=AOeEu9UbM?T)pT350m!%`o~Dh$Xfd)EpBBSJ)db->!tus2nYAxSKs()E7tfG ztZu`rKpwV-yI4Y$a8+_N#kCmC9-l6^PJ7)TLq4gzFFeCzx28w+F~c}zL6`@!qxg7R zg~N?Ex>Ihe@#q5`Y}JmdGw9!R?YAV0*>+4TYSQyI=WcmGC8ZM#kLWnbG6r|Ww5P$w zOiUVMI(oh|0o;dByAlZ-U{?=`EVYM(^hngk84Nqj*9P*q-14E$vLN3*ew!RAWm7BZEkFIF+#sL=Qe*$JduQkgrQ!MXg6zwIqq=J7gR^ zk8yBP5z|o~H5hQ_kg)lS*Y$HT!^Q}#E1ARg z1ys_(2&sMy%2&~Pc$?PH);SNNOo<{BByy!npP~kfAdVyfqBqT@B(h6CMPKa!MyUZ zsUy6}TJ41x4uPf`AHAnigb?qk0&xSq=#B6MjoygBE}{M38RbVD>?2(dLTLhYJl;#P zsv4wOmH!z|^hj5H}<-;&( zgtIs~?pdO4hrG{9zR41FE70G!d3%X*0{F63hVq^XSP#CbQLc7rcor`~S?t4pd!`en zazZ!cOR5Hv&=T83x1;;a;ODJe#p6n=_ux4VLh`_iK;rie`BxCo zr*6WQgvEhOb+CB`Fh+VyuMwre%_J>fQu*idei~fKozKbOCXR*beVEPaz=1oe%<^f! zAM;PS^2$y7YyxX>^{e4yb9Spz+WOAwUy{?7L@BO`XV_bgzIXHlF6cfsMn<+>*LUz- zy>!s3+FQ)r+naedocl-zrp6<$IC)9AgR2%7sHRpB_-%QFraPnuOu52YPc0eFX z2E3J__?-a>%og=C0sq&P{*~H) z{EvDsc&oVV_4L-xL+>$Q=p&lxTO#@cA76sEdT(6cT%GBUjea_CKuCOhGuzgP>L%dl zc2r3A|4HKid>%(Y;M+>z#a0YH0d)a;@YYR}t4muukN=qLZ*2P?ll`5-|6{TrB7pwz zACvu!TmI8zf1{4y|I=jua~}U`vcLNJ|Cx(_w@v;t7yn8f+Zg^on~ScW;)*Y)pEfBw zovxF})e-Tr$~{)e6qzZ_v=OXhRO(0@RBgL3QLdr%QzKHURo(3ClM_Tmm8JRFiT?N!!jh z+4@>#eb#xusdoSHC^qx(?(V_%3OcGZfR_1g}jnq)4&8A#I${w2@?+;5CYyh31=gyYxX^Stv)ZJ@Bc z|A~)t4<*@qzOrJe*95Dfwt988u!%3gq{?OLnhkv5Qjzn5do3YRdNzIsysowIPV-aw z<$g=c+5ST4bXG>X3@F=`=JO?fsaU5`1ff+EN#3_X?Vr6b^|4V-Y3)0Kt$3*KPO$dp zp2Y|WQD=)g8pGvA4qGSty1kkNQ^YQTs_fefU*F^VEhCYy0{M&&i}uQoK4QIXb`Ehl z$n9g)(EttKh4;H`qvQwD%I)A5AMC<~Zkfv5@xj%Q($8WnBNR@6!uT50>{4BVs;^f9 z*fEsQCI`<7OoXjYf#7dsQ=KL{Yc{H;r!-#L_UU}P zPm5SNs9;3Sy@g zHbgW(2+&N!RSjNtItjzwM?5Er)aLRL^C6(jE>98;Zu!aVq)AsKs?;9D4u=jVD!E5U zbtp?cX5lw?yUcpxisnQjseca<0nuE{MA@on77u^Qj#Q;ec_ia&EuNGAm4jjItHs3cbt+C#As zO5!?z0_{p;s^VlFj1@~}@)0-U7jqzMcHs+4!6%argF!OcvIWyfzzNwMU0bSF>i1zN zl||>M!3ke9_<7;v7Rc@Sp-TKhJ&Oh?hXKjVFVL0Iz@~4_&(xBQUN5yubu<)Nwe-r9 z0BGWndw1)+bGyPxkgs6}g>=CrG`ueN7^=)6zTKNN?4HvGskm>^nxvwn@~m(dzt68g z3L87<6oxLD<=pP**7#pR`ZrqoA9j8_#aIRk#A;{3W1L}kGMvLZz47;OM5=qlJ1vld1cqC`j zGhNjeNzIO*o9R?<=iKXtx9YQV=#bu0O!Mv34q5xCl>+#h{gBs8-o+dh^0#M}@eXCd zN2Knu@uH2D2FsoJ`^?-y{#D`3d<8Z_;?CqqjdypRi5!fCKu0G~8d!DTSn?{moKk2q zj(#Kg@2vj!gUNit5HF)iZRB=pIAWb}5J(cU?YOLrq>$rUF&C0meR~fNqLd!*J8~$H z&&!4`iy)d`fTD0AVdxifDeQ1zBlXVy{F-HyCc`H)gqxYAGTI_<@eph))IM+^vsj_! zu(XZdK5p65%xcsH`E@Ema6mf_T6SQ+>ifxcY>X+1@5-JL_XTXiYe*R3C+o`5{VHr ziQ#}C+~y`h75xyf7`_0Cm)KE>Z{!YbuUwtZkvalcG%q(djN7r|s1BH=@w(J-Zpbzx z@iuh@4;_DDoxbf`ykk|?uS0~E*>ABX)-!Tl!^|0EDR?^}_PYhE9)2KB zl`u?HGAXsWAz0g|u{oXpc5KfbHw}!WWwMwJ=b)#2oj&xChz`o_g!p}{*M;{-KiKrY zwK!pSb;n;X@+Y?4Ez8|^Ko`454+#k7lZgEj?#J@?#<4I*9+6XhDy6XKYcS!EHAhm5 z;{~B2ZRrtW0{F0-xNnH^kLZh`9LO~mRkng!Suu0CrPpcd3YMtkK2&HsI|3{3 zFTmDsv!bwZ>Ak`%bKF+a$9`ObQOyES zm>ecah55Ai(M8g8-}(~?@|NSd6^k!tu%Ml!88(mKSNh~r!jyGVxV7_96o0b2oAkZU zx57P5wCJ;K4!YX)0c2UM2H~#KrO?EehN3EaZlM*Zy0(zi$KemoVp(&-5t|~;Kj`Y7 z7{d#uLk0E%6-8zTXEC4zhLlktk={~hQ(|uHAuryp(jdFU#%l_^a zAB$`BAma&THQ{zu+=msooGIrLz=*bp*$KjKcAe11jH791l9Hv5d+~J3*!5%^%sB*O zA^bFNRA13Wrn4A3Me&~y&kxHGpeyn&3pk&}WQQ6Klxg?0a1GRsEh_<3Prey*kn=iW z*O$zz^>(Gz3ci>2A!v`|mX?+9v|YuPt@>CDni*&4(BA7(s81Njm!7Yyl8MgUT<7NR zHt#z{rt9c@*F-1x`bIYTXm0?REFrRkKj=8A!$|N#!oK+`EvtRx`L7=`lx0_g&v`q9 zN2-~#CHoZFWnfF&L}g-T=i|}BmE8&9!^yJ^)v7Z0W^n|cEyTLOwCt!U-r(}<#0PaV z^(+%?=^)A@^ipoH@P3V#<@%apqk{GR;IWq2ix)5E(L=lQ<+qn?CFhppFn$N&!4oHx z`9oF7LcCzqILrm-c8dqz`g?z6(J&Y3`kaR=YDRKV0v?L*&<+Dkk%qJBs&bwE0_&R` zo{5@t`?}2_BV!Iiolf@{>oM?~Y!O#gGcLC)ZA=h~@@Xe?W~?dAb)U|!CcgfV0Hzl; zF{;%l*EoXiM^vIe0fYad+h|!yK*pL!bf{8JJ61T_%z`F{wr?x@ooVEi$qJI+lPq1@ zdw$r8g%6lXM~KC@%oGGGy3RCsa#E#L*NDX>>(**D0)W}B#_5+zE>Gd{6eZkcgyICX zZhaVcows)@!Jm6yfRkaeuvlV0s5sr7&)X6s5gDoal2yvOhIbBCCQL>K@}tVX8J+oc z44H-RY6)!YOw-C~(_(mk`Lo+>S4V^E`GTN3f!NzkglFjuR)*%R&EoIKf_qMPcv0qv zJWJp7p`YY*g3g}gg@`pMD{=N~j|(+*rX{1};g=Q>w~-+q65g0C86|<})4J?nH=V&J zlh+sJY>gDvBuGc5u1+g3al%YJF42-2WY&k?IE{T`&vLgyl;Qg`4G@Zvyre5Uu}nss zN|z?4+tBb&auE`;7x}j$kwe)(f)=u^{dhX`j$wI$a)|?##?|atMizX5E^hLhvKljH zK#7@Hy zrvme2H6+)l_SgxXmpmO|aB)@g9jK|-SPwqbJ}|$K0xf!mH>l5-cog&uH;o$mJre>) zcqqE#wDcj(bISue zme+x@PObriy=6|R(mbvXt$#f~HS~BtR|CEY#&;+mWCKv>0;hs=Q7e$j`>uro&_j(n z3~kMYW;I?@5$#g}YFlt;{kV=r1p~D)q*H@DSATiEiYF(jcsfa6hv%@#xww^XeVq`| z$L5?`58n4w62_6o4NZBqx#S||ckv#k%+?sr5~6EfTZMAii#azxJr12>?|X9;bq5oL zV~!|TAG8SY-pS*7VTd$wkWSUXAlZ@bHwrO_Yg;JqISg?ddrJ@Hq2%trdxGbGs4E)A z@9nbgF*Rn0Y2q}CnLk8b5L^!er99%p!{xu)ZkGXhm&4)QxC3>oGH4S5BzJliY0DM ztSKUobFRtfS9=-6$&*1-a(d&qrsig zJwo}|B{lMOt`o4M3#O#+?gkBN4JP-qQ+eI$wWlnb!g%>ryj}8EL)to3Z>{S< zDe$!u`=@Bm-IE?`;Zr@ef|WY$gX_O44?C=67QpjRD^eXgr%X>G=3*3=2fT68nh9Zb zQG-x9exal&ro$7Fe8Y(7A=?#iYpS=pE%LU6*Np_1*uXTmMmbl&Sl1@eA=uxf!Gs7dgc5Z;V$Kt&_s< z^q%xm&4glkZ-4z=u*-+a*$gz=&fiZjz=PxoBBWBFDT0ElZcD&Yg)eO`v7J0aRXqX%(d@cD_2#DzU zNP%v_xh-{6)O)t$466|B7?H{hhKts+0`ggVJtbpTZ5il$dvYwcfz+-j$026?{BZy} zfp((=?a)^ev2$sLbNYx&=bkV57bd;Pi(>)9u$-NbxusXAG?u#^5s9LM97(Zbm(U|B z*fO;t-e#SSbuLmX=mu-5q<|W8%Wvnl9yq&&Dj9WI#KV=* z!%riH_9??@{zTS6T$z^iz5ch~CS+}gId?-OC!;EbV9aikvmVw(CS8JR?F9 zA8@&nH#j*G1e+U`aJ8JcS`uvaCZv=bKW@s0+k!#RtDn;AH3AWIP_%|l zQ_5<#zg0;=JL$Oq$Xr+P^!2SY6IKsD)Gy-S5ItvHP38cg8LytZf z)jmtmp|s_X0|0dJ$&qeN z^=B9cbdt!Q%<0zoMckLI^rg+*!{BQ7LBlc3X%;xsjUKZ*eaHQF&GPdq&9YwhaECgQ zhL~Wh#gl`;itUD?K{7@=*R=3VdYz155*NjVYox7?`Ubm`!@uq_1k39dl$-$9G4*U) zD)b>Q9(G#`hc@&vhwAVyhmW_pg}o>qiFmTURt{`n+bKa%2SpAsN)~m$=GG{Wk)=XH`ICQ3#7-D~H zV5GS9ALZG2>ab8s;gw=a;`s z6H*@*H1qPXC8B8ulRh;3hs#(xb3j!eyjl$voXPw)h zeEoIvsQ9t`x^izDvsaxZ&mSJ2FCly~@{Nbn2Iqgt1yVwBwR^IsGe2r~{->M=JO|?P zg?An8|D;at{B?1g@9Q0J9?7r2C(B-PPyd|V>)#){b(M^LmeSSNrF&m-k=x zlMPv0-G8^dJ9uBAQ{wf9&(-YYPtLTT^nTx;N%N0@a^3x{f%W~CzgFycysr9Exr(*s z{h8cu>h9lT40fF>yS?=7s`_7F-_8ZDB&aQYb)|D2`}Y&^zepJsxZ=^cR>@;d{LEP! zxC{S9f8PJ~Rj%~&xwUB}cfM;EzGqej&av5hFdzN-@dgu$BzI@vTzCD zP?K3(O?d&ZWS{PI|5zTVuU2yW$$#KA@D;%27MEkEX=`aIP1<|{SP=4E+~PTThH-hG z)vd1wm=`%V|8h-xMap4lS7aKyrtVz)tmfUU_*rj^Y|`%hcyV)YcFu|gAAn{1v7X5M z{0r&pem;#~m;Q12{A1DiXFJT7pZ9P+__qAE>7>|}$p-It$pD*b{rq-+41m*gf@`C< z^|-S^? zO6mBON$HiT6#M=E$N%^*f7C?GUn}!A zF&7rq-CX>UZ|Cpg=hH2(J-?^_dEeH%li#@9+2Gi`)wi?W#%z^<(!HIVa%HY;JstI2 znz%_tNGt0K3#cvSSElIabmXMro3`1DfGw)8Md#Z)uD@2yTlbNPTTG`1cyz>(BjReu zKiJ=mUy**nQ{6?#u8foxTh=azUVb5U?gC&{ocyC!`SR8q0gexz%zQ2FweIDA%Wcoz z0}tJ9xF{(Zas z{sI5*7C^(+>_6~j=KE#*nO7Ix{AGVe7$df^cIj?tu^Meu{us8isO#PerjvR{PQE0r zKLd%ml+XoXVcfw)-HwEmCYJ(_{PVsoLE0Xc36a1(J!kaR9wN?XFmFr&o_491o7{pj z*+9q$hcuuIjdyS1B5lGt0CfK3mR>>P;}#T*GevLJaEnZV$UG@8RuDw@$E5xIaKt;0}SY!0J!7<}p) wcroH=qXT8sb+pkj+UP*a9>{?)G#VX0;+y6QDxSIGY03Zup00i_>zopr0Jbf;uK)l5 literal 0 HcmV?d00001 diff --git a/packages/docs/docs/access/projects.md b/packages/docs/docs/access/projects.md index 62731e8c7c..15ef630534 100644 --- a/packages/docs/docs/access/projects.md +++ b/packages/docs/docs/access/projects.md @@ -45,6 +45,13 @@ Logging into the Super Admin project allows for potential dangerous operations a ::: +:::note Checking If You Are In The SuperAdmin Project + +To switch to the SuperAdmin project or check if you are already in it, you can use the [**profile selector**](/docs/app/app-introduction/index.md#profile-selector). + +![project switcher](project-switcher.png) +::: + ## Creating a Project #### Medplum App @@ -57,13 +64,13 @@ Logging into the Super Admin project allows for potential dangerous operations a You can find the full `Project` resource schema [here](/docs/api/fhir/medplum/project) -| Setting | Description | Default | -| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -| `superAdmin` | Whether this project is the super administrator project ([see above](#superadmin)). | `false` | -| `strictMode` | Whether this project uses strict FHIR validation, based on [FHIR profiles](/docs/fhir-datastore/profiles). **Strongly recommend setting this to `true`.** | `true` | -| `checkReferencesOnWrite` | If `true`, the the server will reject any create or write operations to a FHIR resource with invalid references. | `false` | +| Setting | Description | Default | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `superAdmin` | Whether this project is the super administrator project ([see above](#superadmin)). | `false` | +| `strictMode` | Whether this project uses strict FHIR validation, based on [FHIR profiles](/docs/fhir-datastore/profiles). **Strongly recommend setting this to `true`.** | `true` | +| `checkReferencesOnWrite` | If `true`, the the server will reject any create or write operations to a FHIR resource with invalid references. | `false` | | `features` | A list of optional features that are enabled for the project. Allowed values are:
  • `bots`: This [`Project`](/docs/api/fhir/medplum/project) is allowed to create and run [Bots](/docs/bots/bot-basics).
  • `email`: Bots in this project can [send emails](/docs/sdk/core.medplumclient.sendemail).
  • `cron`: This [`Project`](/docs/api/fhir/medplum/project) can run Bots on [CRON timers](https://www.medplum.com/docs/bots/bot-cron-job)
  • `google-auth-required`: [Google authentication](/docs/auth/methods/google-auth) is the only method allowed for this [`Project`](/docs/api/fhir/medplum/project)
| | -| `defaultPatientAccessPolicy` | The default [`AccessPolicy`](/docs/access/access-policies) applied to all [Patient Users](/docs/auth/user-management-guide#project-scoped-users) invited to this [`Project`](/docs/api/fhir/medplum/project). This is required to enable [open patient registration](/docs/auth/open-patient-registration). | | +| `defaultPatientAccessPolicy` | The default [`AccessPolicy`](/docs/access/access-policies) applied to all [Patient Users](/docs/auth/user-management-guide#project-scoped-users) invited to this [`Project`](/docs/api/fhir/medplum/project). This is required to enable [open patient registration](/docs/auth/open-patient-registration). | | ## Project Secrets From c34ad9a1a37533eb42fdef3c99b5565c439dba2f Mon Sep 17 00:00:00 2001 From: Josh Parnham Date: Thu, 8 Feb 2024 07:04:07 +1100 Subject: [PATCH 09/81] Fix return documentation for client read methods (#3891) --- packages/core/src/client.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 2ee8428d17..1ef41854d1 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -1534,7 +1534,7 @@ export class MedplumClient extends EventTarget { * @param resourceType - The FHIR resource type. * @param id - The resource ID. * @param options - Optional fetch options. - * @returns The resource if available; undefined otherwise. + * @returns The resource if available. */ readResource( resourceType: K, @@ -1562,7 +1562,7 @@ export class MedplumClient extends EventTarget { * @category Read * @param reference - The FHIR reference object. * @param options - Optional fetch options. - * @returns The resource if available; undefined otherwise. + * @returns The resource if available. */ readReference(reference: Reference, options?: RequestInit): ReadablePromise { const refString = reference.reference; @@ -1736,7 +1736,7 @@ export class MedplumClient extends EventTarget { * @param id - The resource ID. * @param vid - The version ID. * @param options - Optional fetch options. - * @returns The resource if available; undefined otherwise. + * @returns The resource if available. */ readVersion( resourceType: K, From 7c9ce41695a245365a4ff1dcd2453700e8d37159 Mon Sep 17 00:00:00 2001 From: dillonstreator Date: Wed, 7 Feb 2024 16:15:33 -0600 Subject: [PATCH 10/81] fix-3807 rectify fhirpath `exists` and `empty` (#3808) * fix-3807 rectify fhirpath exists and empty * remove unnecessary/erroneous type assertion * simplify isFhirCriteriaMet * unit test exists and empty * prettier --- packages/core/src/fhirpath/functions.test.ts | 3 ++ packages/core/src/fhirpath/functions.ts | 6 ++-- packages/core/src/fhirpath/parse.test.ts | 30 ++++++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/packages/core/src/fhirpath/functions.test.ts b/packages/core/src/fhirpath/functions.test.ts index 511cfb8c2d..4a067f8ff1 100644 --- a/packages/core/src/fhirpath/functions.test.ts +++ b/packages/core/src/fhirpath/functions.test.ts @@ -29,6 +29,7 @@ const TYPED_Y = toTypedValue('y'); const TYPED_Z = toTypedValue('z'); const TYPED_APPLE = toTypedValue('apple'); const TYPED_XYZ = toTypedValue('xyz'); +const TYPED_EMPTY = toTypedValue({}); const LITERAL_TRUE = new LiteralAtom(TYPED_TRUE); const LITERAL_FALSE = new LiteralAtom(TYPED_FALSE); @@ -40,6 +41,7 @@ describe('FHIRPath functions', () => { test('empty', () => { expect(functions.empty(context, [])).toEqual([TYPED_TRUE]); + expect(functions.empty(context, [TYPED_EMPTY])).toEqual([TYPED_TRUE]); expect(functions.empty(context, [TYPED_1])).toEqual([TYPED_FALSE]); expect(functions.empty(context, [TYPED_1, TYPED_2])).toEqual([TYPED_FALSE]); }); @@ -52,6 +54,7 @@ describe('FHIRPath functions', () => { test('exists', () => { expect(functions.exists(context, [])).toEqual([TYPED_FALSE]); + expect(functions.exists(context, [TYPED_EMPTY])).toEqual([TYPED_FALSE]); expect(functions.exists(context, [TYPED_1])).toEqual([TYPED_TRUE]); expect(functions.exists(context, [TYPED_1, TYPED_2])).toEqual([TYPED_TRUE]); expect(functions.exists(context, [], isEven)).toEqual([TYPED_FALSE]); diff --git a/packages/core/src/fhirpath/functions.ts b/packages/core/src/fhirpath/functions.ts index cc1674df1a..84011d6b76 100644 --- a/packages/core/src/fhirpath/functions.ts +++ b/packages/core/src/fhirpath/functions.ts @@ -1,7 +1,7 @@ import { Reference } from '@medplum/fhirtypes'; import { Atom, AtomContext } from '../fhirlexer/parse'; import { PropertyType, TypedValue, isResource } from '../types'; -import { calculateAge } from '../utils'; +import { calculateAge, isEmpty } from '../utils'; import { DotAtom, SymbolAtom } from './atoms'; import { parseDateString } from './date'; import { booleanToTypedValue, fhirPathIs, isQuantity, removeDuplicates, toJsBoolean, toTypedValue } from './utils'; @@ -34,7 +34,7 @@ export const functions: Record = { * @returns True if the input collection is empty ({ }) and false otherwise. */ empty: (_context: AtomContext, input: TypedValue[]): TypedValue[] => { - return booleanToTypedValue(input.length === 0); + return booleanToTypedValue(input.length === 0 || input.every((e) => isEmpty(e.value))); }, /** @@ -67,7 +67,7 @@ export const functions: Record = { if (criteria) { return booleanToTypedValue(input.filter((e) => toJsBoolean(criteria.eval(context, [e]))).length > 0); } else { - return booleanToTypedValue(input.length > 0); + return booleanToTypedValue(input.length > 0 && input.every((e) => !isEmpty(e.value))); } }, diff --git a/packages/core/src/fhirpath/parse.test.ts b/packages/core/src/fhirpath/parse.test.ts index 62fb696c5d..1f42f4bd03 100644 --- a/packages/core/src/fhirpath/parse.test.ts +++ b/packages/core/src/fhirpath/parse.test.ts @@ -479,6 +479,36 @@ describe('FHIRPath parser', () => { ]); }); + test('%previous.empty() returns true for an empty %previous value', () => { + const patient: Patient = { + resourceType: 'Patient', + }; + const result = evalFhirPathTyped('%previous.empty()', [toTypedValue(patient)], { '%previous': toTypedValue({}) }); + + expect(result).toEqual([ + { + type: PropertyType.boolean, + value: true, + }, + ]); + }); + + test('%previous.exists().not() returns true for an empty %previous value', () => { + const patient: Patient = { + resourceType: 'Patient', + }; + const result = evalFhirPathTyped('%previous.exists().not()', [toTypedValue(patient)], { + '%previous': toTypedValue({}), + }); + + expect(result).toEqual([ + { + type: PropertyType.boolean, + value: true, + }, + ]); + }); + test('Context type comparison false', () => { const patient: Patient = { resourceType: 'Patient', From 417a5b086192d55e906fae1eee131c61b4350957 Mon Sep 17 00:00:00 2001 From: dillonstreator Date: Wed, 7 Feb 2024 16:16:09 -0600 Subject: [PATCH 11/81] Maintain full `traceparent` header value for `traceId` (#3877) * support `Sentry-Trace` header * update doc * traceparent parser allowing some divergence from the spec and maintain full traceparent in logs * allow some grace on flags format * feedback * refactor --- .../docs/docs/integration/log-streaming.md | 2 +- packages/server/src/context.ts | 42 +++++++++---------- packages/server/src/traceparent.test.ts | 38 +++++++++++++++++ packages/server/src/traceparent.ts | 27 ++++++++++++ 4 files changed, 87 insertions(+), 22 deletions(-) create mode 100644 packages/server/src/traceparent.test.ts create mode 100644 packages/server/src/traceparent.ts diff --git a/packages/docs/docs/integration/log-streaming.md b/packages/docs/docs/integration/log-streaming.md index 34c0b3f96b..7908571269 100644 --- a/packages/docs/docs/integration/log-streaming.md +++ b/packages/docs/docs/integration/log-streaming.md @@ -35,7 +35,7 @@ Medplum uses includes both request IDs and trace IDs to aid in log correlation, The request ID is automatically generated by the Medplum server for each unique HTTP request. -Clients can pass in their own trace id in their request headers. Medplum supports both the headers `X-TRACE-ID` or `traceParent`. +Clients can pass in their own trace id in their request headers. Medplum supports both the headers `X-TRACE-ID` or [`traceparent`](https://www.w3.org/TR/trace-context/#traceparent-header).
diff --git a/packages/server/src/context.ts b/packages/server/src/context.ts index 1d0c161ac4..cb60efce8b 100644 --- a/packages/server/src/context.ts +++ b/packages/server/src/context.ts @@ -4,6 +4,7 @@ import { AsyncLocalStorage } from 'async_hooks'; import { randomUUID } from 'crypto'; import { NextFunction, Request, Response } from 'express'; import { Repository, systemRepo } from './fhir/repo'; +import { parseTraceparent } from './traceparent'; export class RequestContext { readonly requestId: string; @@ -101,29 +102,28 @@ export function closeRequestContext(): void { } } -function requestIds(req: Request): { requestId: string; traceId: string } { - const requestId = randomUUID(); - const traceIdHeader = req.header('x-trace-id'); - const traceParentHeader = req.header('traceparent'); - let traceId: string | undefined; - if (traceIdHeader && isUUID(traceIdHeader)) { - traceId = traceIdHeader; - } else if (traceParentHeader?.startsWith('00-')) { - const id = traceParentHeader.split('-')[1]; - const uuid = [ - id.substring(0, 8), - id.substring(8, 12), - id.substring(12, 16), - id.substring(16, 20), - id.substring(20, 32), - ].join('-'); - if (isUUID(uuid)) { - traceId = uuid; +const traceIdHeaderMap: { + [key: string]: (traceId: string) => boolean; +} = { + 'x-trace-id': (value) => isUUID(value), + traceparent: (value) => !!parseTraceparent(value), +} as const; +const traceIdHeaders = Object.entries(traceIdHeaderMap); + +const getTraceId = (req: Request): string | undefined => { + for (const [headerKey, isTraceId] of traceIdHeaders) { + const value = req.header(headerKey); + if (value && isTraceId(value)) { + return value; } } - if (!traceId) { - traceId = randomUUID(); - } + return undefined; +}; + +function requestIds(req: Request): { requestId: string; traceId: string } { + const requestId = randomUUID(); + const traceId = getTraceId(req) ?? randomUUID(); + return { requestId, traceId }; } diff --git a/packages/server/src/traceparent.test.ts b/packages/server/src/traceparent.test.ts new file mode 100644 index 0000000000..7cc00023eb --- /dev/null +++ b/packages/server/src/traceparent.test.ts @@ -0,0 +1,38 @@ +import { Traceparent, parseTraceparent } from './traceparent'; + +describe('parseTraceparent', () => { + const tp: Traceparent = { + version: '00', + traceId: '12345678901234567890123456789012', + parentId: '3456789012345678', + flags: '01', + }; + + it('parses traceparent spec', () => { + expect(parseTraceparent(`${tp.version}-${tp.traceId}-${tp.parentId}-${tp.flags}`)).toEqual(tp); + }); + + it('allow missing version', () => { + expect(parseTraceparent(`${tp.traceId}-${tp.parentId}-${tp.flags}`)).toEqual({ ...tp, version: undefined }); + }); + + it('allow missing flags', () => { + expect(parseTraceparent(`${tp.version}-${tp.traceId}-${tp.parentId}`)).toEqual({ ...tp, flags: undefined }); + }); + + it('allow missing version and flags', () => { + expect(parseTraceparent(`${tp.traceId}-${tp.parentId}`)).toEqual({ ...tp, version: undefined, flags: undefined }); + }); + + it('allow 1 character for flags', () => { + expect(parseTraceparent(`${tp.traceId}-${tp.parentId}-1`)).toEqual({ ...tp, version: undefined, flags: '1' }); + }); + + it('no more than 2 characters for flags', () => { + expect(parseTraceparent(`${tp.traceId}-${tp.parentId}-001`)).toEqual(null); + }); + + it('reports invalid', () => { + expect(parseTraceparent(`invalid-traceparent`)).toEqual(null); + }); +}); diff --git a/packages/server/src/traceparent.ts b/packages/server/src/traceparent.ts new file mode 100644 index 0000000000..0f1d043746 --- /dev/null +++ b/packages/server/src/traceparent.ts @@ -0,0 +1,27 @@ +// https://www.w3.org/TR/trace-context/#traceparent-header + +type hex = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f'; +type twohex = `${hex}${hex}`; + +export type Traceparent = { + version?: twohex; + traceId: string; + parentId: string; + flags?: hex | twohex; +}; + +const traceparentRegex = /^([0-9a-f]{2})?-?([0-9a-f]{32})-([0-9a-f]{16})-?([0-9a-f]{1,2})?$/i; + +export function parseTraceparent(traceparent: string): Traceparent | null { + const match = traceparent.match(traceparentRegex); + if (!match) { + return null; + } + + return { + version: (match[1] ?? undefined) as Traceparent['version'], + traceId: match[2], + parentId: match[3], + flags: (match[4] ?? undefined) as Traceparent['flags'], + }; +} From 60755ff6d03abb9360976a8327f540a0f5acdd39 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Thu, 8 Feb 2024 08:53:15 -0800 Subject: [PATCH 12/81] Remove global systemRepo (#3884) --- packages/server/src/admin/bot.ts | 5 +- packages/server/src/admin/client.ts | 5 +- packages/server/src/admin/invite.ts | 19 ++++-- packages/server/src/admin/super.test.ts | 66 ++----------------- packages/server/src/admin/super.ts | 5 +- packages/server/src/auth/changepassword.ts | 10 +-- packages/server/src/auth/exchange.test.ts | 6 +- packages/server/src/auth/external.test.ts | 10 ++- packages/server/src/auth/external.ts | 3 +- packages/server/src/auth/google.test.ts | 9 ++- packages/server/src/auth/google.ts | 6 +- packages/server/src/auth/login.test.ts | 3 +- packages/server/src/auth/login.ts | 2 +- packages/server/src/auth/me.ts | 18 +++-- packages/server/src/auth/method.test.ts | 7 +- packages/server/src/auth/method.ts | 7 +- packages/server/src/auth/mfa.ts | 7 +- packages/server/src/auth/newpatient.test.ts | 4 +- packages/server/src/auth/newpatient.ts | 6 +- packages/server/src/auth/newproject.ts | 5 +- packages/server/src/auth/newuser.test.ts | 6 +- packages/server/src/auth/newuser.ts | 6 +- packages/server/src/auth/profile.test.ts | 6 +- packages/server/src/auth/profile.ts | 5 +- packages/server/src/auth/register.ts | 3 +- .../server/src/auth/resetpassword.test.ts | 6 +- packages/server/src/auth/resetpassword.ts | 4 +- packages/server/src/auth/revoke.ts | 5 +- packages/server/src/auth/scope.test.ts | 13 ++-- packages/server/src/auth/scope.ts | 3 +- packages/server/src/auth/setpassword.ts | 4 +- packages/server/src/auth/status.test.ts | 9 +-- packages/server/src/auth/status.ts | 6 +- packages/server/src/auth/utils.ts | 8 ++- packages/server/src/context.ts | 4 +- packages/server/src/email/email.test.ts | 5 +- packages/server/src/fhir/accesspolicy.test.ts | 4 +- packages/server/src/fhir/accesspolicy.ts | 3 +- .../server/src/fhir/lookups/address.test.ts | 4 +- .../server/src/fhir/lookups/coding.test.ts | 4 +- .../server/src/fhir/lookups/humanname.test.ts | 4 +- .../server/src/fhir/lookups/token.test.ts | 4 +- packages/server/src/fhir/operations/deploy.ts | 5 +- .../server/src/fhir/operations/execute.ts | 9 ++- .../server/src/fhir/operations/expand.test.ts | 9 +-- packages/server/src/fhir/operations/expand.ts | 3 +- .../server/src/fhir/operations/export.test.ts | 7 +- .../src/fhir/operations/expunge.test.ts | 9 +-- .../src/fhir/operations/groupexport.test.ts | 9 +-- .../src/fhir/operations/projectclone.test.ts | 7 +- .../server/src/fhir/operations/projectinit.ts | 17 +++-- .../operations/utils/asyncjobexecutor.test.ts | 9 +-- .../fhir/operations/utils/asyncjobexecutor.ts | 3 +- .../src/fhir/operations/utils/bulkexporter.ts | 5 +- packages/server/src/fhir/patient.test.ts | 3 +- packages/server/src/fhir/references.ts | 3 +- packages/server/src/fhir/repo.test.ts | 4 +- packages/server/src/fhir/repo.ts | 27 +++++--- packages/server/src/fhir/rewrite.test.ts | 3 +- packages/server/src/fhir/search.test.ts | 4 +- packages/server/src/oauth/authorize.test.ts | 15 +++-- packages/server/src/oauth/authorize.ts | 5 +- packages/server/src/oauth/keys.ts | 3 +- packages/server/src/oauth/middleware.test.ts | 9 +-- packages/server/src/oauth/middleware.ts | 3 +- packages/server/src/oauth/token.test.ts | 25 +++---- packages/server/src/oauth/token.ts | 8 ++- packages/server/src/oauth/userinfo.ts | 3 +- packages/server/src/oauth/utils.ts | 15 ++++- packages/server/src/scim/routes.test.ts | 11 ++-- packages/server/src/scim/utils.ts | 6 +- packages/server/src/seed.ts | 5 +- packages/server/src/seeds/searchparameters.ts | 10 +-- .../server/src/seeds/structuredefinitions.ts | 15 +++-- packages/server/src/seeds/valuesets.ts | 7 +- packages/server/src/storage.test.ts | 24 ++++--- packages/server/src/storage.ts | 3 +- packages/server/src/test.setup.ts | 6 +- packages/server/src/workers/cron.test.ts | 21 +++--- packages/server/src/workers/cron.ts | 5 +- packages/server/src/workers/download.ts | 3 +- .../server/src/workers/subscription.test.ts | 8 +-- packages/server/src/workers/subscription.ts | 17 +++-- packages/server/src/workers/utils.ts | 5 +- 84 files changed, 414 insertions(+), 273 deletions(-) diff --git a/packages/server/src/admin/bot.ts b/packages/server/src/admin/bot.ts index 03c631f8e4..8350a7dfc9 100644 --- a/packages/server/src/admin/bot.ts +++ b/packages/server/src/admin/bot.ts @@ -3,9 +3,9 @@ import { AccessPolicy, Binary, Bot, Project, ProjectMembership, Reference } from import { Request, Response } from 'express'; import { body } from 'express-validator'; import { Readable } from 'stream'; -import { Repository, systemRepo } from '../fhir/repo'; -import { getBinaryStorage } from '../fhir/storage'; import { getAuthenticatedContext } from '../context'; +import { Repository, getSystemRepo } from '../fhir/repo'; +import { getBinaryStorage } from '../fhir/storage'; import { makeValidationMiddleware } from '../util/validator'; export const createBotValidator = makeValidationMiddleware([ @@ -62,6 +62,7 @@ export async function createBot(repo: Repository, request: CreateBotRequest): Pr }, }); + const systemRepo = getSystemRepo(); await systemRepo.createResource({ meta: { project: request.project.id, diff --git a/packages/server/src/admin/client.ts b/packages/server/src/admin/client.ts index 723898af77..e8cc026764 100644 --- a/packages/server/src/admin/client.ts +++ b/packages/server/src/admin/client.ts @@ -9,9 +9,9 @@ import { } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { body } from 'express-validator'; -import { Repository, systemRepo } from '../fhir/repo'; -import { generateSecret } from '../oauth/keys'; import { getAuthenticatedContext } from '../context'; +import { Repository, getSystemRepo } from '../fhir/repo'; +import { generateSecret } from '../oauth/keys'; import { makeValidationMiddleware } from '../util/validator'; export const createClientValidator = makeValidationMiddleware([ @@ -57,6 +57,7 @@ export async function createClient(repo: Repository, request: CreateClientReques identityProvider: request.identityProvider, }); + const systemRepo = getSystemRepo(); await systemRepo.createResource({ meta: { project: request.project.id, diff --git a/packages/server/src/admin/invite.ts b/packages/server/src/admin/invite.ts index febeb75f76..7630123dfb 100644 --- a/packages/server/src/admin/invite.ts +++ b/packages/server/src/admin/invite.ts @@ -18,7 +18,7 @@ import { bcryptHashPassword, createProfile, createProjectMembership } from '../a import { getConfig } from '../config'; import { getAuthenticatedContext } from '../context'; import { sendEmail } from '../email/email'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo, Repository } from '../fhir/repo'; import { sendResponse } from '../fhir/response'; import { generateSecret } from '../oauth/keys'; import { getUserByEmailInProject, getUserByEmailWithoutProject } from '../oauth/utils'; @@ -43,6 +43,7 @@ export async function inviteHandler(req: Request, res: Response): Promise const inviteRequest = { ...req.body } as ServerInviteRequest; const { projectId } = req.params; if (ctx.project.superAdmin) { + const systemRepo = getSystemRepo(); inviteRequest.project = await systemRepo.readResource('Project', projectId as string); } else { inviteRequest.project = ctx.project; @@ -63,6 +64,7 @@ export interface ServerInviteResponse { } export async function inviteUser(request: ServerInviteRequest): Promise { + const systemRepo = getSystemRepo(); const ctx = getAuthenticatedContext(); if (request.email) { request.email = request.email.toLowerCase(); @@ -123,6 +125,7 @@ export async function inviteUser(request: ServerInviteRequest): Promise { project = createReference(request.project); } + const systemRepo = getSystemRepo(); return systemRepo.createResource({ resourceType: 'User', firstName, @@ -164,6 +168,7 @@ async function createUser(request: ServerInviteRequest): Promise { async function searchForExistingProfile(request: ServerInviteRequest): Promise { const { project, resourceType, membership, email } = request; + const systemRepo = getSystemRepo(); if (membership?.profile) { const result = await systemRepo.readReference(membership.profile); @@ -198,13 +203,14 @@ async function searchForExistingProfile(request: ServerInviteRequest): Promise

, upsert: boolean ): Promise { - const existingMembership = await searchForExistingMembership(user, project); + const existingMembership = await searchForExistingMembership(systemRepo, user, project); if (existingMembership) { if (!upsert) { throw new OperationOutcomeError(badRequest('User is already a member of this project')); @@ -231,7 +237,11 @@ async function createOrUpdateProjectMembership( return createProjectMembership(user, project, profile, membershipTemplate); } -async function searchForExistingMembership(user: User, project: Project): Promise { +async function searchForExistingMembership( + systemRepo: Repository, + user: User, + project: Project +): Promise { return systemRepo.searchOne({ resourceType: 'ProjectMembership', filters: [ @@ -250,6 +260,7 @@ async function searchForExistingMembership(user: User, project: Project): Promis } async function sendInviteEmail( + systemRepo: Repository, request: ServerInviteRequest, user: User, existing: boolean, diff --git a/packages/server/src/admin/super.test.ts b/packages/server/src/admin/super.test.ts index 06604d5480..83c00e285d 100644 --- a/packages/server/src/admin/super.test.ts +++ b/packages/server/src/admin/super.test.ts @@ -6,13 +6,13 @@ import request from 'supertest'; import { initApp, shutdownApp } from '../app'; import { registerNew } from '../auth/register'; import { loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { AuthenticatedRequestContext, requestContextStore } from '../context'; +import { getSystemRepo } from '../fhir/repo'; import { generateAccessToken } from '../oauth/keys'; import { rebuildR4SearchParameters } from '../seeds/searchparameters'; import { rebuildR4StructureDefinitions } from '../seeds/structuredefinitions'; -import { createTestProject, waitForAsyncJob, withTestContext } from '../test.setup'; -import { AuthenticatedRequestContext, requestContextStore } from '../context'; import { rebuildR4ValueSets } from '../seeds/valuesets'; +import { createTestProject, waitForAsyncJob, withTestContext } from '../test.setup'; jest.mock('../seeds/valuesets'); jest.mock('../seeds/structuredefinitions'); @@ -32,6 +32,8 @@ describe('Super Admin routes', () => { requestContextStore.enterWith(AuthenticatedRequestContext.system()); ({ project, client } = await createTestProject()); + const systemRepo = getSystemRepo(); + // Mark the project as a "Super Admin" project await systemRepo.updateResource({ ...project, superAdmin: true }); @@ -142,24 +144,6 @@ describe('Super Admin routes', () => { await waitForAsyncJob(res.headers['content-location'], app, adminAccessToken); }); - test('Rebuild ValueSetElements as super admin with respond-async error', async () => { - const err = new Error('createvalueSet test error'); - (rebuildR4ValueSets as unknown as jest.Mock).mockImplementationOnce((): Promise => { - return Promise.reject(err); - }); - - const res = await request(app) - .post('/admin/super/valuesets') - .set('Authorization', 'Bearer ' + adminAccessToken) - .set('Prefer', 'respond-async') - .type('json') - .send({}); - - expect(res.status).toEqual(202); - const job = await waitForAsyncJob(res.headers['content-location'], app, adminAccessToken); - expect(job.status).toEqual('error'); - }); - test('Rebuild ValueSetElements access denied', async () => { const res = await request(app) .post('/admin/super/valuesets') @@ -334,26 +318,6 @@ describe('Super Admin routes', () => { await waitForAsyncJob(res.headers['content-location'], app, adminAccessToken); }); - test('Reindex with respond-async error', async () => { - const err = new Error('reindex test error'); - jest.spyOn(systemRepo, 'reindexResourceType').mockImplementationOnce((): Promise => { - return Promise.reject(err); - }); - - const res = await request(app) - .post('/admin/super/reindex') - .set('Authorization', 'Bearer ' + adminAccessToken) - .set('Prefer', 'respond-async') - .type('json') - .send({ - resourceType: 'PaymentNotice', - }); - - expect(res.status).toEqual(202); - const job = await waitForAsyncJob(res.headers['content-location'], app, adminAccessToken); - expect(job.status).toEqual('error'); - }); - test('Rebuild compartments access denied', async () => { const res = await request(app) .post('/admin/super/compartments') @@ -405,26 +369,6 @@ describe('Super Admin routes', () => { expect(res.headers['content-location']).toBeDefined(); }); - test('Rebuild compartments with respond-async error', async () => { - const err = new Error('rebuildCompartmentsForResourceType test error'); - jest.spyOn(systemRepo, 'rebuildCompartmentsForResourceType').mockImplementationOnce((): Promise => { - return Promise.reject(err); - }); - - const res = await request(app) - .post('/admin/super/compartments') - .set('Authorization', 'Bearer ' + adminAccessToken) - .set('Prefer', 'respond-async') - .type('json') - .send({ - resourceType: 'PaymentNotice', - }); - - expect(res.status).toEqual(202); - const job = await waitForAsyncJob(res.headers['content-location'], app, adminAccessToken); - expect(job.status).toEqual('error'); - }); - test('Set password access denied', async () => { const res = await request(app) .post('/admin/super/setpassword') diff --git a/packages/server/src/admin/super.ts b/packages/server/src/admin/super.ts index 5e689b3e70..4d53e35ee9 100644 --- a/packages/server/src/admin/super.ts +++ b/packages/server/src/admin/super.ts @@ -16,7 +16,7 @@ import { AuthenticatedRequestContext, getAuthenticatedContext } from '../context import { getDatabasePool } from '../database'; import { AsyncJobExecutor } from '../fhir/operations/utils/asyncjobexecutor'; import { invalidRequest, sendOutcome } from '../fhir/outcomes'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import * as dataMigrations from '../migrations/data'; import { authenticateRequest } from '../oauth/middleware'; import { getUserByEmail } from '../oauth/utils'; @@ -80,6 +80,7 @@ superAdminRouter.post( validateResourceType(resourceType); await sendAsyncResponse(req, res, async () => { + const systemRepo = getSystemRepo(); await systemRepo.reindexResourceType(resourceType); }); }) @@ -98,6 +99,7 @@ superAdminRouter.post( validateResourceType(resourceType); await sendAsyncResponse(req, res, async () => { + const systemRepo = getSystemRepo(); await systemRepo.rebuildCompartmentsForResourceType(resourceType); }); }) @@ -203,6 +205,7 @@ superAdminRouter.post( requireAsync(req); await sendAsyncResponse(req, res, async () => { + const systemRepo = getSystemRepo(); const client = getDatabasePool(); const result = await client.query('SELECT "dataVersion" FROM "DatabaseMigration"'); const version = result.rows[0]?.dataVersion as number; diff --git a/packages/server/src/auth/changepassword.ts b/packages/server/src/auth/changepassword.ts index 54ffff05ef..1172b47ab1 100644 --- a/packages/server/src/auth/changepassword.ts +++ b/packages/server/src/auth/changepassword.ts @@ -4,11 +4,11 @@ import bcrypt from 'bcryptjs'; import { Request, Response } from 'express'; import { body } from 'express-validator'; import { pwnedPassword } from 'hibp'; -import { sendOutcome } from '../fhir/outcomes'; -import { systemRepo } from '../fhir/repo'; -import { bcryptHashPassword } from './utils'; import { getAuthenticatedContext } from '../context'; +import { sendOutcome } from '../fhir/outcomes'; +import { getSystemRepo } from '../fhir/repo'; import { makeValidationMiddleware } from '../util/validator'; +import { bcryptHashPassword } from './utils'; export const changePasswordValidator = makeValidationMiddleware([ body('oldPassword').notEmpty().withMessage('Missing oldPassword'), @@ -18,6 +18,7 @@ export const changePasswordValidator = makeValidationMiddleware([ export async function changePasswordHandler(req: Request, res: Response): Promise { const ctx = getAuthenticatedContext(); + const systemRepo = getSystemRepo(); const user = await systemRepo.readReference(ctx.membership.user as Reference); await changePassword({ @@ -35,7 +36,7 @@ export interface ChangePasswordRequest { newPassword: string; } -export async function changePassword(request: ChangePasswordRequest): Promise { +async function changePassword(request: ChangePasswordRequest): Promise { const oldPasswordHash = request.user.passwordHash as string; const bcryptResult = await bcrypt.compare(request.oldPassword, oldPasswordHash); if (!bcryptResult) { @@ -48,6 +49,7 @@ export async function changePassword(request: ChangePasswordRequest): Promise({ ...request.user, passwordHash: newPasswordHash, diff --git a/packages/server/src/auth/exchange.test.ts b/packages/server/src/auth/exchange.test.ts index 346e5a6b2c..eaddd8f57c 100644 --- a/packages/server/src/auth/exchange.test.ts +++ b/packages/server/src/auth/exchange.test.ts @@ -8,9 +8,9 @@ import { createClient } from '../admin/client'; import { inviteUser } from '../admin/invite'; import { initApp, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; -import { registerNew } from './register'; +import { getSystemRepo } from '../fhir/repo'; import { withTestContext } from '../test.setup'; +import { registerNew } from './register'; jest.mock('node-fetch'); @@ -51,6 +51,8 @@ describe('Token Exchange', () => { clientSecret: '456', }; + const systemRepo = getSystemRepo(); + // Create a new client application with external auth externalAuthClient = await createClient(systemRepo, { project, diff --git a/packages/server/src/auth/external.test.ts b/packages/server/src/auth/external.test.ts index 9b7502977a..3b7d193604 100644 --- a/packages/server/src/auth/external.test.ts +++ b/packages/server/src/auth/external.test.ts @@ -8,7 +8,7 @@ import { createClient } from '../admin/client'; import { inviteUser } from '../admin/invite'; import { initApp, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { withTestContext } from '../test.setup'; import { registerNew } from './register'; @@ -51,6 +51,8 @@ describe('External', () => { project = registerResult.project; defaultClient = registerResult.client; + const systemRepo = getSystemRepo(); + // Create a domain configuration with external identity provider await systemRepo.createResource({ resourceType: 'DomainConfiguration', @@ -321,6 +323,8 @@ describe('External', () => { test('Subject auth success', async () => { const subjectAuthClient = await withTestContext(async () => { + const systemRepo = getSystemRepo(); + // Create a new client application with external subject auth const client = await createClient(systemRepo, { project, @@ -377,6 +381,8 @@ describe('External', () => { test('Client secret post', async () => { const clientSecretPostClient = await withTestContext(async () => { + const systemRepo = getSystemRepo(); + // Create a new client application with external subject auth const client = await createClient(systemRepo, { project, @@ -437,6 +443,8 @@ describe('External', () => { const domain = `${randomUUID()}.example.com`; const redirectUri = `https://${domain}/auth/callback`; const client = await withTestContext(async () => { + const systemRepo = getSystemRepo(); + // Create a new project const { project, client } = await registerNew({ firstName: 'External', diff --git a/packages/server/src/auth/external.ts b/packages/server/src/auth/external.ts index 61f26c4d94..fc68b61887 100644 --- a/packages/server/src/auth/external.ts +++ b/packages/server/src/auth/external.ts @@ -13,7 +13,7 @@ import { Request, Response } from 'express'; import fetch from 'node-fetch'; import { getConfig } from '../config'; import { sendOutcome } from '../fhir/outcomes'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { globalLogger } from '../logger'; import { CodeChallengeMethod, tryLogin } from '../oauth/utils'; import { getDomainConfiguration } from './method'; @@ -130,6 +130,7 @@ async function getIdentityProvider( state: ExternalAuthState ): Promise<{ idp?: IdentityProvider; client?: ClientApplication }> { if (state.clientId) { + const systemRepo = getSystemRepo(); const client = await systemRepo.readResource('ClientApplication', state.clientId); if (client.identityProvider) { return { idp: client.identityProvider, client }; diff --git a/packages/server/src/auth/google.test.ts b/packages/server/src/auth/google.test.ts index eee041f1e1..1950d10be6 100644 --- a/packages/server/src/auth/google.test.ts +++ b/packages/server/src/auth/google.test.ts @@ -3,10 +3,10 @@ import express from 'express'; import request from 'supertest'; import { initApp, shutdownApp } from '../app'; import { getConfig, loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { getUserByEmail } from '../oauth/utils'; -import { registerNew } from './register'; import { withTestContext } from '../test.setup'; +import { registerNew } from './register'; jest.mock('jose', () => { const original = jest.requireActual('jose'); @@ -144,6 +144,7 @@ describe('Google Auth', () => { }); // As a super admin, update the project to require Google auth + const systemRepo = getSystemRepo(); await systemRepo.updateResource({ ...project, features: ['google-auth-required'], @@ -179,6 +180,7 @@ describe('Google Auth', () => { }); // As a super admin, set the google client ID + const systemRepo = getSystemRepo(); await systemRepo.updateResource({ ...project, site: [ @@ -221,6 +223,7 @@ describe('Google Auth', () => { }); // As a super admin, set the google client ID + const systemRepo = getSystemRepo(); await systemRepo.updateResource({ ...project, site: [ @@ -266,6 +269,7 @@ describe('Google Auth', () => { }); // As a super admin, set the google client ID + const systemRepo = getSystemRepo(); await systemRepo.updateResource({ ...project, site: [ @@ -308,6 +312,7 @@ describe('Google Auth', () => { }); // As a super admin, set the google client ID + const systemRepo = getSystemRepo(); await systemRepo.updateResource({ ...project, site: [ diff --git a/packages/server/src/auth/google.ts b/packages/server/src/auth/google.ts index e6069febfd..03cd507a26 100644 --- a/packages/server/src/auth/google.ts +++ b/packages/server/src/auth/google.ts @@ -7,11 +7,11 @@ import { createRemoteJWKSet, jwtVerify, JWTVerifyOptions } from 'jose'; import { URL } from 'url'; import { getConfig } from '../config'; import { sendOutcome } from '../fhir/outcomes'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { getUserByEmail, GoogleCredentialClaims, tryLogin } from '../oauth/utils'; +import { makeValidationMiddleware } from '../util/validator'; import { isExternalAuth } from './method'; import { getProjectIdByClientId, sendLoginResult } from './utils'; -import { makeValidationMiddleware } from '../util/validator'; /* * Integrating Google Sign-In into your web app @@ -101,6 +101,7 @@ export async function googleHandler(req: Request, res: Response): Promise sendOutcome(res, badRequest('User not found')); return; } + const systemRepo = getSystemRepo(); await systemRepo.createResource({ resourceType: 'User', firstName: claims.given_name, @@ -146,5 +147,6 @@ function getProjectsByGoogleClientId(googleClientId: string, projectId: string | }); } + const systemRepo = getSystemRepo(); return systemRepo.searchResources({ resourceType: 'Project', filters }); } diff --git a/packages/server/src/auth/login.test.ts b/packages/server/src/auth/login.test.ts index ce926cd983..34ecfa822a 100644 --- a/packages/server/src/auth/login.test.ts +++ b/packages/server/src/auth/login.test.ts @@ -10,7 +10,7 @@ import request from 'supertest'; import { inviteUser } from '../admin/invite'; import { initApp, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { createTestProject, setupPwnedPasswordMock, setupRecaptchaMock, withTestContext } from '../test.setup'; import { registerNew } from './register'; import { setPassword } from './setpassword'; @@ -343,6 +343,7 @@ describe('Login', () => { }); // As a super admin, update the project to require Google auth + const systemRepo = getSystemRepo(); await systemRepo.updateResource({ ...project, features: ['google-auth-required'], diff --git a/packages/server/src/auth/login.ts b/packages/server/src/auth/login.ts index 1fbfbc704a..00f17a4c13 100644 --- a/packages/server/src/auth/login.ts +++ b/packages/server/src/auth/login.ts @@ -3,8 +3,8 @@ import { randomUUID } from 'crypto'; import { Request, Response } from 'express'; import { body } from 'express-validator'; import { tryLogin } from '../oauth/utils'; -import { getProjectIdByClientId, sendLoginResult } from './utils'; import { makeValidationMiddleware } from '../util/validator'; +import { getProjectIdByClientId, sendLoginResult } from './utils'; export const loginValidator = makeValidationMiddleware([ body('email').isEmail().withMessage('Valid email address is required'), diff --git a/packages/server/src/auth/me.ts b/packages/server/src/auth/me.ts index 7b94810ff5..adab756c80 100644 --- a/packages/server/src/auth/me.ts +++ b/packages/server/src/auth/me.ts @@ -2,10 +2,10 @@ import { getReferenceString, Operator, ProfileResource } from '@medplum/core'; import { Login, Project, ProjectMembership, Reference, User, UserConfiguration } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { UAParser } from 'ua-parser-js'; +import { getAuthenticatedContext } from '../context'; import { getAccessPolicyForLogin } from '../fhir/accesspolicy'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo, Repository } from '../fhir/repo'; import { rewriteAttachments, RewriteMode } from '../fhir/rewrite'; -import { getAuthenticatedContext } from '../context'; interface UserSession { id: string; @@ -22,15 +22,17 @@ interface UserSecurity { } export async function meHandler(req: Request, res: Response): Promise { + const systemRepo = getSystemRepo(); + const { login, project, membership, profile: profileRef } = getAuthenticatedContext(); const profile = await systemRepo.readReference(profileRef); - const config = await getUserConfiguration(project, membership); + const config = await getUserConfiguration(systemRepo, project, membership); const accessPolicy = await getAccessPolicyForLogin(login, membership); let security: UserSecurity | undefined = undefined; if (membership.user?.reference?.startsWith('User/')) { const user = await systemRepo.readReference(membership.user as Reference); - const sessions = await getSessions(user); + const sessions = await getSessions(systemRepo, user); security = { mfaEnrolled: !!user.mfaEnrolled, sessions, @@ -62,7 +64,11 @@ export async function meHandler(req: Request, res: Response): Promise { res.status(200).json(await rewriteAttachments(RewriteMode.PRESIGNED_URL, systemRepo, result)); } -async function getUserConfiguration(project: Project, membership: ProjectMembership): Promise { +async function getUserConfiguration( + systemRepo: Repository, + project: Project, + membership: ProjectMembership +): Promise { if (membership.userConfiguration) { return systemRepo.readReference(membership.userConfiguration); } @@ -105,7 +111,7 @@ async function getUserConfiguration(project: Project, membership: ProjectMembers return result; } -async function getSessions(user: User): Promise { +async function getSessions(systemRepo: Repository, user: User): Promise { const logins = await systemRepo.searchResources({ resourceType: 'Login', filters: [ diff --git a/packages/server/src/auth/method.test.ts b/packages/server/src/auth/method.test.ts index 39136c6f01..8ca4167728 100644 --- a/packages/server/src/auth/method.test.ts +++ b/packages/server/src/auth/method.test.ts @@ -4,12 +4,13 @@ import express from 'express'; import request from 'supertest'; import { initApp, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { withTestContext } from '../test.setup'; -const app = express(); - describe('Method', () => { + const app = express(); + const systemRepo = getSystemRepo(); + beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); diff --git a/packages/server/src/auth/method.ts b/packages/server/src/auth/method.ts index 9eaeede3f5..68dcd660ca 100644 --- a/packages/server/src/auth/method.ts +++ b/packages/server/src/auth/method.ts @@ -1,11 +1,11 @@ -import { Operator, OperationOutcomeError, badRequest } from '@medplum/core'; +import { OperationOutcomeError, Operator, badRequest } from '@medplum/core'; import { DomainConfiguration } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { body } from 'express-validator'; import { getConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; -import { makeValidationMiddleware } from '../util/validator'; +import { getSystemRepo } from '../fhir/repo'; import { globalLogger } from '../logger'; +import { makeValidationMiddleware } from '../util/validator'; /* * The method handler is used to determine available login methods. @@ -68,6 +68,7 @@ export async function isExternalAuth(email: string): Promise<{ domain: string; a * @returns The domain configuration for the domain name if available. */ export async function getDomainConfiguration(domain: string): Promise { + const systemRepo = getSystemRepo(); const results = await systemRepo.search({ resourceType: 'DomainConfiguration', filters: [ diff --git a/packages/server/src/auth/mfa.ts b/packages/server/src/auth/mfa.ts index 821e92cc5a..1102452588 100644 --- a/packages/server/src/auth/mfa.ts +++ b/packages/server/src/auth/mfa.ts @@ -4,12 +4,12 @@ import { Request, Response, Router } from 'express'; import { body, validationResult } from 'express-validator'; import { authenticator } from 'otplib'; import { asyncWrap } from '../async'; +import { getAuthenticatedContext } from '../context'; import { invalidRequest, sendOutcome } from '../fhir/outcomes'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { authenticateRequest } from '../oauth/middleware'; import { verifyMfaToken } from '../oauth/utils'; import { sendLoginResult } from './utils'; -import { getAuthenticatedContext } from '../context'; authenticator.options = { window: 1, @@ -21,6 +21,7 @@ mfaRouter.get( '/status', authenticateRequest, asyncWrap(async (_req: Request, res: Response) => { + const systemRepo = getSystemRepo(); const ctx = getAuthenticatedContext(); let user = await systemRepo.readReference(ctx.membership.user as Reference); if (user.mfaEnrolled) { @@ -52,6 +53,7 @@ mfaRouter.post( authenticateRequest, [body('token').notEmpty().withMessage('Missing token')], asyncWrap(async (req: Request, res: Response) => { + const systemRepo = getSystemRepo(); const ctx = getAuthenticatedContext(); const user = await systemRepo.readReference(ctx.membership.user as Reference); @@ -90,6 +92,7 @@ mfaRouter.post( return Promise.resolve(); } + const systemRepo = getSystemRepo(); const login = await systemRepo.readResource('Login', req.body.login); const result = await verifyMfaToken(login, req.body.token); return sendLoginResult(res, result); diff --git a/packages/server/src/auth/newpatient.test.ts b/packages/server/src/auth/newpatient.test.ts index f486e1df2e..9ab17727f7 100644 --- a/packages/server/src/auth/newpatient.test.ts +++ b/packages/server/src/auth/newpatient.test.ts @@ -7,7 +7,7 @@ import fetch from 'node-fetch'; import request from 'supertest'; import { initApp, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { setupPwnedPasswordMock, setupRecaptchaMock, withTestContext } from '../test.setup'; jest.mock('hibp'); @@ -33,6 +33,8 @@ describe('New patient', () => { }); test('Patient registration', async () => { + const systemRepo = getSystemRepo(); + // Register as Christina const res1 = await request(app) .post('/auth/newuser') diff --git a/packages/server/src/auth/newpatient.ts b/packages/server/src/auth/newpatient.ts index d01d532daa..b1359ef37c 100644 --- a/packages/server/src/auth/newpatient.ts +++ b/packages/server/src/auth/newpatient.ts @@ -3,10 +3,10 @@ import { Login, Patient, Project, ProjectMembership, Reference, User } from '@me import { Request, Response } from 'express'; import { body } from 'express-validator'; import { sendOutcome } from '../fhir/outcomes'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { setLoginMembership } from '../oauth/utils'; -import { createProfile, createProjectMembership } from './utils'; import { makeValidationMiddleware } from '../util/validator'; +import { createProfile, createProjectMembership } from './utils'; export const newPatientValidator = makeValidationMiddleware([ body('login').notEmpty().withMessage('Missing login'), @@ -20,6 +20,7 @@ export const newPatientValidator = makeValidationMiddleware([ * @param res - The HTTP response. */ export async function newPatientHandler(req: Request, res: Response): Promise { + const systemRepo = getSystemRepo(); const login = await systemRepo.readResource('Login', req.body.login); if (login.membership) { @@ -61,6 +62,7 @@ export async function createPatient( firstName: string, lastName: string ): Promise { + const systemRepo = getSystemRepo(); const user = await systemRepo.readReference(login.user as Reference); const project = await systemRepo.readResource('Project', projectId); diff --git a/packages/server/src/auth/newproject.ts b/packages/server/src/auth/newproject.ts index 7669844a79..24c3d531b9 100644 --- a/packages/server/src/auth/newproject.ts +++ b/packages/server/src/auth/newproject.ts @@ -2,10 +2,10 @@ import { badRequest, createReference } from '@medplum/core'; import { Login, Reference, User } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { body } from 'express-validator'; +import { createProject } from '../fhir/operations/projectinit'; import { sendOutcome } from '../fhir/outcomes'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { makeValidationMiddleware } from '../util/validator'; -import { createProject } from '../fhir/operations/projectinit'; export interface NewProjectRequest { readonly loginId: string; @@ -25,6 +25,7 @@ export const newProjectValidator = makeValidationMiddleware([ * @param res - The HTTP response. */ export async function newProjectHandler(req: Request, res: Response): Promise { + const systemRepo = getSystemRepo(); const login = await systemRepo.readResource('Login', req.body.login); if (login.membership) { diff --git a/packages/server/src/auth/newuser.test.ts b/packages/server/src/auth/newuser.test.ts index 5295c53ede..070bb78229 100644 --- a/packages/server/src/auth/newuser.test.ts +++ b/packages/server/src/auth/newuser.test.ts @@ -7,7 +7,7 @@ import fetch from 'node-fetch'; import request from 'supertest'; import { initApp, shutdownApp } from '../app'; import { getConfig, loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { setupPwnedPasswordMock, setupRecaptchaMock, withTestContext } from '../test.setup'; import { registerNew } from './register'; @@ -205,6 +205,7 @@ describe('New user', () => { }); // As a super admin, set the recaptcha site key // and the default access policy + const systemRepo = getSystemRepo(); await systemRepo.updateResource({ ...project, site: [ @@ -257,6 +258,7 @@ describe('New user', () => { }); // As a super admin, set the recaptcha site key // and the default access policy + const systemRepo = getSystemRepo(); await systemRepo.updateResource({ ...project, site: [ @@ -308,6 +310,7 @@ describe('New user', () => { }); // As a super admin, set the recaptcha site key // but *not* the access policy + const systemRepo = getSystemRepo(); await systemRepo.updateResource({ ...project, site: [ @@ -370,6 +373,7 @@ describe('New user', () => { password, }); // As a super admin, set the recaptcha site key + const systemRepo = getSystemRepo(); await systemRepo.updateResource({ ...project, site: [ diff --git a/packages/server/src/auth/newuser.ts b/packages/server/src/auth/newuser.ts index b912628c51..dbd05e90a7 100644 --- a/packages/server/src/auth/newuser.ts +++ b/packages/server/src/auth/newuser.ts @@ -6,7 +6,7 @@ import { body } from 'express-validator'; import { pwnedPassword } from 'hibp'; import { getConfig } from '../config'; import { sendOutcome } from '../fhir/outcomes'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { globalLogger } from '../logger'; import { getUserByEmailInProject, getUserByEmailWithoutProject, tryLogin } from '../oauth/utils'; import { makeValidationMiddleware } from '../util/validator'; @@ -36,6 +36,8 @@ export async function newUserHandler(req: Request, res: Response): Promise return; } + const systemRepo = getSystemRepo(); + let projectId = req.body.projectId as string | undefined; // If the user specifies a client ID, then make sure it is compatible with the project @@ -101,6 +103,8 @@ export async function createUser(request: Omit globalLogger.info('User creation request received', { email }); const passwordHash = await bcryptHashPassword(password); + + const systemRepo = getSystemRepo(); const result = await systemRepo.createResource({ resourceType: 'User', firstName, diff --git a/packages/server/src/auth/profile.test.ts b/packages/server/src/auth/profile.test.ts index 2af903cd16..f85275bbcd 100644 --- a/packages/server/src/auth/profile.test.ts +++ b/packages/server/src/auth/profile.test.ts @@ -5,7 +5,7 @@ import express from 'express'; import request from 'supertest'; import { initApp, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { withTestContext } from '../test.setup'; import { registerNew } from './register'; @@ -91,6 +91,7 @@ describe('Profile', () => { expect(res1.status).toBe(200); expect(res1.body.login).toBeDefined(); + const systemRepo = getSystemRepo(); const login = await systemRepo.readResource('Login', res1.body.login); await withTestContext(() => systemRepo.updateResource({ @@ -120,6 +121,7 @@ describe('Profile', () => { expect(res1.status).toBe(200); expect(res1.body.login).toBeDefined(); + const systemRepo = getSystemRepo(); const login = await systemRepo.readResource('Login', res1.body.login); await withTestContext(() => systemRepo.updateResource({ @@ -149,6 +151,7 @@ describe('Profile', () => { expect(res1.status).toBe(200); expect(res1.body.login).toBeDefined(); + const systemRepo = getSystemRepo(); const login = await systemRepo.readResource('Login', res1.body.login); await withTestContext(() => systemRepo.updateResource({ @@ -196,6 +199,7 @@ describe('Profile', () => { test('Membership for different user', async () => { // Create a dummy ProjectMembership + const systemRepo = getSystemRepo(); const membership = await withTestContext(() => systemRepo.createResource({ resourceType: 'ProjectMembership', diff --git a/packages/server/src/auth/profile.ts b/packages/server/src/auth/profile.ts index d62c9a78c6..2a07460399 100644 --- a/packages/server/src/auth/profile.ts +++ b/packages/server/src/auth/profile.ts @@ -1,10 +1,10 @@ import { Login } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { body } from 'express-validator'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { setLoginMembership } from '../oauth/utils'; -import { sendLoginCookie } from './utils'; import { makeValidationMiddleware } from '../util/validator'; +import { sendLoginCookie } from './utils'; /* * The profile handler is used during login when a user has multiple profiles. @@ -17,6 +17,7 @@ export const profileValidator = makeValidationMiddleware([ ]); export async function profileHandler(req: Request, res: Response): Promise { + const systemRepo = getSystemRepo(); const login = await systemRepo.readResource('Login', req.body.login); // Update the login diff --git a/packages/server/src/auth/register.ts b/packages/server/src/auth/register.ts index be75e8befe..a745093f49 100644 --- a/packages/server/src/auth/register.ts +++ b/packages/server/src/auth/register.ts @@ -2,7 +2,7 @@ import { createReference, ProfileResource } from '@medplum/core'; import { ClientApplication, Login, Project, ProjectMembership, User } from '@medplum/fhirtypes'; import { randomUUID } from 'crypto'; import { createProject } from '../fhir/operations/projectinit'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { getAuthTokens, getUserByEmailWithoutProject, tryLogin } from '../oauth/utils'; import { bcryptHashPassword } from './utils'; @@ -45,6 +45,7 @@ export async function registerNew(request: RegisterRequest): Promise({ resourceType: 'User', firstName, diff --git a/packages/server/src/auth/resetpassword.test.ts b/packages/server/src/auth/resetpassword.test.ts index 078f1b5cae..437d4f60f0 100644 --- a/packages/server/src/auth/resetpassword.test.ts +++ b/packages/server/src/auth/resetpassword.test.ts @@ -9,7 +9,7 @@ import fetch from 'node-fetch'; import request from 'supertest'; import { initApp, shutdownApp } from '../app'; import { getConfig, loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { setupPwnedPasswordMock, setupRecaptchaMock, withTestContext } from '../test.setup'; import { registerNew } from './register'; @@ -17,9 +17,9 @@ jest.mock('@aws-sdk/client-sesv2'); jest.mock('hibp'); jest.mock('node-fetch'); -const app = express(); - describe('Reset Password', () => { + const app = express(); + const systemRepo = getSystemRepo(); const testRecaptchaSecretKey = 'testrecaptchasecretkey'; beforeAll(async () => { diff --git a/packages/server/src/auth/resetpassword.ts b/packages/server/src/auth/resetpassword.ts index 70e4086802..a52590f82e 100644 --- a/packages/server/src/auth/resetpassword.ts +++ b/packages/server/src/auth/resetpassword.ts @@ -5,7 +5,7 @@ import { body } from 'express-validator'; import { getConfig } from '../config'; import { sendEmail } from '../email/email'; import { sendOutcome } from '../fhir/outcomes'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { generateSecret } from '../oauth/keys'; import { makeValidationMiddleware } from '../util/validator'; import { isExternalAuth } from './method'; @@ -51,6 +51,7 @@ export async function resetPasswordHandler(req: Request, res: Response): Promise } // Search for a user based on the defined filters + const systemRepo = getSystemRepo(); const user = await systemRepo.searchOne({ resourceType: 'User', filters, @@ -98,6 +99,7 @@ export async function resetPasswordHandler(req: Request, res: Response): Promise */ export async function resetPassword(user: User, type: 'invite' | 'reset', redirectUri?: string): Promise { // Create the password change request + const systemRepo = getSystemRepo(); const pcr = await systemRepo.createResource({ resourceType: 'PasswordChangeRequest', meta: { diff --git a/packages/server/src/auth/revoke.ts b/packages/server/src/auth/revoke.ts index c8af7f5371..8fdf8b763c 100644 --- a/packages/server/src/auth/revoke.ts +++ b/packages/server/src/auth/revoke.ts @@ -2,10 +2,10 @@ import { allOk, notFound } from '@medplum/core'; import { Login } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { body } from 'express-validator'; +import { getAuthenticatedContext } from '../context'; import { sendOutcome } from '../fhir/outcomes'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { revokeLogin } from '../oauth/utils'; -import { getAuthenticatedContext } from '../context'; import { makeValidationMiddleware } from '../util/validator'; export const revokeValidator = makeValidationMiddleware([ @@ -15,6 +15,7 @@ export const revokeValidator = makeValidationMiddleware([ export async function revokeHandler(req: Request, res: Response): Promise { const ctx = getAuthenticatedContext(); + const systemRepo = getSystemRepo(); const login = await systemRepo.readResource('Login', req.body.loginId); // Make sure the login belongs to the current user diff --git a/packages/server/src/auth/scope.test.ts b/packages/server/src/auth/scope.test.ts index 8ecb912f7b..bf7e151e12 100644 --- a/packages/server/src/auth/scope.test.ts +++ b/packages/server/src/auth/scope.test.ts @@ -4,15 +4,16 @@ import express from 'express'; import request from 'supertest'; import { initApp, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; -import { registerNew } from './register'; +import { getSystemRepo } from '../fhir/repo'; import { withTestContext } from '../test.setup'; - -const app = express(); -const email = `multi${randomUUID()}@example.com`; -const password = randomUUID(); +import { registerNew } from './register'; describe('Scope', () => { + const app = express(); + const systemRepo = getSystemRepo(); + const email = `multi${randomUUID()}@example.com`; + const password = randomUUID(); + beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); diff --git a/packages/server/src/auth/scope.ts b/packages/server/src/auth/scope.ts index 7244c5d632..1c93f67412 100644 --- a/packages/server/src/auth/scope.ts +++ b/packages/server/src/auth/scope.ts @@ -1,7 +1,7 @@ import { Login } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { body } from 'express-validator'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { setLoginScope } from '../oauth/utils'; import { makeValidationMiddleware } from '../util/validator'; @@ -16,6 +16,7 @@ export const scopeValidator = makeValidationMiddleware([ ]); export async function scopeHandler(req: Request, res: Response): Promise { + const systemRepo = getSystemRepo(); const login = await systemRepo.readResource('Login', req.body.login); // Update the login diff --git a/packages/server/src/auth/setpassword.ts b/packages/server/src/auth/setpassword.ts index b7c5f25ac2..216ebedfe1 100644 --- a/packages/server/src/auth/setpassword.ts +++ b/packages/server/src/auth/setpassword.ts @@ -4,7 +4,7 @@ import { Request, Response } from 'express'; import { body } from 'express-validator'; import { pwnedPassword } from 'hibp'; import { sendOutcome } from '../fhir/outcomes'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { timingSafeEqualStr } from '../oauth/utils'; import { makeValidationMiddleware } from '../util/validator'; import { bcryptHashPassword } from './utils'; @@ -16,6 +16,7 @@ export const setPasswordValidator = makeValidationMiddleware([ ]); export async function setPasswordHandler(req: Request, res: Response): Promise { + const systemRepo = getSystemRepo(); const pcr = await systemRepo.readResource('PasswordChangeRequest', req.body.id); if (pcr.used) { @@ -43,5 +44,6 @@ export async function setPasswordHandler(req: Request, res: Response): Promise { const passwordHash = await bcryptHashPassword(password); + const systemRepo = getSystemRepo(); await systemRepo.updateResource({ ...user, passwordHash }); } diff --git a/packages/server/src/auth/status.test.ts b/packages/server/src/auth/status.test.ts index 085877d1e2..4f22c5524e 100644 --- a/packages/server/src/auth/status.test.ts +++ b/packages/server/src/auth/status.test.ts @@ -5,13 +5,14 @@ import express from 'express'; import request from 'supertest'; import { initApp, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { withTestContext } from '../test.setup'; -const app = express(); -let user: User; - describe('Status', () => { + const app = express(); + const systemRepo = getSystemRepo(); + let user: User; + beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); diff --git a/packages/server/src/auth/status.ts b/packages/server/src/auth/status.ts index 7f29b9f61a..113872199d 100644 --- a/packages/server/src/auth/status.ts +++ b/packages/server/src/auth/status.ts @@ -3,9 +3,9 @@ import { Login } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { param } from 'express-validator'; import { sendOutcome } from '../fhir/outcomes'; -import { systemRepo } from '../fhir/repo'; -import { sendLoginResult } from './utils'; +import { getSystemRepo } from '../fhir/repo'; import { makeValidationMiddleware } from '../util/validator'; +import { sendLoginResult } from './utils'; /* * The status handler gets an in-process login status. @@ -15,6 +15,8 @@ import { makeValidationMiddleware } from '../util/validator'; export const statusValidator = makeValidationMiddleware([param('login').isUUID().withMessage('Login ID is required')]); export async function statusHandler(req: Request, res: Response): Promise { + const systemRepo = getSystemRepo(); + const loginId = req.params.login; const login = await systemRepo.readResource('Login', loginId); diff --git a/packages/server/src/auth/utils.ts b/packages/server/src/auth/utils.ts index 525748e325..7def7390f9 100644 --- a/packages/server/src/auth/utils.ts +++ b/packages/server/src/auth/utils.ts @@ -13,7 +13,7 @@ import fetch from 'node-fetch'; import { getConfig } from '../config'; import { getRequestContext } from '../context'; import { sendOutcome } from '../fhir/outcomes'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { rewriteAttachments, RewriteMode } from '../fhir/rewrite'; import { getClientApplication, getMembershipsForLogin } from '../oauth/utils'; @@ -30,6 +30,8 @@ export async function createProfile( if (email) { telecom = [{ system: 'email', use: 'work', value: email }]; } + + const systemRepo = getSystemRepo(); const result = await systemRepo.createResource({ resourceType, meta: { @@ -55,6 +57,8 @@ export async function createProjectMembership( ): Promise { const ctx = getRequestContext(); ctx.logger.info('Creating project membership', { name: project.name }); + + const systemRepo = getSystemRepo(); const result = await systemRepo.createResource({ ...details, resourceType: 'ProjectMembership', @@ -74,6 +78,7 @@ export async function createProjectMembership( * @param login - The login details. */ export async function sendLoginResult(res: Response, login: Login): Promise { + const systemRepo = getSystemRepo(); const user = await systemRepo.readReference(login.user as Reference); if (user.mfaEnrolled && login.authMethod === 'password' && !login.mfaVerified) { res.json({ login: login.id, mfaRequired: true }); @@ -199,6 +204,7 @@ export function getProjectByRecaptchaSiteKey( }); } + const systemRepo = getSystemRepo(); return systemRepo.searchOne({ resourceType: 'Project', filters }); } diff --git a/packages/server/src/context.ts b/packages/server/src/context.ts index cb60efce8b..7445308685 100644 --- a/packages/server/src/context.ts +++ b/packages/server/src/context.ts @@ -3,7 +3,7 @@ import { Login, Project, ProjectMembership, Reference } from '@medplum/fhirtypes import { AsyncLocalStorage } from 'async_hooks'; import { randomUUID } from 'crypto'; import { NextFunction, Request, Response } from 'express'; -import { Repository, systemRepo } from './fhir/repo'; +import { Repository, getSystemRepo } from './fhir/repo'; import { parseTraceparent } from './traceparent'; export class RequestContext { @@ -66,7 +66,7 @@ export class AuthenticatedRequestContext extends RequestContext { {} as unknown as Login, {} as unknown as Project, {} as unknown as ProjectMembership, - systemRepo, + getSystemRepo(), systemLogger ); } diff --git a/packages/server/src/email/email.test.ts b/packages/server/src/email/email.test.ts index 7b69e5a46f..498e39a317 100644 --- a/packages/server/src/email/email.test.ts +++ b/packages/server/src/email/email.test.ts @@ -10,12 +10,13 @@ import Mail from 'nodemailer/lib/mailer'; import { Readable } from 'stream'; import { initAppServices, shutdownApp } from '../app'; import { getConfig, loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { getBinaryStorage } from '../fhir/storage'; -import { sendEmail } from './email'; import { withTestContext } from '../test.setup'; +import { sendEmail } from './email'; describe('Email', () => { + const systemRepo = getSystemRepo(); let mockSESv2Client: AwsClientStub; beforeAll(async () => { diff --git a/packages/server/src/fhir/accesspolicy.test.ts b/packages/server/src/fhir/accesspolicy.test.ts index cef86aa04f..36a5aa4832 100644 --- a/packages/server/src/fhir/accesspolicy.test.ts +++ b/packages/server/src/fhir/accesspolicy.test.ts @@ -31,9 +31,11 @@ import { registerNew } from '../auth/register'; import { loadTestConfig } from '../config'; import { withTestContext } from '../test.setup'; import { getRepoForLogin } from './accesspolicy'; -import { Repository, systemRepo } from './repo'; +import { getSystemRepo, Repository } from './repo'; describe('AccessPolicy', () => { + const systemRepo = getSystemRepo(); + beforeAll(async () => { const config = await loadTestConfig(); await initAppServices(config); diff --git a/packages/server/src/fhir/accesspolicy.ts b/packages/server/src/fhir/accesspolicy.ts index dfd31d192f..3b3fb19611 100644 --- a/packages/server/src/fhir/accesspolicy.ts +++ b/packages/server/src/fhir/accesspolicy.ts @@ -8,7 +8,7 @@ import { ProjectMembershipAccess, Reference, } from '@medplum/fhirtypes'; -import { Repository, systemRepo } from './repo'; +import { Repository, getSystemRepo } from './repo'; import { applySmartScopes } from './smart'; /** @@ -128,6 +128,7 @@ async function buildAccessPolicyResources( access: ProjectMembershipAccess, profile: Reference ): Promise { + const systemRepo = getSystemRepo(); const original = await systemRepo.readReference(access.policy as Reference); const params = access.parameter || []; params.push({ name: 'profile', valueReference: profile }); diff --git a/packages/server/src/fhir/lookups/address.test.ts b/packages/server/src/fhir/lookups/address.test.ts index 61ce833117..cc6bf68bd0 100644 --- a/packages/server/src/fhir/lookups/address.test.ts +++ b/packages/server/src/fhir/lookups/address.test.ts @@ -3,10 +3,12 @@ import { Patient } from '@medplum/fhirtypes'; import { randomUUID } from 'crypto'; import { initAppServices, shutdownApp } from '../../app'; import { loadTestConfig } from '../../config'; -import { systemRepo } from '../repo'; import { withTestContext } from '../../test.setup'; +import { getSystemRepo } from '../repo'; describe('Address Lookup Table', () => { + const systemRepo = getSystemRepo(); + beforeAll(async () => { const config = await loadTestConfig(); await initAppServices(config); diff --git a/packages/server/src/fhir/lookups/coding.test.ts b/packages/server/src/fhir/lookups/coding.test.ts index 94fe130b5a..37baa8ecd5 100644 --- a/packages/server/src/fhir/lookups/coding.test.ts +++ b/packages/server/src/fhir/lookups/coding.test.ts @@ -3,9 +3,11 @@ import { initAppServices, shutdownApp } from '../../app'; import { loadTestConfig } from '../../config'; import { getDatabasePool } from '../../database'; import { withTestContext } from '../../test.setup'; -import { systemRepo } from '../repo'; +import { getSystemRepo } from '../repo'; describe('Coding lookup table', () => { + const systemRepo = getSystemRepo(); + beforeAll(async () => { const config = await loadTestConfig(); await initAppServices(config); diff --git a/packages/server/src/fhir/lookups/humanname.test.ts b/packages/server/src/fhir/lookups/humanname.test.ts index 2c9498d114..926f3e3979 100644 --- a/packages/server/src/fhir/lookups/humanname.test.ts +++ b/packages/server/src/fhir/lookups/humanname.test.ts @@ -4,9 +4,11 @@ import { randomUUID } from 'crypto'; import { initAppServices, shutdownApp } from '../../app'; import { loadTestConfig } from '../../config'; import { bundleContains, withTestContext } from '../../test.setup'; -import { systemRepo } from '../repo'; +import { getSystemRepo } from '../repo'; describe('HumanName Lookup Table', () => { + const systemRepo = getSystemRepo(); + beforeAll(async () => { const config = await loadTestConfig(); await initAppServices(config); diff --git a/packages/server/src/fhir/lookups/token.test.ts b/packages/server/src/fhir/lookups/token.test.ts index d8ca43bb70..3281c2748c 100644 --- a/packages/server/src/fhir/lookups/token.test.ts +++ b/packages/server/src/fhir/lookups/token.test.ts @@ -4,9 +4,11 @@ import { randomUUID } from 'crypto'; import { initAppServices, shutdownApp } from '../../app'; import { loadTestConfig } from '../../config'; import { bundleContains, withTestContext } from '../../test.setup'; -import { systemRepo } from '../repo'; +import { getSystemRepo } from '../repo'; describe('Identifier Lookup Table', () => { + const systemRepo = getSystemRepo(); + beforeAll(async () => { const config = await loadTestConfig(); await initAppServices(config); diff --git a/packages/server/src/fhir/operations/deploy.ts b/packages/server/src/fhir/operations/deploy.ts index 76a0fd7234..b93df768d2 100644 --- a/packages/server/src/fhir/operations/deploy.ts +++ b/packages/server/src/fhir/operations/deploy.ts @@ -9,7 +9,7 @@ import { UpdateFunctionCodeCommand, UpdateFunctionConfigurationCommand, } from '@aws-sdk/client-lambda'; -import { allOk, badRequest, ContentType, getReferenceString, sleep } from '@medplum/core'; +import { ContentType, allOk, badRequest, getReferenceString, sleep } from '@medplum/core'; import { Binary, Bot } from '@medplum/fhirtypes'; import { ConfiguredRetryStrategy } from '@smithy/util-retry'; import { Request, Response } from 'express'; @@ -19,7 +19,7 @@ import { asyncWrap } from '../../async'; import { getConfig } from '../../config'; import { getAuthenticatedContext, getRequestContext } from '../../context'; import { sendOutcome } from '../outcomes'; -import { systemRepo } from '../repo'; +import { getSystemRepo } from '../repo'; import { getBinaryStorage } from '../storage'; import { isBotEnabled } from './execute'; @@ -111,6 +111,7 @@ export const deployHandler = asyncWrap(async (req: Request, res: Response) => { await ctx.repo.readResource('Bot', id); // Then read the bot as system user to load extended metadata + const systemRepo = getSystemRepo(); const bot = await systemRepo.readResource('Bot', id); if (!(await isBotEnabled(bot))) { diff --git a/packages/server/src/fhir/operations/execute.ts b/packages/server/src/fhir/operations/execute.ts index 21cd38f981..925bcaf62c 100644 --- a/packages/server/src/fhir/operations/execute.ts +++ b/packages/server/src/fhir/operations/execute.ts @@ -40,7 +40,7 @@ import { AuditEventOutcome, logAuditEvent } from '../../util/auditevent'; import { MockConsole } from '../../util/console'; import { createAuditEventEntities } from '../../workers/utils'; import { sendOutcome } from '../outcomes'; -import { systemRepo } from '../repo'; +import { getSystemRepo } from '../repo'; import { getBinaryStorage } from '../storage'; export const EXECUTE_CONTENT_TYPES = [ContentType.JSON, ContentType.FHIR_JSON, ContentType.TEXT, ContentType.HL7_V2]; @@ -81,6 +81,7 @@ export const executeHandler = asyncWrap(async (req: Request, res: Response) => { } // Then read the bot as system user to load extended metadata + const systemRepo = getSystemRepo(); const bot = await systemRepo.readResource('Bot', userBot.id as string); // Execute the bot @@ -188,6 +189,7 @@ export async function executeBot(request: BotExecutionRequest): Promise { + const systemRepo = getSystemRepo(); const project = await systemRepo.readResource('Project', bot.meta?.project as string); return !!project.features?.includes('bots'); } @@ -385,6 +387,7 @@ async function runInVmContext(request: BotExecutionRequest): Promise({ reference: codeUrl } as Reference); const stream = await getBinaryStorage().readBinary(binary); const code = await readStreamToString(stream); @@ -470,6 +473,8 @@ async function runInVmContext(request: BotExecutionRequest): Promise { + const systemRepo = getSystemRepo(); + // Create the Login resource const login = await systemRepo.createResource({ resourceType: 'Login', @@ -493,6 +498,7 @@ async function getBotAccessToken(runAs: ProjectMembership): Promise { } async function getBotSecrets(bot: Bot): Promise> { + const systemRepo = getSystemRepo(); const project = await systemRepo.readResource('Project', bot.meta?.project as string); const secrets = Object.fromEntries(project.secret?.map((secret) => [secret.name, secret]) || []); return secrets; @@ -568,6 +574,7 @@ async function createAuditEvent( const destination = bot.auditEventDestination ?? ['resource']; if (destination.includes('resource')) { + const systemRepo = getSystemRepo(); await systemRepo.createResource(auditEvent); } if (destination.includes('log')) { diff --git a/packages/server/src/fhir/operations/expand.test.ts b/packages/server/src/fhir/operations/expand.test.ts index 42975f4cf8..7cefb80817 100644 --- a/packages/server/src/fhir/operations/expand.test.ts +++ b/packages/server/src/fhir/operations/expand.test.ts @@ -6,12 +6,13 @@ import request from 'supertest'; import { initApp, shutdownApp } from '../../app'; import { loadTestConfig } from '../../config'; import { initTestAuth, withTestContext } from '../../test.setup'; -import { systemRepo } from '../repo'; - -const app = express(); -let accessToken: string; +import { getSystemRepo } from '../repo'; describe('Expand', () => { + const app = express(); + const systemRepo = getSystemRepo(); + let accessToken: string; + beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); diff --git a/packages/server/src/fhir/operations/expand.ts b/packages/server/src/fhir/operations/expand.ts index 270b8a7b95..71f36b834b 100644 --- a/packages/server/src/fhir/operations/expand.ts +++ b/packages/server/src/fhir/operations/expand.ts @@ -4,7 +4,7 @@ import { Request, Response } from 'express'; import { asyncWrap } from '../../async'; import { getDatabasePool } from '../../database'; import { sendOutcome } from '../outcomes'; -import { systemRepo } from '../repo'; +import { getSystemRepo } from '../repo'; import { Condition, Conjunction, Disjunction, Expression, SelectQuery } from '../sql'; // Implements FHIR "Value Set Expansion" @@ -110,6 +110,7 @@ function filterToTsvectorQuery(filter: string | undefined): string | undefined { } function getValueSetByUrl(url: string): Promise { + const systemRepo = getSystemRepo(); return systemRepo.searchOne({ resourceType: 'ValueSet', filters: [{ code: 'url', operator: SearchOperator.EQUALS, value: url }], diff --git a/packages/server/src/fhir/operations/export.test.ts b/packages/server/src/fhir/operations/export.test.ts index 6e75840237..09c424eb7b 100644 --- a/packages/server/src/fhir/operations/export.test.ts +++ b/packages/server/src/fhir/operations/export.test.ts @@ -5,13 +5,14 @@ import request from 'supertest'; import { initApp, shutdownApp } from '../../app'; import { loadTestConfig } from '../../config'; import { createTestProject, initTestAuth, waitForAsyncJob } from '../../test.setup'; -import { systemRepo } from '../repo'; +import { getSystemRepo } from '../repo'; import { exportResourceType } from './export'; import { BulkExporter } from './utils/bulkexporter'; -const app = express(); - describe('Export', () => { + const app = express(); + const systemRepo = getSystemRepo(); + beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); diff --git a/packages/server/src/fhir/operations/expunge.test.ts b/packages/server/src/fhir/operations/expunge.test.ts index 9dd1431c02..1f8ecda15c 100644 --- a/packages/server/src/fhir/operations/expunge.test.ts +++ b/packages/server/src/fhir/operations/expunge.test.ts @@ -8,14 +8,15 @@ import { loadTestConfig } from '../../config'; import { getDatabasePool } from '../../database'; import { getRedis } from '../../redis'; import { createTestProject, initTestAuth, waitForAsyncJob, withTestContext } from '../../test.setup'; -import { systemRepo } from '../repo'; +import { getSystemRepo } from '../repo'; import { SelectQuery } from '../sql'; import { Expunger } from './expunge'; -const app = express(); -let superAdminAccessToken: string; - describe('Expunge', () => { + const app = express(); + const systemRepo = getSystemRepo(); + let superAdminAccessToken: string; + beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); diff --git a/packages/server/src/fhir/operations/groupexport.test.ts b/packages/server/src/fhir/operations/groupexport.test.ts index 758c7fbc1d..19ec16188c 100644 --- a/packages/server/src/fhir/operations/groupexport.test.ts +++ b/packages/server/src/fhir/operations/groupexport.test.ts @@ -5,14 +5,15 @@ import request from 'supertest'; import { initApp, shutdownApp } from '../../app'; import { loadTestConfig } from '../../config'; import { createTestProject, initTestAuth, waitForAsyncJob, withTestContext } from '../../test.setup'; -import { systemRepo } from '../repo'; +import { getSystemRepo } from '../repo'; import { groupExportResources } from './groupexport'; import { BulkExporter } from './utils/bulkexporter'; -const app = express(); -let accessToken: string; - describe('Group Export', () => { + const app = express(); + const systemRepo = getSystemRepo(); + let accessToken: string; + beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); diff --git a/packages/server/src/fhir/operations/projectclone.test.ts b/packages/server/src/fhir/operations/projectclone.test.ts index 428de26d99..275563bb2f 100644 --- a/packages/server/src/fhir/operations/projectclone.test.ts +++ b/packages/server/src/fhir/operations/projectclone.test.ts @@ -27,16 +27,17 @@ import { setupRecaptchaMock, withTestContext, } from '../../test.setup'; -import { systemRepo } from '../repo'; +import { getSystemRepo } from '../repo'; import { getBinaryStorage } from '../storage'; import { createProject } from './projectinit'; jest.mock('node-fetch'); jest.mock('hibp'); -const app = express(); - describe('Project clone', () => { + const app = express(); + const systemRepo = getSystemRepo(); + beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); diff --git a/packages/server/src/fhir/operations/projectinit.ts b/packages/server/src/fhir/operations/projectinit.ts index 846ba5cc37..d927461890 100644 --- a/packages/server/src/fhir/operations/projectinit.ts +++ b/packages/server/src/fhir/operations/projectinit.ts @@ -1,3 +1,4 @@ +import { ProfileResource, badRequest, createReference, created } from '@medplum/core'; import { ClientApplication, OperationDefinition, @@ -6,17 +7,16 @@ import { Reference, User, } from '@medplum/fhirtypes'; -import { getAuthenticatedContext, getRequestContext } from '../../context'; -import { systemRepo } from '../repo'; -import { ProfileResource, badRequest, createReference, created } from '@medplum/core'; -import { parseInputParameters, sendOutputParameters } from './utils/parameters'; +import { randomUUID } from 'crypto'; import { Request, Response } from 'express'; -import { sendOutcome } from '../outcomes'; import { createClient } from '../../admin/client'; +import { createUser } from '../../auth/newuser'; import { createProfile, createProjectMembership } from '../../auth/utils'; +import { getAuthenticatedContext, getRequestContext } from '../../context'; import { getUserByEmailWithoutProject } from '../../oauth/utils'; -import { createUser } from '../../auth/newuser'; -import { randomUUID } from 'crypto'; +import { sendOutcome } from '../outcomes'; +import { getSystemRepo } from '../repo'; +import { parseInputParameters, sendOutputParameters } from './utils/parameters'; const projectInitOperation: OperationDefinition = { resourceType: 'OperationDefinition', @@ -98,6 +98,8 @@ export async function projectInitHandler(req: Request, res: Response): Promise { const ctx = getRequestContext(); + const systemRepo = getSystemRepo(); ctx.logger.info('Project creation request received', { name: projectName }); const project = await systemRepo.createResource({ diff --git a/packages/server/src/fhir/operations/utils/asyncjobexecutor.test.ts b/packages/server/src/fhir/operations/utils/asyncjobexecutor.test.ts index 5c8538785c..ec59fb6900 100644 --- a/packages/server/src/fhir/operations/utils/asyncjobexecutor.test.ts +++ b/packages/server/src/fhir/operations/utils/asyncjobexecutor.test.ts @@ -1,13 +1,14 @@ import express from 'express'; -import { systemRepo } from '../../repo'; -import { AsyncJobExecutor } from './asyncjobexecutor'; import { initApp, shutdownApp } from '../../../app'; import { loadTestConfig } from '../../../config'; import { withTestContext } from '../../../test.setup'; - -const app = express(); +import { getSystemRepo } from '../../repo'; +import { AsyncJobExecutor } from './asyncjobexecutor'; describe('AsyncJobExecutor', () => { + const app = express(); + const systemRepo = getSystemRepo(); + beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); diff --git a/packages/server/src/fhir/operations/utils/asyncjobexecutor.ts b/packages/server/src/fhir/operations/utils/asyncjobexecutor.ts index 6356c19bd6..d0663cf54f 100644 --- a/packages/server/src/fhir/operations/utils/asyncjobexecutor.ts +++ b/packages/server/src/fhir/operations/utils/asyncjobexecutor.ts @@ -1,7 +1,7 @@ import { AsyncJob } from '@medplum/fhirtypes'; import { AsyncLocalStorage } from 'async_hooks'; import { getRequestContext } from '../../../context'; -import { Repository, systemRepo } from '../../repo'; +import { Repository, getSystemRepo } from '../../repo'; export class AsyncJobExecutor { readonly repo: Repository; @@ -39,6 +39,7 @@ export class AsyncJobExecutor { if (!this.resource) { throw new Error('AsyncJob missing'); } + const systemRepo = getSystemRepo(); try { await callback(); await systemRepo.updateResource({ diff --git a/packages/server/src/fhir/operations/utils/bulkexporter.ts b/packages/server/src/fhir/operations/utils/bulkexporter.ts index 8572db4843..746d9be213 100644 --- a/packages/server/src/fhir/operations/utils/bulkexporter.ts +++ b/packages/server/src/fhir/operations/utils/bulkexporter.ts @@ -1,7 +1,7 @@ -import { Binary, BulkDataExport, Bundle, Project, Resource, ResourceType } from '@medplum/fhirtypes'; import { getReferenceString } from '@medplum/core'; -import { Repository, systemRepo } from '../../repo'; +import { Binary, BulkDataExport, Bundle, Project, Resource, ResourceType } from '@medplum/fhirtypes'; import { PassThrough } from 'node:stream'; +import { Repository, getSystemRepo } from '../../repo'; import { getBinaryStorage } from '../../storage'; const NDJSON_CONTENT_TYPE = 'application/fhir+ndjson'; @@ -104,6 +104,7 @@ export class BulkExporter { } // Update the BulkDataExport + const systemRepo = getSystemRepo(); return systemRepo.updateResource({ ...this.resource, meta: { diff --git a/packages/server/src/fhir/patient.test.ts b/packages/server/src/fhir/patient.test.ts index c5aa800e1c..23ff1b73ec 100644 --- a/packages/server/src/fhir/patient.test.ts +++ b/packages/server/src/fhir/patient.test.ts @@ -12,7 +12,7 @@ import { initAppServices, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; import { withTestContext } from '../test.setup'; import { getPatientCompartmentParams, getPatientResourceTypes, getPatients } from './patient'; -import { systemRepo } from './repo'; +import { getSystemRepo } from './repo'; describe('FHIR Patient utils', () => { beforeAll(async () => { @@ -155,6 +155,7 @@ describe('FHIR Patient utils', () => { // and that resource has a reference to a patient, // but the patient reference is an external patient ID, // we should silently ignore the patient reference + const systemRepo = getSystemRepo(); const eob = await systemRepo.createResource({ resourceType: 'ExplanationOfBenefit', status: 'active', diff --git a/packages/server/src/fhir/references.ts b/packages/server/src/fhir/references.ts index 9f5e365320..cf8fc62f37 100644 --- a/packages/server/src/fhir/references.ts +++ b/packages/server/src/fhir/references.ts @@ -9,7 +9,7 @@ import { } from '@medplum/core'; import { OperationOutcomeIssue, Reference, Resource } from '@medplum/fhirtypes'; import { randomUUID } from 'crypto'; -import { systemRepo } from './repo'; +import { getSystemRepo } from './repo'; export async function validateReferences(resource: T): Promise { return new FhirReferenceValidator(resource).validate(); @@ -81,6 +81,7 @@ export class FhirReferenceValidator { } try { + const systemRepo = getSystemRepo(); const existing = await systemRepo.readReference(reference); if (existing.meta?.project && this.root.meta?.project && existing.meta.project !== this.root.meta.project) { this.issues.push(createStructureIssue(path, `Invalid reference (Not found)`)); diff --git a/packages/server/src/fhir/repo.test.ts b/packages/server/src/fhir/repo.test.ts index 68fda8e6a5..2b9b0c6268 100644 --- a/packages/server/src/fhir/repo.test.ts +++ b/packages/server/src/fhir/repo.test.ts @@ -20,12 +20,14 @@ import { loadTestConfig } from '../config'; import { getDatabasePool } from '../database'; import { bundleContains, withTestContext } from '../test.setup'; import { getRepoForLogin } from './accesspolicy'; -import { Repository, systemRepo } from './repo'; +import { getSystemRepo, Repository } from './repo'; jest.mock('hibp'); jest.mock('ioredis'); describe('FHIR Repo', () => { + const systemRepo = getSystemRepo(); + beforeAll(async () => { const config = await loadTestConfig(); await initAppServices(config); diff --git a/packages/server/src/fhir/repo.ts b/packages/server/src/fhir/repo.ts index fb23d221ea..e33cd7ae38 100644 --- a/packages/server/src/fhir/repo.ts +++ b/packages/server/src/fhir/repo.ts @@ -180,7 +180,7 @@ const lookupTables: LookupTable[] = [ * It is a thin layer on top of the database. * Repository instances should be created per author and project. */ -export class Repository extends BaseRepository implements FhirRepository { +export class Repository extends BaseRepository implements FhirRepository, Disposable { private readonly context: RepositoryContext; private closed = false; @@ -1506,6 +1506,7 @@ export class Repository extends BaseRepository implements FhirRepository { + const systemRepo = getSystemRepo(); let config: MedplumServerConfig; let binary: Binary; diff --git a/packages/server/src/fhir/search.test.ts b/packages/server/src/fhir/search.test.ts index 29048b6a19..8672e9fdad 100644 --- a/packages/server/src/fhir/search.test.ts +++ b/packages/server/src/fhir/search.test.ts @@ -44,12 +44,14 @@ import { randomUUID } from 'crypto'; import { initAppServices, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; import { bundleContains, withTestContext } from '../test.setup'; -import { systemRepo } from './repo'; +import { getSystemRepo } from './repo'; jest.mock('hibp'); jest.mock('ioredis'); describe('FHIR Search', () => { + const systemRepo = getSystemRepo(); + beforeAll(async () => { const config = await loadTestConfig(); await initAppServices(config); diff --git a/packages/server/src/oauth/authorize.test.ts b/packages/server/src/oauth/authorize.test.ts index dfd40bfb63..4d54fb24a2 100644 --- a/packages/server/src/oauth/authorize.test.ts +++ b/packages/server/src/oauth/authorize.test.ts @@ -9,19 +9,20 @@ import { inviteUser } from '../admin/invite'; import { initApp, shutdownApp } from '../app'; import { setPassword } from '../auth/setpassword'; import { loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { createTestProject, withTestContext } from '../test.setup'; import { revokeLogin } from './utils'; jest.mock('@aws-sdk/client-sesv2'); -const app = express(); -const email = randomUUID() + '@example.com'; -const password = randomUUID(); -let project: Project; -let client: ClientApplication; - describe('OAuth Authorize', () => { + const app = express(); + const systemRepo = getSystemRepo(); + const email = randomUUID() + '@example.com'; + const password = randomUUID(); + let project: Project; + let client: ClientApplication; + beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); diff --git a/packages/server/src/oauth/authorize.ts b/packages/server/src/oauth/authorize.ts index bb44d70022..bbf160fad5 100644 --- a/packages/server/src/oauth/authorize.ts +++ b/packages/server/src/oauth/authorize.ts @@ -5,7 +5,7 @@ import { URL } from 'url'; import { asyncWrap } from '../async'; import { getConfig } from '../config'; import { getRequestContext } from '../context'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { MedplumIdTokenClaims, verifyJwt } from './keys'; import { getClientApplication } from './utils'; @@ -110,6 +110,7 @@ async function validateAuthorizeRequest(req: Request, res: Response, params: Rec } if (prompt !== 'login' && existingLogin) { + const systemRepo = getSystemRepo(); await systemRepo.updateResource({ ...existingLogin, nonce: params.nonce as string, @@ -196,6 +197,7 @@ async function getExistingLoginFromIdTokenHint(req: Request): Promise('Login', existingLoginId); } @@ -212,6 +214,7 @@ async function getExistingLoginFromCookie(req: Request, client: ClientApplicatio return undefined; } + const systemRepo = getSystemRepo(); const bundle = await systemRepo.search({ resourceType: 'Login', filters: [ diff --git a/packages/server/src/oauth/keys.ts b/packages/server/src/oauth/keys.ts index c02acd431c..831bcecde7 100644 --- a/packages/server/src/oauth/keys.ts +++ b/packages/server/src/oauth/keys.ts @@ -14,7 +14,7 @@ import { SignJWT, } from 'jose'; import { MedplumServerConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { globalLogger } from '../logger'; export interface MedplumBaseClaims extends JWTPayload { @@ -101,6 +101,7 @@ export async function initKeys(config: MedplumServerConfig): Promise { throw new Error('Missing issuer'); } + const systemRepo = getSystemRepo(); const searchResult = await systemRepo.searchResources({ resourceType: 'JsonWebKey', filters: [{ code: 'active', operator: Operator.EQUALS, value: 'true' }], diff --git a/packages/server/src/oauth/middleware.test.ts b/packages/server/src/oauth/middleware.test.ts index 7845571154..935d671179 100644 --- a/packages/server/src/oauth/middleware.test.ts +++ b/packages/server/src/oauth/middleware.test.ts @@ -5,14 +5,15 @@ import express from 'express'; import request from 'supertest'; import { initApp, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { createTestClient, createTestProject, withTestContext } from '../test.setup'; import { generateAccessToken, generateSecret } from './keys'; -const app = express(); -let client: ClientApplication; - describe('Auth middleware', () => { + const app = express(); + const systemRepo = getSystemRepo(); + let client: ClientApplication; + beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); diff --git a/packages/server/src/oauth/middleware.ts b/packages/server/src/oauth/middleware.ts index c3f2268676..8bc374be75 100644 --- a/packages/server/src/oauth/middleware.ts +++ b/packages/server/src/oauth/middleware.ts @@ -4,7 +4,7 @@ import { NextFunction, Request, Response } from 'express'; import { IncomingMessage } from 'http'; import { AuthenticatedRequestContext, getRequestContext, requestContextStore } from '../context'; import { getRepoForLogin } from '../fhir/accesspolicy'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { getClientApplicationMembership, getLoginForAccessToken, timingSafeEqualStr } from './utils'; export interface AuthState { @@ -61,6 +61,7 @@ async function authenticateBasicAuth(req: IncomingMessage, token: string): Promi throw new OperationOutcomeError(unauthorized); } + const systemRepo = getSystemRepo(); let client = undefined; try { client = await systemRepo.readResource('ClientApplication', username); diff --git a/packages/server/src/oauth/token.test.ts b/packages/server/src/oauth/token.test.ts index b311cdf5d3..880a508b96 100644 --- a/packages/server/src/oauth/token.test.ts +++ b/packages/server/src/oauth/token.test.ts @@ -18,7 +18,7 @@ import { inviteUser } from '../admin/invite'; import { initApp, shutdownApp } from '../app'; import { setPassword } from '../auth/setpassword'; import { loadTestConfig, MedplumServerConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { createTestProject, withTestContext } from '../test.setup'; import { generateSecret } from './keys'; import { hashCode } from './token'; @@ -56,18 +56,19 @@ jest.mock('jose', () => { jest.mock('node-fetch'); -const app = express(); -const domain = randomUUID() + '.example.com'; -const email = `text@${domain}`; -const password = randomUUID(); -const redirectUri = `https://${domain}/auth/callback`; -let config: MedplumServerConfig; -let project: Project; -let client: ClientApplication; -let pkceOptionalClient: ClientApplication; -let externalAuthClient: ClientApplication; - describe('OAuth2 Token', () => { + const app = express(); + const systemRepo = getSystemRepo(); + const domain = randomUUID() + '.example.com'; + const email = `text@${domain}`; + const password = randomUUID(); + const redirectUri = `https://${domain}/auth/callback`; + let config: MedplumServerConfig; + let project: Project; + let client: ClientApplication; + let pkceOptionalClient: ClientApplication; + let externalAuthClient: ClientApplication; + beforeAll(async () => { config = await loadTestConfig(); await initApp(app, config); diff --git a/packages/server/src/oauth/token.ts b/packages/server/src/oauth/token.ts index 922cae830a..a0619e59e1 100644 --- a/packages/server/src/oauth/token.ts +++ b/packages/server/src/oauth/token.ts @@ -20,7 +20,7 @@ import { JWTVerifyOptions, createRemoteJWKSet, jwtVerify } from 'jose'; import { asyncWrap } from '../async'; import { getProjectIdByClientId } from '../auth/utils'; import { getConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { getTopicForUser } from '../fhircast/utils'; import { MedplumRefreshTokenClaims, generateSecret, verifyJwt } from './keys'; import { @@ -100,6 +100,7 @@ async function handleClientCredentials(req: Request, res: Response): Promise('ClientApplication', clientId); @@ -160,6 +161,7 @@ async function handleAuthorizationCode(req: Request, res: Response): Promise { return; } + const systemRepo = getSystemRepo(); const login = await systemRepo.readResource('Login', claims.login_id); if (login.refreshSecret === undefined) { @@ -353,6 +356,7 @@ export async function exchangeExternalAuthToken( return; } + const systemRepo = getSystemRepo(); const projectId = await getProjectIdByClientId(clientId, undefined); const client = await systemRepo.readResource('ClientApplication', clientId); const idp = client.identityProvider; @@ -467,6 +471,7 @@ async function parseClientAssertion( return { error: 'Invalid client assertion issuer' }; } + const systemRepo = getSystemRepo(); const clientId = claims.iss as string; let client: ClientApplication; try { @@ -551,6 +556,7 @@ async function sendTokenResponse(res: Response, login: Login, membership: Projec let encounter = undefined; if (login.launch) { + const systemRepo = getSystemRepo(); const launch = await systemRepo.readReference(login.launch); patient = resolveId(launch.patient); encounter = resolveId(launch.encounter); diff --git a/packages/server/src/oauth/userinfo.ts b/packages/server/src/oauth/userinfo.ts index e0fb14651a..d7c951a8fe 100644 --- a/packages/server/src/oauth/userinfo.ts +++ b/packages/server/src/oauth/userinfo.ts @@ -11,13 +11,14 @@ import { Reference, User } from '@medplum/fhirtypes'; import { Request, RequestHandler, Response } from 'express'; import { asyncWrap } from '../async'; import { getAuthenticatedContext } from '../context'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; /** * Handles the OAuth/OpenID UserInfo Endpoint. * See: https://openid.net/specs/openid-connect-core-1_0.html#UserInfo */ export const userInfoHandler: RequestHandler = asyncWrap(async (_req: Request, res: Response) => { + const systemRepo = getSystemRepo(); const ctx = getAuthenticatedContext(); const user = await systemRepo.readReference(ctx.login.user as Reference); const profile = await ctx.repo.readReference(ctx.profile); diff --git a/packages/server/src/oauth/utils.ts b/packages/server/src/oauth/utils.ts index a9c073edcf..7f5bdb0a81 100644 --- a/packages/server/src/oauth/utils.ts +++ b/packages/server/src/oauth/utils.ts @@ -31,7 +31,7 @@ import fetch from 'node-fetch'; import { authenticator } from 'otplib'; import { getRequestContext } from '../context'; import { getAccessPolicyForLogin } from '../fhir/accesspolicy'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { AuditEventOutcome, logAuthEvent, LoginEvent } from '../util/auditevent'; import { generateAccessToken, @@ -119,6 +119,7 @@ export async function getClientApplication(clientId: string): Promise('ClientApplication', clientId); } @@ -132,6 +133,7 @@ export async function tryLogin(request: LoginRequest): Promise { validatePkce(request, client); + const systemRepo = getSystemRepo(); let launch: SmartAppLaunch | undefined; if (request.launchId) { launch = await systemRepo.readResource('SmartAppLaunch', request.launchId); @@ -275,6 +277,7 @@ export async function verifyMfaToken(login: Login, token: string): Promise); if (!user.mfaEnrolled) { throw new OperationOutcomeError(badRequest('User not enrolled in MFA')); @@ -324,6 +327,7 @@ export async function getMembershipsForLogin(login: Login): Promise({ resourceType: 'ProjectMembership', count: 100, @@ -344,6 +348,7 @@ export async function getMembershipsForLogin(login: Login): Promise { + const systemRepo = getSystemRepo(); return systemRepo.searchOne({ resourceType: 'ProjectMembership', filters: [ @@ -379,6 +384,7 @@ export async function setLoginMembership(login: Login, membershipId: string): Pr } // Find the membership for the user + const systemRepo = getSystemRepo(); let membership = undefined; try { membership = await systemRepo.readResource('ProjectMembership', membershipId); @@ -477,6 +483,7 @@ export async function setLoginScope(login: Login, scope: string): Promise } // Otherwise update scope + const systemRepo = getSystemRepo(); return systemRepo.updateResource({ ...login, scope: submittedScopes.join(' '), @@ -495,6 +502,7 @@ export async function getAuthTokens(login: Login, profile: Reference({ ...login, granted: true, @@ -536,6 +544,7 @@ export async function getAuthTokens(login: Login, profile: Reference { + const systemRepo = getSystemRepo(); await systemRepo.updateResource({ ...login, revoked: true, @@ -550,6 +559,7 @@ export async function revokeLogin(login: Login): Promise { * @returns The user if found; otherwise, undefined. */ export async function getUserByExternalId(externalId: string, projectId: string): Promise { + const systemRepo = getSystemRepo(); const membership = await systemRepo.searchOne({ resourceType: 'ProjectMembership', filters: [ @@ -612,6 +622,7 @@ export async function getUserByEmail(email: string, projectId: string | undefine * @returns The user if found; otherwise, undefined. */ export async function getUserByEmailInProject(email: string, projectId: string): Promise { + const systemRepo = getSystemRepo(); const bundle = await systemRepo.search({ resourceType: 'User', filters: [ @@ -637,6 +648,7 @@ export async function getUserByEmailInProject(email: string, projectId: string): * @returns The user if found; otherwise, undefined. */ export async function getUserByEmailWithoutProject(email: string): Promise { + const systemRepo = getSystemRepo(); const bundle = await systemRepo.search({ resourceType: 'User', filters: [ @@ -784,6 +796,7 @@ export async function getLoginForAccessToken(accessToken: string): Promise('Login', claims.login_id); diff --git a/packages/server/src/scim/routes.test.ts b/packages/server/src/scim/routes.test.ts index 9e85dc9ec6..d8bed0a694 100644 --- a/packages/server/src/scim/routes.test.ts +++ b/packages/server/src/scim/routes.test.ts @@ -6,14 +6,15 @@ import request from 'supertest'; import { initApp, shutdownApp } from '../app'; import { registerNew } from '../auth/register'; import { loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; -import { addTestUser, withTestContext } from '../test.setup'; import { AuthenticatedRequestContext, requestContextStore } from '../context'; - -const app = express(); -let accessToken: string; +import { getSystemRepo } from '../fhir/repo'; +import { addTestUser, withTestContext } from '../test.setup'; describe('SCIM Routes', () => { + const app = express(); + const systemRepo = getSystemRepo(); + let accessToken: string; + beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); diff --git a/packages/server/src/scim/utils.ts b/packages/server/src/scim/utils.ts index 9f67248ba4..c499aed8d9 100644 --- a/packages/server/src/scim/utils.ts +++ b/packages/server/src/scim/utils.ts @@ -2,7 +2,7 @@ import { badRequest, forbidden, getReferenceString, OperationOutcomeError, Opera import { Project, ProjectMembership, Reference, User } from '@medplum/fhirtypes'; import { inviteUser } from '../admin/invite'; import { getConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { ScimListResponse, ScimUser } from './types'; /** @@ -14,6 +14,7 @@ import { ScimListResponse, ScimUser } from './types'; * @returns List of SCIM users in the project. */ export async function searchScimUsers(project: Project): Promise> { + const systemRepo = getSystemRepo(); const memberships = await systemRepo.searchResources({ resourceType: 'ProjectMembership', count: 1000, @@ -88,6 +89,7 @@ export async function createScimUser( * @returns The user. */ export async function readScimUser(project: Project, id: string): Promise { + const systemRepo = getSystemRepo(); const membership = await systemRepo.readResource('ProjectMembership', id); if (membership.project?.reference !== getReferenceString(project)) { throw new OperationOutcomeError(forbidden); @@ -107,6 +109,7 @@ export async function readScimUser(project: Project, id: string): Promise { + const systemRepo = getSystemRepo(); let membership = await systemRepo.readResource('ProjectMembership', scimUser.id as string); if (membership.project?.reference !== getReferenceString(project)) { throw new OperationOutcomeError(forbidden); @@ -139,6 +142,7 @@ export async function updateScimUser(project: Project, scimUser: ScimUser): Prom * @returns The user. */ export async function deleteScimUser(project: Project, id: string): Promise { + const systemRepo = getSystemRepo(); const membership = await systemRepo.readResource('ProjectMembership', id); if (membership.project?.reference !== getReferenceString(project)) { throw new OperationOutcomeError(forbidden); diff --git a/packages/server/src/seed.ts b/packages/server/src/seed.ts index 9072492607..a63f1f1f9c 100644 --- a/packages/server/src/seed.ts +++ b/packages/server/src/seed.ts @@ -2,7 +2,7 @@ import { createReference } from '@medplum/core'; import { Practitioner, Project, ProjectMembership, User } from '@medplum/fhirtypes'; import { NIL as nullUuid, v5 } from 'uuid'; import { bcryptHashPassword } from './auth/utils'; -import { systemRepo } from './fhir/repo'; +import { getSystemRepo } from './fhir/repo'; import { globalLogger } from './logger'; import { rebuildR4SearchParameters } from './seeds/searchparameters'; import { rebuildR4StructureDefinitions } from './seeds/structuredefinitions'; @@ -16,6 +16,8 @@ export async function seedDatabase(): Promise { return; } + const systemRepo = getSystemRepo(); + const [firstName, lastName, email] = ['Medplum', 'Admin', 'admin@example.com']; const passwordHash = await bcryptHashPassword('medplum_admin'); const superAdmin = await systemRepo.createResource({ @@ -78,5 +80,6 @@ export async function seedDatabase(): Promise { * @returns True if already seeded. */ function isSeeded(): Promise { + const systemRepo = getSystemRepo(); return systemRepo.searchOne({ resourceType: 'User' }); } diff --git a/packages/server/src/seeds/searchparameters.ts b/packages/server/src/seeds/searchparameters.ts index 81455738c7..efa2c15904 100644 --- a/packages/server/src/seeds/searchparameters.ts +++ b/packages/server/src/seeds/searchparameters.ts @@ -1,7 +1,7 @@ import { readJson } from '@medplum/definitions'; import { BundleEntry, SearchParameter } from '@medplum/fhirtypes'; import { getDatabasePool } from '../database'; -import { systemRepo } from '../fhir/repo'; +import { Repository, getSystemRepo } from '../fhir/repo'; import { globalLogger } from '../logger'; import { r4ProjectId } from '../seed'; @@ -12,15 +12,17 @@ export async function rebuildR4SearchParameters(): Promise { const client = getDatabasePool(); await client.query('DELETE FROM "SearchParameter" WHERE "projectId" = $1', [r4ProjectId]); + const systemRepo = getSystemRepo(); + for (const entry of readJson('fhir/r4/search-parameters.json').entry as BundleEntry[]) { - await createParameter(entry.resource as SearchParameter); + await createParameter(systemRepo, entry.resource as SearchParameter); } for (const entry of readJson('fhir/r4/search-parameters-medplum.json').entry as BundleEntry[]) { - await createParameter(entry.resource as SearchParameter); + await createParameter(systemRepo, entry.resource as SearchParameter); } } -async function createParameter(param: SearchParameter): Promise { +async function createParameter(systemRepo: Repository, param: SearchParameter): Promise { globalLogger.debug('SearchParameter: ' + param.name); await systemRepo.createResource({ ...param, diff --git a/packages/server/src/seeds/structuredefinitions.ts b/packages/server/src/seeds/structuredefinitions.ts index 771bafafda..72ff392c2c 100644 --- a/packages/server/src/seeds/structuredefinitions.ts +++ b/packages/server/src/seeds/structuredefinitions.ts @@ -1,7 +1,7 @@ import { readJson } from '@medplum/definitions'; import { Bundle, BundleEntry, Resource, StructureDefinition } from '@medplum/fhirtypes'; import { getDatabasePool } from '../database'; -import { systemRepo } from '../fhir/repo'; +import { Repository, getSystemRepo } from '../fhir/repo'; import { globalLogger } from '../logger'; import { r4ProjectId } from '../seed'; @@ -11,12 +11,17 @@ import { r4ProjectId } from '../seed'; export async function rebuildR4StructureDefinitions(): Promise { const client = getDatabasePool(); await client.query(`DELETE FROM "StructureDefinition" WHERE "projectId" = $1`, [r4ProjectId]); - await createStructureDefinitionsForBundle(readJson('fhir/r4/profiles-resources.json') as Bundle); - await createStructureDefinitionsForBundle(readJson('fhir/r4/profiles-medplum.json') as Bundle); - await createStructureDefinitionsForBundle(readJson('fhir/r4/profiles-others.json') as Bundle); + + const systemRepo = getSystemRepo(); + await createStructureDefinitionsForBundle(systemRepo, readJson('fhir/r4/profiles-resources.json') as Bundle); + await createStructureDefinitionsForBundle(systemRepo, readJson('fhir/r4/profiles-medplum.json') as Bundle); + await createStructureDefinitionsForBundle(systemRepo, readJson('fhir/r4/profiles-others.json') as Bundle); } -async function createStructureDefinitionsForBundle(structureDefinitions: Bundle): Promise { +async function createStructureDefinitionsForBundle( + systemRepo: Repository, + structureDefinitions: Bundle +): Promise { for (const entry of structureDefinitions.entry as BundleEntry[]) { const resource = entry.resource as Resource; diff --git a/packages/server/src/seeds/valuesets.ts b/packages/server/src/seeds/valuesets.ts index bf5600cb2d..cd30f66a3a 100644 --- a/packages/server/src/seeds/valuesets.ts +++ b/packages/server/src/seeds/valuesets.ts @@ -1,19 +1,20 @@ import { Operator } from '@medplum/core'; import { readJson } from '@medplum/definitions'; import { Bundle, BundleEntry, CodeSystem, ValueSet } from '@medplum/fhirtypes'; -import { systemRepo } from '../fhir/repo'; +import { Repository, getSystemRepo } from '../fhir/repo'; import { r4ProjectId } from '../seed'; /** * Imports all built-in ValueSets and CodeSystems into the database. */ export async function rebuildR4ValueSets(): Promise { + const systemRepo = getSystemRepo(); const files = ['valuesets.json', 'v2-tables.json', 'v3-codesystems.json', 'valuesets-medplum.json']; for (const file of files) { const bundle = readJson('fhir/r4/' + file) as Bundle; for (const entry of bundle.entry as BundleEntry[]) { const resource = entry.resource as CodeSystem | ValueSet; - await deleteExisting(resource); + await deleteExisting(systemRepo, resource); await systemRepo.createResource({ ...resource, meta: { ...resource.meta, project: r4ProjectId }, @@ -22,7 +23,7 @@ export async function rebuildR4ValueSets(): Promise { } } -async function deleteExisting(resource: CodeSystem | ValueSet): Promise { +async function deleteExisting(systemRepo: Repository, resource: CodeSystem | ValueSet): Promise { const bundle = await systemRepo.search({ resourceType: resource.resourceType, filters: [{ code: 'url', operator: Operator.EQUALS, value: resource.url as string }], diff --git a/packages/server/src/storage.test.ts b/packages/server/src/storage.test.ts index cc8a105b4e..b8335fbd62 100644 --- a/packages/server/src/storage.test.ts +++ b/packages/server/src/storage.test.ts @@ -7,8 +7,8 @@ import { resolve } from 'path'; import { Readable } from 'stream'; import request from 'supertest'; import { initApp, shutdownApp } from './app'; -import { loadTestConfig, MedplumServerConfig } from './config'; -import { systemRepo } from './fhir/repo'; +import { MedplumServerConfig, loadTestConfig } from './config'; +import { getSystemRepo } from './fhir/repo'; import { getBinaryStorage } from './fhir/storage'; import { withTestContext } from './test.setup'; @@ -21,12 +21,14 @@ describe('Storage Routes', () => { config = await loadTestConfig(); await initApp(app, config); - binary = await withTestContext(() => - systemRepo.createResource({ + binary = await withTestContext(async () => { + const systemRepo = getSystemRepo(); + const result = await systemRepo.createResource({ resourceType: 'Binary', contentType: ContentType.TEXT, - }) - ); + }); + return result; + }); const req = new Readable(); req.push('hello world'); @@ -55,12 +57,14 @@ describe('Storage Routes', () => { }); test('File not found', async () => { - const resource = await withTestContext(() => - systemRepo.createResource({ + const resource = await withTestContext(async () => { + const systemRepo = getSystemRepo(); + const result = await systemRepo.createResource({ resourceType: 'Binary', contentType: ContentType.TEXT, - }) - ); + }); + return result; + }); const req = new Readable(); req.push('hello world'); diff --git a/packages/server/src/storage.ts b/packages/server/src/storage.ts index 6730ceca6f..035b170a28 100644 --- a/packages/server/src/storage.ts +++ b/packages/server/src/storage.ts @@ -1,7 +1,7 @@ import { Binary } from '@medplum/fhirtypes'; import { Request, Response, Router } from 'express'; import { asyncWrap } from './async'; -import { systemRepo } from './fhir/repo'; +import { getSystemRepo } from './fhir/repo'; import { getBinaryStorage } from './fhir/storage'; export const storageRouter = Router(); @@ -17,6 +17,7 @@ storageRouter.get( } const { id } = req.params; + const systemRepo = getSystemRepo(); const binary = await systemRepo.readResource('Binary', id); try { diff --git a/packages/server/src/test.setup.ts b/packages/server/src/test.setup.ts index 91325a00c9..4ccd4e52b1 100644 --- a/packages/server/src/test.setup.ts +++ b/packages/server/src/test.setup.ts @@ -15,7 +15,7 @@ import { Express } from 'express'; import request from 'supertest'; import { inviteUser } from './admin/invite'; import { AuthenticatedRequestContext, requestContextStore } from './context'; -import { systemRepo } from './fhir/repo'; +import { getSystemRepo } from './fhir/repo'; import { generateAccessToken } from './oauth/keys'; import { tryLogin } from './oauth/utils'; @@ -30,6 +30,9 @@ export async function createTestProject( accessToken: string; }> { requestContextStore.enterWith(AuthenticatedRequestContext.system()); + + const systemRepo = getSystemRepo(); + const project = await systemRepo.createResource({ resourceType: 'Project', name: 'Test Project', @@ -116,6 +119,7 @@ export async function addTestUser( ): Promise<{ user: User; profile: ProfileResource; accessToken: string }> { requestContextStore.enterWith(AuthenticatedRequestContext.system()); if (accessPolicy) { + const systemRepo = getSystemRepo(); accessPolicy = await systemRepo.createResource({ ...accessPolicy, meta: { project: project.id }, diff --git a/packages/server/src/workers/cron.test.ts b/packages/server/src/workers/cron.test.ts index e36cd71a2e..b02c6c794a 100644 --- a/packages/server/src/workers/cron.test.ts +++ b/packages/server/src/workers/cron.test.ts @@ -1,19 +1,20 @@ -import { Repository, systemRepo } from '../fhir/repo'; -import { loadTestConfig } from '../config'; -import { initAppServices, shutdownApp } from '../app'; -import { AuditEvent, Bot, Project, ProjectMembership } from '@medplum/fhirtypes'; -import { createTestProject, withTestContext } from '../test.setup'; import { createReference } from '@medplum/core'; -import { convertTimingToCron, CronJobData, execBot, getCronQueue } from './cron'; -import { randomUUID } from 'crypto'; +import { AuditEvent, Bot, Project, ProjectMembership } from '@medplum/fhirtypes'; import { Job } from 'bullmq'; +import { randomUUID } from 'crypto'; +import { initAppServices, shutdownApp } from '../app'; +import { loadTestConfig } from '../config'; +import { Repository, getSystemRepo } from '../fhir/repo'; +import { createTestProject, withTestContext } from '../test.setup'; +import { CronJobData, convertTimingToCron, execBot, getCronQueue } from './cron'; jest.mock('node-fetch'); -let botProject: Project; -let botRepo: Repository; - describe('Cron Worker', () => { + const systemRepo = getSystemRepo(); + let botProject: Project; + let botRepo: Repository; + beforeAll(async () => { const config = await loadTestConfig(); await initAppServices(config); diff --git a/packages/server/src/workers/cron.ts b/packages/server/src/workers/cron.ts index c4d38ec0af..963cccbec1 100644 --- a/packages/server/src/workers/cron.ts +++ b/packages/server/src/workers/cron.ts @@ -5,7 +5,7 @@ import { isValidCron } from 'cron-validator'; import { MedplumServerConfig } from '../config'; import { getRequestContext } from '../context'; import { executeBot } from '../fhir/operations/execute'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { globalLogger } from '../logger'; import { findProjectMembership } from './utils'; @@ -104,7 +104,9 @@ export async function addCronJobs(resource: Resource): Promise { } const bot = resource; + // Adding a new feature for project that allows users to add a cron + const systemRepo = getSystemRepo(); const project = await systemRepo.readResource('Project', resource.meta?.project as string); if (!project.features?.includes('cron')) { ctx.logger.debug('Cron not enabled. Cron needs to be enabled in project to create cron job for bot'); @@ -206,6 +208,7 @@ export function convertTimingToCron(timing: Timing): string | undefined { } export async function execBot(job: Job): Promise { + const systemRepo = getSystemRepo(); const bot = await systemRepo.readReference({ reference: 'Bot/' + job.data.botId }); const project = bot.meta?.project as string; const runAs = await findProjectMembership(project, createReference(bot)); diff --git a/packages/server/src/workers/download.ts b/packages/server/src/workers/download.ts index 7712b23988..28a3838b87 100644 --- a/packages/server/src/workers/download.ts +++ b/packages/server/src/workers/download.ts @@ -5,7 +5,7 @@ import fetch from 'node-fetch'; import { Readable } from 'stream'; import { getConfig, MedplumServerConfig } from '../config'; import { getRequestContext } from '../context'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { getBinaryStorage } from '../fhir/storage'; import { globalLogger } from '../logger'; @@ -151,6 +151,7 @@ async function addDownloadJobData(job: DownloadJobData): Promise { * @param job - The download job details. */ export async function execDownloadJob(job: Job): Promise { + const systemRepo = getSystemRepo(); const ctx = getRequestContext(); const { resourceType, id, url } = job.data; diff --git a/packages/server/src/workers/subscription.test.ts b/packages/server/src/workers/subscription.test.ts index e7817d3830..20a2f181cf 100644 --- a/packages/server/src/workers/subscription.test.ts +++ b/packages/server/src/workers/subscription.test.ts @@ -8,7 +8,7 @@ import fetch from 'node-fetch'; import { initAppServices, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; import { getDatabasePool } from '../database'; -import { Repository, systemRepo } from '../fhir/repo'; +import { Repository, getSystemRepo } from '../fhir/repo'; import { getRedis } from '../redis'; import { createTestProject, withTestContext } from '../test.setup'; import { AuditEventOutcome } from '../util/auditevent'; @@ -17,10 +17,10 @@ import { closeSubscriptionWorker, execSubscriptionJob, getSubscriptionQueue } fr jest.mock('node-fetch'); jest.mock('ioredis'); -let repo: Repository; -let botRepo: Repository; - describe('Subscription Worker', () => { + const systemRepo = getSystemRepo(); + let repo: Repository; + let botRepo: Repository; let mockLambdaClient: AwsClientStub; beforeEach(() => { diff --git a/packages/server/src/workers/subscription.ts b/packages/server/src/workers/subscription.ts index 7b71dfa353..a66a994803 100644 --- a/packages/server/src/workers/subscription.ts +++ b/packages/server/src/workers/subscription.ts @@ -20,7 +20,7 @@ import { URL } from 'url'; import { MedplumServerConfig } from '../config'; import { getRequestContext } from '../context'; import { executeBot } from '../fhir/operations/execute'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo, Repository } from '../fhir/repo'; import { globalLogger } from '../logger'; import { getRedis } from '../redis'; import { createSubEventNotification } from '../subscriptions/websockets'; @@ -263,6 +263,7 @@ async function getSubscriptions(resource: Resource): Promise { if (!project) { return []; } + const systemRepo = getSystemRepo(); const subscriptions = await systemRepo.searchResources({ resourceType: 'Subscription', count: 1000, @@ -292,9 +293,10 @@ async function getSubscriptions(resource: Resource): Promise { * @param job - The subscription job details. */ export async function execSubscriptionJob(job: Job): Promise { + const systemRepo = getSystemRepo(); const { subscriptionId, resourceType, id, versionId, interaction, requestTime } = job.data; - const subscription = await tryGetSubscription(subscriptionId); + const subscription = await tryGetSubscription(systemRepo, subscriptionId); if (!subscription) { // If the subscription was deleted, then stop processing it. return; @@ -306,7 +308,7 @@ export async function execSubscriptionJob(job: Job): Promis } if (interaction !== 'delete') { - const currentVersion = await tryGetCurrentVersion(resourceType, id); + const currentVersion = await tryGetCurrentVersion(systemRepo, resourceType, id); if (!currentVersion) { // If the resource was deleted, then stop processing it. return; @@ -343,7 +345,7 @@ export async function execSubscriptionJob(job: Job): Promis } } -async function tryGetSubscription(subscriptionId: string): Promise { +async function tryGetSubscription(systemRepo: Repository, subscriptionId: string): Promise { try { return await systemRepo.readResource('Subscription', subscriptionId); } catch (err) { @@ -357,7 +359,11 @@ async function tryGetSubscription(subscriptionId: string): Promise { +async function tryGetCurrentVersion( + systemRepo: Repository, + resourceType: string, + id: string +): Promise { try { return await systemRepo.readResource(resourceType, id); } catch (err) { @@ -489,6 +495,7 @@ async function execBot( } // URL should be a Bot reference string + const systemRepo = getSystemRepo(); const bot = await systemRepo.readReference({ reference: url }); const project = bot.meta?.project as string; diff --git a/packages/server/src/workers/utils.ts b/packages/server/src/workers/utils.ts index 9fb3b2b53e..9e78db634e 100644 --- a/packages/server/src/workers/utils.ts +++ b/packages/server/src/workers/utils.ts @@ -11,10 +11,11 @@ import { Subscription, } from '@medplum/fhirtypes'; import { getRequestContext } from '../context'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { AuditEventOutcome } from '../util/auditevent'; export function findProjectMembership(project: string, profile: Reference): Promise { + const systemRepo = getSystemRepo(); return systemRepo.searchOne({ resourceType: 'ProjectMembership', filters: [ @@ -49,6 +50,7 @@ export async function createAuditEvent( subscription?: Subscription, bot?: Bot ): Promise { + const systemRepo = getSystemRepo(); const auditedEvent = subscription ?? resource; await systemRepo.createResource({ @@ -124,6 +126,7 @@ export async function isFhirCriteriaMet(subscription: Subscription, currentResou } async function getPreviousResource(currentResource: Resource): Promise { + const systemRepo = getSystemRepo(); const history = await systemRepo.readHistory(currentResource.resourceType, currentResource?.id as string); return history.entry?.find((_, idx) => { From 85cce868e31ea57186edc61f400d85b6fd117be1 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Thu, 8 Feb 2024 10:00:58 -0800 Subject: [PATCH 13/81] Server config setting for accurate count threshold (#3902) * Server config setting for accurate count threshold * Added config setting docs --- .../docs/docs/self-hosting/config-settings.md | 79 ++++++++++--------- packages/server/src/config.ts | 8 +- packages/server/src/fhir/search.ts | 4 +- 3 files changed, 47 insertions(+), 44 deletions(-) diff --git a/packages/docs/docs/self-hosting/config-settings.md b/packages/docs/docs/self-hosting/config-settings.md index d1d278100f..5956b6a8c3 100644 --- a/packages/docs/docs/self-hosting/config-settings.md +++ b/packages/docs/docs/self-hosting/config-settings.md @@ -109,45 +109,46 @@ For example, if your environment name is "prod", then the "baseUrl" parameter na You will also be prompted for a parameter "Type". The default option is "String". Medplum supports both "String" and "SecureString". "SecureString" is recommended for security and compliance purposes. -| Key | Description | Required | Created by | Default | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------- | ----------------------------------- | -| `port` | The port number that the API server binds to inside the Docker image. By default, you should use `8103`. In some cases, you may need to use `5000`. | yes | `init` | `8103` | -| `baseUrl` | The fully qualified base URL of the API server including a trailing slash. For example, `https://api.example.com/`. | yes | `init` | | -| `issuer` | The JWK issuer. By default, Medplum server uses built in OAuth, so `issuer` should be the same as `baseUrl`. | | | `baseUrl` | -| `jwksUrl` | The JWKS URL. By default, Medplum server uses built in OAuth, so `jwksUrl` should be `baseUrl` + `.well-known/jwks.json`. | | | `baseUrl` + `.well-known/jwks.json` | -| `authorizeUrl` | The OAuth authorize URL. By default, Medplum server uses built in OAuth, so `authorizeUrl` should be `baseUrl` + `oauth2/authorize`. | | | `baseUrl` + `oauth2/authorize` | -| `tokenUrl` | The OAuth token URL. By default, Medplum server uses built in OAuth, so `tokenUrl` should be `baseUrl` + `oauth2/token`. | | | `baseUrl` + `oauth2/token` | -| `userInfoUrl` | The OAuth userinfo URL. By default, Medplum server uses built in OAuth, so `userInfoUrl` should be `baseUrl` + `oauth2/userinfo`. | | | `baseUrl` + `oauth2/userinfo` | -| `appBaseUrl` | The fully qualified URL of the user-facing app. This is used for CORS and system generated emails. For example, `https://app.example.com/`. | yes | `init` | | -| `logLevel` | Verbosity of logging: `'NONE'`, `'ERROR'`, `'WARN'`, `'INFO'`, `'DEBUG'` | | | `'INFO'` | -| `binaryStorage` | Where to store binary contents. This should be the CDK config `storageBucketName` with `s3:` prefix. For example, `s3:medplum-storage`. | yes | `init` | | -| `storageBaseUrl` | The fully qualified base URL of the binary storage. This should be the CDK config `storageDomainName` with `https://` prefix. For example, `https://storage.medplum.com/binary/`. | yes | `init` | | -| `signingKeyId` | The AWS key ID of the CloudFront signing key that you created before. | yes | `cdk` | | -| `signingKey` | The private key of the CloudFront signing key. | yes | `init` | | -| `signingKeyPassphrase` | The passphrase of the CloudFront signing key. | yes | `init` | | -| `allowedOrigins` | Optional comma separated list of allowed origins for [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) requests. `appBaseUrl` is included automatically. See [Setting Up CORS](/docs/self-hosting/setting-up-cors) for more details. | | | | -| `supportEmail` | The email address to use when sending system generated messages. This email address must be registered in AWS SES. | yes | `init` | | -| `googleClientId` | If using Google Authentication, this is the Google Client ID. | | | | -| `googleClientSecret` | If using Google Authentication, this is the Google Client Secret. | | | | -| `recaptchaSiteKey` | If using reCAPTCHA, this is the reCAPTCHA site key. | | | | -| `recaptchaSecretKey` | If using reCAPTCHA, this is the reCAPTCHA secret key. | | | | -| `botLambdaRoleArn` | If using Medplum Bots, this is the ARN of the [Lambda execution role](https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html). | | `cdk` | | -| `botLambdaLayerName` | If using Medplum Bots, this is the name of the [Lambda layer](https://docs.aws.amazon.com/lambda/latest/dg/invocation-layers.html). For example, `medplum-bot-layer`. | | | `medplum-bot-layer` | -| `database` | The database connection details as a JSON object. Only available when using JSON config file. See [AWS Secrets](#aws-secrets). | | | | -| `DatabaseSecrets` | The AWS Secret ID containing database connection details (created automatically by CDK). Only available when using AWS Parameter Store config. See [AWS Secrets](#aws-secrets). | | `cdk` | | -| `redis` | The redis connection details as a JSON object. Only available when using JSON config file. See [AWS Secrets](#aws-secrets). | | | | -| `RedisSecrets` | The AWS Secret ID containing Redis connection details (created automatically by CDK). Only available when using AWS Parameter Store config. See [AWS Secrets](#aws-secrets). | | `cdk` | | -| `logRequests` | Optional flag to log individual HTTP requests. | | | `false` | -| `saveAuditEvents` | Optional flag to save `AuditEvent` resources for all auth and RESTful operations in the database. | | | `false` | -| `logAuditEvents` | Optional flag to log `AuditEvent` resources for all auth and RESTful operations to the logger. | | | `false` | -| `auditEventLogGroup` | Optional AWS CloudWatch Log Group name for `AuditEvent` logs. If not specified, `AuditEvent` logs use the default logger. | | | | -| `auditEventLogStream` | Optional AWS CloudWatch Log Stream name for `AuditEvent` logs. Only applies if `auditEventLogGroup` is set. Uses `os.hostname()` as the default. | | | `os.hostname()` | -| `registerEnabled` | Optional flag whether new user registration is enabled. | | | `true` | -| `maxJsonSize` | Maximum JSON size for API calls. String is parsed with the [bytes](https://www.npmjs.com/package/bytes) library. Default is `1mb`. | | | `1mb` | -| `smtp` | Optional SMTP email settings to use SMTP for email. See [Sending SMTP Emails](/docs/self-hosting/sendgrid) for more details. | | | | -| `awsRegion` | The AWS Region identifier. | | `cdk` | `us-east-1` | -| `otlpMetricsEndpoint` | Optional OTLP metrics endpoint for OpenTelemetry. For example, `http://localhost:4318/v1/metrics`. See [OpenTelemetry](/docs/self-hosting/opentelemetry) for more details. | | | | -| `otlpTraceEndpoint` | Optional OTLP trace endpoint for OpenTelemetry. For example, `http://localhost:4318/v1/traces`. See [OpenTelemetry](/docs/self-hosting/opentelemetry) for more details. | | | | +| Key | Description | Required | Created by | Default | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------- | ----------------------------------- | +| `port` | The port number that the API server binds to inside the Docker image. By default, you should use `8103`. In some cases, you may need to use `5000`. | yes | `init` | `8103` | +| `baseUrl` | The fully qualified base URL of the API server including a trailing slash. For example, `https://api.example.com/`. | yes | `init` | | +| `issuer` | The JWK issuer. By default, Medplum server uses built in OAuth, so `issuer` should be the same as `baseUrl`. | | | `baseUrl` | +| `jwksUrl` | The JWKS URL. By default, Medplum server uses built in OAuth, so `jwksUrl` should be `baseUrl` + `.well-known/jwks.json`. | | | `baseUrl` + `.well-known/jwks.json` | +| `authorizeUrl` | The OAuth authorize URL. By default, Medplum server uses built in OAuth, so `authorizeUrl` should be `baseUrl` + `oauth2/authorize`. | | | `baseUrl` + `oauth2/authorize` | +| `tokenUrl` | The OAuth token URL. By default, Medplum server uses built in OAuth, so `tokenUrl` should be `baseUrl` + `oauth2/token`. | | | `baseUrl` + `oauth2/token` | +| `userInfoUrl` | The OAuth userinfo URL. By default, Medplum server uses built in OAuth, so `userInfoUrl` should be `baseUrl` + `oauth2/userinfo`. | | | `baseUrl` + `oauth2/userinfo` | +| `appBaseUrl` | The fully qualified URL of the user-facing app. This is used for CORS and system generated emails. For example, `https://app.example.com/`. | yes | `init` | | +| `logLevel` | Verbosity of logging: `'NONE'`, `'ERROR'`, `'WARN'`, `'INFO'`, `'DEBUG'` | | | `'INFO'` | +| `binaryStorage` | Where to store binary contents. This should be the CDK config `storageBucketName` with `s3:` prefix. For example, `s3:medplum-storage`. | yes | `init` | | +| `storageBaseUrl` | The fully qualified base URL of the binary storage. This should be the CDK config `storageDomainName` with `https://` prefix. For example, `https://storage.medplum.com/binary/`. | yes | `init` | | +| `signingKeyId` | The AWS key ID of the CloudFront signing key that you created before. | yes | `cdk` | | +| `signingKey` | The private key of the CloudFront signing key. | yes | `init` | | +| `signingKeyPassphrase` | The passphrase of the CloudFront signing key. | yes | `init` | | +| `allowedOrigins` | Optional comma separated list of allowed origins for [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) requests. `appBaseUrl` is included automatically. See [Setting Up CORS](/docs/self-hosting/setting-up-cors) for more details. | | | | +| `supportEmail` | The email address to use when sending system generated messages. This email address must be registered in AWS SES. | yes | `init` | | +| `googleClientId` | If using Google Authentication, this is the Google Client ID. | | | | +| `googleClientSecret` | If using Google Authentication, this is the Google Client Secret. | | | | +| `recaptchaSiteKey` | If using reCAPTCHA, this is the reCAPTCHA site key. | | | | +| `recaptchaSecretKey` | If using reCAPTCHA, this is the reCAPTCHA secret key. | | | | +| `botLambdaRoleArn` | If using Medplum Bots, this is the ARN of the [Lambda execution role](https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html). | | `cdk` | | +| `botLambdaLayerName` | If using Medplum Bots, this is the name of the [Lambda layer](https://docs.aws.amazon.com/lambda/latest/dg/invocation-layers.html). For example, `medplum-bot-layer`. | | | `medplum-bot-layer` | +| `database` | The database connection details as a JSON object. Only available when using JSON config file. See [AWS Secrets](#aws-secrets). | | | | +| `DatabaseSecrets` | The AWS Secret ID containing database connection details (created automatically by CDK). Only available when using AWS Parameter Store config. See [AWS Secrets](#aws-secrets). | | `cdk` | | +| `redis` | The redis connection details as a JSON object. Only available when using JSON config file. See [AWS Secrets](#aws-secrets). | | | | +| `RedisSecrets` | The AWS Secret ID containing Redis connection details (created automatically by CDK). Only available when using AWS Parameter Store config. See [AWS Secrets](#aws-secrets). | | `cdk` | | +| `logRequests` | Optional flag to log individual HTTP requests. | | | `false` | +| `saveAuditEvents` | Optional flag to save `AuditEvent` resources for all auth and RESTful operations in the database. | | | `false` | +| `logAuditEvents` | Optional flag to log `AuditEvent` resources for all auth and RESTful operations to the logger. | | | `false` | +| `auditEventLogGroup` | Optional AWS CloudWatch Log Group name for `AuditEvent` logs. If not specified, `AuditEvent` logs use the default logger. | | | | +| `auditEventLogStream` | Optional AWS CloudWatch Log Stream name for `AuditEvent` logs. Only applies if `auditEventLogGroup` is set. Uses `os.hostname()` as the default. | | | `os.hostname()` | +| `registerEnabled` | Optional flag whether new user registration is enabled. | | | `true` | +| `maxJsonSize` | Maximum JSON size for API calls. String is parsed with the [bytes](https://www.npmjs.com/package/bytes) library. Default is `1mb`. | | | `1mb` | +| `smtp` | Optional SMTP email settings to use SMTP for email. See [Sending SMTP Emails](/docs/self-hosting/sendgrid) for more details. | | | | +| `awsRegion` | The AWS Region identifier. | | `cdk` | `us-east-1` | +| `otlpMetricsEndpoint` | Optional OTLP metrics endpoint for OpenTelemetry. For example, `http://localhost:4318/v1/metrics`. See [OpenTelemetry](/docs/self-hosting/opentelemetry) for more details. | | | | +| `otlpTraceEndpoint` | Optional OTLP trace endpoint for OpenTelemetry. For example, `http://localhost:4318/v1/traces`. See [OpenTelemetry](/docs/self-hosting/opentelemetry) for more details. | | | | +| `accurateCountThreshold` | Optional threshold for accurate count queries. The server will always perform an estimate count first (to protect database performance), and an accurate count if the estimate is below this threshold. | | | `1000000` | :::tip Local Config To make changes to the server config after your first deploy, you must the edit parameter values _directly in AWS parameter store_ diff --git a/packages/server/src/config.ts b/packages/server/src/config.ts index 63021d7f9e..6ce9c707ba 100644 --- a/packages/server/src/config.ts +++ b/packages/server/src/config.ts @@ -50,6 +50,7 @@ export interface MedplumServerConfig { shutdownTimeoutMilliseconds?: number; heartbeatMilliseconds?: number; heartbeatEnabled?: boolean; + accurateCountThreshold: number; /** @deprecated */ auditEventLogGroup?: string; @@ -189,7 +190,7 @@ function loadEnvConfig(): MedplumServerConfig { key = key.toLowerCase().replace(/_([a-z])/g, (g) => g[1].toUpperCase()); if (isIntegerConfig(key)) { - currConfig.port = parseInt(value ?? '', 10); + currConfig[key] = parseInt(value ?? '', 10); } else if (isBooleanConfig(key)) { currConfig[key] = value === 'true'; } else if (isObjectConfig(key)) { @@ -244,7 +245,7 @@ async function loadAwsConfig(path: string): Promise { } else if (key === 'RedisSecrets') { config['redis'] = await loadAwsSecrets(region, value); } else if (isIntegerConfig(key)) { - config.port = parseInt(value, 10); + config[key] = parseInt(value, 10); } else if (isBooleanConfig(key)) { config[key] = value === 'true'; } else { @@ -294,11 +295,12 @@ function addDefaults(config: MedplumServerConfig): MedplumServerConfig { config.bcryptHashSalt = config.bcryptHashSalt || 10; config.bullmq = { concurrency: 10, removeOnComplete: { count: 1 }, removeOnFail: { count: 1 }, ...config.bullmq }; config.shutdownTimeoutMilliseconds = config.shutdownTimeoutMilliseconds ?? 30000; + config.accurateCountThreshold = config.accurateCountThreshold ?? 1000000; return config; } function isIntegerConfig(key: string): boolean { - return key === 'port'; + return key === 'port' || key === 'accurateCountThreshold'; } function isBooleanConfig(key: string): boolean { diff --git a/packages/server/src/fhir/search.ts b/packages/server/src/fhir/search.ts index 03377ed243..2aeaa372fe 100644 --- a/packages/server/src/fhir/search.ts +++ b/packages/server/src/fhir/search.ts @@ -394,7 +394,7 @@ function getSearchUrl(searchRequest: SearchRequest): string { * Returns the count for a search request. * This ignores page number and page size. * We always start with an "estimate" count to protect against expensive queries. - * If the estimate is less than 100,000, then we run an accurate count. + * If the estimate is less than the "accurateCountThreshold" config setting (default 1,000,000), then we run an accurate count. * @param repo - The repository. * @param searchRequest - The search request. * @param rowCount - The number of matching results if found. @@ -402,7 +402,7 @@ function getSearchUrl(searchRequest: SearchRequest): string { */ async function getCount(repo: Repository, searchRequest: SearchRequest, rowCount: number | undefined): Promise { const estimateCount = await getEstimateCount(repo, searchRequest, rowCount); - if (estimateCount < 100000) { + if (estimateCount < getConfig().accurateCountThreshold) { return getAccurateCount(repo, searchRequest); } return estimateCount; From 806f4a6f2328383333ab468e4586137d05dd8fd6 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Thu, 8 Feb 2024 10:14:43 -0800 Subject: [PATCH 14/81] Remove TURBO_REMOTE_ONLY from build step (#3907) --- .github/workflows/build.yml | 4 ++-- .github/workflows/deploy.yml | 4 ++-- .github/workflows/publish.yml | 12 ++++++------ .github/workflows/staging.yml | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5fde95babb..b71d0ce6e7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,8 +23,8 @@ jobs: NODE_VERSION: ${{ matrix.node-version }} PG_VERSION: ${{ matrix.pg-version }} TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: 'medplum' - TURBO_REMOTE_ONLY: true + TURBO_TEAM: ${{ secrets.TURBO_TEAM }} + TURBO_REMOTE_ONLY: ${{ secrets.TURBO_REMOTE_ONLY }} services: postgres: image: postgres:${{ matrix.pg-version }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ca0ee0898e..926e5f7248 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -13,8 +13,8 @@ jobs: env: NODE_VERSION: '20' TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: 'medplum' - TURBO_REMOTE_ONLY: true + TURBO_TEAM: ${{ secrets.TURBO_TEAM }} + TURBO_REMOTE_ONLY: ${{ secrets.TURBO_REMOTE_ONLY }} steps: - uses: actions/checkout@v3 with: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6b2ee28ff0..636f95f3cd 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,8 +13,8 @@ jobs: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: 'medplum' - TURBO_REMOTE_ONLY: true + TURBO_TEAM: ${{ secrets.TURBO_TEAM }} + TURBO_REMOTE_ONLY: ${{ secrets.TURBO_REMOTE_ONLY }} permissions: actions: read contents: write @@ -96,8 +96,8 @@ jobs: env: NODE_VERSION: '20' TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: 'medplum' - TURBO_REMOTE_ONLY: true + TURBO_TEAM: ${{ secrets.TURBO_TEAM }} + TURBO_REMOTE_ONLY: ${{ secrets.TURBO_REMOTE_ONLY }} permissions: actions: read contents: write @@ -171,8 +171,8 @@ jobs: env: NODE_VERSION: '20' TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: 'medplum' - TURBO_REMOTE_ONLY: true + TURBO_TEAM: ${{ secrets.TURBO_TEAM }} + TURBO_REMOTE_ONLY: ${{ secrets.TURBO_REMOTE_ONLY }} permissions: actions: read contents: write diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index b6ec15068c..6cc948ccda 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -13,8 +13,8 @@ jobs: env: NODE_VERSION: '20' TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: 'medplum' - TURBO_REMOTE_ONLY: true + TURBO_TEAM: ${{ secrets.TURBO_TEAM }} + TURBO_REMOTE_ONLY: ${{ secrets.TURBO_REMOTE_ONLY }} steps: - uses: actions/checkout@v3 with: From bc52f0152f41529ac8559d2113319db2c7a250ed Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Thu, 8 Feb 2024 10:14:57 -0800 Subject: [PATCH 15/81] feat: Support OperationDefinition inputs for patient-everything (#3908) * feat: Support OperationDefinition inputs for patient-everything * Implement _since to start * Read the OperationDefinition * Define a PatientEverythingParameters type, modelled after the CodeSystemLookup and CodeSystemValidate Operations * Read parameters with parseInputParameters and extend getPatientEverything to support params input * Formal spec for the OperationDefinition is here: https://build.fhir.org/operation-patient-everything.html * Cleanup and tests --------- Co-authored-by: Joshua Kelly --- .../fhir/operations/patienteverything.test.ts | 20 +++++++++ .../src/fhir/operations/patienteverything.ts | 45 +++++++++++++++---- .../src/fhir/operations/utils/parameters.ts | 7 ++- 3 files changed, 61 insertions(+), 11 deletions(-) diff --git a/packages/server/src/fhir/operations/patienteverything.test.ts b/packages/server/src/fhir/operations/patienteverything.test.ts index 268dbcd7b8..1f5de73474 100644 --- a/packages/server/src/fhir/operations/patienteverything.test.ts +++ b/packages/server/src/fhir/operations/patienteverything.test.ts @@ -71,5 +71,25 @@ describe('Patient Everything Operation', () => { .set('Authorization', 'Bearer ' + accessToken); expect(res4.status).toBe(200); expect(res4.body.entry).toHaveLength(3); + + // Create another observation + const res5 = await request(app) + .post(`/fhir/R4/Observation`) + .set('Authorization', 'Bearer ' + accessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send({ + resourceType: 'Observation', + status: 'final', + code: { coding: [{ system: LOINC, code: '12345-6' }] }, + subject: createReference(res1.body as Patient), + }); + expect(res5.status).toBe(201); + + // Execute the operation with _since + const res6 = await request(app) + .get(`/fhir/R4/Patient/${res1.body.id}/$everything?_since=${res5.body.meta?.lastUpdated}`) + .set('Authorization', 'Bearer ' + accessToken); + expect(res6.status).toBe(200); + expect(res6.body.entry).toHaveLength(1); }); }); diff --git a/packages/server/src/fhir/operations/patienteverything.ts b/packages/server/src/fhir/operations/patienteverything.ts index 8e9377e195..54cab79074 100644 --- a/packages/server/src/fhir/operations/patienteverything.ts +++ b/packages/server/src/fhir/operations/patienteverything.ts @@ -8,11 +8,18 @@ import { ResourceType, } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; -import { getConfig } from '../../config'; import { getAuthenticatedContext } from '../../context'; import { getPatientCompartments } from '../patient'; import { Repository } from '../repo'; -import { sendResponse } from '../response'; +import { getFullUrl, sendResponse } from '../response'; +import { getOperationDefinition } from './definitions'; +import { parseInputParameters } from './utils/parameters'; + +const operation = getOperationDefinition('Patient', 'everything'); + +type PatientEverythingParameters = { + _since?: string; +}; // Patient everything operation. // https://hl7.org/fhir/operation-patient-everything.html @@ -26,12 +33,13 @@ import { sendResponse } from '../response'; export async function patientEverythingHandler(req: Request, res: Response): Promise { const ctx = getAuthenticatedContext(); const { id } = req.params; + const params = parseInputParameters(operation, req); // First read the patient to verify access const patient = await ctx.repo.readResource('Patient', id); // Then read all of the patient data - const bundle = await getPatientEverything(ctx.repo, patient); + const bundle = await getPatientEverything(ctx.repo, patient, params); await sendResponse(res, allOk, bundle); } @@ -41,13 +49,28 @@ export async function patientEverythingHandler(req: Request, res: Response): Pro * Searches for all resources related to the patient. * @param repo - The repository. * @param patient - The root patient. + * @param params - The operation input parameters. * @returns The patient everything search result bundle. */ -export async function getPatientEverything(repo: Repository, patient: Patient): Promise { +export async function getPatientEverything( + repo: Repository, + patient: Patient, + params?: PatientEverythingParameters +): Promise { const patientRef = getReferenceString(patient); const resourceList = getPatientCompartments().resource as CompartmentDefinitionResource[]; const searches: SearchRequest[] = []; + // Build a list of filters to apply to the searches + const filters = []; + if (params?._since) { + filters.push({ + code: '_lastUpdated', + operator: Operator.GREATER_THAN_OR_EQUALS, + value: params._since, + }); + } + // Build a list of searches for (const resource of resourceList) { const searchParams = resource.param; @@ -64,6 +87,7 @@ export async function getPatientEverything(repo: Repository, patient: Patient): operator: Operator.EQUALS, value: patientRef, }, + ...filters, ], }); } @@ -75,12 +99,15 @@ export async function getPatientEverything(repo: Repository, patient: Patient): const searchResults = await Promise.all(promises); // Build the result bundle - const entry: BundleEntry[] = [ - { - fullUrl: `${getConfig().baseUrl}fhir/R4/Patient/${patient.id}`, + const entry: BundleEntry[] = []; + + if (!params?._since || (patient.meta?.lastUpdated as string) >= params?._since) { + entry.push({ + fullUrl: getFullUrl('Patient', patient.id as string), resource: patient, - }, - ]; + }); + } + const resourceSet = new Set([getReferenceString(patient)]); for (const searchResult of searchResults) { if (searchResult.entry) { diff --git a/packages/server/src/fhir/operations/utils/parameters.ts b/packages/server/src/fhir/operations/utils/parameters.ts index 0f6ff1ee54..22713bf2e1 100644 --- a/packages/server/src/fhir/operations/utils/parameters.ts +++ b/packages/server/src/fhir/operations/utils/parameters.ts @@ -16,9 +16,9 @@ import { ParametersParameter, } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; +import { getRequestContext } from '../../../context'; import { sendOutcome } from '../../outcomes'; import { sendResponse } from '../../response'; -import { getRequestContext } from '../../../context'; export function parseParameters(input: T | Parameters): T { if (input && typeof input === 'object' && 'resourceType' in input && input.resourceType === 'Parameters') { @@ -42,7 +42,10 @@ export function parseInputParameters(operation: OperationDefinition, req: Req return {} as any; } - const input = req.body; + // If the request is a GET request, use the query parameters + // Otherwise, use the body + const input = req.method === 'GET' ? req.query : req.body; + const inputParameters = operation.parameter.filter((p) => p.use === 'in'); if (input.resourceType === 'Parameters') { if (!input.parameter) { From 11822023656c4b9491d2def8182ccd55585ec32f Mon Sep 17 00:00:00 2001 From: Matt Long Date: Thu, 8 Feb 2024 10:25:37 -0800 Subject: [PATCH 16/81] Expand profile operation (#3875) * Add StructureDefinition $expandProfile operation * Use new endpoint in medplum.requestProfileSchema * Use new endpoint when viewing profiles * Add tests * Make most search tests project-scoped Previously, all search tests used systemRepo. Since systemRepo searches all projects by default, this had the potential for resources created by other tests to alter the results of search tests * Fix profile stories * More tests * Reduce complexity * Add circuit breaker(s) * PR feedback on client * Remove test.only --- .../app/src/resource/ProfilesPage.test.tsx | 13 +- packages/core/src/client.test.ts | 53 +- packages/core/src/client.ts | 50 +- .../ResourceArrayInput.utils.ts | 4 +- .../src/ResourceForm/ResourceForm.stories.tsx | 35 +- .../src/ResourceForm/ResourceForm.test.tsx | 31 +- .../react/src/ResourceForm/ResourceForm.tsx | 8 +- .../structuredefinitionexpandprofile.test.ts | 342 + .../structuredefinitionexpandprofile.ts | 122 + packages/server/src/fhir/routes.ts | 4 + packages/server/src/fhir/search.test.ts | 5921 +++++++++-------- 11 files changed, 3598 insertions(+), 2985 deletions(-) create mode 100644 packages/server/src/fhir/operations/structuredefinitionexpandprofile.test.ts create mode 100644 packages/server/src/fhir/operations/structuredefinitionexpandprofile.ts diff --git a/packages/app/src/resource/ProfilesPage.test.tsx b/packages/app/src/resource/ProfilesPage.test.tsx index 1290c9bb65..ad280d1438 100644 --- a/packages/app/src/resource/ProfilesPage.test.tsx +++ b/packages/app/src/resource/ProfilesPage.test.tsx @@ -7,15 +7,26 @@ import { act, fireEvent, render, screen, waitFor } from '@testing-library/react' import { Suspense } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { AppRoutes } from '../AppRoutes'; +import { loadDataType } from '@medplum/core'; const medplum = new MockClient(); describe('ProfilesPage', () => { const fishPatientProfile = FishPatientResources.getFishPatientProfileSD(); beforeAll(async () => { + const loadedProfileUrls: string[] = []; for (const profile of [fishPatientProfile, FishPatientResources.getFishSpeciesExtensionSD()]) { - await medplum.createResourceIfNoneExist(profile, `url:${profile.url}`); + const sd = await medplum.createResourceIfNoneExist(profile, `url:${profile.url}`); + loadedProfileUrls.push(sd.url); + loadDataType(sd, sd.url); } + medplum.requestProfileSchema = jest.fn((profileUrl) => { + if (loadedProfileUrls.includes(profileUrl)) { + return Promise.resolve([profileUrl]); + } else { + throw new Error('unexpected profileUrl'); + } + }); }); async function setup(url: string): Promise { diff --git a/packages/core/src/client.test.ts b/packages/core/src/client.test.ts index 009affa6c8..f9c08d4a21 100644 --- a/packages/core/src/client.test.ts +++ b/packages/core/src/client.test.ts @@ -66,8 +66,9 @@ const schemaResponse = { }; const patientProfileUrl = 'http://example.com/patient-profile'; +const patientProfileExtensionUrl = 'http://example.com/patient-profile-extension'; -const profileSchemaResponse = { +const profileSD = { resourceType: 'StructureDefinition', name: 'PatientProfile', url: patientProfileUrl, @@ -102,6 +103,31 @@ const profileSchemaResponse = { }, ], }, + { + path: 'Patient.extension', + sliceName: 'fancy', + type: [ + { + code: 'Extension', + profile: [patientProfileExtensionUrl], + }, + ], + }, + ], + }, +}; + +const profileExtensionSD = { + resourceType: 'StructureDefinition', + type: 'Extension', + derivation: 'constraint', + name: 'PatientProfile', + url: patientProfileExtensionUrl, + snapshot: { + element: [ + { + path: 'Extension', + }, ], }, }; @@ -1709,7 +1735,7 @@ describe('Client', () => { test('requestProfileSchema', async () => { const fetch = mockFetch(200, { resourceType: 'Bundle', - entry: [{ resource: profileSchemaResponse }], + entry: [{ resource: profileSD }], }); const client = new MedplumClient({ fetch }); @@ -1721,7 +1747,28 @@ describe('Client', () => { await request1; expect(isProfileLoaded(patientProfileUrl)).toBe(true); - expect(getDataType(profileSchemaResponse.name, patientProfileUrl)).toBeDefined(); + expect(getDataType(profileSD.name, patientProfileUrl)).toBeDefined(); + }); + + test('requestProfileSchema expandProfile', async () => { + const fetch = mockFetch(200, { + resourceType: 'Bundle', + entry: [{ resource: profileSD }, { resource: profileExtensionSD }], + }); + + const client = new MedplumClient({ fetch }); + + // Issue two requests simultaneously + const request1 = client.requestProfileSchema(patientProfileUrl, { expandProfile: true }); + const request2 = client.requestProfileSchema(patientProfileUrl, { expandProfile: true }); + expect(request2).toBe(request1); + + await request1; + await request2; + expect(isProfileLoaded(patientProfileUrl)).toBe(true); + expect(isProfileLoaded(patientProfileExtensionUrl)).toBe(true); + expect(getDataType(profileSD.name, patientProfileUrl)).toBeDefined(); + expect(getDataType(profileExtensionSD.name, patientProfileExtensionUrl)).toBeDefined(); }); test('Search', async () => { diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 1ef41854d1..e7370c6fc0 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -67,7 +67,7 @@ import { import { ReadablePromise } from './readablepromise'; import { ClientStorage, IClientStorage } from './storage'; import { indexSearchParameter } from './types'; -import { indexStructureDefinitionBundle, isDataTypeLoaded, isProfileLoaded } from './typeschema/types'; +import { indexStructureDefinitionBundle, isDataTypeLoaded, isProfileLoaded, loadDataType } from './typeschema/types'; import { CodeChallengeMethod, ProfileResource, @@ -590,6 +590,11 @@ export interface ValueSetExpandParams { count?: number; } +export interface RequestProfileSchemaOptions { + /** (optional) Whether to include nested profiles, e.g. from extensions. Defaults to false. */ + expandProfile?: boolean; +} + /** * The MedplumClient class provides a client for the Medplum FHIR server. * @@ -1658,32 +1663,45 @@ export class MedplumClient extends EventTarget { * If the schema is already cached, the promise is resolved immediately. * @category Schema * @param profileUrl - The FHIR URL of the profile - * @returns Promise to a schema with the requested profile. + * @param options - (optional) Additional options + * @returns Promise with an array of URLs of the profile(s) loaded. */ - requestProfileSchema(profileUrl: string): Promise { - if (isProfileLoaded(profileUrl)) { - return Promise.resolve(); + requestProfileSchema(profileUrl: string, options?: RequestProfileSchemaOptions): Promise { + if (!options?.expandProfile && isProfileLoaded(profileUrl)) { + return Promise.resolve([profileUrl]); } - const cacheKey = profileUrl + '-requestSchema'; + const cacheKey = profileUrl + '-requestSchema' + (options?.expandProfile ? '-nested' : ''); const cached = this.getCacheEntry(cacheKey, undefined); if (cached) { return cached.value; } - const promise = new ReadablePromise( + const promise = new ReadablePromise( (async () => { - // Just sort by lastUpdated. Ideally, it would also be based on a logical sort of version - // See https://hl7.org/fhir/references.html#canonical-matching for more discussion - const sd = await this.searchOne('StructureDefinition', { - url: profileUrl, - _sort: '-_lastUpdated', - }); - - if (!sd) { - console.warn(`No StructureDefinition found for ${profileUrl}!`); + if (options?.expandProfile) { + const url = this.fhirUrl('StructureDefinition', '$expand-profile'); + url.search = new URLSearchParams({ url: profileUrl }).toString(); + const sdBundle = await this.get>(url.toString()); + return bundleToResourceArray(sdBundle).map((sd) => { + loadDataType(sd, sd.url); + return sd.url; + }); } else { + // Just sort by lastUpdated. Ideally, it would also be based on a logical sort of version + // See https://hl7.org/fhir/references.html#canonical-matching for more discussion + const sd = await this.searchOne('StructureDefinition', { + url: profileUrl, + _sort: '-_lastUpdated', + }); + + if (!sd) { + console.warn(`No StructureDefinition found for ${profileUrl}!`); + return []; + } + indexStructureDefinitionBundle([sd], profileUrl); + return [profileUrl]; } })() ); diff --git a/packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.ts b/packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.ts index 4026bcf38b..926c451769 100644 --- a/packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.ts +++ b/packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.ts @@ -113,7 +113,7 @@ export async function prepareSlices({ const supportedSlices: SupportedSliceDefinition[] = []; const profileUrls: (string | undefined)[] = []; - const promises: Promise[] = []; + const promises: Promise[] = []; for (const slice of property.slicing.slices) { if (!isSupportedSliceDefinition(slice)) { console.debug('Unsupported slice definition', slice); @@ -132,7 +132,7 @@ export async function prepareSlices({ if (profileUrl) { promises.push(medplum.requestProfileSchema(profileUrl)); } else { - promises.push(Promise.resolve()); + promises.push(Promise.resolve([])); } } diff --git a/packages/react/src/ResourceForm/ResourceForm.stories.tsx b/packages/react/src/ResourceForm/ResourceForm.stories.tsx index 2a3c0a79a6..17e84c743a 100644 --- a/packages/react/src/ResourceForm/ResourceForm.stories.tsx +++ b/packages/react/src/ResourceForm/ResourceForm.stories.tsx @@ -10,8 +10,8 @@ import { Meta } from '@storybook/react'; import { Document } from '../Document/Document'; import { ResourceForm } from './ResourceForm'; import { useMedplum } from '@medplum/react-hooks'; -import { useEffect, useMemo, useState } from 'react'; -import { MedplumClient, deepClone, loadDataType } from '@medplum/core'; +import { useEffect, useLayoutEffect, useMemo, useState } from 'react'; +import { MedplumClient, RequestProfileSchemaOptions, deepClone, loadDataType } from '@medplum/core'; import { StructureDefinition } from '@medplum/fhirtypes'; export default { @@ -167,7 +167,30 @@ function useUSCoreDataTypes({ medplum }: { medplum: MedplumClient }): { loaded: return result; } -function useProfile(profileName: string): StructureDefinition { +function useFakeRequestProfileSchema(medplum: MedplumClient): void { + useLayoutEffect(() => { + const realRequestProfileSchema = medplum.requestProfileSchema; + async function fakeRequestProfileSchema( + profileUrl: string, + options?: RequestProfileSchemaOptions + ): Promise { + console.log( + 'Fake medplum.requestProfileSchema invoked but not doing anything; ensure expected profiles are already loaded', + profileUrl, + options + ); + return [profileUrl]; + } + + medplum.requestProfileSchema = fakeRequestProfileSchema; + + return () => { + medplum.requestProfileSchema = realRequestProfileSchema; + }; + }, [medplum]); +} + +function useUSCoreProfile(profileName: string): StructureDefinition { const profileSD = useMemo(() => { const result = (USCoreStructureDefinitionList as StructureDefinition[]).find((sd) => sd.name === profileName); if (!result) { @@ -181,8 +204,9 @@ function useProfile(profileName: string): StructureDefinition { export const USCorePatient = (): JSX.Element => { const medplum = useMedplum(); + useFakeRequestProfileSchema(medplum); const { loaded } = useUSCoreDataTypes({ medplum }); - const profileSD = useProfile('USCorePatientProfile'); + const profileSD = useUSCoreProfile('USCorePatientProfile'); const homerSimpsonUSCorePatient = useMemo(() => { return deepClone(HomerSimpsonUSCorePatient); @@ -207,8 +231,9 @@ export const USCorePatient = (): JSX.Element => { export const USCoreImplantableDevice = (): JSX.Element => { const medplum = useMedplum(); + useFakeRequestProfileSchema(medplum); const { loaded } = useUSCoreDataTypes({ medplum }); - const profileSD = useProfile('USCoreImplantableDeviceProfile'); + const profileSD = useUSCoreProfile('USCoreImplantableDeviceProfile'); const implantedKnee = useMemo(() => { return deepClone(ImplantableDeviceKnee); diff --git a/packages/react/src/ResourceForm/ResourceForm.test.tsx b/packages/react/src/ResourceForm/ResourceForm.test.tsx index ace1eee9b0..60bedfd1ba 100644 --- a/packages/react/src/ResourceForm/ResourceForm.test.tsx +++ b/packages/react/src/ResourceForm/ResourceForm.test.tsx @@ -1,6 +1,6 @@ -import { createReference } from '@medplum/core'; +import { HTTP_HL7_ORG, createReference, loadDataType } from '@medplum/core'; import { Observation, Patient, Specimen } from '@medplum/fhirtypes'; -import { HomerObservation1, MockClient } from '@medplum/mock'; +import { HomerObservation1, MockClient, USCoreStructureDefinitionList } from '@medplum/mock'; import { MedplumProvider } from '@medplum/react-hooks'; import { convertIsoToLocal, convertLocalToIso } from '../DateTimeInput/DateTimeInput.utils'; import { act, fireEvent, render, screen, waitFor } from '../test-utils/render'; @@ -9,10 +9,10 @@ import { ResourceForm, ResourceFormProps } from './ResourceForm'; const medplum = new MockClient(); describe('ResourceForm', () => { - async function setup(props: ResourceFormProps): Promise { + async function setup(props: ResourceFormProps, medplumClient?: MockClient): Promise { await act(async () => { render( - + ); @@ -223,4 +223,27 @@ describe('ResourceForm', () => { expect(patient.resourceType).toBe('Patient'); expect(patient.active).toBe(true); }); + + test('With profileUrl specified', async () => { + const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-implantable-device`; + const profilesToLoad = [profileUrl, `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-patient`]; + for (const url of profilesToLoad) { + const sd = USCoreStructureDefinitionList.find((sd) => sd.url === url); + if (!sd) { + fail(`could not find structure definition for ${url}`); + } + loadDataType(sd, sd.url); + } + + const onSubmit = jest.fn(); + + const mockedMedplum = new MockClient(); + const fakeRequestProfileSchema = jest.fn(async (profileUrl: string) => { + return [profileUrl]; + }); + mockedMedplum.requestProfileSchema = fakeRequestProfileSchema; + await setup({ defaultValue: { resourceType: 'Device' }, profileUrl, onSubmit }, mockedMedplum); + + expect(fakeRequestProfileSchema).toHaveBeenCalledTimes(1); + }); }); diff --git a/packages/react/src/ResourceForm/ResourceForm.tsx b/packages/react/src/ResourceForm/ResourceForm.tsx index 95e2811d9c..5511969d94 100644 --- a/packages/react/src/ResourceForm/ResourceForm.tsx +++ b/packages/react/src/ResourceForm/ResourceForm.tsx @@ -29,16 +29,18 @@ export function ResourceForm(props: ResourceFormProps): JSX.Element { if (props.profileUrl) { const profileUrl: string = props.profileUrl; medplum - .requestProfileSchema(props.profileUrl) + .requestProfileSchema(props.profileUrl, { expandProfile: true }) .then(() => { const profile = tryGetProfile(profileUrl); if (profile) { setSchemaLoaded(profile.name); } else { - console.log(`Schema not found for ${profileUrl}`); + console.error(`Schema not found for ${profileUrl}`); } }) - .catch(console.log); + .catch((reason) => { + console.error('Error in requestProfileSchema', reason); + }); } else { const schemaName = props.schemaName ?? defaultValue?.resourceType; medplum diff --git a/packages/server/src/fhir/operations/structuredefinitionexpandprofile.test.ts b/packages/server/src/fhir/operations/structuredefinitionexpandprofile.test.ts new file mode 100644 index 0000000000..00a735bc8a --- /dev/null +++ b/packages/server/src/fhir/operations/structuredefinitionexpandprofile.test.ts @@ -0,0 +1,342 @@ +import { USCoreStructureDefinitionList } from '@medplum/mock'; +import { ContentType, HTTP_HL7_ORG } from '@medplum/core'; +import express from 'express'; +import request from 'supertest'; +import { loadTestConfig } from '../../config'; +import { initApp, shutdownApp } from '../../app'; +import { createTestProject } from '../../test.setup'; +import { Bundle, ElementDefinition, StructureDefinition, StructureDefinitionSnapshot } from '@medplum/fhirtypes'; + +jest.mock('node-fetch'); + +const app = express(); + +async function createSDs(profileUrls: string[], accessToken: string): Promise { + for (const profileUrl of profileUrls) { + const sd = USCoreStructureDefinitionList.find((sd) => sd.url === profileUrl); + + if (!sd) { + fail(`could not find structure definition for ${profileUrl}`); + } + const res = await request(app) + .post(`/fhir/R4/StructureDefinition`) + .set('Authorization', 'Bearer ' + accessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send(sd); + expect(res.status).toEqual(201); + } +} + +describe('StructureDefinition $expand-profile', () => { + let accessToken: string; + + beforeAll(async () => { + const config = await loadTestConfig(); + await initApp(app, config); + }); + + beforeEach(async () => { + // A new project per test since tests are dependent on SDs being within search scope or not. + const project = await createTestProject(); + accessToken = project.accessToken; + }); + + afterAll(async () => { + await shutdownApp(); + }); + + test('Success with nested profiles', async () => { + const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-patient`; + const expectedProfiles = [ + profileUrl, + `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-race`, + `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-ethnicity`, + `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-birthsex`, + `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-genderIdentity`, + ]; + await createSDs(expectedProfiles, accessToken); + + const res = await request(app) + .get(`/fhir/R4/StructureDefinition/$expand-profile?url=${profileUrl}`) + .set('Authorization', 'Bearer ' + accessToken) + .send(); + expect(res.status).toEqual(200); + expect(res.body.resourceType).toEqual('Bundle'); + + const bundle = res.body as Bundle; + expect(bundle.entry?.length).toEqual(expectedProfiles.length); + for (const entry of bundle.entry || []) { + expect(expectedProfiles.includes(entry.resource?.url ?? '')).toEqual(true); + } + }); + + test('Success with missing nested profiles', async () => { + const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-patient`; + // us-core-patient references several other profiles, but they are not in the database + // so we expect to only get the profiles that are + const expectedProfiles = [profileUrl, `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-race`]; + await createSDs(expectedProfiles, accessToken); + + const res = await request(app) + .get(`/fhir/R4/StructureDefinition/$expand-profile?url=${profileUrl}`) + .set('Authorization', 'Bearer ' + accessToken) + .send(); + expect(res.status).toEqual(200); + expect(res.body.resourceType).toEqual('Bundle'); + + const bundle = res.body as Bundle; + expect(bundle.entry?.length).toEqual(expectedProfiles.length); + for (const entry of bundle.entry || []) { + expect(expectedProfiles.includes(entry.resource?.url ?? '')).toEqual(true); + } + }); + + test('Profile not found', async () => { + const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-race`; + + // Note that nothing is created in the database, so we expect an empty bundle + + const res = await request(app) + .get(`/fhir/R4/StructureDefinition/$expand-profile?url=${profileUrl}`) + .set('Authorization', 'Bearer ' + accessToken) + .send(); + expect(res.status).toEqual(400); + }); + + test('Profile URL not specified', async () => { + const res = await request(app) + .get(`/fhir/R4/StructureDefinition/$expand-profile`) + .set('Authorization', 'Bearer ' + accessToken) + .send(); + expect(res.status).toEqual(400); + }); + + test('Circuit breaker for deeply nested profiles', async () => { + const extensionCount = 10; + const sds = await createNestedStructureDefinitions(accessToken, extensionCount); + const sdUrls = sds.map((sd) => sd.url); + expect(sds.length).toBe(extensionCount + 1); + const profileUrl = sds[0].url; + const res = await request(app) + .get(`/fhir/R4/StructureDefinition/$expand-profile?url=${profileUrl}`) + .set('Authorization', 'Bearer ' + accessToken) + .send(); + expect(res.status).toEqual(200); + const bundle = res.body as Bundle; + expect(bundle.entry?.length).toEqual(sds.length); + for (const entry of bundle.entry || []) { + expect(sdUrls.includes(entry.resource?.url ?? '')).toEqual(true); + } + }); + + test('Circuit breaker for too deeply nested profiles', async () => { + const extensionCount = 11; + const sds = await createNestedStructureDefinitions(accessToken, extensionCount); + const sdUrls = sds.map((sd) => sd.url); + expect(sds.length).toBe(extensionCount + 1); + const profileUrl = sds[0].url; + const res = await request(app) + .get(`/fhir/R4/StructureDefinition/$expand-profile?url=${profileUrl}`) + .set('Authorization', 'Bearer ' + accessToken) + .send(); + expect(res.status).toEqual(200); + const bundle = res.body as Bundle; + expect(bundle.entry?.length).toEqual(sds.length - 1); // -1 because the last extension was not recursed due to circuit breaker + + for (const sdUrl of sdUrls.slice(0, -1)) { + expect(bundle.entry?.some((entry) => entry.resource?.url === sdUrl)).toEqual(true); + } + + const missingSdUrl = sdUrls[sdUrls.length - 1]; + expect(bundle.entry?.some((entry) => entry.resource?.url === missingSdUrl)).toEqual(false); + }); +}); + +async function createNestedStructureDefinitions( + accessToken: string, + extensionDepthCount: number +): Promise { + if (extensionDepthCount < 1) { + throw new Error('extensionDepthCount must be at least one'); + } + + const sds: StructureDefinition[] = []; + const sd: StructureDefinition = { + resourceType: 'StructureDefinition', + id: 'deeply-nested', + url: 'http://hl7.org/fhir/StructureDefinition/deeply-nested-profile', + name: 'DeeplyNestedProfile', + experimental: true, + date: '2024-02-07', + description: '', + kind: 'resource', + abstract: false, + status: 'draft', + type: 'Patient', + baseDefinition: 'http://hl7.org/fhir/StructureDefinition/Patient', + derivation: 'constraint', + snapshot: { + element: [ + { + id: 'Patient', + path: 'Patient', + definition: '\\-', + min: 0, + max: '*', + base: { path: 'Patient', min: 0, max: '*' }, + isModifier: false, + isSummary: false, + }, + { + id: 'Patient.extension', + path: 'Patient.extension', + definition: '\\-', + slicing: { discriminator: [{ type: 'value', path: 'url' }], ordered: false, rules: 'open' }, + min: 0, + max: '*', + base: { path: 'DomainResource.extension', min: 0, max: '*' }, + type: [{ code: 'Extension' }], + isModifier: false, + isSummary: false, + }, + { + id: 'Patient.extension:someExtension', + path: 'Patient.extension', + definition: '\\-', + sliceName: 'someExtension', + min: 0, + max: '1', + base: { path: 'DomainResource.extension', min: 0, max: '*' }, + type: [{ code: 'Extension', profile: [getExtensionUrl(0)] }], + }, + ], + }, + }; + + sds.push(sd); + for (let i = 0; i < extensionDepthCount; i++) { + const extension = createExtension( + `nested-extension-${i}`, + getExtensionUrl(i), + i === extensionDepthCount - 1 ? undefined : getExtensionUrl(i + 1) + ); + + sds.push(extension); + } + + for (const sd of sds) { + const res = await request(app) + .post(`/fhir/R4/StructureDefinition`) + .set('Authorization', 'Bearer ' + accessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send(sd); + expect(res.status).toEqual(201); + } + + return sds; +} + +function getExtensionUrl(index: number): string { + return `${HTTP_HL7_ORG}/fhir/StructureDefinition/deeply-nested-extension-${index}`; +} + +function createExtension(id: string, url: string, nestedExtensionUrl: string | undefined): StructureDefinition { + let nestedExtensionElement: ElementDefinition | undefined; + if (nestedExtensionUrl) { + nestedExtensionElement = { + id: 'Extension.extension:nestedExtension', + path: 'Extension.extension', + definition: '\\-', + sliceName: 'nestedExtension', + min: 0, + max: '1', + base: { path: 'Extension.extension', min: 0, max: '*' }, + type: [{ code: 'Extension', profile: [nestedExtensionUrl] }], + mustSupport: true, + isModifier: false, + isSummary: false, + }; + } + + const extension: StructureDefinition & { snapshot: StructureDefinitionSnapshot } = { + resourceType: 'StructureDefinition', + id, + url, + name: 'DeeplyNestedExtension', + title: 'Deeply Nested Extension', + status: 'draft', + description: 'An arbitrarily deeply nested extension for testing purposes', + fhirVersion: '4.0.1', + kind: 'complex-type', + abstract: false, + type: 'Extension', + baseDefinition: 'http://hl7.org/fhir/StructureDefinition/Extension', + derivation: 'constraint', + context: [{ type: 'element', expression: 'Element' }], + snapshot: { + element: [ + { + id: 'Extension', + path: 'Extension', + definition: '\\-', + min: 0, + max: '*', + base: { path: 'Extension', min: 0, max: '*' }, + isModifier: false, + }, + { + id: 'Extension.id', + path: 'Extension.id', + definition: '\\-', + min: 0, + max: '1', + base: { path: 'Extension.id', min: 0, max: '*' }, + type: [ + { + extension: [ + { + url: 'http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type', + valueUrl: 'string', + }, + ], + code: 'http://hl7.org/fhirpath/System.String', + }, + ], + isModifier: false, + isSummary: false, + }, + { + id: 'Extension.extension', + path: 'Extension.extension', + definition: '\\-', + slicing: { + discriminator: [ + { + type: 'value', + path: 'url', + }, + ], + description: 'Extensions are always sliced by (at least) url', + rules: 'open', + }, + min: 0, + max: '*', + base: { path: 'Extension.extension', min: 0, max: '*' }, + type: [ + { + code: 'Extension', + }, + ], + isModifier: false, + isSummary: false, + }, + ], + }, + }; + + if (nestedExtensionElement) { + extension.snapshot.element.push(nestedExtensionElement); + } + + return extension; +} diff --git a/packages/server/src/fhir/operations/structuredefinitionexpandprofile.ts b/packages/server/src/fhir/operations/structuredefinitionexpandprofile.ts new file mode 100644 index 0000000000..48021ee927 --- /dev/null +++ b/packages/server/src/fhir/operations/structuredefinitionexpandprofile.ts @@ -0,0 +1,122 @@ +import { allOk, badRequest, Operator } from '@medplum/core'; +import { Bundle, BundleEntry, StructureDefinition } from '@medplum/fhirtypes'; +import { Request, Response } from 'express'; +import { getAuthenticatedContext } from '../../context'; +import { Repository } from '../repo'; +import { getFullUrl, sendResponse } from '../response'; +import { sendOutcome } from '../outcomes'; + +/** + * Handles a StructureDefinition profile expansion request. + * Searches for all extensions related to the profile. + * @param req - The HTTP request. + * @param res - The HTTP response. + */ +export async function structureDefinitionExpandProfileHandler(req: Request, res: Response): Promise { + const ctx = getAuthenticatedContext(); + const { url } = req.query; + + if (!url || typeof url !== 'string') { + sendOutcome(res, badRequest('Profile url not specified')); + return; + } + + const profile = await fetchProfileByUrl(ctx.repo, url); + + if (!profile) { + sendOutcome(res, badRequest('Profile not found')); + return; + } + + const sds = await loadNestedStructureDefinitions(ctx.repo, profile, new Set([url]), 1); + + const bundle = bundleResults([profile, ...sds]); + + await sendResponse(res, allOk, bundle); +} + +async function fetchProfileByUrl(repo: Repository, url: string): Promise { + return repo.searchOne({ + resourceType: 'StructureDefinition', + filters: [ + { + code: 'url', + operator: Operator.EQUALS, + value: url, + }, + ], + sortRules: [ + { + code: 'version', + descending: true, + }, + ], + }); +} + +async function loadNestedStructureDefinitions( + repo: Repository, + profile: StructureDefinition, + searchedProfiles: Set, + depth: number +): Promise { + // Recurse at most 10 levels deep + if (depth > 10) { + return []; + } + + const profilesUrlsToLoad: string[] = []; + + profile.snapshot?.element?.forEach((element) => { + const profileUrls: string[] | undefined = element.type + ?.map((t) => t.profile) + .flat() + .filter((p): p is NonNullable => p !== undefined); + + profileUrls?.forEach((p) => { + if (!searchedProfiles.has(p)) { + profilesUrlsToLoad.push(p); + searchedProfiles.add(p); + } + }); + }); + + const promises: Promise[] = profilesUrlsToLoad.map((url) => + fetchProfileByUrl(repo, url) + ); + const response = []; + + const sds = await Promise.all(promises); + for (const result of sds) { + if (result === undefined) { + continue; + } + + response.push(result); + + // recursive loop + const nested = await loadNestedStructureDefinitions(repo, result, searchedProfiles, depth + 1); + response.push(...nested); + } + + return response; +} + +function bundleResults(profiles: StructureDefinition[]): Bundle { + const entry: BundleEntry[] = []; + + for (const profile of profiles) { + if (profile.id !== undefined) { + entry.push({ + fullUrl: getFullUrl('StructureDefinition', profile.id), + resource: profile, + }); + } + } + + return { + resourceType: 'Bundle', + type: 'searchset', + entry, + }; +} diff --git a/packages/server/src/fhir/routes.ts b/packages/server/src/fhir/routes.ts index f12100bacb..233e9296f2 100644 --- a/packages/server/src/fhir/routes.ts +++ b/packages/server/src/fhir/routes.ts @@ -30,6 +30,7 @@ import { resourceGraphHandler } from './operations/resourcegraph'; import { sendOutcome } from './outcomes'; import { isFhirJsonContentType, sendResponse } from './response'; import { smartConfigurationHandler, smartStylingHandler } from './smart'; +import { structureDefinitionExpandProfileHandler } from './operations/structuredefinitionexpandprofile'; export const fhirRouter = Router(); @@ -163,6 +164,9 @@ protectedRoutes.post('/:resourceType/:id/([$]|%24)expunge', asyncWrap(expungeHan // $get-ws-binding-token operation protectedRoutes.get('/Subscription/:id/([$]|%24)get-ws-binding-token', asyncWrap(getWsBindingTokenHandler)); +// StructureDefinition $expand-profile operation +protectedRoutes.get('/StructureDefinition/([$]|%24)expand-profile', asyncWrap(structureDefinitionExpandProfileHandler)); + // Validate create resource protectedRoutes.post( '/:resourceType/([$])validate', diff --git a/packages/server/src/fhir/search.test.ts b/packages/server/src/fhir/search.test.ts index 8672e9fdad..bf40fc433b 100644 --- a/packages/server/src/fhir/search.test.ts +++ b/packages/server/src/fhir/search.test.ts @@ -43,2109 +43,1893 @@ import { import { randomUUID } from 'crypto'; import { initAppServices, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; -import { bundleContains, withTestContext } from '../test.setup'; -import { getSystemRepo } from './repo'; +import { bundleContains, createTestProject, withTestContext } from '../test.setup'; +import { Repository, getSystemRepo } from './repo'; jest.mock('hibp'); jest.mock('ioredis'); describe('FHIR Search', () => { - const systemRepo = getSystemRepo(); + describe('project-scoped Repository', () => { + let repo: Repository; - beforeAll(async () => { - const config = await loadTestConfig(); - await initAppServices(config); - }); - - afterAll(async () => { - await shutdownApp(); - }); - - test('Search total', async () => { - const result1 = await systemRepo.search({ - resourceType: 'Patient', + beforeAll(async () => { + const config = await loadTestConfig(); + await initAppServices(config); + const testProject = await createTestProject(); + repo = new Repository({ + project: testProject.project.id as string, + author: { reference: 'User/' + randomUUID() }, + }); }); - expect(result1.total).toBeUndefined(); - expect(result1.link?.length).toBe(3); - const result2 = await systemRepo.search({ - resourceType: 'Patient', - total: 'none', + afterAll(async () => { + await shutdownApp(); }); - expect(result2.total).toBeUndefined(); - const result3 = await systemRepo.search({ - resourceType: 'Patient', - total: 'accurate', - }); - expect(result3.total).toBeDefined(); - expect(typeof result3.total).toBe('number'); + test('Search total', async () => { + withTestContext(async () => { + await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family: 'Smith' }], + }); + await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Bob'], family: 'Smith' }], + }); + const result1 = await repo.search({ + resourceType: 'Patient', + count: 1, + }); + expect(result1.total).toBeUndefined(); + expect(result1.link?.length).toBe(3); - const result4 = await systemRepo.search({ - resourceType: 'Patient', - total: 'estimate', - }); - expect(result4.total).toBeDefined(); - expect(typeof result4.total).toBe('number'); - }); + const result2 = await repo.search({ + resourceType: 'Patient', + total: 'none', + }); + expect(result2.total).toBeUndefined(); - test('Search count=0', async () => { - const result1 = await systemRepo.search({ - resourceType: 'Patient', - count: 0, + const result3 = await repo.search({ + resourceType: 'Patient', + total: 'accurate', + }); + expect(result3.total).toBeDefined(); + expect(typeof result3.total).toBe('number'); + + const result4 = await repo.search({ + resourceType: 'Patient', + total: 'estimate', + }); + expect(result4.total).toBeDefined(); + expect(typeof result4.total).toBe('number'); + }).catch((err) => { + throw err; + }); }); - expect(result1.entry).toBeUndefined(); - expect(result1.link).toBeDefined(); - expect(result1.link?.length).toBe(1); - }); - test('Search _summary', () => - withTestContext(async () => { - const subsetTag: Coding = { system: 'http://hl7.org/fhir/v3/ObservationValue', code: 'SUBSETTED' }; - const patient: Patient = { + test('Search count=0', async () => { + const result1 = await repo.search({ resourceType: 'Patient', - meta: { - profile: ['http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient'], - tag: [{ system: 'http://example.com/', code: 'test' }], - }, - text: { - status: 'generated', - div: '

', - }, - identifier: [ - { - use: 'usual', - type: { - coding: [ - { - system: 'http://terminology.hl7.org/CodeSystem/v2-0203', - code: 'MR', - display: 'Medical Record Number', - }, - ], - text: 'Medical Record Number', - }, - system: 'http://hospital.smarthealthit.org', - value: '1032702', + count: 0, + }); + expect(result1.entry).toBeUndefined(); + expect(result1.link).toBeDefined(); + expect(result1.link?.length).toBe(1); + }); + + test('Search _summary', () => + withTestContext(async () => { + const subsetTag: Coding = { system: 'http://hl7.org/fhir/v3/ObservationValue', code: 'SUBSETTED' }; + const patient: Patient = { + resourceType: 'Patient', + meta: { + profile: ['http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient'], + tag: [{ system: 'http://example.com/', code: 'test' }], }, - ], - active: true, - name: [ - { - use: 'old', - family: 'Shaw', - given: ['Amy', 'V.'], - period: { - start: '2016-12-06', - end: '2020-07-22', - }, + text: { + status: 'generated', + div: '
', }, - { - family: 'Baxter', - given: ['Amy', 'V.'], - suffix: ['PharmD'], - period: { - start: '2020-07-22', + identifier: [ + { + use: 'usual', + type: { + coding: [ + { + system: 'http://terminology.hl7.org/CodeSystem/v2-0203', + code: 'MR', + display: 'Medical Record Number', + }, + ], + text: 'Medical Record Number', + }, + system: 'http://hospital.smarthealthit.org', + value: '1032702', }, - }, - ], - telecom: [ - { - system: 'phone', - value: '555-555-5555', - use: 'home', - }, - { - system: 'email', - value: 'amy.shaw@example.com', - }, - ], - gender: 'female', - birthDate: '1987-02-20', - multipleBirthInteger: 2, - address: [ - { - use: 'old', - line: ['49 Meadow St'], - city: 'Mounds', - state: 'OK', - postalCode: '74047', - country: 'US', - period: { - start: '2016-12-06', - end: '2020-07-22', + ], + active: true, + name: [ + { + use: 'old', + family: 'Shaw', + given: ['Amy', 'V.'], + period: { + start: '2016-12-06', + end: '2020-07-22', + }, }, - }, - { - line: ['183 Mountain View St'], - city: 'Mounds', - state: 'OK', - postalCode: '74048', - country: 'US', - period: { - start: '2020-07-22', + { + family: 'Baxter', + given: ['Amy', 'V.'], + suffix: ['PharmD'], + period: { + start: '2020-07-22', + }, + }, + ], + telecom: [ + { + system: 'phone', + value: '555-555-5555', + use: 'home', }, - }, - ], - }; - const resource = await systemRepo.createResource(patient); - - // _summary=text - const textResults = await systemRepo.search({ - resourceType: 'Patient', - filters: [{ code: '_id', operator: Operator.EQUALS, value: resource.id as string }], - summary: 'text', - }); - expect(textResults.entry).toHaveLength(1); - const textResult = textResults.entry?.[0]?.resource as Resource; - expect(textResult).toEqual>({ - resourceType: 'Patient', - id: resource.id, - meta: expect.objectContaining({ - profile: ['http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient'], - tag: [{ system: 'http://example.com/', code: 'test' }, subsetTag], - }), - text: { - status: 'generated', - div: '
', - }, - }); - - // _summary=data - const dataResults = await systemRepo.search({ - resourceType: 'Patient', - filters: [{ code: '_id', operator: Operator.EQUALS, value: resource.id as string }], - summary: 'data', - }); - expect(dataResults.entry).toHaveLength(1); - const dataResult = dataResults.entry?.[0]?.resource as Resource; - const { text: _1, ...dataExpected } = resource; - dataExpected.meta?.tag?.push(subsetTag); - expect(dataResult).toEqual>({ ...dataExpected }); - - // _summary=true - const summaryResults = await systemRepo.search({ - resourceType: 'Patient', - filters: [{ code: '_id', operator: Operator.EQUALS, value: resource.id as string }], - summary: 'true', - }); - expect(summaryResults.entry).toHaveLength(1); - const summaryResult = summaryResults.entry?.[0]?.resource as Resource; - const { multipleBirthInteger: _2, text: _3, ...summaryResource } = resource; - expect(summaryResult).toEqual>(summaryResource); - })); - - test('Search _elements', () => - withTestContext(async () => { - const subsetTag: Coding = { system: 'http://hl7.org/fhir/v3/ObservationValue', code: 'SUBSETTED' }; - const patient: Patient = { - resourceType: 'Patient', - birthDate: '2000-01-01', - _birthDate: { - extension: [ { - url: 'http://hl7.org/fhir/StructureDefinition/patient-birthTime', - valueDateTime: '2000-01-01T00:00:00.001Z', + system: 'email', + value: 'amy.shaw@example.com', }, ], - }, - multipleBirthInteger: 2, - deceasedBoolean: false, - } as unknown as Patient; - const resource = await systemRepo.createResource(patient); + gender: 'female', + birthDate: '1987-02-20', + multipleBirthInteger: 2, + address: [ + { + use: 'old', + line: ['49 Meadow St'], + city: 'Mounds', + state: 'OK', + postalCode: '74047', + country: 'US', + period: { + start: '2016-12-06', + end: '2020-07-22', + }, + }, + { + line: ['183 Mountain View St'], + city: 'Mounds', + state: 'OK', + postalCode: '74048', + country: 'US', + period: { + start: '2020-07-22', + }, + }, + ], + }; + const resource = await repo.createResource(patient); - const results = await systemRepo.search({ - resourceType: 'Patient', - filters: [{ code: '_id', operator: Operator.EQUALS, value: resource.id as string }], - fields: ['birthDate', 'deceased'], - }); - expect(results.entry).toHaveLength(1); - const result = results.entry?.[0]?.resource as Resource; - expect(result).toEqual>({ - resourceType: 'Patient', - id: resource.id, - meta: expect.objectContaining({ - tag: [subsetTag], - }), - birthDate: resource.birthDate, - _birthDate: (resource as any)._birthDate, - deceasedBoolean: resource.deceasedBoolean, - } as unknown as Patient); - })); - - test('Search next link', () => - withTestContext(async () => { - const family = randomUUID(); - - for (let i = 0; i < 2; i++) { - await systemRepo.createResource({ + // _summary=text + const textResults = await repo.search({ resourceType: 'Patient', - name: [{ family }], + filters: [{ code: '_id', operator: Operator.EQUALS, value: resource.id as string }], + summary: 'text', + }); + expect(textResults.entry).toHaveLength(1); + const textResult = textResults.entry?.[0]?.resource as Resource; + expect(textResult).toEqual>({ + resourceType: 'Patient', + id: resource.id, + meta: expect.objectContaining({ + profile: ['http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient'], + tag: [{ system: 'http://example.com/', code: 'test' }, subsetTag], + }), + text: { + status: 'generated', + div: '
', + }, }); - } - const result1 = await systemRepo.search({ - resourceType: 'Patient', - filters: [{ code: 'name', operator: Operator.EQUALS, value: family }], - count: 1, - }); - expect(result1.entry).toHaveLength(1); - expect(result1.link).toBeDefined(); - expect(result1.link?.find((e) => e.relation === 'next')).toBeDefined(); + // _summary=data + const dataResults = await repo.search({ + resourceType: 'Patient', + filters: [{ code: '_id', operator: Operator.EQUALS, value: resource.id as string }], + summary: 'data', + }); + expect(dataResults.entry).toHaveLength(1); + const dataResult = dataResults.entry?.[0]?.resource as Resource; + const { text: _1, ...dataExpected } = resource; + dataExpected.meta?.tag?.push(subsetTag); + expect(dataResult).toEqual>({ ...dataExpected }); + + // _summary=true + const summaryResults = await repo.search({ + resourceType: 'Patient', + filters: [{ code: '_id', operator: Operator.EQUALS, value: resource.id as string }], + summary: 'true', + }); + expect(summaryResults.entry).toHaveLength(1); + const summaryResult = summaryResults.entry?.[0]?.resource as Resource; + const { multipleBirthInteger: _2, text: _3, ...summaryResource } = resource; + expect(summaryResult).toEqual>(summaryResource); + })); + + test('Search _elements', () => + withTestContext(async () => { + const subsetTag: Coding = { system: 'http://hl7.org/fhir/v3/ObservationValue', code: 'SUBSETTED' }; + const patient: Patient = { + resourceType: 'Patient', + birthDate: '2000-01-01', + _birthDate: { + extension: [ + { + url: 'http://hl7.org/fhir/StructureDefinition/patient-birthTime', + valueDateTime: '2000-01-01T00:00:00.001Z', + }, + ], + }, + multipleBirthInteger: 2, + deceasedBoolean: false, + } as unknown as Patient; + const resource = await repo.createResource(patient); - const result2 = await systemRepo.search({ - resourceType: 'Patient', - filters: [{ code: 'name', operator: Operator.EQUALS, value: family }], - count: 2, - }); - expect(result2.entry).toHaveLength(2); - expect(result2.link).toBeDefined(); - expect(result2.link?.find((e) => e.relation === 'next')).toBeUndefined(); + const results = await repo.search({ + resourceType: 'Patient', + filters: [{ code: '_id', operator: Operator.EQUALS, value: resource.id as string }], + fields: ['birthDate', 'deceased'], + }); + expect(results.entry).toHaveLength(1); + const result = results.entry?.[0]?.resource as Resource; + expect(result).toEqual>({ + resourceType: 'Patient', + id: resource.id, + meta: expect.objectContaining({ + tag: [subsetTag], + }), + birthDate: resource.birthDate, + _birthDate: (resource as any)._birthDate, + deceasedBoolean: resource.deceasedBoolean, + } as unknown as Patient); + })); + + test('Search next link', () => + withTestContext(async () => { + const family = randomUUID(); + + for (let i = 0; i < 2; i++) { + await repo.createResource({ + resourceType: 'Patient', + name: [{ family }], + }); + } - const result3 = await systemRepo.search({ - resourceType: 'Patient', - filters: [{ code: 'name', operator: Operator.EQUALS, value: family }], - count: 3, - }); - expect(result3.entry).toHaveLength(2); - expect(result3.link).toBeDefined(); - expect(result3.link?.find((e) => e.relation === 'next')).toBeUndefined(); - })); + const result1 = await repo.search({ + resourceType: 'Patient', + filters: [{ code: 'name', operator: Operator.EQUALS, value: family }], + count: 1, + }); + expect(result1.entry).toHaveLength(1); + expect(result1.link).toBeDefined(); + expect(result1.link?.find((e) => e.relation === 'next')).toBeDefined(); - test('Search previous link', () => - withTestContext(async () => { - const family = randomUUID(); + const result2 = await repo.search({ + resourceType: 'Patient', + filters: [{ code: 'name', operator: Operator.EQUALS, value: family }], + count: 2, + }); + expect(result2.entry).toHaveLength(2); + expect(result2.link).toBeDefined(); + expect(result2.link?.find((e) => e.relation === 'next')).toBeUndefined(); - for (let i = 0; i < 2; i++) { - await systemRepo.createResource({ + const result3 = await repo.search({ resourceType: 'Patient', - name: [{ family }], + filters: [{ code: 'name', operator: Operator.EQUALS, value: family }], + count: 3, }); - } + expect(result3.entry).toHaveLength(2); + expect(result3.link).toBeDefined(); + expect(result3.link?.find((e) => e.relation === 'next')).toBeUndefined(); + })); + + test('Search previous link', () => + withTestContext(async () => { + const family = randomUUID(); + + for (let i = 0; i < 2; i++) { + await repo.createResource({ + resourceType: 'Patient', + name: [{ family }], + }); + } - const result1 = await systemRepo.search({ - resourceType: 'Patient', - filters: [{ code: 'name', operator: Operator.EQUALS, value: family }], - count: 1, - offset: 1, - }); - expect(result1.entry).toHaveLength(1); - expect(result1.link).toBeDefined(); - expect(result1.link?.find((e) => e.relation === 'previous')).toBeDefined(); - })); + const result1 = await repo.search({ + resourceType: 'Patient', + filters: [{ code: 'name', operator: Operator.EQUALS, value: family }], + count: 1, + offset: 1, + }); + expect(result1.entry).toHaveLength(1); + expect(result1.link).toBeDefined(); + expect(result1.link?.find((e) => e.relation === 'previous')).toBeDefined(); + })); + + test('Search for Communications by Encounter', () => + withTestContext(async () => { + const patient1 = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family: 'Smith' }], + }); - test('Search for Communications by Encounter', () => - withTestContext(async () => { - const patient1 = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice'], family: 'Smith' }], - }); + expect(patient1).toBeDefined(); - expect(patient1).toBeDefined(); + const encounter1 = await repo.createResource({ + resourceType: 'Encounter', + status: 'in-progress', + class: { + code: 'HH', + display: 'home health', + }, + subject: createReference(patient1 as Patient), + }); - const encounter1 = await systemRepo.createResource({ - resourceType: 'Encounter', - status: 'in-progress', - class: { - code: 'HH', - display: 'home health', - }, - subject: createReference(patient1 as Patient), - }); + expect(encounter1).toBeDefined(); - expect(encounter1).toBeDefined(); + const comm1 = await repo.createResource({ + resourceType: 'Communication', + status: 'completed', + encounter: createReference(encounter1 as Encounter), + subject: createReference(patient1 as Patient), + sender: createReference(patient1 as Patient), + payload: [{ contentString: 'This is a test' }], + }); - const comm1 = await systemRepo.createResource({ - resourceType: 'Communication', - status: 'completed', - encounter: createReference(encounter1 as Encounter), - subject: createReference(patient1 as Patient), - sender: createReference(patient1 as Patient), - payload: [{ contentString: 'This is a test' }], - }); + expect(comm1).toBeDefined(); - expect(comm1).toBeDefined(); + const patient2 = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Bob'], family: 'Jones' }], + }); - const patient2 = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Bob'], family: 'Jones' }], - }); + expect(patient2).toBeDefined(); - expect(patient2).toBeDefined(); + const encounter2 = await repo.createResource({ + resourceType: 'Encounter', + status: 'in-progress', + class: { + code: 'HH', + display: 'home health', + }, + subject: createReference(patient2 as Patient), + }); - const encounter2 = await systemRepo.createResource({ - resourceType: 'Encounter', - status: 'in-progress', - class: { - code: 'HH', - display: 'home health', - }, - subject: createReference(patient2 as Patient), - }); + expect(encounter2).toBeDefined(); - expect(encounter2).toBeDefined(); + const comm2 = await repo.createResource({ + resourceType: 'Communication', + status: 'completed', + encounter: createReference(encounter2 as Encounter), + subject: createReference(patient2 as Patient), + sender: createReference(patient2 as Patient), + payload: [{ contentString: 'This is another test' }], + }); - const comm2 = await systemRepo.createResource({ - resourceType: 'Communication', - status: 'completed', - encounter: createReference(encounter2 as Encounter), - subject: createReference(patient2 as Patient), - sender: createReference(patient2 as Patient), - payload: [{ contentString: 'This is another test' }], - }); + expect(comm2).toBeDefined(); - expect(comm2).toBeDefined(); + const searchResult = await repo.search({ + resourceType: 'Communication', + filters: [ + { + code: 'encounter', + operator: Operator.EQUALS, + value: getReferenceString(encounter1 as Encounter), + }, + ], + }); - const searchResult = await systemRepo.search({ - resourceType: 'Communication', - filters: [ - { - code: 'encounter', - operator: Operator.EQUALS, - value: getReferenceString(encounter1 as Encounter), - }, - ], - }); + expect(searchResult.entry?.length).toEqual(1); + expect(searchResult.entry?.[0]?.resource?.id).toEqual(comm1.id); + })); - expect(searchResult.entry?.length).toEqual(1); - expect(searchResult.entry?.[0]?.resource?.id).toEqual(comm1.id); - })); + test('Search for Communications by ServiceRequest', () => + withTestContext(async () => { + const patient1 = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family: 'Smith' }], + }); - test('Search for Communications by ServiceRequest', () => - withTestContext(async () => { - const patient1 = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice'], family: 'Smith' }], - }); + expect(patient1).toBeDefined(); - expect(patient1).toBeDefined(); + const serviceRequest1 = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + code: { + text: 'text', + }, + subject: createReference(patient1 as Patient), + }); - const serviceRequest1 = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - code: { - text: 'text', - }, - subject: createReference(patient1 as Patient), - }); + expect(serviceRequest1).toBeDefined(); - expect(serviceRequest1).toBeDefined(); + const comm1 = await repo.createResource({ + resourceType: 'Communication', + status: 'completed', + basedOn: [createReference(serviceRequest1 as ServiceRequest)], + subject: createReference(patient1 as Patient), + sender: createReference(patient1 as Patient), + payload: [{ contentString: 'This is a test' }], + }); - const comm1 = await systemRepo.createResource({ - resourceType: 'Communication', - status: 'completed', - basedOn: [createReference(serviceRequest1 as ServiceRequest)], - subject: createReference(patient1 as Patient), - sender: createReference(patient1 as Patient), - payload: [{ contentString: 'This is a test' }], - }); + expect(comm1).toBeDefined(); - expect(comm1).toBeDefined(); + const patient2 = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Bob'], family: 'Jones' }], + }); - const patient2 = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Bob'], family: 'Jones' }], - }); + expect(patient2).toBeDefined(); - expect(patient2).toBeDefined(); + const serviceRequest2 = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + code: { + text: 'test', + }, + subject: createReference(patient2 as Patient), + }); - const serviceRequest2 = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - code: { - text: 'test', - }, - subject: createReference(patient2 as Patient), - }); + expect(serviceRequest2).toBeDefined(); - expect(serviceRequest2).toBeDefined(); + const comm2 = await repo.createResource({ + resourceType: 'Communication', + status: 'completed', + basedOn: [createReference(serviceRequest2 as ServiceRequest)], + subject: createReference(patient2 as Patient), + sender: createReference(patient2 as Patient), + payload: [{ contentString: 'This is another test' }], + }); - const comm2 = await systemRepo.createResource({ - resourceType: 'Communication', - status: 'completed', - basedOn: [createReference(serviceRequest2 as ServiceRequest)], - subject: createReference(patient2 as Patient), - sender: createReference(patient2 as Patient), - payload: [{ contentString: 'This is another test' }], - }); + expect(comm2).toBeDefined(); - expect(comm2).toBeDefined(); + const searchResult = await repo.search({ + resourceType: 'Communication', + filters: [ + { + code: 'based-on', + operator: Operator.EQUALS, + value: getReferenceString(serviceRequest1 as ServiceRequest), + }, + ], + }); - const searchResult = await systemRepo.search({ - resourceType: 'Communication', - filters: [ - { - code: 'based-on', - operator: Operator.EQUALS, - value: getReferenceString(serviceRequest1 as ServiceRequest), - }, - ], - }); + expect(searchResult.entry?.length).toEqual(1); + expect(searchResult.entry?.[0]?.resource?.id).toEqual(comm1.id); + })); - expect(searchResult.entry?.length).toEqual(1); - expect(searchResult.entry?.[0]?.resource?.id).toEqual(comm1.id); - })); + test('Search for QuestionnaireResponse by Questionnaire', () => + withTestContext(async () => { + const questionnaire = await repo.createResource({ + resourceType: 'Questionnaire', + status: 'active', + }); - test('Search for QuestionnaireResponse by Questionnaire', () => - withTestContext(async () => { - const questionnaire = await systemRepo.createResource({ - resourceType: 'Questionnaire', - status: 'active', - }); + const response1 = await repo.createResource({ + resourceType: 'QuestionnaireResponse', + status: 'completed', + questionnaire: getReferenceString(questionnaire), + }); - const response1 = await systemRepo.createResource({ - resourceType: 'QuestionnaireResponse', - status: 'completed', - questionnaire: getReferenceString(questionnaire), - }); + await repo.createResource({ + resourceType: 'QuestionnaireResponse', + status: 'completed', + questionnaire: `Questionnaire/${randomUUID()}`, + }); - await systemRepo.createResource({ - resourceType: 'QuestionnaireResponse', - status: 'completed', - questionnaire: `Questionnaire/${randomUUID()}`, - }); + const bundle = await repo.search({ + resourceType: 'QuestionnaireResponse', + filters: [ + { + code: 'questionnaire', + operator: Operator.EQUALS, + value: getReferenceString(questionnaire), + }, + ], + }); + expect(bundle.entry?.length).toEqual(1); + expect(bundle.entry?.[0]?.resource?.id).toEqual(response1.id); + })); - const bundle = await systemRepo.search({ - resourceType: 'QuestionnaireResponse', + test('Search for token in array', async () => { + const bundle = await repo.search({ + resourceType: 'SearchParameter', filters: [ { - code: 'questionnaire', + code: 'base', operator: Operator.EQUALS, - value: getReferenceString(questionnaire), + value: 'Patient', }, ], + count: 100, }); - expect(bundle.entry?.length).toEqual(1); - expect(bundle.entry?.[0]?.resource?.id).toEqual(response1.id); - })); - - test('Search for token in array', async () => { - const bundle = await systemRepo.search({ - resourceType: 'SearchParameter', - filters: [ - { - code: 'base', - operator: Operator.EQUALS, - value: 'Patient', - }, - ], - count: 100, - }); - - expect(bundle.entry?.find((e) => (e.resource as SearchParameter).code === 'name')).toBeDefined(); - expect(bundle.entry?.find((e) => (e.resource as SearchParameter).code === 'email')).toBeDefined(); - }); - - test('Search sort by Patient.id', async () => { - const bundle = await systemRepo.search({ - resourceType: 'Patient', - sortRules: [{ code: '_id' }], - }); - - expect(bundle).toBeDefined(); - }); - test('Search sort by Patient.meta.lastUpdated', async () => { - const bundle = await systemRepo.search({ - resourceType: 'Patient', - sortRules: [{ code: '_lastUpdated' }], + expect(bundle.entry?.find((e) => (e.resource as SearchParameter).code === 'name')).toBeDefined(); + expect(bundle.entry?.find((e) => (e.resource as SearchParameter).code === 'email')).toBeDefined(); }); - expect(bundle).toBeDefined(); - }); + test('Search sort by Patient.id', async () => { + const bundle = await repo.search({ + resourceType: 'Patient', + sortRules: [{ code: '_id' }], + }); - test('Search sort by Patient.identifier', async () => { - const bundle = await systemRepo.search({ - resourceType: 'Patient', - sortRules: [{ code: 'identifier' }], + expect(bundle).toBeDefined(); }); - expect(bundle).toBeDefined(); - }); + test('Search sort by Patient.meta.lastUpdated', async () => { + const bundle = await repo.search({ + resourceType: 'Patient', + sortRules: [{ code: '_lastUpdated' }], + }); - test('Search sort by Patient.name', async () => { - const bundle = await systemRepo.search({ - resourceType: 'Patient', - sortRules: [{ code: 'name' }], + expect(bundle).toBeDefined(); }); - expect(bundle).toBeDefined(); - }); + test('Search sort by Patient.identifier', async () => { + const bundle = await repo.search({ + resourceType: 'Patient', + sortRules: [{ code: 'identifier' }], + }); - test('Search sort by Patient.given', async () => { - const bundle = await systemRepo.search({ - resourceType: 'Patient', - sortRules: [{ code: 'given' }], + expect(bundle).toBeDefined(); }); - expect(bundle).toBeDefined(); - }); + test('Search sort by Patient.name', async () => { + const bundle = await repo.search({ + resourceType: 'Patient', + sortRules: [{ code: 'name' }], + }); - test('Search sort by Patient.address', async () => { - const bundle = await systemRepo.search({ - resourceType: 'Patient', - sortRules: [{ code: 'address' }], + expect(bundle).toBeDefined(); }); - expect(bundle).toBeDefined(); - }); + test('Search sort by Patient.given', async () => { + const bundle = await repo.search({ + resourceType: 'Patient', + sortRules: [{ code: 'given' }], + }); - test('Search sort by Patient.telecom', async () => { - const bundle = await systemRepo.search({ - resourceType: 'Patient', - sortRules: [{ code: 'telecom' }], + expect(bundle).toBeDefined(); }); - expect(bundle).toBeDefined(); - }); + test('Search sort by Patient.address', async () => { + const bundle = await repo.search({ + resourceType: 'Patient', + sortRules: [{ code: 'address' }], + }); - test('Search sort by Patient.email', async () => { - const bundle = await systemRepo.search({ - resourceType: 'Patient', - sortRules: [{ code: 'email' }], + expect(bundle).toBeDefined(); }); - expect(bundle).toBeDefined(); - }); + test('Search sort by Patient.telecom', async () => { + const bundle = await repo.search({ + resourceType: 'Patient', + sortRules: [{ code: 'telecom' }], + }); - test('Search sort by Patient.birthDate', async () => { - const bundle = await systemRepo.search({ - resourceType: 'Patient', - sortRules: [{ code: 'birthdate' }], + expect(bundle).toBeDefined(); }); - expect(bundle).toBeDefined(); - }); - - test('Filter and sort on same search parameter', () => - withTestContext(async () => { - await systemRepo.createResource({ + test('Search sort by Patient.email', async () => { + const bundle = await repo.search({ resourceType: 'Patient', - name: [{ given: ['Marge'], family: 'Simpson' }], + sortRules: [{ code: 'email' }], }); - await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Homer'], family: 'Simpson' }], - }); + expect(bundle).toBeDefined(); + }); - const bundle = await systemRepo.search({ + test('Search sort by Patient.birthDate', async () => { + const bundle = await repo.search({ resourceType: 'Patient', - filters: [{ code: 'family', operator: Operator.EQUALS, value: 'Simpson' }], - sortRules: [{ code: 'family' }], + sortRules: [{ code: 'birthdate' }], }); - expect(bundle.entry).toBeDefined(); - expect(bundle.entry?.length).toBeGreaterThanOrEqual(2); - })); + expect(bundle).toBeDefined(); + }); - test('Search birthDate after delete', () => - withTestContext(async () => { - const family = randomUUID(); + test('Filter and sort on same search parameter', () => + withTestContext(async () => { + await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Marge'], family: 'Simpson' }], + }); - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice'], family }], - birthDate: '1971-02-02', - }); + await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Homer'], family: 'Simpson' }], + }); - const searchResult1 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: 'family', - operator: Operator.EQUALS, - value: family, - }, - { - code: 'birthdate', - operator: Operator.EQUALS, - value: '1971-02-02', - }, - ], - }); + const bundle = await repo.search({ + resourceType: 'Patient', + filters: [{ code: 'family', operator: Operator.EQUALS, value: 'Simpson' }], + sortRules: [{ code: 'family' }], + }); - expect(searchResult1.entry?.length).toEqual(1); - expect(searchResult1.entry?.[0]?.resource?.id).toEqual(patient.id); + expect(bundle.entry).toBeDefined(); + expect(bundle.entry?.length).toBeGreaterThanOrEqual(2); + })); - await systemRepo.deleteResource('Patient', patient.id as string); + test('Search birthDate after delete', () => + withTestContext(async () => { + const family = randomUUID(); - const searchResult2 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: 'family', - operator: Operator.EQUALS, - value: family, - }, - { - code: 'birthdate', - operator: Operator.EQUALS, - value: '1971-02-02', - }, - ], - }); - - expect(searchResult2.entry?.length).toEqual(0); - })); + const patient = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family }], + birthDate: '1971-02-02', + }); - test('Search identifier after delete', () => - withTestContext(async () => { - const identifier = randomUUID(); + const searchResult1 = await repo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'family', + operator: Operator.EQUALS, + value: family, + }, + { + code: 'birthdate', + operator: Operator.EQUALS, + value: '1971-02-02', + }, + ], + }); - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice'], family: 'Smith' }], - identifier: [{ system: 'https://www.example.com', value: identifier }], - }); + expect(searchResult1.entry?.length).toEqual(1); + expect(searchResult1.entry?.[0]?.resource?.id).toEqual(patient.id); - const searchResult1 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: 'identifier', - operator: Operator.EQUALS, - value: identifier, - }, - ], - }); + await repo.deleteResource('Patient', patient.id as string); - expect(searchResult1.entry?.length).toEqual(1); - expect(searchResult1.entry?.[0]?.resource?.id).toEqual(patient.id); + const searchResult2 = await repo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'family', + operator: Operator.EQUALS, + value: family, + }, + { + code: 'birthdate', + operator: Operator.EQUALS, + value: '1971-02-02', + }, + ], + }); - await systemRepo.deleteResource('Patient', patient.id as string); + expect(searchResult2.entry?.length).toEqual(0); + })); - const searchResult2 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: 'identifier', - operator: Operator.EQUALS, - value: identifier, - }, - ], - }); + test('Search identifier after delete', () => + withTestContext(async () => { + const identifier = randomUUID(); - expect(searchResult2.entry?.length).toEqual(0); - })); + const patient = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family: 'Smith' }], + identifier: [{ system: 'https://www.example.com', value: identifier }], + }); - test('String filter', async () => { - const bundle1 = await systemRepo.search({ - resourceType: 'StructureDefinition', - filters: [ - { - code: 'name', - operator: Operator.EQUALS, - value: 'Questionnaire', - }, - ], - sortRules: [ - { - code: 'name', - descending: false, - }, - ], - }); - expect(bundle1.entry?.map((e) => e.resource?.name)).toEqual(['Questionnaire', 'QuestionnaireResponse']); - - const bundle2 = await systemRepo.search({ - resourceType: 'StructureDefinition', - filters: [ - { - code: 'name', - operator: Operator.EXACT, - value: 'Questionnaire', - }, - ], - }); - expect(bundle2.entry?.length).toEqual(1); - expect((bundle2.entry?.[0]?.resource as StructureDefinition).name).toEqual('Questionnaire'); - }); + const searchResult1 = await repo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'identifier', + operator: Operator.EQUALS, + value: identifier, + }, + ], + }); - test('Filter by _id', () => - withTestContext(async () => { - // Unique family name to isolate the test - const family = randomUUID(); + expect(searchResult1.entry?.length).toEqual(1); + expect(searchResult1.entry?.[0]?.resource?.id).toEqual(patient.id); - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice'], family }], - }); - expect(patient).toBeDefined(); + await repo.deleteResource('Patient', patient.id as string); - const searchResult1 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: '_id', - operator: Operator.EQUALS, - value: patient.id as string, - }, - ], - }); + const searchResult2 = await repo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'identifier', + operator: Operator.EQUALS, + value: identifier, + }, + ], + }); - expect(searchResult1.entry?.length).toEqual(1); - expect(bundleContains(searchResult1 as Bundle, patient as Patient)).toEqual(true); + expect(searchResult2.entry?.length).toEqual(0); + })); - const searchResult2 = await systemRepo.search({ - resourceType: 'Patient', + test('String filter', async () => { + const bundle1 = await repo.search({ + resourceType: 'StructureDefinition', filters: [ { code: 'name', operator: Operator.EQUALS, - value: family, + value: 'Questionnaire', }, + ], + sortRules: [ { - code: '_id', - operator: Operator.NOT_EQUALS, - value: patient.id as string, + code: 'name', + descending: false, }, ], }); + expect(bundle1.entry?.map((e) => e.resource?.name)).toEqual(['Questionnaire', 'QuestionnaireResponse']); - expect(searchResult2.entry?.length).toEqual(0); - })); - - test('Empty _id', async () => { - const searchResult1 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: '_id', - operator: Operator.EQUALS, - value: '', - }, - ], - }); - - expect(searchResult1.entry?.length).toEqual(0); - }); - - test('Non UUID _id', async () => { - const searchResult1 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: '_id', - operator: Operator.EQUALS, - value: 'x', - }, - ], - }); - - expect(searchResult1.entry?.length).toEqual(0); - }); - - test('Non UUID _compartment', async () => { - const searchResult1 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: '_compartment', - operator: Operator.EQUALS, - value: 'x', - }, - ], - }); - - expect(searchResult1.entry?.length).toEqual(0); - }); - - test('Reference string _compartment', () => - withTestContext(async () => { - const patient = await systemRepo.createResource({ resourceType: 'Patient' }); - - const searchResult1 = await systemRepo.search({ - resourceType: 'Patient', + const bundle2 = await repo.search({ + resourceType: 'StructureDefinition', filters: [ { - code: '_compartment', - operator: Operator.EQUALS, - value: getReferenceString(patient), + code: 'name', + operator: Operator.EXACT, + value: 'Questionnaire', }, ], }); + expect(bundle2.entry?.length).toEqual(1); + expect((bundle2.entry?.[0]?.resource as StructureDefinition).name).toEqual('Questionnaire'); + }); - expect(searchResult1.entry?.length).toEqual(1); - expect(bundleContains(searchResult1 as Bundle, patient as Patient)).toEqual(true); - })); - - test('Filter by _project', () => - withTestContext(async () => { - const project1 = randomUUID(); - const project2 = randomUUID(); - - const patient1 = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice1'], family: 'Smith1' }], - meta: { - project: project1, - }, - }); - expect(patient1).toBeDefined(); - - const patient2 = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice2'], family: 'Smith2' }], - meta: { - project: project2, - }, - }); - expect(patient2).toBeDefined(); + test('Filter by _id', () => + withTestContext(async () => { + // Unique family name to isolate the test + const family = randomUUID(); - const bundle = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: '_project', - operator: Operator.EQUALS, - value: project1, - }, - ], - }); - expect(bundle.entry?.length).toEqual(1); - expect(bundleContains(bundle as Bundle, patient1 as Patient)).toEqual(true); - expect(bundleContains(bundle as Bundle, patient2 as Patient)).toEqual(false); - })); - - test('Handle malformed _lastUpdated', async () => { - try { - await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: '_lastUpdated', - operator: Operator.GREATER_THAN, - value: 'xyz', - }, - ], - }); - fail('Expected error'); - } catch (err) { - expect(normalizeErrorString(err)).toEqual('Invalid date value: xyz'); - } - }); + const patient = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family }], + }); + expect(patient).toBeDefined(); - test('Filter by _lastUpdated', () => - withTestContext(async () => { - // Create 2 patients - // One with a _lastUpdated of 1 second ago - // One with a _lastUpdated of 2 seconds ago - const family = randomUUID(); - const now = new Date(); - const nowMinus1Second = new Date(now.getTime() - 1000); - const nowMinus2Seconds = new Date(now.getTime() - 2000); - const nowMinus3Seconds = new Date(now.getTime() - 3000); - - const patient1 = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice'], family }], - meta: { - lastUpdated: nowMinus1Second.toISOString(), - }, - }); - expect(patient1).toBeDefined(); + const searchResult1 = await repo.search({ + resourceType: 'Patient', + filters: [ + { + code: '_id', + operator: Operator.EQUALS, + value: patient.id as string, + }, + ], + }); - const patient2 = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice'], family }], - meta: { - lastUpdated: nowMinus2Seconds.toISOString(), - }, - }); - expect(patient2).toBeDefined(); + expect(searchResult1.entry?.length).toEqual(1); + expect(bundleContains(searchResult1 as Bundle, patient as Patient)).toEqual(true); - // Greater than (newer than) 2 seconds ago should only return patient 1 - const searchResult1 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: 'name', - operator: Operator.EQUALS, - value: family, - }, - { - code: '_lastUpdated', - operator: Operator.GREATER_THAN, - value: nowMinus2Seconds.toISOString(), - }, - ], - }); + const searchResult2 = await repo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'name', + operator: Operator.EQUALS, + value: family, + }, + { + code: '_id', + operator: Operator.NOT_EQUALS, + value: patient.id as string, + }, + ], + }); - expect(bundleContains(searchResult1 as Bundle, patient1 as Patient)).toEqual(true); - expect(bundleContains(searchResult1 as Bundle, patient2 as Patient)).toEqual(false); + expect(searchResult2.entry?.length).toEqual(0); + })); - // Greater than (newer than) or equal to 2 seconds ago should return both patients - const searchResult2 = await systemRepo.search({ + test('Empty _id', async () => { + const searchResult1 = await repo.search({ resourceType: 'Patient', filters: [ { - code: 'name', + code: '_id', operator: Operator.EQUALS, - value: family, - }, - { - code: '_lastUpdated', - operator: Operator.GREATER_THAN_OR_EQUALS, - value: nowMinus2Seconds.toISOString(), + value: '', }, ], }); - expect(bundleContains(searchResult2 as Bundle, patient1 as Patient)).toEqual(true); - expect(bundleContains(searchResult2 as Bundle, patient2 as Patient)).toEqual(true); + expect(searchResult1.entry?.length).toEqual(0); + }); - // Less than (older than) to 1 seconds ago should only return patient 2 - const searchResult3 = await systemRepo.search({ + test('Non UUID _id', async () => { + const searchResult1 = await repo.search({ resourceType: 'Patient', filters: [ { - code: 'name', + code: '_id', operator: Operator.EQUALS, - value: family, - }, - { - code: '_lastUpdated', - operator: Operator.GREATER_THAN, - value: nowMinus3Seconds.toISOString(), - }, - { - code: '_lastUpdated', - operator: Operator.LESS_THAN, - value: nowMinus1Second.toISOString(), + value: 'x', }, ], }); - expect(bundleContains(searchResult3 as Bundle, patient1 as Patient)).toEqual(false); - expect(bundleContains(searchResult3 as Bundle, patient2 as Patient)).toEqual(true); + expect(searchResult1.entry?.length).toEqual(0); + }); - // Less than (older than) or equal to 1 seconds ago should return both patients - const searchResult4 = await systemRepo.search({ + test('Non UUID _compartment', async () => { + const searchResult1 = await repo.search({ resourceType: 'Patient', filters: [ { - code: 'name', + code: '_compartment', operator: Operator.EQUALS, - value: family, - }, - { - code: '_lastUpdated', - operator: Operator.GREATER_THAN, - value: nowMinus3Seconds.toISOString(), - }, - { - code: '_lastUpdated', - operator: Operator.LESS_THAN_OR_EQUALS, - value: nowMinus1Second.toISOString(), + value: 'x', }, ], }); - expect(bundleContains(searchResult4 as Bundle, patient1 as Patient)).toEqual(true); - expect(bundleContains(searchResult4 as Bundle, patient2 as Patient)).toEqual(true); - })); - - test('Sort by _lastUpdated', () => - withTestContext(async () => { - const project = randomUUID(); - - const patient1 = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice1'], family: 'Smith1' }], - meta: { - lastUpdated: '2020-01-01T00:00:00.000Z', - project, - }, - }); - expect(patient1).toBeDefined(); - - const patient2 = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice2'], family: 'Smith2' }], - meta: { - lastUpdated: '2020-01-02T00:00:00.000Z', - project, - }, - }); - expect(patient2).toBeDefined(); + expect(searchResult1.entry?.length).toEqual(0); + }); - const bundle3 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: '_project', - operator: Operator.EQUALS, - value: project, - }, - ], - sortRules: [ - { - code: '_lastUpdated', - descending: false, - }, - ], - }); - expect(bundle3.entry?.length).toEqual(2); - expect(bundle3.entry?.[0]?.resource?.id).toEqual(patient1.id); - expect(bundle3.entry?.[1]?.resource?.id).toEqual(patient2.id); + test('Reference string _compartment', () => + withTestContext(async () => { + const patient = await repo.createResource({ resourceType: 'Patient' }); - const bundle4 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: '_project', - operator: Operator.EQUALS, - value: project, - }, - ], - sortRules: [ - { - code: '_lastUpdated', - descending: true, - }, - ], - }); - expect(bundle4.entry?.length).toEqual(2); - expect(bundle4.entry?.[0]?.resource?.id).toEqual(patient2.id); - expect(bundle4.entry?.[1]?.resource?.id).toEqual(patient1.id); - })); - - test('Filter by Coding', () => - withTestContext(async () => { - const auditEvents = [] as AuditEvent[]; - - for (let i = 0; i < 3; i++) { - const resource = await systemRepo.createResource({ - resourceType: 'AuditEvent', - recorded: new Date().toISOString(), - type: { - code: randomUUID(), - }, - agent: [ + const searchResult1 = await repo.search({ + resourceType: 'Patient', + filters: [ { - who: { reference: 'Practitioner/' + randomUUID() }, - requestor: true, + code: '_compartment', + operator: Operator.EQUALS, + value: getReferenceString(patient), }, ], - source: { - observer: { reference: 'Practitioner/' + randomUUID() }, - }, }); - auditEvents.push(resource); - } - for (let i = 0; i < 3; i++) { - const bundle = await systemRepo.search({ - resourceType: 'AuditEvent', + expect(searchResult1.entry?.length).toEqual(1); + expect(bundleContains(searchResult1 as Bundle, patient as Patient)).toEqual(true); + })); + + test('Handle malformed _lastUpdated', async () => { + try { + await repo.search({ + resourceType: 'Patient', filters: [ { - code: 'type', - operator: Operator.CONTAINS, - value: auditEvents[i].type?.code as string, + code: '_lastUpdated', + operator: Operator.GREATER_THAN, + value: 'xyz', }, ], }); - expect(bundle.entry?.length).toEqual(1); - expect(bundle.entry?.[0]?.resource?.id).toEqual(auditEvents[i].id); + fail('Expected error'); + } catch (err) { + expect(normalizeErrorString(err)).toEqual('Invalid date value: xyz'); } - })); - - test('Filter by CodeableConcept', () => - withTestContext(async () => { - const x1 = randomUUID(); - const x2 = randomUUID(); - const x3 = randomUUID(); + }); - // Create test patient - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['John'], family: 'CodeableConcept' }], - }); + test('Filter by Coding', () => + withTestContext(async () => { + const auditEvents = [] as AuditEvent[]; - const serviceRequest1 = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - subject: createReference(patient), - code: { coding: [{ code: x1 }] }, - }); + for (let i = 0; i < 3; i++) { + const resource = await repo.createResource({ + resourceType: 'AuditEvent', + recorded: new Date().toISOString(), + type: { + code: randomUUID(), + }, + agent: [ + { + who: { reference: 'Practitioner/' + randomUUID() }, + requestor: true, + }, + ], + source: { + observer: { reference: 'Practitioner/' + randomUUID() }, + }, + }); + auditEvents.push(resource); + } - const serviceRequest2 = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - subject: createReference(patient), - code: { coding: [{ code: x2 }] }, - }); + for (let i = 0; i < 3; i++) { + const bundle = await repo.search({ + resourceType: 'AuditEvent', + filters: [ + { + code: 'type', + operator: Operator.CONTAINS, + value: auditEvents[i].type?.code as string, + }, + ], + }); + expect(bundle.entry?.length).toEqual(1); + expect(bundle.entry?.[0]?.resource?.id).toEqual(auditEvents[i].id); + } + })); - const serviceRequest3 = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - subject: createReference(patient), - code: { coding: [{ code: x3 }] }, - }); + test('Filter by CodeableConcept', () => + withTestContext(async () => { + const x1 = randomUUID(); + const x2 = randomUUID(); + const x3 = randomUUID(); - const bundle1 = await systemRepo.search({ - resourceType: 'ServiceRequest', - filters: [ - { - code: 'code', - operator: Operator.EQUALS, - value: x1, - }, - ], - }); - expect(bundle1.entry?.length).toEqual(1); - expect(bundleContains(bundle1, serviceRequest1)).toEqual(true); - expect(bundleContains(bundle1, serviceRequest2)).toEqual(false); - expect(bundleContains(bundle1, serviceRequest3)).toEqual(false); + // Create test patient + const patient = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['John'], family: 'CodeableConcept' }], + }); - const bundle2 = await systemRepo.search({ - resourceType: 'ServiceRequest', - filters: [ - { - code: 'code', - operator: Operator.EQUALS, - value: x2, - }, - ], - }); - expect(bundle2.entry?.length).toEqual(1); - expect(bundleContains(bundle2, serviceRequest1)).toEqual(false); - expect(bundleContains(bundle2, serviceRequest2)).toEqual(true); - expect(bundleContains(bundle2, serviceRequest3)).toEqual(false); + const serviceRequest1 = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + subject: createReference(patient), + code: { coding: [{ code: x1 }] }, + }); - const bundle3 = await systemRepo.search({ - resourceType: 'ServiceRequest', - filters: [ - { - code: 'code', - operator: Operator.EQUALS, - value: x3, - }, - ], - }); - expect(bundle3.entry?.length).toEqual(1); - expect(bundleContains(bundle3, serviceRequest1)).toEqual(false); - expect(bundleContains(bundle3, serviceRequest2)).toEqual(false); - expect(bundleContains(bundle3, serviceRequest3)).toEqual(true); - })); + const serviceRequest2 = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + subject: createReference(patient), + code: { coding: [{ code: x2 }] }, + }); - test('Filter by Quantity.value', () => - withTestContext(async () => { - const code = randomUUID(); + const serviceRequest3 = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + subject: createReference(patient), + code: { coding: [{ code: x3 }] }, + }); - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['John'], family: 'Quantity' }], - }); + const bundle1 = await repo.search({ + resourceType: 'ServiceRequest', + filters: [ + { + code: 'code', + operator: Operator.EQUALS, + value: x1, + }, + ], + }); + expect(bundle1.entry?.length).toEqual(1); + expect(bundleContains(bundle1, serviceRequest1)).toEqual(true); + expect(bundleContains(bundle1, serviceRequest2)).toEqual(false); + expect(bundleContains(bundle1, serviceRequest3)).toEqual(false); - const observation1 = await systemRepo.createResource({ - resourceType: 'Observation', - status: 'final', - subject: createReference(patient), - code: { coding: [{ code }] }, - valueQuantity: { value: 1, unit: 'mg' }, - }); + const bundle2 = await repo.search({ + resourceType: 'ServiceRequest', + filters: [ + { + code: 'code', + operator: Operator.EQUALS, + value: x2, + }, + ], + }); + expect(bundle2.entry?.length).toEqual(1); + expect(bundleContains(bundle2, serviceRequest1)).toEqual(false); + expect(bundleContains(bundle2, serviceRequest2)).toEqual(true); + expect(bundleContains(bundle2, serviceRequest3)).toEqual(false); - const observation2 = await systemRepo.createResource({ - resourceType: 'Observation', - status: 'final', - subject: createReference(patient), - code: { coding: [{ code }] }, - valueQuantity: { value: 5, unit: 'mg' }, - }); + const bundle3 = await repo.search({ + resourceType: 'ServiceRequest', + filters: [ + { + code: 'code', + operator: Operator.EQUALS, + value: x3, + }, + ], + }); + expect(bundle3.entry?.length).toEqual(1); + expect(bundleContains(bundle3, serviceRequest1)).toEqual(false); + expect(bundleContains(bundle3, serviceRequest2)).toEqual(false); + expect(bundleContains(bundle3, serviceRequest3)).toEqual(true); + })); - const observation3 = await systemRepo.createResource({ - resourceType: 'Observation', - status: 'final', - subject: createReference(patient), - code: { coding: [{ code }] }, - valueQuantity: { value: 10, unit: 'mg' }, - }); + test('Filter by Quantity.value', () => + withTestContext(async () => { + const code = randomUUID(); - const bundle1 = await systemRepo.search({ - resourceType: 'Observation', - filters: [{ code: 'code', operator: Operator.EQUALS, value: code }], - sortRules: [{ code: 'value-quantity', descending: false }], - }); - expect(bundle1.entry?.length).toEqual(3); - expect(bundle1.entry?.[0]?.resource?.id).toEqual(observation1.id); - expect(bundle1.entry?.[1]?.resource?.id).toEqual(observation2.id); - expect(bundle1.entry?.[2]?.resource?.id).toEqual(observation3.id); - - const bundle2 = await systemRepo.search({ - resourceType: 'Observation', - filters: [{ code: 'code', operator: Operator.EQUALS, value: code }], - sortRules: [{ code: 'value-quantity', descending: true }], - }); - expect(bundle2.entry?.length).toEqual(3); - expect(bundle2.entry?.[0]?.resource?.id).toEqual(observation3.id); - expect(bundle2.entry?.[1]?.resource?.id).toEqual(observation2.id); - expect(bundle2.entry?.[2]?.resource?.id).toEqual(observation1.id); + const patient = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['John'], family: 'Quantity' }], + }); - const bundle3 = await systemRepo.search({ - resourceType: 'Observation', - filters: [ - { code: 'code', operator: Operator.EQUALS, value: code }, - { code: 'value-quantity', operator: Operator.GREATER_THAN, value: '8' }, - ], - }); - expect(bundle3.entry?.length).toEqual(1); - expect(bundle3.entry?.[0]?.resource?.id).toEqual(observation3.id); - })); - - test('ServiceRequest.orderDetail search', () => - withTestContext(async () => { - const orderDetailText = randomUUID(); - const orderDetailCode = randomUUID(); - - const serviceRequest = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - subject: { - reference: 'Patient/' + randomUUID(), - }, - code: { - coding: [ - { - code: 'order-type', - }, - ], - }, - orderDetail: [ - { - text: orderDetailText, + const observation1 = await repo.createResource({ + resourceType: 'Observation', + status: 'final', + subject: createReference(patient), + code: { coding: [{ code }] }, + valueQuantity: { value: 1, unit: 'mg' }, + }); + + const observation2 = await repo.createResource({ + resourceType: 'Observation', + status: 'final', + subject: createReference(patient), + code: { coding: [{ code }] }, + valueQuantity: { value: 5, unit: 'mg' }, + }); + + const observation3 = await repo.createResource({ + resourceType: 'Observation', + status: 'final', + subject: createReference(patient), + code: { coding: [{ code }] }, + valueQuantity: { value: 10, unit: 'mg' }, + }); + + const bundle1 = await repo.search({ + resourceType: 'Observation', + filters: [{ code: 'code', operator: Operator.EQUALS, value: code }], + sortRules: [{ code: 'value-quantity', descending: false }], + }); + expect(bundle1.entry?.length).toEqual(3); + expect(bundle1.entry?.[0]?.resource?.id).toEqual(observation1.id); + expect(bundle1.entry?.[1]?.resource?.id).toEqual(observation2.id); + expect(bundle1.entry?.[2]?.resource?.id).toEqual(observation3.id); + + const bundle2 = await repo.search({ + resourceType: 'Observation', + filters: [{ code: 'code', operator: Operator.EQUALS, value: code }], + sortRules: [{ code: 'value-quantity', descending: true }], + }); + expect(bundle2.entry?.length).toEqual(3); + expect(bundle2.entry?.[0]?.resource?.id).toEqual(observation3.id); + expect(bundle2.entry?.[1]?.resource?.id).toEqual(observation2.id); + expect(bundle2.entry?.[2]?.resource?.id).toEqual(observation1.id); + + const bundle3 = await repo.search({ + resourceType: 'Observation', + filters: [ + { code: 'code', operator: Operator.EQUALS, value: code }, + { code: 'value-quantity', operator: Operator.GREATER_THAN, value: '8' }, + ], + }); + expect(bundle3.entry?.length).toEqual(1); + expect(bundle3.entry?.[0]?.resource?.id).toEqual(observation3.id); + })); + + test('ServiceRequest.orderDetail search', () => + withTestContext(async () => { + const orderDetailText = randomUUID(); + const orderDetailCode = randomUUID(); + + const serviceRequest = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + subject: { + reference: 'Patient/' + randomUUID(), + }, + code: { coding: [ { - system: 'custom-order-system', - code: orderDetailCode, + code: 'order-type', }, ], }, - ], - }); + orderDetail: [ + { + text: orderDetailText, + coding: [ + { + system: 'custom-order-system', + code: orderDetailCode, + }, + ], + }, + ], + }); - const bundle1 = await systemRepo.search({ - resourceType: 'ServiceRequest', - filters: [ - { - code: 'order-detail', - operator: Operator.CONTAINS, - value: orderDetailText, - }, - ], - }); - expect(bundle1.entry?.length).toEqual(1); - expect(bundle1.entry?.[0]?.resource?.id).toEqual(serviceRequest.id); - })); - - test('Comma separated value', () => - withTestContext(async () => { - const category = randomUUID(); - const codes = [randomUUID(), randomUUID(), randomUUID()]; - const serviceRequests = []; - - for (const code of codes) { - const serviceRequest = await systemRepo.createResource({ + const bundle1 = await repo.search({ + resourceType: 'ServiceRequest', + filters: [ + { + code: 'order-detail', + operator: Operator.CONTAINS, + value: orderDetailText, + }, + ], + }); + expect(bundle1.entry?.length).toEqual(1); + expect(bundle1.entry?.[0]?.resource?.id).toEqual(serviceRequest.id); + })); + + test('Comma separated value', () => + withTestContext(async () => { + const category = randomUUID(); + const codes = [randomUUID(), randomUUID(), randomUUID()]; + const serviceRequests = []; + + for (const code of codes) { + const serviceRequest = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + subject: { reference: 'Patient/' + randomUUID() }, + category: [{ coding: [{ code: category }] }], + code: { coding: [{ code: code }] }, + }); + serviceRequests.push(serviceRequest); + } + + const bundle1 = await repo.search( + parseSearchRequest('ServiceRequest', { category, code: `${codes[0]},${codes[1]}` }) + ); + expect(bundle1.entry?.length).toEqual(2); + expect(bundleContains(bundle1, serviceRequests[0])).toEqual(true); + expect(bundleContains(bundle1, serviceRequests[1])).toEqual(true); + })); + + test('Token not equals', () => + withTestContext(async () => { + const category = randomUUID(); + const code1 = randomUUID(); + const code2 = randomUUID(); + + const serviceRequest1 = await repo.createResource({ resourceType: 'ServiceRequest', status: 'active', intent: 'order', subject: { reference: 'Patient/' + randomUUID() }, category: [{ coding: [{ code: category }] }], - code: { coding: [{ code: code }] }, + code: { coding: [{ code: code1 }] }, }); - serviceRequests.push(serviceRequest); - } - const bundle1 = await systemRepo.search( - parseSearchRequest('ServiceRequest', { category, code: `${codes[0]},${codes[1]}` }) - ); - expect(bundle1.entry?.length).toEqual(2); - expect(bundleContains(bundle1, serviceRequests[0])).toEqual(true); - expect(bundleContains(bundle1, serviceRequests[1])).toEqual(true); - })); - - test('Token not equals', () => - withTestContext(async () => { - const category = randomUUID(); - const code1 = randomUUID(); - const code2 = randomUUID(); - - const serviceRequest1 = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - subject: { reference: 'Patient/' + randomUUID() }, - category: [{ coding: [{ code: category }] }], - code: { coding: [{ code: code1 }] }, - }); + const serviceRequest2 = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + subject: { reference: 'Patient/' + randomUUID() }, + category: [{ coding: [{ code: category }] }], + code: { coding: [{ code: code2 }] }, + }); - const serviceRequest2 = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - subject: { reference: 'Patient/' + randomUUID() }, - category: [{ coding: [{ code: category }] }], - code: { coding: [{ code: code2 }] }, - }); + const bundle1 = await repo.search(parseSearchRequest('ServiceRequest', { category, 'code:not': code1 })); + expect(bundle1.entry?.length).toEqual(1); + expect(bundleContains(bundle1, serviceRequest1)).toEqual(false); + expect(bundleContains(bundle1, serviceRequest2)).toEqual(true); + })); - const bundle1 = await systemRepo.search(parseSearchRequest('ServiceRequest', { category, 'code:not': code1 })); - expect(bundle1.entry?.length).toEqual(1); - expect(bundleContains(bundle1, serviceRequest1)).toEqual(false); - expect(bundleContains(bundle1, serviceRequest2)).toEqual(true); - })); - - test('Token array not equals', () => - withTestContext(async () => { - const category1 = randomUUID(); - const category2 = randomUUID(); - const code = randomUUID(); - - const serviceRequest1 = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - subject: { reference: 'Patient/' + randomUUID() }, - category: [{ coding: [{ code: category1 }] }], - code: { coding: [{ code }] }, - }); + test('Token array not equals', () => + withTestContext(async () => { + const category1 = randomUUID(); + const category2 = randomUUID(); + const code = randomUUID(); - const serviceRequest2 = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - subject: { reference: 'Patient/' + randomUUID() }, - category: [{ coding: [{ code: category2 }] }], - code: { coding: [{ code }] }, - }); + const serviceRequest1 = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + subject: { reference: 'Patient/' + randomUUID() }, + category: [{ coding: [{ code: category1 }] }], + code: { coding: [{ code }] }, + }); - const bundle1 = await systemRepo.search( - parseSearchRequest('ServiceRequest', { code, 'category:not': category1 }) - ); - expect(bundle1.entry?.length).toEqual(1); - expect(bundleContains(bundle1, serviceRequest1)).toEqual(false); - expect(bundleContains(bundle1, serviceRequest2)).toEqual(true); - })); - - test('Null token array not equals', () => - withTestContext(async () => { - const category1 = randomUUID(); - const code = randomUUID(); - - const serviceRequest1 = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - subject: { reference: 'Patient/' + randomUUID() }, - category: [{ coding: [{ code: category1 }] }], - code: { coding: [{ code }] }, - }); + const serviceRequest2 = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + subject: { reference: 'Patient/' + randomUUID() }, + category: [{ coding: [{ code: category2 }] }], + code: { coding: [{ code }] }, + }); - const serviceRequest2 = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - subject: { reference: 'Patient/' + randomUUID() }, - code: { coding: [{ code }] }, - }); + const bundle1 = await repo.search(parseSearchRequest('ServiceRequest', { code, 'category:not': category1 })); + expect(bundle1.entry?.length).toEqual(1); + expect(bundleContains(bundle1, serviceRequest1)).toEqual(false); + expect(bundleContains(bundle1, serviceRequest2)).toEqual(true); + })); - const bundle1 = await systemRepo.search( - parseSearchRequest('ServiceRequest', { code, 'category:not': category1 }) - ); - expect(bundle1.entry?.length).toEqual(1); - expect(bundleContains(bundle1, serviceRequest1)).toEqual(false); - expect(bundleContains(bundle1, serviceRequest2)).toEqual(true); - })); - - test('Missing', () => - withTestContext(async () => { - const code = randomUUID(); - - // Test both an array column (specimen) and a non-array column (encounter), - // because the resulting SQL could be subtly different. - - const serviceRequest1 = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - code: { coding: [{ code }] }, - subject: { reference: 'Patient/' + randomUUID() }, - specimen: [{ reference: 'Specimen/' + randomUUID() }], - encounter: { reference: 'Encounter/' + randomUUID() }, - }); + test('Null token array not equals', () => + withTestContext(async () => { + const category1 = randomUUID(); + const code = randomUUID(); - const serviceRequest2 = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - code: { coding: [{ code }] }, - subject: { reference: 'Patient/' + randomUUID() }, - }); + const serviceRequest1 = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + subject: { reference: 'Patient/' + randomUUID() }, + category: [{ coding: [{ code: category1 }] }], + code: { coding: [{ code }] }, + }); - const bundle1 = await systemRepo.search( - parseSearchRequest('ServiceRequest', { code, 'specimen:missing': 'true' }) - ); - expect(bundle1.entry?.length).toEqual(1); - expect(bundleContains(bundle1, serviceRequest1)).toEqual(false); - expect(bundleContains(bundle1, serviceRequest2)).toEqual(true); + const serviceRequest2 = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + subject: { reference: 'Patient/' + randomUUID() }, + code: { coding: [{ code }] }, + }); - const bundle2 = await systemRepo.search( - parseSearchRequest('ServiceRequest', { code, 'specimen:missing': 'false' }) - ); - expect(bundle2.entry?.length).toEqual(1); - expect(bundleContains(bundle2, serviceRequest1)).toEqual(true); - expect(bundleContains(bundle2, serviceRequest2)).toEqual(false); - - const bundle3 = await systemRepo.search( - parseSearchRequest('ServiceRequest', { code, 'encounter:missing': 'true' }) - ); - expect(bundle3.entry?.length).toEqual(1); - expect(bundleContains(bundle3, serviceRequest1)).toEqual(false); - expect(bundleContains(bundle3, serviceRequest2)).toEqual(true); - - const bundle4 = await systemRepo.search( - parseSearchRequest('ServiceRequest', { code, 'encounter:missing': 'false' }) - ); - expect(bundle4.entry?.length).toEqual(1); - expect(bundleContains(bundle4, serviceRequest1)).toEqual(true); - expect(bundleContains(bundle4, serviceRequest2)).toEqual(false); - })); - - test('Missing with logical (identifier) references', () => - withTestContext(async () => { - const patientIdentifier = randomUUID(); - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - identifier: [ - { - system: 'http://example.com/guid', - value: patientIdentifier, - }, - ], - generalPractitioner: [ - { + const bundle1 = await repo.search(parseSearchRequest('ServiceRequest', { code, 'category:not': category1 })); + expect(bundle1.entry?.length).toEqual(1); + expect(bundleContains(bundle1, serviceRequest1)).toEqual(false); + expect(bundleContains(bundle1, serviceRequest2)).toEqual(true); + })); + + test('Missing', () => + withTestContext(async () => { + const code = randomUUID(); + + // Test both an array column (specimen) and a non-array column (encounter), + // because the resulting SQL could be subtly different. + + const serviceRequest1 = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + code: { coding: [{ code }] }, + subject: { reference: 'Patient/' + randomUUID() }, + specimen: [{ reference: 'Specimen/' + randomUUID() }], + encounter: { reference: 'Encounter/' + randomUUID() }, + }); + + const serviceRequest2 = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + code: { coding: [{ code }] }, + subject: { reference: 'Patient/' + randomUUID() }, + }); + + const bundle1 = await repo.search(parseSearchRequest('ServiceRequest', { code, 'specimen:missing': 'true' })); + expect(bundle1.entry?.length).toEqual(1); + expect(bundleContains(bundle1, serviceRequest1)).toEqual(false); + expect(bundleContains(bundle1, serviceRequest2)).toEqual(true); + + const bundle2 = await repo.search(parseSearchRequest('ServiceRequest', { code, 'specimen:missing': 'false' })); + expect(bundle2.entry?.length).toEqual(1); + expect(bundleContains(bundle2, serviceRequest1)).toEqual(true); + expect(bundleContains(bundle2, serviceRequest2)).toEqual(false); + + const bundle3 = await repo.search(parseSearchRequest('ServiceRequest', { code, 'encounter:missing': 'true' })); + expect(bundle3.entry?.length).toEqual(1); + expect(bundleContains(bundle3, serviceRequest1)).toEqual(false); + expect(bundleContains(bundle3, serviceRequest2)).toEqual(true); + + const bundle4 = await repo.search(parseSearchRequest('ServiceRequest', { code, 'encounter:missing': 'false' })); + expect(bundle4.entry?.length).toEqual(1); + expect(bundleContains(bundle4, serviceRequest1)).toEqual(true); + expect(bundleContains(bundle4, serviceRequest2)).toEqual(false); + })); + + test('Missing with logical (identifier) references', () => + withTestContext(async () => { + const patientIdentifier = randomUUID(); + const patient = await repo.createResource({ + resourceType: 'Patient', + identifier: [ + { + system: 'http://example.com/guid', + value: patientIdentifier, + }, + ], + generalPractitioner: [ + { + identifier: { + system: 'http://hl7.org/fhir/sid/us-npi', + value: '9876543210', + }, + }, + ], + managingOrganization: { identifier: { system: 'http://hl7.org/fhir/sid/us-npi', - value: '9876543210', + value: '0123456789', }, }, - ], - managingOrganization: { - identifier: { - system: 'http://hl7.org/fhir/sid/us-npi', - value: '0123456789', - }, - }, - }); - - // Test singlet reference column - let results = await systemRepo.searchResources( - parseSearchDefinition(`Patient?identifier=${patientIdentifier}&organization:missing=false`) - ); - expect(results).toHaveLength(1); - expect(results[0]?.id).toEqual(patient.id); - - // Test array reference column - results = await systemRepo.searchResources( - parseSearchDefinition(`Patient?identifier=${patientIdentifier}&general-practitioner:missing=false`) - ); - expect(results).toHaveLength(1); - expect(results[0]?.id).toEqual(patient.id); - })); - - test('Starts after', () => - withTestContext(async () => { - // Create 2 appointments - // One with a start date of 1 second ago - // One with a start date of 2 seconds ago - const code = randomUUID(); - const now = new Date(); - const nowMinus1Second = new Date(now.getTime() - 1000); - const nowMinus2Seconds = new Date(now.getTime() - 2000); - const nowMinus3Seconds = new Date(now.getTime() - 3000); - const patient: Patient = { resourceType: 'Patient' }; - const patientReference = createReference(patient); - const appt1 = await systemRepo.createResource({ - resourceType: 'Appointment', - status: 'booked', - serviceType: [{ coding: [{ code }] }], - participant: [{ status: 'accepted', actor: patientReference }], - start: nowMinus1Second.toISOString(), - end: now.toISOString(), - }); - expect(appt1).toBeDefined(); - - const appt2 = await systemRepo.createResource({ - resourceType: 'Appointment', - status: 'booked', - serviceType: [{ coding: [{ code }] }], - participant: [{ status: 'accepted', actor: patientReference }], - start: nowMinus2Seconds.toISOString(), - end: now.toISOString(), - }); - expect(appt2).toBeDefined(); + }); - // Greater than (newer than) 2 seconds ago should only return appt 1 - const searchResult1 = await systemRepo.search({ - resourceType: 'Appointment', - filters: [ - { - code: 'service-type', - operator: Operator.EQUALS, - value: code, - }, - { - code: 'date', - operator: Operator.STARTS_AFTER, - value: nowMinus2Seconds.toISOString(), - }, - ], - }); + // Test singlet reference column + let results = await repo.searchResources( + parseSearchDefinition(`Patient?identifier=${patientIdentifier}&organization:missing=false`) + ); + expect(results).toHaveLength(1); + expect(results[0]?.id).toEqual(patient.id); - expect(bundleContains(searchResult1 as Bundle, appt1 as Appointment)).toEqual(true); - expect(bundleContains(searchResult1 as Bundle, appt2 as Appointment)).toEqual(false); + // Test array reference column + results = await repo.searchResources( + parseSearchDefinition(`Patient?identifier=${patientIdentifier}&general-practitioner:missing=false`) + ); + expect(results).toHaveLength(1); + expect(results[0]?.id).toEqual(patient.id); + })); + + test('Starts after', () => + withTestContext(async () => { + // Create 2 appointments + // One with a start date of 1 second ago + // One with a start date of 2 seconds ago + const code = randomUUID(); + const now = new Date(); + const nowMinus1Second = new Date(now.getTime() - 1000); + const nowMinus2Seconds = new Date(now.getTime() - 2000); + const nowMinus3Seconds = new Date(now.getTime() - 3000); + const patient: Patient = { resourceType: 'Patient' }; + const patientReference = createReference(patient); + const appt1 = await repo.createResource({ + resourceType: 'Appointment', + status: 'booked', + serviceType: [{ coding: [{ code }] }], + participant: [{ status: 'accepted', actor: patientReference }], + start: nowMinus1Second.toISOString(), + end: now.toISOString(), + }); + expect(appt1).toBeDefined(); + + const appt2 = await repo.createResource({ + resourceType: 'Appointment', + status: 'booked', + serviceType: [{ coding: [{ code }] }], + participant: [{ status: 'accepted', actor: patientReference }], + start: nowMinus2Seconds.toISOString(), + end: now.toISOString(), + }); + expect(appt2).toBeDefined(); - // Greater than (newer than) or equal to 2 seconds ago should return both appts - const searchResult2 = await systemRepo.search({ - resourceType: 'Appointment', - filters: [ - { - code: 'service-type', - operator: Operator.EQUALS, - value: code, - }, - { - code: 'date', - operator: Operator.GREATER_THAN_OR_EQUALS, - value: nowMinus2Seconds.toISOString(), - }, - ], - }); + // Greater than (newer than) 2 seconds ago should only return appt 1 + const searchResult1 = await repo.search({ + resourceType: 'Appointment', + filters: [ + { + code: 'service-type', + operator: Operator.EQUALS, + value: code, + }, + { + code: 'date', + operator: Operator.STARTS_AFTER, + value: nowMinus2Seconds.toISOString(), + }, + ], + }); - expect(bundleContains(searchResult2 as Bundle, appt1 as Appointment)).toEqual(true); - expect(bundleContains(searchResult2 as Bundle, appt2 as Appointment)).toEqual(true); + expect(bundleContains(searchResult1 as Bundle, appt1 as Appointment)).toEqual(true); + expect(bundleContains(searchResult1 as Bundle, appt2 as Appointment)).toEqual(false); - // Less than (older than) to 1 seconds ago should only return appt 2 - const searchResult3 = await systemRepo.search({ - resourceType: 'Appointment', - filters: [ - { - code: 'service-type', - operator: Operator.EQUALS, - value: code, - }, - { - code: 'date', - operator: Operator.STARTS_AFTER, - value: nowMinus3Seconds.toISOString(), - }, - { - code: 'date', - operator: Operator.ENDS_BEFORE, - value: nowMinus1Second.toISOString(), - }, - ], - }); + // Greater than (newer than) or equal to 2 seconds ago should return both appts + const searchResult2 = await repo.search({ + resourceType: 'Appointment', + filters: [ + { + code: 'service-type', + operator: Operator.EQUALS, + value: code, + }, + { + code: 'date', + operator: Operator.GREATER_THAN_OR_EQUALS, + value: nowMinus2Seconds.toISOString(), + }, + ], + }); - expect(bundleContains(searchResult3 as Bundle, appt1 as Appointment)).toEqual(false); - expect(bundleContains(searchResult3 as Bundle, appt2 as Appointment)).toEqual(true); + expect(bundleContains(searchResult2 as Bundle, appt1 as Appointment)).toEqual(true); + expect(bundleContains(searchResult2 as Bundle, appt2 as Appointment)).toEqual(true); - // Less than (older than) or equal to 1 seconds ago should return both appts - const searchResult4 = await systemRepo.search({ - resourceType: 'Appointment', - filters: [ - { - code: 'service-type', - operator: Operator.EQUALS, - value: code, - }, - { - code: 'date', - operator: Operator.STARTS_AFTER, - value: nowMinus3Seconds.toISOString(), - }, - { - code: 'date', - operator: Operator.LESS_THAN_OR_EQUALS, - value: nowMinus1Second.toISOString(), - }, - ], - }); + // Less than (older than) to 1 seconds ago should only return appt 2 + const searchResult3 = await repo.search({ + resourceType: 'Appointment', + filters: [ + { + code: 'service-type', + operator: Operator.EQUALS, + value: code, + }, + { + code: 'date', + operator: Operator.STARTS_AFTER, + value: nowMinus3Seconds.toISOString(), + }, + { + code: 'date', + operator: Operator.ENDS_BEFORE, + value: nowMinus1Second.toISOString(), + }, + ], + }); - expect(bundleContains(searchResult4 as Bundle, appt1 as Appointment)).toEqual(true); - expect(bundleContains(searchResult4 as Bundle, appt2 as Appointment)).toEqual(true); - })); + expect(bundleContains(searchResult3 as Bundle, appt1 as Appointment)).toEqual(false); + expect(bundleContains(searchResult3 as Bundle, appt2 as Appointment)).toEqual(true); - test('Boolean search', () => - withTestContext(async () => { - const family = randomUUID(); - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ family }], - active: true, - }); - const searchResult = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: 'name', - operator: Operator.EQUALS, - value: family, - }, - { - code: 'active', - operator: Operator.EQUALS, - value: 'true', - }, - ], - }); - expect(searchResult.entry).toHaveLength(1); - expect(searchResult.entry?.[0]?.resource?.id).toEqual(patient.id); - })); - - test('Not equals with comma separated values', () => - withTestContext(async () => { - // Create 3 service requests - // All 3 have the same category for test isolation - // Each have a different code - const category = randomUUID(); - const serviceRequests = []; - for (let i = 0; i < 3; i++) { - serviceRequests.push( - await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - subject: { reference: 'Patient/' + randomUUID() }, - category: [{ coding: [{ code: category }] }], - code: { coding: [{ code: randomUUID() }] }, - }) - ); - } + // Less than (older than) or equal to 1 seconds ago should return both appts + const searchResult4 = await repo.search({ + resourceType: 'Appointment', + filters: [ + { + code: 'service-type', + operator: Operator.EQUALS, + value: code, + }, + { + code: 'date', + operator: Operator.STARTS_AFTER, + value: nowMinus3Seconds.toISOString(), + }, + { + code: 'date', + operator: Operator.LESS_THAN_OR_EQUALS, + value: nowMinus1Second.toISOString(), + }, + ], + }); - // Search for service requests with category - // and code "not equals" the first two separated by a comma - const searchResult = await systemRepo.search({ - resourceType: 'ServiceRequest', - filters: [ - { - code: 'category', - operator: Operator.EQUALS, - value: category, - }, - { - code: 'code', - operator: Operator.NOT_EQUALS, - value: serviceRequests[0].code.coding[0].code + ',' + serviceRequests[1].code.coding[0].code, - }, - ], - }); - expect(searchResult.entry).toHaveLength(1); - })); - - test('_id equals with comma separated values', () => - withTestContext(async () => { - // Create 3 service requests - const serviceRequests = []; - for (let i = 0; i < 3; i++) { - serviceRequests.push( - await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - subject: { reference: 'Patient/' + randomUUID() }, - code: { text: randomUUID() }, - }) - ); - } + expect(bundleContains(searchResult4 as Bundle, appt1 as Appointment)).toEqual(true); + expect(bundleContains(searchResult4 as Bundle, appt2 as Appointment)).toEqual(true); + })); - // Search for service requests with _id equals the first two separated by a comma - const searchResult = await systemRepo.search({ - resourceType: 'ServiceRequest', - filters: [ - { - code: '_id', - operator: Operator.EQUALS, - value: serviceRequests[0].id + ',' + serviceRequests[1].id, - }, - ], - }); - expect(searchResult.entry).toHaveLength(2); - })); + test('Boolean search', () => + withTestContext(async () => { + const family = randomUUID(); + const patient = await repo.createResource({ + resourceType: 'Patient', + name: [{ family }], + active: true, + }); + const searchResult = await repo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'name', + operator: Operator.EQUALS, + value: family, + }, + { + code: 'active', + operator: Operator.EQUALS, + value: 'true', + }, + ], + }); + expect(searchResult.entry).toHaveLength(1); + expect(searchResult.entry?.[0]?.resource?.id).toEqual(patient.id); + })); + + test('Not equals with comma separated values', () => + withTestContext(async () => { + // Create 3 service requests + // All 3 have the same category for test isolation + // Each have a different code + const category = randomUUID(); + const serviceRequests = []; + for (let i = 0; i < 3; i++) { + serviceRequests.push( + await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + subject: { reference: 'Patient/' + randomUUID() }, + category: [{ coding: [{ code: category }] }], + code: { coding: [{ code: randomUUID() }] }, + }) + ); + } - test('Error on invalid search parameter', async () => { - try { - await systemRepo.search({ - resourceType: 'ServiceRequest', - filters: [ - { - code: 'basedOn', // should be "based-on" - operator: Operator.EQUALS, - value: 'ServiceRequest/123', - }, - ], - }); - } catch (err) { - const outcome = (err as OperationOutcomeError).outcome; - expect(outcome.issue?.[0]?.details?.text).toEqual('Unknown search parameter: basedOn'); - } - }); + // Search for service requests with category + // and code "not equals" the first two separated by a comma + const searchResult = await repo.search({ + resourceType: 'ServiceRequest', + filters: [ + { + code: 'category', + operator: Operator.EQUALS, + value: category, + }, + { + code: 'code', + operator: Operator.NOT_EQUALS, + value: serviceRequests[0].code.coding[0].code + ',' + serviceRequests[1].code.coding[0].code, + }, + ], + }); + expect(searchResult.entry).toHaveLength(1); + })); + + test('_id equals with comma separated values', () => + withTestContext(async () => { + // Create 3 service requests + const serviceRequests = []; + for (let i = 0; i < 3; i++) { + serviceRequests.push( + await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + subject: { reference: 'Patient/' + randomUUID() }, + code: { text: randomUUID() }, + }) + ); + } - test('Patient search without resource type', () => - withTestContext(async () => { - // Create Patient - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - }); + // Search for service requests with _id equals the first two separated by a comma + const searchResult = await repo.search({ + resourceType: 'ServiceRequest', + filters: [ + { + code: '_id', + operator: Operator.EQUALS, + value: serviceRequests[0].id + ',' + serviceRequests[1].id, + }, + ], + }); + expect(searchResult.entry).toHaveLength(2); + })); - // Create AllergyIntolerance - const allergyIntolerance = await systemRepo.createResource({ - resourceType: 'AllergyIntolerance', - patient: createReference(patient), - clinicalStatus: { text: 'active' }, - }); + test('Error on invalid search parameter', async () => { + try { + await repo.search({ + resourceType: 'ServiceRequest', + filters: [ + { + code: 'basedOn', // should be "based-on" + operator: Operator.EQUALS, + value: 'ServiceRequest/123', + }, + ], + }); + } catch (err) { + const outcome = (err as OperationOutcomeError).outcome; + expect(outcome.issue?.[0]?.details?.text).toEqual('Unknown search parameter: basedOn'); + } + }); - // Search by patient - const searchResult = await systemRepo.search({ - resourceType: 'AllergyIntolerance', - filters: [ - { - code: 'patient', - operator: Operator.EQUALS, - value: patient.id as string, - }, - ], - }); - expect(searchResult.entry?.[0]?.resource?.id).toEqual(allergyIntolerance.id); - })); + test('Patient search without resource type', () => + withTestContext(async () => { + // Create Patient + const patient = await repo.createResource({ + resourceType: 'Patient', + }); - test('Subject search without resource type', () => - withTestContext(async () => { - // Create Patient - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - }); + // Create AllergyIntolerance + const allergyIntolerance = await repo.createResource({ + resourceType: 'AllergyIntolerance', + patient: createReference(patient), + clinicalStatus: { text: 'active' }, + }); - // Create Observation - const observation = await systemRepo.createResource({ - resourceType: 'Observation', - status: 'final', - code: { text: 'test' }, - subject: createReference(patient), - }); + // Search by patient + const searchResult = await repo.search({ + resourceType: 'AllergyIntolerance', + filters: [ + { + code: 'patient', + operator: Operator.EQUALS, + value: patient.id as string, + }, + ], + }); + expect(searchResult.entry?.[0]?.resource?.id).toEqual(allergyIntolerance.id); + })); - // Search by patient - const searchResult = await systemRepo.search({ - resourceType: 'Observation', - filters: [ - { - code: 'subject', - operator: Operator.EQUALS, - value: patient.id as string, - }, - ], - }); - expect(searchResult.entry?.[0]?.resource?.id).toEqual(observation.id); - })); - - test('Chained search on array columns', () => - withTestContext(async () => { - // Create Practitioner - const pcp = await systemRepo.createResource({ - resourceType: 'Practitioner', - }); - // Create Patient - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - generalPractitioner: [createReference(pcp)], - }); + test('Subject search without resource type', () => + withTestContext(async () => { + // Create Patient + const patient = await repo.createResource({ + resourceType: 'Patient', + }); - // Create CareTeam - const code = randomUUID(); - const categorySystem = 'http://example.com/care-team-category'; - await systemRepo.createResource({ - resourceType: 'CareTeam', - category: [ - { - coding: [ - { - system: categorySystem, - code, - display: 'Public health-focused care team', - }, - ], - }, - ], - participant: [{ member: createReference(pcp) }], - }); + // Create Observation + const observation = await repo.createResource({ + resourceType: 'Observation', + status: 'final', + code: { text: 'test' }, + subject: createReference(patient), + }); - // Search chain - const searchResult = await systemRepo.search( - parseSearchDefinition( - `Patient?general-practitioner:Practitioner._has:CareTeam:participant:category=${categorySystem}|${code}` - ) - ); - expect(searchResult.entry?.[0]?.resource?.id).toEqual(patient.id); - })); - - test('Chained search on singlet columns', () => - withTestContext(async () => { - const code = randomUUID(); - // Create linked resources - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - }); - const encounter = await systemRepo.createResource({ - resourceType: 'Encounter', - status: 'finished', - class: { system: 'http://example.com/appt-type', code }, - }); - const observation = await systemRepo.createResource({ - resourceType: 'Observation', - status: 'final', - code: { text: 'Throat culture' }, - subject: createReference(patient), - encounter: createReference(encounter), - }); - await systemRepo.createResource({ - resourceType: 'DiagnosticReport', - status: 'final', - code: { text: 'Strep test' }, - encounter: createReference(encounter), - result: [createReference(observation)], - }); + // Search by patient + const searchResult = await repo.search({ + resourceType: 'Observation', + filters: [ + { + code: 'subject', + operator: Operator.EQUALS, + value: patient.id as string, + }, + ], + }); + expect(searchResult.entry?.[0]?.resource?.id).toEqual(observation.id); + })); + + test('Chained search on array columns', () => + withTestContext(async () => { + // Create Practitioner + const pcp = await repo.createResource({ + resourceType: 'Practitioner', + }); + // Create Patient + const patient = await repo.createResource({ + resourceType: 'Patient', + generalPractitioner: [createReference(pcp)], + }); - const result = await systemRepo.search( - parseSearchDefinition(`Patient?_has:Observation:subject:encounter:Encounter.class=${code}`) - ); - expect(result.entry?.[0]?.resource?.id).toEqual(patient.id); - })); + // Create CareTeam + const code = randomUUID(); + const categorySystem = 'http://example.com/care-team-category'; + await repo.createResource({ + resourceType: 'CareTeam', + category: [ + { + coding: [ + { + system: categorySystem, + code, + display: 'Public health-focused care team', + }, + ], + }, + ], + participant: [{ member: createReference(pcp) }], + }); - test('Rejects too long chained search', () => - withTestContext(async () => { - await expect(() => - systemRepo.search( + // Search chain + const searchResult = await repo.search( parseSearchDefinition( - `Patient?_has:Observation:subject:encounter:Encounter._has:DiagnosticReport:encounter:result.specimen.parent.collected=2023` + `Patient?general-practitioner:Practitioner._has:CareTeam:participant:category=${categorySystem}|${code}` ) - ) - ).rejects.toEqual(new Error('Search chains longer than three links are not currently supported')); - })); - - test.each([ - ['Patient?organization.invalid.name=Kaiser', 'Invalid search parameter in chain: Organization?invalid'], - ['Patient?organization.invalid=true', 'Invalid search parameter at end of chain: Organization?invalid'], - [ - 'Patient?general-practitioner.qualification-period=2023', - 'Unable to identify next resource type for search parameter: Patient?general-practitioner', - ], - ['Patient?_has:Observation:invalid:status=active', 'Invalid search parameter in chain: Observation?invalid'], - [ - 'Patient?_has:Observation:encounter:status=active', - 'Invalid reverse chain link: search parameter Observation?encounter does not refer to Patient', - ], - ['Patient?_has:Observation:status=active', 'Invalid search chain: _has:Observation:status'], - ])('Invalid chained search parameters: %s', (searchString: string, errorMsg: string) => { - return expect(systemRepo.search(parseSearchDefinition(searchString))).rejects.toEqual(new Error(errorMsg)); - }); + ); + expect(searchResult.entry?.[0]?.resource?.id).toEqual(patient.id); + })); + + test('Chained search on singlet columns', () => + withTestContext(async () => { + const code = randomUUID(); + // Create linked resources + const patient = await repo.createResource({ + resourceType: 'Patient', + }); + const encounter = await repo.createResource({ + resourceType: 'Encounter', + status: 'finished', + class: { system: 'http://example.com/appt-type', code }, + }); + const observation = await repo.createResource({ + resourceType: 'Observation', + status: 'final', + code: { text: 'Throat culture' }, + subject: createReference(patient), + encounter: createReference(encounter), + }); + await repo.createResource({ + resourceType: 'DiagnosticReport', + status: 'final', + code: { text: 'Strep test' }, + encounter: createReference(encounter), + result: [createReference(observation)], + }); - test('Include references success', () => - withTestContext(async () => { - const patient = await systemRepo.createResource({ resourceType: 'Patient' }); - const order = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - subject: createReference(patient), - }); - const bundle = await systemRepo.search({ - resourceType: 'ServiceRequest', - include: [ - { - resourceType: 'ServiceRequest', - searchParam: 'subject', - }, - ], - total: 'accurate', - filters: [{ code: '_id', operator: Operator.EQUALS, value: order.id as string }], - }); - expect(bundle.total).toEqual(1); - expect(bundleContains(bundle, order)).toBeTruthy(); - expect(bundleContains(bundle, patient)).toBeTruthy(); - })); - - test('Include canonical success', () => - withTestContext(async () => { - const canonicalURL = 'http://example.com/fhir/Questionnaire/PHQ-9/' + randomUUID(); - const questionnaire = await systemRepo.createResource({ - resourceType: 'Questionnaire', - status: 'active', - url: canonicalURL, - }); - const response = await systemRepo.createResource({ - resourceType: 'QuestionnaireResponse', - status: 'in-progress', - questionnaire: canonicalURL, - }); - const bundle = await systemRepo.search({ - resourceType: 'QuestionnaireResponse', - include: [ - { - resourceType: 'QuestionnaireResponse', - searchParam: 'questionnaire', - }, - ], - total: 'accurate', - filters: [{ code: '_id', operator: Operator.EQUALS, value: response.id as string }], - }); - expect(bundle.total).toEqual(1); - expect(bundleContains(bundle, response)).toBeTruthy(); - expect(bundleContains(bundle, questionnaire)).toBeTruthy(); - })); - - test('Include PlanDefinition mixed types', () => - withTestContext(async () => { - const canonical = 'http://example.com/fhir/R4/ActivityDefinition/' + randomUUID(); - const uri = 'http://example.com/fhir/R4/ActivityDefinition/' + randomUUID(); - const plan = await systemRepo.createResource({ - resourceType: 'PlanDefinition', - status: 'active', - action: [{ definitionCanonical: canonical }, { definitionUri: uri }], - }); - const activity1 = await systemRepo.createResource({ - resourceType: 'ActivityDefinition', - status: 'active', - url: canonical, - }); - const activity2 = await systemRepo.createResource({ - resourceType: 'ActivityDefinition', - status: 'active', - url: uri, - }); - const bundle = await systemRepo.search({ - resourceType: 'PlanDefinition', - include: [ - { - resourceType: 'PlanDefinition', - searchParam: 'definition', - }, - ], - total: 'accurate', - filters: [{ code: '_id', operator: Operator.EQUALS, value: plan.id as string }], - }); - expect(bundle.total).toEqual(1); - expect(bundleContains(bundle, plan)).toBeTruthy(); - expect(bundleContains(bundle, activity1)).toBeTruthy(); - expect(bundleContains(bundle, activity2)).toBeTruthy(); - })); - - test('Include references invalid search param', async () => { - try { - await systemRepo.search({ - resourceType: 'ServiceRequest', - include: [ - { - resourceType: 'ServiceRequest', - searchParam: 'xyz', - }, - ], - }); - } catch (err) { - const outcome = (err as OperationOutcomeError).outcome; - expect(outcome.issue?.[0]?.details?.text).toEqual('Invalid include parameter: ServiceRequest:xyz'); - } - }); + const result = await repo.search( + parseSearchDefinition(`Patient?_has:Observation:subject:encounter:Encounter.class=${code}`) + ); + expect(result.entry?.[0]?.resource?.id).toEqual(patient.id); + })); + + test('Rejects too long chained search', () => + withTestContext(async () => { + await expect(() => + repo.search( + parseSearchDefinition( + `Patient?_has:Observation:subject:encounter:Encounter._has:DiagnosticReport:encounter:result.specimen.parent.collected=2023` + ) + ) + ).rejects.toEqual(new Error('Search chains longer than three links are not currently supported')); + })); + + test.each([ + ['Patient?organization.invalid.name=Kaiser', 'Invalid search parameter in chain: Organization?invalid'], + ['Patient?organization.invalid=true', 'Invalid search parameter at end of chain: Organization?invalid'], + [ + 'Patient?general-practitioner.qualification-period=2023', + 'Unable to identify next resource type for search parameter: Patient?general-practitioner', + ], + ['Patient?_has:Observation:invalid:status=active', 'Invalid search parameter in chain: Observation?invalid'], + [ + 'Patient?_has:Observation:encounter:status=active', + 'Invalid reverse chain link: search parameter Observation?encounter does not refer to Patient', + ], + ['Patient?_has:Observation:status=active', 'Invalid search chain: _has:Observation:status'], + ])('Invalid chained search parameters: %s', (searchString: string, errorMsg: string) => { + return expect(repo.search(parseSearchDefinition(searchString))).rejects.toEqual(new Error(errorMsg)); + }); + + test('Include references success', () => + withTestContext(async () => { + const patient = await repo.createResource({ resourceType: 'Patient' }); + const order = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + subject: createReference(patient), + }); + const bundle = await repo.search({ + resourceType: 'ServiceRequest', + include: [ + { + resourceType: 'ServiceRequest', + searchParam: 'subject', + }, + ], + total: 'accurate', + filters: [{ code: '_id', operator: Operator.EQUALS, value: order.id as string }], + }); + expect(bundle.total).toEqual(1); + expect(bundleContains(bundle, order)).toBeTruthy(); + expect(bundleContains(bundle, patient)).toBeTruthy(); + })); + + test('Include canonical success', () => + withTestContext(async () => { + const canonicalURL = 'http://example.com/fhir/Questionnaire/PHQ-9/' + randomUUID(); + const questionnaire = await repo.createResource({ + resourceType: 'Questionnaire', + status: 'active', + url: canonicalURL, + }); + const response = await repo.createResource({ + resourceType: 'QuestionnaireResponse', + status: 'in-progress', + questionnaire: canonicalURL, + }); + const bundle = await repo.search({ + resourceType: 'QuestionnaireResponse', + include: [ + { + resourceType: 'QuestionnaireResponse', + searchParam: 'questionnaire', + }, + ], + total: 'accurate', + filters: [{ code: '_id', operator: Operator.EQUALS, value: response.id as string }], + }); + expect(bundle.total).toEqual(1); + expect(bundleContains(bundle, response)).toBeTruthy(); + expect(bundleContains(bundle, questionnaire)).toBeTruthy(); + })); + + test('Include PlanDefinition mixed types', () => + withTestContext(async () => { + const canonical = 'http://example.com/fhir/R4/ActivityDefinition/' + randomUUID(); + const uri = 'http://example.com/fhir/R4/ActivityDefinition/' + randomUUID(); + const plan = await repo.createResource({ + resourceType: 'PlanDefinition', + status: 'active', + action: [{ definitionCanonical: canonical }, { definitionUri: uri }], + }); + const activity1 = await repo.createResource({ + resourceType: 'ActivityDefinition', + status: 'active', + url: canonical, + }); + const activity2 = await repo.createResource({ + resourceType: 'ActivityDefinition', + status: 'active', + url: uri, + }); + const bundle = await repo.search({ + resourceType: 'PlanDefinition', + include: [ + { + resourceType: 'PlanDefinition', + searchParam: 'definition', + }, + ], + total: 'accurate', + filters: [{ code: '_id', operator: Operator.EQUALS, value: plan.id as string }], + }); + expect(bundle.total).toEqual(1); + expect(bundleContains(bundle, plan)).toBeTruthy(); + expect(bundleContains(bundle, activity1)).toBeTruthy(); + expect(bundleContains(bundle, activity2)).toBeTruthy(); + })); + + test('Include references invalid search param', async () => { + try { + await repo.search({ + resourceType: 'ServiceRequest', + include: [ + { + resourceType: 'ServiceRequest', + searchParam: 'xyz', + }, + ], + }); + } catch (err) { + const outcome = (err as OperationOutcomeError).outcome; + expect(outcome.issue?.[0]?.details?.text).toEqual('Invalid include parameter: ServiceRequest:xyz'); + } + }); - test('Reverse include Provenance', () => - withTestContext(async () => { - const family = randomUUID(); + test('Reverse include Provenance', () => + withTestContext(async () => { + const family = randomUUID(); - const practitioner1 = await systemRepo.createResource({ - resourceType: 'Practitioner', - name: [{ given: ['Homer'], family }], - }); + const practitioner1 = await repo.createResource({ + resourceType: 'Practitioner', + name: [{ given: ['Homer'], family }], + }); - const practitioner2 = await systemRepo.createResource({ - resourceType: 'Practitioner', - name: [{ given: ['Marge'], family }], - }); + const practitioner2 = await repo.createResource({ + resourceType: 'Practitioner', + name: [{ given: ['Marge'], family }], + }); - const searchRequest: SearchRequest = { - resourceType: 'Practitioner', - filters: [{ code: 'name', operator: Operator.EQUALS, value: family }], - revInclude: [ - { - resourceType: 'Provenance', - searchParam: 'target', - }, - ], - }; - - const searchResult1 = await systemRepo.search(searchRequest); - expect(searchResult1.entry).toHaveLength(2); - expect(bundleContains(searchResult1, practitioner1)).toBeTruthy(); - expect(bundleContains(searchResult1, practitioner2)).toBeTruthy(); - - const provenance1 = await systemRepo.createResource({ - resourceType: 'Provenance', - target: [createReference(practitioner1)], - agent: [{ who: createReference(practitioner1) }], - recorded: new Date().toISOString(), - }); + const searchRequest: SearchRequest = { + resourceType: 'Practitioner', + filters: [{ code: 'name', operator: Operator.EQUALS, value: family }], + revInclude: [ + { + resourceType: 'Provenance', + searchParam: 'target', + }, + ], + }; + + const searchResult1 = await repo.search(searchRequest); + expect(searchResult1.entry).toHaveLength(2); + expect(bundleContains(searchResult1, practitioner1)).toBeTruthy(); + expect(bundleContains(searchResult1, practitioner2)).toBeTruthy(); + + const provenance1 = await repo.createResource({ + resourceType: 'Provenance', + target: [createReference(practitioner1)], + agent: [{ who: createReference(practitioner1) }], + recorded: new Date().toISOString(), + }); - const provenance2 = await systemRepo.createResource({ - resourceType: 'Provenance', - target: [createReference(practitioner2)], - agent: [{ who: createReference(practitioner2) }], - recorded: new Date().toISOString(), - }); + const provenance2 = await repo.createResource({ + resourceType: 'Provenance', + target: [createReference(practitioner2)], + agent: [{ who: createReference(practitioner2) }], + recorded: new Date().toISOString(), + }); - const searchResult2 = await systemRepo.search(searchRequest); - expect(searchResult2.entry).toHaveLength(4); - expect(bundleContains(searchResult2, practitioner1)).toBeTruthy(); - expect(bundleContains(searchResult2, practitioner2)).toBeTruthy(); - expect(bundleContains(searchResult2, provenance1)).toBeTruthy(); - expect(bundleContains(searchResult2, provenance2)).toBeTruthy(); - })); - - test('Reverse include canonical', () => - withTestContext(async () => { - const canonicalURL = 'http://example.com/fhir/Questionnaire/PHQ-9/' + randomUUID(); - const questionnaire = await systemRepo.createResource({ - resourceType: 'Questionnaire', - status: 'active', - url: canonicalURL, - }); - const response = await systemRepo.createResource({ - resourceType: 'QuestionnaireResponse', - status: 'in-progress', - questionnaire: canonicalURL, - }); - const bundle = await systemRepo.search({ - resourceType: 'Questionnaire', - revInclude: [ - { - resourceType: 'QuestionnaireResponse', - searchParam: 'questionnaire', - }, - ], - total: 'accurate', - filters: [{ code: '_id', operator: Operator.EQUALS, value: questionnaire.id as string }], - }); - expect(bundle.total).toEqual(1); - expect(bundleContains(bundle, response)).toBeTruthy(); - expect(bundleContains(bundle, questionnaire)).toBeTruthy(); - })); - - test('_include:iterate', () => - withTestContext(async () => { - /* + const searchResult2 = await repo.search(searchRequest); + expect(searchResult2.entry).toHaveLength(4); + expect(bundleContains(searchResult2, practitioner1)).toBeTruthy(); + expect(bundleContains(searchResult2, practitioner2)).toBeTruthy(); + expect(bundleContains(searchResult2, provenance1)).toBeTruthy(); + expect(bundleContains(searchResult2, provenance2)).toBeTruthy(); + })); + + test('Reverse include canonical', () => + withTestContext(async () => { + const canonicalURL = 'http://example.com/fhir/Questionnaire/PHQ-9/' + randomUUID(); + const questionnaire = await repo.createResource({ + resourceType: 'Questionnaire', + status: 'active', + url: canonicalURL, + }); + const response = await repo.createResource({ + resourceType: 'QuestionnaireResponse', + status: 'in-progress', + questionnaire: canonicalURL, + }); + const bundle = await repo.search({ + resourceType: 'Questionnaire', + revInclude: [ + { + resourceType: 'QuestionnaireResponse', + searchParam: 'questionnaire', + }, + ], + total: 'accurate', + filters: [{ code: '_id', operator: Operator.EQUALS, value: questionnaire.id as string }], + }); + expect(bundle.total).toEqual(1); + expect(bundleContains(bundle, response)).toBeTruthy(); + expect(bundleContains(bundle, questionnaire)).toBeTruthy(); + })); + + test('_include:iterate', () => + withTestContext(async () => { + /* Construct resources for the search to operate on. The test search query and resource graph it will act on are shown below. Query: /Patient?identifier=patient @@ -2173,103 +1957,103 @@ describe('FHIR Search', () => { 3. _include w/o :iterate does not apply recursively (Patient:organization) 4. Resources which are included multiple times are deduplicated in the search results */ - const rootPatientIdentifier = randomUUID(); - const organization1 = await systemRepo.createResource({ - resourceType: 'Organization', - name: 'org1', - }); - const organization2 = await systemRepo.createResource({ - resourceType: 'Organization', - name: 'org2', - }); - const practitioner1 = await systemRepo.createResource({ resourceType: 'Practitioner' }); - const practitioner2 = await systemRepo.createResource({ resourceType: 'Practitioner' }); - const linked3 = await systemRepo.createResource({ - resourceType: 'Patient', - managingOrganization: { reference: `Organization/${organization2.id}` }, - generalPractitioner: [ - { - reference: `Practitioner/${practitioner2.id}`, - }, - ], - }); - const linked1 = await systemRepo.createResource({ - resourceType: 'Patient', - link: [ - { - other: { reference: `Patient/${linked3.id}` }, - type: 'replaces', - }, - ], - }); - const linked2 = await systemRepo.createResource({ - resourceType: 'Patient', - generalPractitioner: [ - { - reference: `Practitioner/${practitioner2.id}`, - }, - ], - }); - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - identifier: [ - { - value: rootPatientIdentifier, - }, - ], - link: [ - { - other: { reference: `Patient/${linked1.id}` }, - type: 'replaces', - }, - { - other: { reference: `Patient/${linked2.id}` }, - type: 'replaces', - }, - ], - managingOrganization: { - reference: `Organization/${organization1.id}`, - }, - generalPractitioner: [ - { - reference: `Practitioner/${practitioner1.id}`, + const rootPatientIdentifier = randomUUID(); + const organization1 = await repo.createResource({ + resourceType: 'Organization', + name: 'org1', + }); + const organization2 = await repo.createResource({ + resourceType: 'Organization', + name: 'org2', + }); + const practitioner1 = await repo.createResource({ resourceType: 'Practitioner' }); + const practitioner2 = await repo.createResource({ resourceType: 'Practitioner' }); + const linked3 = await repo.createResource({ + resourceType: 'Patient', + managingOrganization: { reference: `Organization/${organization2.id}` }, + generalPractitioner: [ + { + reference: `Practitioner/${practitioner2.id}`, + }, + ], + }); + const linked1 = await repo.createResource({ + resourceType: 'Patient', + link: [ + { + other: { reference: `Patient/${linked3.id}` }, + type: 'replaces', + }, + ], + }); + const linked2 = await repo.createResource({ + resourceType: 'Patient', + generalPractitioner: [ + { + reference: `Practitioner/${practitioner2.id}`, + }, + ], + }); + const patient = await repo.createResource({ + resourceType: 'Patient', + identifier: [ + { + value: rootPatientIdentifier, + }, + ], + link: [ + { + other: { reference: `Patient/${linked1.id}` }, + type: 'replaces', + }, + { + other: { reference: `Patient/${linked2.id}` }, + type: 'replaces', + }, + ], + managingOrganization: { + reference: `Organization/${organization1.id}`, }, - ], - }); + generalPractitioner: [ + { + reference: `Practitioner/${practitioner1.id}`, + }, + ], + }); - // Run the test search query - const bundle = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: 'identifier', - operator: Operator.EQUALS, - value: rootPatientIdentifier, - }, - ], - include: [ - { resourceType: 'Patient', searchParam: 'organization' }, - { resourceType: 'Patient', searchParam: 'link', modifier: Operator.ITERATE }, - { resourceType: 'Patient', searchParam: 'general-practitioner', modifier: Operator.ITERATE }, - ], - }); + // Run the test search query + const bundle = await repo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'identifier', + operator: Operator.EQUALS, + value: rootPatientIdentifier, + }, + ], + include: [ + { resourceType: 'Patient', searchParam: 'organization' }, + { resourceType: 'Patient', searchParam: 'link', modifier: Operator.ITERATE }, + { resourceType: 'Patient', searchParam: 'general-practitioner', modifier: Operator.ITERATE }, + ], + }); - const expected = [ - `Patient/${patient.id}`, - `Patient/${linked1.id}`, - `Patient/${linked2.id}`, - `Patient/${linked3.id}`, - `Organization/${organization1.id}`, - `Practitioner/${practitioner1.id}`, - `Practitioner/${practitioner2.id}`, - ].sort(); - - expect(bundle.entry?.map((e) => `${e.resource?.resourceType}/${e.resource?.id}`).sort()).toEqual(expected); - })); - - test('_revinclude:iterate', () => - withTestContext(async () => { - /* + const expected = [ + `Patient/${patient.id}`, + `Patient/${linked1.id}`, + `Patient/${linked2.id}`, + `Patient/${linked3.id}`, + `Organization/${organization1.id}`, + `Practitioner/${practitioner1.id}`, + `Practitioner/${practitioner2.id}`, + ].sort(); + + expect(bundle.entry?.map((e) => `${e.resource?.resourceType}/${e.resource?.id}`).sort()).toEqual(expected); + })); + + test('_revinclude:iterate', () => + withTestContext(async () => { + /* Construct resources for the search to operate on. The test search query and resource graph it will act on are shown below. Query: /Patient?identifier=patient @@ -2296,1116 +2080,1351 @@ describe('FHIR Search', () => { 2. _revinclude w/ :iterate applies to resources from other _revinclude parameters (Observation:subject) 3. _revinclude w/o :iterate does not apply recursively (Patient:link) */ - const rootPatientIdentifier = randomUUID(); - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - identifier: [ - { - value: rootPatientIdentifier, - }, - ], - }); - const linked1 = await systemRepo.createResource({ - resourceType: 'Patient', - link: [ - { - other: { reference: `Patient/${patient.id}` }, - type: 'replaced-by', + const rootPatientIdentifier = randomUUID(); + const patient = await repo.createResource({ + resourceType: 'Patient', + identifier: [ + { + value: rootPatientIdentifier, + }, + ], + }); + const linked1 = await repo.createResource({ + resourceType: 'Patient', + link: [ + { + other: { reference: `Patient/${patient.id}` }, + type: 'replaced-by', + }, + ], + }); + const linked2 = await repo.createResource({ + resourceType: 'Patient', + link: [ + { + other: { reference: `Patient/${patient.id}` }, + type: 'replaced-by', + }, + ], + }); + await repo.createResource({ + resourceType: 'Patient', + link: [ + { + other: { reference: `Patient/${linked1.id}` }, + type: 'replaced-by', + }, + ], + }); + const baseObservation: Observation = { + resourceType: 'Observation', + status: 'final', + code: { + coding: [ + { + system: LOINC, + code: 'fake', + }, + ], }, - ], - }); - const linked2 = await systemRepo.createResource({ - resourceType: 'Patient', - link: [ - { - other: { reference: `Patient/${patient.id}` }, - type: 'replaced-by', + }; + const observation1 = await repo.createResource({ + ...baseObservation, + subject: { + reference: `Patient/${patient.id}`, }, - ], - }); - await systemRepo.createResource({ - resourceType: 'Patient', - link: [ - { - other: { reference: `Patient/${linked1.id}` }, - type: 'replaced-by', + }); + const observation2 = await repo.createResource({ + ...baseObservation, + subject: { + display: 'Alex J. Chalmers', }, - ], - }); - const baseObservation: Observation = { - resourceType: 'Observation', - status: 'final', - code: { - coding: [ - { - system: LOINC, - code: 'fake', - }, - ], - }, - }; - const observation1 = await systemRepo.createResource({ - ...baseObservation, - subject: { - reference: `Patient/${patient.id}`, - }, - }); - const observation2 = await systemRepo.createResource({ - ...baseObservation, - subject: { - display: 'Alex J. Chalmers', - }, - hasMember: [ - { - reference: `Observation/${observation1.id}`, + hasMember: [ + { + reference: `Observation/${observation1.id}`, + }, + ], + }); + const observation3 = await repo.createResource({ + ...baseObservation, + subject: { + reference: `Patient/${linked2.id}`, }, - ], - }); - const observation3 = await systemRepo.createResource({ - ...baseObservation, - subject: { - reference: `Patient/${linked2.id}`, - }, - }); - const observation4 = await systemRepo.createResource({ - ...baseObservation, - subject: { - display: 'Alex J. Chalmers', - }, - hasMember: [ - { - reference: `Observation/${observation2.id}`, + }); + const observation4 = await repo.createResource({ + ...baseObservation, + subject: { + display: 'Alex J. Chalmers', }, - ], - }); + hasMember: [ + { + reference: `Observation/${observation2.id}`, + }, + ], + }); - // Run the test search query - const bundle = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: 'identifier', - operator: Operator.EQUALS, - value: rootPatientIdentifier, - }, - ], - revInclude: [ - { resourceType: 'Patient', searchParam: 'link' }, - { resourceType: 'Observation', searchParam: 'subject', modifier: Operator.ITERATE }, - { resourceType: 'Observation', searchParam: 'has-member', modifier: Operator.ITERATE }, - ], - }); + // Run the test search query + const bundle = await repo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'identifier', + operator: Operator.EQUALS, + value: rootPatientIdentifier, + }, + ], + revInclude: [ + { resourceType: 'Patient', searchParam: 'link' }, + { resourceType: 'Observation', searchParam: 'subject', modifier: Operator.ITERATE }, + { resourceType: 'Observation', searchParam: 'has-member', modifier: Operator.ITERATE }, + ], + }); - const expected = [ - `Patient/${patient.id}`, - `Patient/${linked1.id}`, - `Patient/${linked2.id}`, - `Observation/${observation1.id}`, - `Observation/${observation2.id}`, - `Observation/${observation3.id}`, - `Observation/${observation4.id}`, - ].sort(); - - expect(bundle.entry?.map((e) => `${e.resource?.resourceType}/${e.resource?.id}`).sort()).toEqual(expected); - })); - - test('_include depth limit', () => - withTestContext(async () => { - const rootPatientIdentifier = randomUUID(); - const linked6 = await systemRepo.createResource({ - resourceType: 'Patient', - }); - const linked5 = await systemRepo.createResource({ - resourceType: 'Patient', - link: [ - { - other: { reference: `Patient/${linked6.id}` }, - type: 'replaces', - }, - ], - }); - const linked4 = await systemRepo.createResource({ - resourceType: 'Patient', - link: [ - { - other: { reference: `Patient/${linked5.id}` }, - type: 'replaces', - }, - ], - }); - const linked3 = await systemRepo.createResource({ - resourceType: 'Patient', - link: [ - { - other: { reference: `Patient/${linked4.id}` }, - type: 'replaces', - }, - ], - }); - const linked2 = await systemRepo.createResource({ - resourceType: 'Patient', - link: [ - { - other: { reference: `Patient/${linked3.id}` }, - type: 'replaces', - }, - ], - }); - const linked1 = await systemRepo.createResource({ - resourceType: 'Patient', - link: [ - { - other: { reference: `Patient/${linked2.id}` }, - type: 'replaces', - }, - ], - }); - await systemRepo.createResource({ - resourceType: 'Patient', - identifier: [ - { - value: rootPatientIdentifier, + const expected = [ + `Patient/${patient.id}`, + `Patient/${linked1.id}`, + `Patient/${linked2.id}`, + `Observation/${observation1.id}`, + `Observation/${observation2.id}`, + `Observation/${observation3.id}`, + `Observation/${observation4.id}`, + ].sort(); + + expect(bundle.entry?.map((e) => `${e.resource?.resourceType}/${e.resource?.id}`).sort()).toEqual(expected); + })); + + test('_include depth limit', () => + withTestContext(async () => { + const rootPatientIdentifier = randomUUID(); + const linked6 = await repo.createResource({ + resourceType: 'Patient', + }); + const linked5 = await repo.createResource({ + resourceType: 'Patient', + link: [ + { + other: { reference: `Patient/${linked6.id}` }, + type: 'replaces', + }, + ], + }); + const linked4 = await repo.createResource({ + resourceType: 'Patient', + link: [ + { + other: { reference: `Patient/${linked5.id}` }, + type: 'replaces', + }, + ], + }); + const linked3 = await repo.createResource({ + resourceType: 'Patient', + link: [ + { + other: { reference: `Patient/${linked4.id}` }, + type: 'replaces', + }, + ], + }); + const linked2 = await repo.createResource({ + resourceType: 'Patient', + link: [ + { + other: { reference: `Patient/${linked3.id}` }, + type: 'replaces', + }, + ], + }); + const linked1 = await repo.createResource({ + resourceType: 'Patient', + link: [ + { + other: { reference: `Patient/${linked2.id}` }, + type: 'replaces', + }, + ], + }); + await repo.createResource({ + resourceType: 'Patient', + identifier: [ + { + value: rootPatientIdentifier, + }, + ], + link: [ + { + other: { reference: `Patient/${linked1.id}` }, + type: 'replaces', + }, + ], + }); + + return expect( + repo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'identifier', + operator: Operator.EQUALS, + value: rootPatientIdentifier, + }, + ], + include: [{ resourceType: 'Patient', searchParam: 'link', modifier: Operator.ITERATE }], + }) + ).rejects.toBeDefined(); + })); + + test('_include on empty search results', () => + withTestContext(async () => { + return expect( + repo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'identifier', + operator: Operator.EQUALS, + value: randomUUID(), + }, + ], + include: [{ resourceType: 'Patient', searchParam: 'link', modifier: Operator.ITERATE }], + }) + ).resolves.toMatchObject({ + resourceType: 'Bundle', + type: 'searchset', + entry: [], + total: undefined, + }); + })); + + test('DiagnosticReport category with system', () => + withTestContext(async () => { + const code = randomUUID(); + const dr = await repo.createResource({ + resourceType: 'DiagnosticReport', + status: 'final', + code: { coding: [{ code }] }, + category: [{ coding: [{ system: LOINC, code: 'LP217198-3' }] }], + }); + + const bundle = await repo.search({ + resourceType: 'DiagnosticReport', + filters: [ + { + code: 'code', + operator: Operator.EQUALS, + value: code, + }, + { + code: 'category', + operator: Operator.EQUALS, + value: `${LOINC}|LP217198-3`, + }, + ], + count: 1, + }); + + expect(bundleContains(bundle, dr)).toBeTruthy(); + })); + + test('Encounter.period date search', () => + withTestContext(async () => { + const e = await repo.createResource({ + resourceType: 'Encounter', + identifier: [{ value: randomUUID() }], + status: 'finished', + class: { code: 'test' }, + period: { + start: '2020-02-01', + end: '2020-02-02', }, - ], - link: [ - { - other: { reference: `Patient/${linked1.id}` }, - type: 'replaces', + }); + + const bundle = await repo.search({ + resourceType: 'Encounter', + filters: [ + { + code: 'identifier', + operator: Operator.EQUALS, + value: e.identifier?.[0]?.value as string, + }, + { + code: 'date', + operator: Operator.GREATER_THAN, + value: '2020-01-01', + }, + ], + count: 1, + }); + + expect(bundleContains(bundle, e)).toBeTruthy(); + })); + + test('Encounter.period dateTime search', () => + withTestContext(async () => { + const e = await repo.createResource({ + resourceType: 'Encounter', + identifier: [{ value: randomUUID() }], + status: 'finished', + class: { code: 'test' }, + period: { + start: '2020-02-01T13:30:00Z', + end: '2020-02-01T14:15:00Z', }, - ], - }); + }); - return expect( - systemRepo.search({ - resourceType: 'Patient', + const bundle = await repo.search({ + resourceType: 'Encounter', filters: [ { code: 'identifier', operator: Operator.EQUALS, - value: rootPatientIdentifier, + value: e.identifier?.[0]?.value as string, + }, + { + code: 'date', + operator: Operator.GREATER_THAN, + value: '2020-02-01T12:00Z', }, ], - include: [{ resourceType: 'Patient', searchParam: 'link', modifier: Operator.ITERATE }], - }) - ).rejects.toBeDefined(); - })); + count: 1, + }); - test('_include on empty search results', () => - withTestContext(async () => { - return expect( - systemRepo.search({ + expect(bundleContains(bundle, e)).toBeTruthy(); + })); + + test('Condition.code system search', () => + withTestContext(async () => { + const p = await repo.createResource({ resourceType: 'Patient', + name: [{ family: randomUUID() }], + }); + + const c1 = await repo.createResource({ + resourceType: 'Condition', + subject: createReference(p), + code: { coding: [{ system: SNOMED, code: '165002' }] }, + }); + + const c2 = await repo.createResource({ + resourceType: 'Condition', + subject: createReference(p), + code: { coding: [{ system: 'https://example.com', code: 'test' }] }, + }); + + const bundle = await repo.search({ + resourceType: 'Condition', filters: [ { - code: 'identifier', + code: 'subject', operator: Operator.EQUALS, - value: randomUUID(), + value: getReferenceString(p), + }, + { + code: 'code', + operator: Operator.EQUALS, + value: `${SNOMED}|`, }, ], - include: [{ resourceType: 'Patient', searchParam: 'link', modifier: Operator.ITERATE }], - }) - ).resolves.toMatchObject({ - resourceType: 'Bundle', - type: 'searchset', - entry: [], - total: undefined, - }); - })); - - test('DiagnosticReport category with system', () => - withTestContext(async () => { - const code = randomUUID(); - const dr = await systemRepo.createResource({ - resourceType: 'DiagnosticReport', - status: 'final', - code: { coding: [{ code }] }, - category: [{ coding: [{ system: LOINC, code: 'LP217198-3' }] }], - }); + }); - const bundle = await systemRepo.search({ - resourceType: 'DiagnosticReport', - filters: [ - { - code: 'code', - operator: Operator.EQUALS, - value: code, - }, - { - code: 'category', - operator: Operator.EQUALS, - value: `${LOINC}|LP217198-3`, - }, - ], - count: 1, - }); + expect(bundle.entry?.length).toEqual(1); + expect(bundleContains(bundle, c1)).toBeTruthy(); + expect(bundleContains(bundle, c2)).not.toBeTruthy(); + })); - expect(bundleContains(bundle, dr)).toBeTruthy(); - })); - - test('Encounter.period date search', () => - withTestContext(async () => { - const e = await systemRepo.createResource({ - resourceType: 'Encounter', - identifier: [{ value: randomUUID() }], - status: 'finished', - class: { code: 'test' }, - period: { - start: '2020-02-01', - end: '2020-02-02', - }, - }); + test('Condition.code :not next URL', () => + withTestContext(async () => { + const p = await repo.createResource({ + resourceType: 'Patient', + name: [{ family: randomUUID() }], + }); - const bundle = await systemRepo.search({ - resourceType: 'Encounter', - filters: [ - { - code: 'identifier', - operator: Operator.EQUALS, - value: e.identifier?.[0]?.value as string, - }, - { - code: 'date', - operator: Operator.GREATER_THAN, - value: '2020-01-01', - }, - ], - count: 1, - }); + await repo.createResource({ + resourceType: 'Condition', + subject: createReference(p), + code: { coding: [{ system: SNOMED, code: '165002' }] }, + }); - expect(bundleContains(bundle, e)).toBeTruthy(); - })); - - test('Encounter.period dateTime search', () => - withTestContext(async () => { - const e = await systemRepo.createResource({ - resourceType: 'Encounter', - identifier: [{ value: randomUUID() }], - status: 'finished', - class: { code: 'test' }, - period: { - start: '2020-02-01T13:30:00Z', - end: '2020-02-01T14:15:00Z', - }, - }); + await repo.createResource({ + resourceType: 'Condition', + subject: createReference(p), + code: { coding: [{ system: 'https://example.com', code: 'test' }] }, + }); - const bundle = await systemRepo.search({ - resourceType: 'Encounter', - filters: [ - { - code: 'identifier', - operator: Operator.EQUALS, - value: e.identifier?.[0]?.value as string, - }, - { - code: 'date', - operator: Operator.GREATER_THAN, - value: '2020-02-01T12:00Z', - }, - ], - count: 1, - }); + const bundle = await repo.search( + parseSearchUrl( + new URL(`https://x/Condition?subject=${getReferenceString(p)}&code:not=x&_count=1&_total=accurate`) + ) + ); + expect(bundle.entry?.length).toEqual(1); - expect(bundleContains(bundle, e)).toBeTruthy(); - })); + const nextUrl = bundle.link?.find((l) => l.relation === 'next')?.url; + expect(nextUrl).toBeDefined(); + expect(nextUrl).toContain('code:not=x'); + })); - test('Condition.code system search', () => - withTestContext(async () => { - const p = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ family: randomUUID() }], - }); + test('Condition.code :in search', () => + withTestContext(async () => { + // ValueSet: http://hl7.org/fhir/ValueSet/condition-code + // compose includes codes from http://snomed.info/sct + // but does not include codes from https://example.com - const c1 = await systemRepo.createResource({ - resourceType: 'Condition', - subject: createReference(p), - code: { coding: [{ system: SNOMED, code: '165002' }] }, - }); + const p = await repo.createResource({ + resourceType: 'Patient', + name: [{ family: randomUUID() }], + }); - const c2 = await systemRepo.createResource({ - resourceType: 'Condition', - subject: createReference(p), - code: { coding: [{ system: 'https://example.com', code: 'test' }] }, - }); + const c1 = await repo.createResource({ + resourceType: 'Condition', + subject: createReference(p), + code: { coding: [{ system: SNOMED, code: '165002' }] }, + }); - const bundle = await systemRepo.search({ - resourceType: 'Condition', - filters: [ - { - code: 'subject', - operator: Operator.EQUALS, - value: getReferenceString(p), + const c2 = await repo.createResource({ + resourceType: 'Condition', + subject: createReference(p), + code: { coding: [{ system: 'https://example.com', code: 'test' }] }, + }); + + const bundle = await repo.search({ + resourceType: 'Condition', + filters: [ + { + code: 'subject', + operator: Operator.EQUALS, + value: getReferenceString(p), + }, + { + code: 'code', + operator: Operator.IN, + value: 'http://hl7.org/fhir/ValueSet/condition-code', + }, + ], + }); + + expect(bundle.entry?.length).toEqual(1); + expect(bundleContains(bundle, c1)).toBeTruthy(); + expect(bundleContains(bundle, c2)).not.toBeTruthy(); + })); + + test('Reference identifier search', () => + withTestContext(async () => { + const code = randomUUID(); + + const c1 = await repo.createResource({ + resourceType: 'Condition', + code: { coding: [{ code }] }, + subject: { identifier: { system: 'mrn', value: '123456' } }, + }); + + const c2 = await repo.createResource({ + resourceType: 'Condition', + code: { coding: [{ code }] }, + subject: { identifier: { system: 'xyz', value: '123456' } }, + }); + + // Search with system + const bundle1 = await repo.search( + parseSearchDefinition(`Condition?code=${code}&subject:identifier=mrn|123456`) + ); + expect(bundle1.entry?.length).toEqual(1); + expect(bundleContains(bundle1, c1)).toBeTruthy(); + expect(bundleContains(bundle1, c2)).not.toBeTruthy(); + + // Search without system + const bundle2 = await repo.search(parseSearchDefinition(`Condition?code=${code}&subject:identifier=123456`)); + expect(bundle2.entry?.length).toEqual(2); + expect(bundleContains(bundle2, c1)).toBeTruthy(); + expect(bundleContains(bundle2, c2)).toBeTruthy(); + + // Search with count + const bundle3 = await repo.search( + parseSearchDefinition(`Condition?code=${code}&subject:identifier=mrn|123456&_total=accurate`) + ); + expect(bundle3.entry?.length).toEqual(1); + expect(bundle3.total).toBe(1); + expect(bundleContains(bundle3, c1)).toBeTruthy(); + expect(bundleContains(bundle3, c2)).not.toBeTruthy(); + })); + + test('Task patient identifier search', () => + withTestContext(async () => { + const identifier = randomUUID(); + + // Create a Task with a patient identifier reference _with_ Reference.type + const task1 = await repo.createResource({ + resourceType: 'Task', + status: 'accepted', + intent: 'order', + for: { + type: 'Patient', + identifier: { system: 'mrn', value: identifier }, }, - { - code: 'code', - operator: Operator.EQUALS, - value: `${SNOMED}|`, + }); + + // Create a Task with a patient identifier reference _without_ Reference.type + const task2 = await repo.createResource({ + resourceType: 'Task', + status: 'accepted', + intent: 'order', + for: { + identifier: { system: 'mrn', value: identifier }, }, - ], - }); + }); - expect(bundle.entry?.length).toEqual(1); - expect(bundleContains(bundle, c1)).toBeTruthy(); - expect(bundleContains(bundle, c2)).not.toBeTruthy(); - })); + // Search by "subject" + // This will include both Tasks, because the "subject" search parameter does not care about "type" + const bundle1 = await repo.search( + parseSearchDefinition(`Task?subject:identifier=mrn|${identifier}&_total=accurate`) + ); + expect(bundle1.total).toEqual(2); + expect(bundle1.entry?.length).toEqual(2); + expect(bundleContains(bundle1, task1)).toBeTruthy(); + expect(bundleContains(bundle1, task2)).toBeTruthy(); + + // Search by "patient" + // This will only include the Task with the explicit "Patient" type, because the "patient" search parameter does care about "type" + const bundle2 = await repo.search( + parseSearchDefinition(`Task?patient:identifier=mrn|${identifier}&_total=accurate`) + ); + expect(bundle2.total).toEqual(1); + expect(bundle2.entry?.length).toEqual(1); + expect(bundleContains(bundle2, task1)).toBeTruthy(); + expect(bundleContains(bundle2, task2)).not.toBeTruthy(); + })); + + test('Resource search params', () => + withTestContext(async () => { + const patientIdentifier = randomUUID(); + const patient = await repo.createResource({ + resourceType: 'Patient', + identifier: [{ system: 'http://example.com', value: patientIdentifier }], + meta: { + profile: ['http://example.com/fhir/a-patient-profile'], + security: [{ system: 'http://hl7.org/fhir/v3/Confidentiality', code: 'N' }], + source: 'http://example.org', + tag: [{ system: 'http://hl7.org/fhir/v3/ObservationValue', code: 'SUBSETTED' }], + }, + }); + const identifierFilter = { + code: 'identifier', + operator: Operator.EQUALS, + value: patientIdentifier, + }; - test('Condition.code :not next URL', () => - withTestContext(async () => { - const p = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ family: randomUUID() }], - }); + const bundle1 = await repo.search({ + resourceType: 'Patient', + filters: [ + identifierFilter, + { + code: '_profile', + operator: Operator.EQUALS, + value: 'http://example.com/fhir/a-patient-profile', + }, + ], + }); + expect(bundleContains(bundle1, patient)).toBeTruthy(); - await systemRepo.createResource({ - resourceType: 'Condition', - subject: createReference(p), - code: { coding: [{ system: SNOMED, code: '165002' }] }, - }); + const bundle2 = await repo.search({ + resourceType: 'Patient', + filters: [ + identifierFilter, + { + code: '_security', + operator: Operator.EQUALS, + value: 'http://hl7.org/fhir/v3/Confidentiality|N', + }, + ], + }); + expect(bundleContains(bundle2, patient)).toBeTruthy(); - await systemRepo.createResource({ - resourceType: 'Condition', - subject: createReference(p), - code: { coding: [{ system: 'https://example.com', code: 'test' }] }, - }); + const bundle3 = await repo.search({ + resourceType: 'Patient', + filters: [ + identifierFilter, + { + code: '_source', + operator: Operator.EQUALS, + value: 'http://example.org', + }, + ], + }); + expect(bundleContains(bundle3, patient)).toBeTruthy(); - const bundle = await systemRepo.search( - parseSearchUrl( - new URL(`https://x/Condition?subject=${getReferenceString(p)}&code:not=x&_count=1&_total=accurate`) - ) - ); - expect(bundle.entry?.length).toEqual(1); - - const nextUrl = bundle.link?.find((l) => l.relation === 'next')?.url; - expect(nextUrl).toBeDefined(); - expect(nextUrl).toContain('code:not=x'); - })); - - test('Condition.code :in search', () => - withTestContext(async () => { - // ValueSet: http://hl7.org/fhir/ValueSet/condition-code - // compose includes codes from http://snomed.info/sct - // but does not include codes from https://example.com - - const p = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ family: randomUUID() }], - }); + const bundle4 = await repo.search({ + resourceType: 'Patient', + filters: [ + identifierFilter, + { + code: '_tag', + operator: Operator.EQUALS, + value: 'http://hl7.org/fhir/v3/ObservationValue|SUBSETTED', + }, + ], + }); + expect(bundleContains(bundle4, patient)).toBeTruthy(); + })); + + test('Token :text search', () => + withTestContext(async () => { + const patient = await repo.createResource({ resourceType: 'Patient' }); + + const obs1 = await repo.createResource({ + resourceType: 'Observation', + status: 'final', + code: { text: randomUUID() }, + subject: createReference(patient), + }); - const c1 = await systemRepo.createResource({ - resourceType: 'Condition', - subject: createReference(p), - code: { coding: [{ system: SNOMED, code: '165002' }] }, - }); + const obs2 = await repo.createResource({ + resourceType: 'Observation', + status: 'final', + code: { coding: [{ display: randomUUID() }] }, + subject: createReference(patient), + }); - const c2 = await systemRepo.createResource({ - resourceType: 'Condition', - subject: createReference(p), - code: { coding: [{ system: 'https://example.com', code: 'test' }] }, - }); + const result1 = await repo.search({ + resourceType: 'Observation', + filters: [{ code: 'code', operator: Operator.TEXT, value: obs1.code?.text as string }], + }); + expect(result1.entry?.[0]?.resource?.id).toEqual(obs1.id); - const bundle = await systemRepo.search({ - resourceType: 'Condition', - filters: [ - { - code: 'subject', - operator: Operator.EQUALS, - value: getReferenceString(p), - }, - { - code: 'code', - operator: Operator.IN, - value: 'http://hl7.org/fhir/ValueSet/condition-code', - }, - ], - }); + const result2 = await repo.search({ + resourceType: 'Observation', + filters: [{ code: 'code', operator: Operator.TEXT, value: obs2.code?.coding?.[0]?.display as string }], + }); + expect(result2.entry?.[0]?.resource?.id).toEqual(obs2.id); + })); + + test('_filter search', () => + withTestContext(async () => { + const patient = await repo.createResource({ resourceType: 'Patient' }); + const statuses: ('preliminary' | 'final')[] = ['preliminary', 'final']; + const codes = ['123', '456']; + const observations = []; + + for (const status of statuses) { + for (const code of codes) { + observations.push( + await repo.createResource({ + resourceType: 'Observation', + subject: createReference(patient), + status, + code: { coding: [{ code }] }, + }) + ); + } + } - expect(bundle.entry?.length).toEqual(1); - expect(bundleContains(bundle, c1)).toBeTruthy(); - expect(bundleContains(bundle, c2)).not.toBeTruthy(); - })); + const result = await repo.search({ + resourceType: 'Observation', + filters: [ + { + code: 'subject', + operator: Operator.EQUALS, + value: getReferenceString(patient), + }, + { + code: '_filter', + operator: Operator.EQUALS, + value: '(status eq preliminary and code eq 123) or (not (status eq preliminary) and code eq 456)', + }, + ], + }); + expect(result.entry).toHaveLength(2); + })); - test('Reference identifier search', () => - withTestContext(async () => { - const code = randomUUID(); + test('_filter ne', () => + withTestContext(async () => { + const patient = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Eve'] }], + managingOrganization: { reference: 'Organization/' + randomUUID() }, + }); - const c1 = await systemRepo.createResource({ - resourceType: 'Condition', - code: { coding: [{ code }] }, - subject: { identifier: { system: 'mrn', value: '123456' } }, - }); + const result = await repo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'organization', + operator: Operator.EQUALS, + value: patient.managingOrganization?.reference as string, + }, + { + code: '_filter', + operator: Operator.EQUALS, + value: 'given ne Eve', + }, + ], + }); - const c2 = await systemRepo.createResource({ - resourceType: 'Condition', - code: { coding: [{ code }] }, - subject: { identifier: { system: 'xyz', value: '123456' } }, - }); + expect(result.entry).toHaveLength(0); + })); - // Search with system - const bundle1 = await systemRepo.search( - parseSearchDefinition(`Condition?code=${code}&subject:identifier=mrn|123456`) - ); - expect(bundle1.entry?.length).toEqual(1); - expect(bundleContains(bundle1, c1)).toBeTruthy(); - expect(bundleContains(bundle1, c2)).not.toBeTruthy(); - - // Search without system - const bundle2 = await systemRepo.search( - parseSearchDefinition(`Condition?code=${code}&subject:identifier=123456`) - ); - expect(bundle2.entry?.length).toEqual(2); - expect(bundleContains(bundle2, c1)).toBeTruthy(); - expect(bundleContains(bundle2, c2)).toBeTruthy(); - - // Search with count - const bundle3 = await systemRepo.search( - parseSearchDefinition(`Condition?code=${code}&subject:identifier=mrn|123456&_total=accurate`) - ); - expect(bundle3.entry?.length).toEqual(1); - expect(bundle3.total).toBe(1); - expect(bundleContains(bundle3, c1)).toBeTruthy(); - expect(bundleContains(bundle3, c2)).not.toBeTruthy(); - })); - - test('Task patient identifier search', () => - withTestContext(async () => { - const identifier = randomUUID(); - - // Create a Task with a patient identifier reference _with_ Reference.type - const task1 = await systemRepo.createResource({ - resourceType: 'Task', - status: 'accepted', - intent: 'order', - for: { - type: 'Patient', - identifier: { system: 'mrn', value: identifier }, - }, - }); + test('_filter re', () => + withTestContext(async () => { + const patient = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Eve'] }], + managingOrganization: { reference: 'Organization/' + randomUUID() }, + }); - // Create a Task with a patient identifier reference _without_ Reference.type - const task2 = await systemRepo.createResource({ - resourceType: 'Task', - status: 'accepted', - intent: 'order', - for: { - identifier: { system: 'mrn', value: identifier }, - }, - }); + const result = await repo.search({ + resourceType: 'Patient', + filters: [ + { + code: '_filter', + operator: Operator.EQUALS, + value: 'organization re ' + patient.managingOrganization?.reference, + }, + ], + }); - // Search by "subject" - // This will include both Tasks, because the "subject" search parameter does not care about "type" - const bundle1 = await systemRepo.search( - parseSearchDefinition(`Task?subject:identifier=mrn|${identifier}&_total=accurate`) - ); - expect(bundle1.total).toEqual(2); - expect(bundle1.entry?.length).toEqual(2); - expect(bundleContains(bundle1, task1)).toBeTruthy(); - expect(bundleContains(bundle1, task2)).toBeTruthy(); - - // Search by "patient" - // This will only include the Task with the explicit "Patient" type, because the "patient" search parameter does care about "type" - const bundle2 = await systemRepo.search( - parseSearchDefinition(`Task?patient:identifier=mrn|${identifier}&_total=accurate`) - ); - expect(bundle2.total).toEqual(1); - expect(bundle2.entry?.length).toEqual(1); - expect(bundleContains(bundle2, task1)).toBeTruthy(); - expect(bundleContains(bundle2, task2)).not.toBeTruthy(); - })); - - test('Resource search params', () => - withTestContext(async () => { - const patientIdentifier = randomUUID(); - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - identifier: [{ system: 'http://example.com', value: patientIdentifier }], - meta: { - profile: ['http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient'], - security: [{ system: 'http://hl7.org/fhir/v3/Confidentiality', code: 'N' }], - source: 'http://example.org', - tag: [{ system: 'http://hl7.org/fhir/v3/ObservationValue', code: 'SUBSETTED' }], - }, - }); - const identifierFilter = { - code: 'identifier', - operator: Operator.EQUALS, - value: patientIdentifier, - }; + expect(result.entry).toHaveLength(1); + expect(result.entry?.[0]?.resource?.id).toEqual(patient.id); + })); - const bundle1 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - identifierFilter, - { - code: '_profile', - operator: Operator.EQUALS, - value: 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient', - }, - ], - }); - expect(bundleContains(bundle1, patient)).toBeTruthy(); + test('Lookup table exact match with comma disjunction', () => + withTestContext(async () => { + const family = randomUUID(); + const p1 = await repo.createResource({ resourceType: 'Patient', name: [{ given: ['x'], family }] }); + const p2 = await repo.createResource({ resourceType: 'Patient', name: [{ given: ['xx'], family }] }); + const p3 = await repo.createResource({ resourceType: 'Patient', name: [{ given: ['y'], family }] }); + const p4 = await repo.createResource({ resourceType: 'Patient', name: [{ given: ['yy'], family }] }); - const bundle2 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - identifierFilter, - { - code: '_security', - operator: Operator.EQUALS, - value: 'http://hl7.org/fhir/v3/Confidentiality|N', - }, - ], - }); - expect(bundleContains(bundle2, patient)).toBeTruthy(); + const bundle = await repo.search({ + resourceType: 'Patient', + total: 'accurate', + filters: [ + { + code: 'given', + operator: Operator.EXACT, + value: 'x,y', + }, + { + code: 'family', + operator: Operator.EXACT, + value: family, + }, + ], + }); + expect(bundle.entry?.length).toEqual(2); + expect(bundle.total).toEqual(2); + expect(bundleContains(bundle, p1)).toBeTruthy(); + expect(bundleContains(bundle, p2)).not.toBeTruthy(); + expect(bundleContains(bundle, p3)).toBeTruthy(); + expect(bundleContains(bundle, p4)).not.toBeTruthy(); + })); + + test('Duplicate rows from token lookup', () => + withTestContext(async () => { + const code = randomUUID(); + + const p = await repo.createResource({ resourceType: 'Patient' }); + const s = await repo.createResource({ + resourceType: 'ServiceRequest', + subject: createReference(p), + status: 'active', + intent: 'order', + category: [ + { + text: code, + coding: [ + { + system: 'https://example.com/category', + code, + }, + ], + }, + ], + }); - const bundle3 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - identifierFilter, - { - code: '_source', - operator: Operator.EQUALS, - value: 'http://example.org', - }, - ], - }); - expect(bundleContains(bundle3, patient)).toBeTruthy(); + const bundle = await repo.search({ + resourceType: 'ServiceRequest', + filters: [{ code: 'category', operator: Operator.EQUALS, value: code }], + }); + expect(bundle.entry?.length).toEqual(1); + expect(bundleContains(bundle, s)).toBeTruthy(); + })); + + test('Filter task by due date', () => + withTestContext(async () => { + const code = randomUUID(); + + // Create 3 tasks + // Mix of "no due date", using "start", and using "end" + const task1 = await repo.createResource({ + resourceType: 'Task', + status: 'requested', + intent: 'order', + code: { coding: [{ code }] }, + }); + const task2 = await repo.createResource({ + resourceType: 'Task', + status: 'requested', + intent: 'order', + code: { coding: [{ code }] }, + restriction: { period: { start: '2023-06-02T00:00:00.000Z' } }, + }); + const task3 = await repo.createResource({ + resourceType: 'Task', + status: 'requested', + intent: 'order', + code: { coding: [{ code }] }, + restriction: { period: { end: '2023-06-03T00:00:00.000Z' } }, + }); + + // Sort and filter by due date + const bundle = await repo.search({ + resourceType: 'Task', + filters: [ + { code: 'code', operator: Operator.EQUALS, value: code }, + { code: 'due-date', operator: Operator.GREATER_THAN, value: '2023-06-01T00:00:00.000Z' }, + ], + sortRules: [{ code: 'due-date' }], + }); + expect(bundle.entry?.length).toEqual(2); + expect(bundle.entry?.[0]?.resource?.id).toEqual(task2.id); + expect(bundle.entry?.[1]?.resource?.id).toEqual(task3.id); + expect(bundleContains(bundle, task1)).not.toBeTruthy(); + })); - const bundle4 = await systemRepo.search({ + test('Get estimated count with filter on human name', async () => { + const result = await repo.search({ resourceType: 'Patient', + total: 'estimate', filters: [ - identifierFilter, { - code: '_tag', + code: 'name', operator: Operator.EQUALS, - value: 'http://hl7.org/fhir/v3/ObservationValue|SUBSETTED', + value: 'John', }, ], }); - expect(bundleContains(bundle4, patient)).toBeTruthy(); - })); - - test('Token :text search', () => - withTestContext(async () => { - const patient = await systemRepo.createResource({ resourceType: 'Patient' }); - - const obs1 = await systemRepo.createResource({ - resourceType: 'Observation', - status: 'final', - code: { text: randomUUID() }, - subject: createReference(patient), - }); + expect(result.total).toBeDefined(); + expect(typeof result.total).toBe('number'); + }); - const obs2 = await systemRepo.createResource({ - resourceType: 'Observation', - status: 'final', - code: { coding: [{ display: randomUUID() }] }, - subject: createReference(patient), - }); + test('Organization by name', () => + withTestContext(async () => { + const org = await repo.createResource({ + resourceType: 'Organization', + name: randomUUID(), + }); + const result = await repo.search({ + resourceType: 'Organization', + filters: [ + { + code: 'name', + operator: Operator.EQUALS, + value: `wrongname,${(org.name as string).slice(0, 5)}`, + }, + ], + }); + expect(result.entry?.length).toBe(1); + })); - const result1 = await systemRepo.search({ - resourceType: 'Observation', - filters: [{ code: 'code', operator: Operator.TEXT, value: obs1.code?.text as string }], - }); - expect(result1.entry?.[0]?.resource?.id).toEqual(obs1.id); + test('Patient by name with stop word', () => + withTestContext(async () => { + const seed = randomUUID(); + await repo.createResource({ + resourceType: 'Patient', + name: [ + { + given: [seed + 'Justin', 'Wynn'], + family: 'Sanders' + seed, + }, + ], + }); + const result = await repo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'name', + operator: Operator.CONTAINS, + value: `${seed.slice(-3)}just`, + }, + ], + }); + expect(result.entry?.length).toBe(1); + })); - const result2 = await systemRepo.search({ - resourceType: 'Observation', - filters: [{ code: 'code', operator: Operator.TEXT, value: obs2.code?.coding?.[0]?.display as string }], - }); - expect(result2.entry?.[0]?.resource?.id).toEqual(obs2.id); - })); + test('Sort by ID', () => + withTestContext(async () => { + const org = await repo.createResource({ resourceType: 'Organization', name: 'org1' }); + const managingOrganization = createReference(org); + await repo.createResource({ resourceType: 'Patient', managingOrganization }); + await repo.createResource({ resourceType: 'Patient', managingOrganization }); - test('_filter search', () => - withTestContext(async () => { - const patient = await systemRepo.createResource({ resourceType: 'Patient' }); - const statuses: ('preliminary' | 'final')[] = ['preliminary', 'final']; - const codes = ['123', '456']; - const observations = []; + const result1 = await repo.search({ + resourceType: 'Patient', + filters: [{ code: 'organization', operator: Operator.EQUALS, value: getReferenceString(org) }], + sortRules: [{ code: '_id', descending: false }], + }); + expect(result1.entry).toHaveLength(2); + expect(result1.entry?.[0]?.resource?.id?.localeCompare(result1.entry?.[1]?.resource?.id as string)).toBe(-1); - for (const status of statuses) { - for (const code of codes) { - observations.push( - await systemRepo.createResource({ - resourceType: 'Observation', - subject: createReference(patient), - status, - code: { coding: [{ code }] }, - }) - ); - } - } + const result2 = await repo.search({ + resourceType: 'Patient', + filters: [{ code: 'organization', operator: Operator.EQUALS, value: getReferenceString(org) }], + sortRules: [{ code: '_id', descending: true }], + }); + expect(result2.entry).toHaveLength(2); + expect(result2.entry?.[0]?.resource?.id?.localeCompare(result2.entry?.[1]?.resource?.id as string)).toBe(1); + })); + + test('Numeric parameter', () => + withTestContext(async () => { + const ident = randomUUID(); + const riskAssessment: RiskAssessment = { + resourceType: 'RiskAssessment', + status: 'final', + identifier: [{ value: ident }], + subject: { + reference: 'Patient/test', + }, + prediction: [ + { + outcome: { text: 'Breast Cancer' }, + probabilityDecimal: 0.000168, + whenRange: { + high: { value: 53, unit: 'years' }, + }, + }, + { + outcome: { text: 'Breast Cancer' }, + probabilityDecimal: 0.000368, + whenRange: { + low: { value: 54, unit: 'years' }, + high: { value: 57, unit: 'years' }, + }, + }, + { + outcome: { text: 'Breast Cancer' }, + probabilityDecimal: 0.000594, + whenRange: { + low: { value: 58, unit: 'years' }, + high: { value: 62, unit: 'years' }, + }, + }, + { + outcome: { text: 'Breast Cancer' }, + probabilityDecimal: 0.000838, + whenRange: { + low: { value: 63, unit: 'years' }, + high: { value: 67, unit: 'years' }, + }, + }, + ], + }; - const result = await systemRepo.search({ - resourceType: 'Observation', - filters: [ - { - code: 'subject', - operator: Operator.EQUALS, - value: getReferenceString(patient), - }, - { - code: '_filter', - operator: Operator.EQUALS, - value: '(status eq preliminary and code eq 123) or (not (status eq preliminary) and code eq 456)', - }, - ], - }); - expect(result.entry).toHaveLength(2); - })); + await repo.createResource(riskAssessment); + const result = await repo.search({ + resourceType: 'RiskAssessment', + filters: [ + { code: 'identifier', operator: Operator.EQUALS, value: ident }, + { code: 'probability', operator: Operator.GREATER_THAN, value: '0.0005' }, + ], + }); + expect(result.entry).toHaveLength(1); + })); - test('_filter ne', () => - withTestContext(async () => { - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Eve'] }], - managingOrganization: { reference: 'Organization/' + randomUUID() }, - }); + test('Disjunction with lookup tables', () => + withTestContext(async () => { + const n1 = randomUUID(); + const n2 = randomUUID(); - const result = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: 'organization', - operator: Operator.EQUALS, - value: patient.managingOrganization?.reference as string, - }, - { - code: '_filter', - operator: Operator.EQUALS, - value: 'given ne Eve', - }, - ], - }); + const p1 = await repo.createResource({ + resourceType: 'Patient', + name: [{ family: n1 }], + }); - expect(result.entry).toHaveLength(0); - })); + const p2 = await repo.createResource({ + resourceType: 'Patient', + name: [{ family: n2 }], + }); - test('_filter re', () => - withTestContext(async () => { - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Eve'] }], - managingOrganization: { reference: 'Organization/' + randomUUID() }, - }); + const result = await repo.search({ + resourceType: 'Patient', + filters: [{ code: '_filter', operator: Operator.EQUALS, value: `name co "${n1}" or name co "${n2}"` }], + }); - const result = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: '_filter', - operator: Operator.EQUALS, - value: 'organization re ' + patient.managingOrganization?.reference, - }, - ], - }); + expect(result.entry).toHaveLength(2); + expect(bundleContains(result, p1)).toBe(true); + expect(bundleContains(result, p2)).toBe(true); + })); - expect(result.entry).toHaveLength(1); - expect(result.entry?.[0]?.resource?.id).toEqual(patient.id); - })); + test('Sort by unknown search parameter', async () => { + try { + await repo.search({ + resourceType: 'Patient', + sortRules: [{ code: 'xyz' }], + }); + } catch (err) { + const outcome = normalizeOperationOutcome(err); + expect(outcome.issue?.[0]?.details?.text).toBe('Unknown search parameter: xyz'); + } + }); - test('Lookup table exact match with comma disjunction', () => - withTestContext(async () => { - const family = randomUUID(); - const p1 = await systemRepo.createResource({ resourceType: 'Patient', name: [{ given: ['x'], family }] }); - const p2 = await systemRepo.createResource({ resourceType: 'Patient', name: [{ given: ['xx'], family }] }); - const p3 = await systemRepo.createResource({ resourceType: 'Patient', name: [{ given: ['y'], family }] }); - const p4 = await systemRepo.createResource({ resourceType: 'Patient', name: [{ given: ['yy'], family }] }); + test('Date range search', () => + withTestContext(async () => { + const ident = randomUUID(); - const bundle = await systemRepo.search({ - resourceType: 'Patient', - total: 'accurate', - filters: [ - { - code: 'given', - operator: Operator.EXACT, - value: 'x,y', - }, - { - code: 'family', - operator: Operator.EXACT, - value: family, + const measureReport = await repo.createResource({ + resourceType: 'MeasureReport', + status: 'complete', + type: 'individual', + measure: 'http://example.com', + identifier: [{ value: ident }], + period: { + start: '2020-01-01T12:00:00.000Z', + end: '2020-01-15T12:00:00.000Z', }, - ], - }); - expect(bundle.entry?.length).toEqual(2); - expect(bundle.total).toEqual(2); - expect(bundleContains(bundle, p1)).toBeTruthy(); - expect(bundleContains(bundle, p2)).not.toBeTruthy(); - expect(bundleContains(bundle, p3)).toBeTruthy(); - expect(bundleContains(bundle, p4)).not.toBeTruthy(); - })); - - test('Duplicate rows from token lookup', () => - withTestContext(async () => { - const code = randomUUID(); - - const p = await systemRepo.createResource({ resourceType: 'Patient' }); - const s = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - subject: createReference(p), - status: 'active', - intent: 'order', - category: [ - { - text: code, - coding: [ - { - system: 'https://example.com/category', - code, - }, + }); + expect(measureReport).toBeDefined(); + + async function searchByPeriod(operator: Operator, value: string): Promise { + const result = await repo.searchOne({ + resourceType: 'MeasureReport', + filters: [ + { code: 'identifier', operator: Operator.EQUALS, value: ident }, + { code: 'period', operator, value }, ], - }, - ], - }); + }); + return !!result; + } - const bundle = await systemRepo.search({ - resourceType: 'ServiceRequest', - filters: [{ code: 'category', operator: Operator.EQUALS, value: code }], - }); - expect(bundle.entry?.length).toEqual(1); - expect(bundleContains(bundle, s)).toBeTruthy(); - })); - - test('Filter task by due date', () => - withTestContext(async () => { - const code = randomUUID(); - - // Create 3 tasks - // Mix of "no due date", using "start", and using "end" - const task1 = await systemRepo.createResource({ - resourceType: 'Task', - status: 'requested', - intent: 'order', - code: { coding: [{ code }] }, - }); - const task2 = await systemRepo.createResource({ - resourceType: 'Task', - status: 'requested', - intent: 'order', - code: { coding: [{ code }] }, - restriction: { period: { start: '2023-06-02T00:00:00.000Z' } }, - }); - const task3 = await systemRepo.createResource({ - resourceType: 'Task', - status: 'requested', - intent: 'order', - code: { coding: [{ code }] }, - restriction: { period: { end: '2023-06-03T00:00:00.000Z' } }, - }); + expect(await searchByPeriod(Operator.EQUALS, '2019-12-31')).toBe(false); + expect(await searchByPeriod(Operator.EQUALS, '2020-01-01')).toBe(true); + expect(await searchByPeriod(Operator.EQUALS, '2020-01-02')).toBe(true); + expect(await searchByPeriod(Operator.EQUALS, '2020-01-15')).toBe(true); + expect(await searchByPeriod(Operator.EQUALS, '2020-01-16')).toBe(false); + + expect(await searchByPeriod(Operator.EQUALS, '2020-01-01T11:59:59.000Z')).toBe(false); + expect(await searchByPeriod(Operator.EQUALS, '2020-01-01T12:00:00.000Z')).toBe(true); + expect(await searchByPeriod(Operator.EQUALS, '2020-01-15T11:59:59.000Z')).toBe(true); + expect(await searchByPeriod(Operator.EQUALS, '2020-01-15T12:00:00.000Z')).toBe(true); + expect(await searchByPeriod(Operator.EQUALS, '2020-01-15T12:00:01.000Z')).toBe(false); + + expect(await searchByPeriod(Operator.NOT_EQUALS, '2019-12-31')).toBe(true); + expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-01')).toBe(false); + expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-02')).toBe(false); + expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-15')).toBe(false); + expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-16')).toBe(true); + + expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-01T11:59:59.000Z')).toBe(true); + expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-01T12:00:00.000Z')).toBe(false); + expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-15T11:59:59.000Z')).toBe(false); + expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-15T12:00:00.000Z')).toBe(false); + expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-15T12:00:01.000Z')).toBe(true); + + expect(await searchByPeriod(Operator.LESS_THAN, '2019-12-31')).toBe(false); + expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-01')).toBe(true); + expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-02')).toBe(true); + expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-15')).toBe(true); + expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-16')).toBe(true); + + expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-01T11:59:59.000Z')).toBe(false); + expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-01T12:00:00.000Z')).toBe(false); + expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-15T11:59:59.000Z')).toBe(true); + expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-15T12:00:00.000Z')).toBe(true); + expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-15T12:00:01.000Z')).toBe(true); + + expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2019-12-31')).toBe(false); + expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-01')).toBe(true); + expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-02')).toBe(true); + expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-15')).toBe(true); + expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-16')).toBe(true); + + expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-01T11:59:59.000Z')).toBe(false); + expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-01T12:00:00.000Z')).toBe(true); + expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-15T11:59:59.000Z')).toBe(true); + expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-15T12:00:00.000Z')).toBe(true); + expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-15T12:00:01.000Z')).toBe(true); + + expect(await searchByPeriod(Operator.GREATER_THAN, '2019-12-31')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-01')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-02')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-15')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-16')).toBe(false); + + expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-01T11:59:59.000Z')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-01T12:00:00.000Z')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-15T11:59:59.000Z')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-15T12:00:00.000Z')).toBe(false); + expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-15T12:00:01.000Z')).toBe(false); + + expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2019-12-31')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-01')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-02')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-15')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-16')).toBe(false); + + expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-01T11:59:59.000Z')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-01T12:00:00.000Z')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-15T11:59:59.000Z')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-15T12:00:00.000Z')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-15T12:00:01.000Z')).toBe(false); + + expect(await searchByPeriod(Operator.STARTS_AFTER, '2019-12-31')).toBe(true); + expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-01')).toBe(false); + expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-02')).toBe(false); + expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-15')).toBe(false); + expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-16')).toBe(false); + + expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-01T11:59:59.000Z')).toBe(true); + expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-01T12:00:00.000Z')).toBe(false); + expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-15T11:59:59.000Z')).toBe(false); + expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-15T12:00:00.000Z')).toBe(false); + expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-15T12:00:01.000Z')).toBe(false); + + expect(await searchByPeriod(Operator.ENDS_BEFORE, '2019-12-31')).toBe(false); + expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-01')).toBe(false); + expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-02')).toBe(false); + expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-15')).toBe(false); + expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-16')).toBe(true); + + expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-01T11:59:59.000Z')).toBe(false); + expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-01T12:00:00.000Z')).toBe(false); + expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-15T11:59:59.000Z')).toBe(false); + expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-15T12:00:00.000Z')).toBe(false); + expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-15T12:00:01.000Z')).toBe(true); + })); + }); - // Sort and filter by due date - const bundle = await systemRepo.search({ - resourceType: 'Task', - filters: [ - { code: 'code', operator: Operator.EQUALS, value: code }, - { code: 'due-date', operator: Operator.GREATER_THAN, value: '2023-06-01T00:00:00.000Z' }, - ], - sortRules: [{ code: 'due-date' }], - }); - expect(bundle.entry?.length).toEqual(2); - expect(bundle.entry?.[0]?.resource?.id).toEqual(task2.id); - expect(bundle.entry?.[1]?.resource?.id).toEqual(task3.id); - expect(bundleContains(bundle, task1)).not.toBeTruthy(); - })); - - test('Get estimated count with filter on human name', async () => { - const result = await systemRepo.search({ - resourceType: 'Patient', - total: 'estimate', - filters: [ - { - code: 'name', - operator: Operator.EQUALS, - value: 'John', - }, - ], + describe('systemRepo', () => { + const systemRepo = getSystemRepo(); + + beforeAll(async () => { + const config = await loadTestConfig(); + await initAppServices(config); }); - expect(result.total).toBeDefined(); - expect(typeof result.total).toBe('number'); - }); - test('Organization by name', () => - withTestContext(async () => { - const org = await systemRepo.createResource({ - resourceType: 'Organization', - name: randomUUID(), - }); - const result = await systemRepo.search({ - resourceType: 'Organization', - filters: [ - { - code: 'name', - operator: Operator.EQUALS, - value: `wrongname,${(org.name as string).slice(0, 5)}`, - }, - ], - }); - expect(result.entry?.length).toBe(1); - })); + afterAll(async () => { + await shutdownApp(); + }); - test('Patient by name with stop word', () => - withTestContext(async () => { - const seed = randomUUID(); - await systemRepo.createResource({ - resourceType: 'Patient', - name: [ - { - given: [seed + 'Justin', 'Wynn'], - family: 'Sanders' + seed, + test('Filter by _project', () => + withTestContext(async () => { + const project1 = randomUUID(); + const project2 = randomUUID(); + + const patient1 = await systemRepo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice1'], family: 'Smith1' }], + meta: { + project: project1, }, - ], - }); - const result = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: 'name', - operator: Operator.CONTAINS, - value: `${seed.slice(-3)}just`, + }); + expect(patient1).toBeDefined(); + + const patient2 = await systemRepo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice2'], family: 'Smith2' }], + meta: { + project: project2, }, - ], - }); - expect(result.entry?.length).toBe(1); - })); + }); + expect(patient2).toBeDefined(); - test('Sort by ID', () => - withTestContext(async () => { - const org = await systemRepo.createResource({ resourceType: 'Organization', name: 'org1' }); - const managingOrganization = createReference(org); - await systemRepo.createResource({ resourceType: 'Patient', managingOrganization }); - await systemRepo.createResource({ resourceType: 'Patient', managingOrganization }); + const bundle = await systemRepo.search({ + resourceType: 'Patient', + filters: [ + { + code: '_project', + operator: Operator.EQUALS, + value: project1, + }, + ], + }); + expect(bundle.entry?.length).toEqual(1); + expect(bundleContains(bundle as Bundle, patient1 as Patient)).toEqual(true); + expect(bundleContains(bundle as Bundle, patient2 as Patient)).toEqual(false); + })); + + test('Filter by _lastUpdated', () => + withTestContext(async () => { + // Create 2 patients + // One with a _lastUpdated of 1 second ago + // One with a _lastUpdated of 2 seconds ago + const family = randomUUID(); + const now = new Date(); + const nowMinus1Second = new Date(now.getTime() - 1000); + const nowMinus2Seconds = new Date(now.getTime() - 2000); + const nowMinus3Seconds = new Date(now.getTime() - 3000); + + const patient1 = await systemRepo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family }], + meta: { + lastUpdated: nowMinus1Second.toISOString(), + }, + }); + expect(patient1).toBeDefined(); - const result1 = await systemRepo.search({ - resourceType: 'Patient', - filters: [{ code: 'organization', operator: Operator.EQUALS, value: getReferenceString(org) }], - sortRules: [{ code: '_id', descending: false }], - }); - expect(result1.entry).toHaveLength(2); - expect(result1.entry?.[0]?.resource?.id?.localeCompare(result1.entry?.[1]?.resource?.id as string)).toBe(-1); + const patient2 = await systemRepo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family }], + meta: { + lastUpdated: nowMinus2Seconds.toISOString(), + }, + }); + expect(patient2).toBeDefined(); - const result2 = await systemRepo.search({ - resourceType: 'Patient', - filters: [{ code: 'organization', operator: Operator.EQUALS, value: getReferenceString(org) }], - sortRules: [{ code: '_id', descending: true }], - }); - expect(result2.entry).toHaveLength(2); - expect(result2.entry?.[0]?.resource?.id?.localeCompare(result2.entry?.[1]?.resource?.id as string)).toBe(1); - })); - - test('Numeric parameter', () => - withTestContext(async () => { - const ident = randomUUID(); - const riskAssessment: RiskAssessment = { - resourceType: 'RiskAssessment', - status: 'final', - identifier: [{ value: ident }], - subject: { - reference: 'Patient/test', - }, - prediction: [ - { - outcome: { text: 'Breast Cancer' }, - probabilityDecimal: 0.000168, - whenRange: { - high: { value: 53, unit: 'years' }, + // Greater than (newer than) 2 seconds ago should only return patient 1 + const searchResult1 = await systemRepo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'name', + operator: Operator.EQUALS, + value: family, }, - }, - { - outcome: { text: 'Breast Cancer' }, - probabilityDecimal: 0.000368, - whenRange: { - low: { value: 54, unit: 'years' }, - high: { value: 57, unit: 'years' }, + { + code: '_lastUpdated', + operator: Operator.GREATER_THAN, + value: nowMinus2Seconds.toISOString(), }, - }, - { - outcome: { text: 'Breast Cancer' }, - probabilityDecimal: 0.000594, - whenRange: { - low: { value: 58, unit: 'years' }, - high: { value: 62, unit: 'years' }, + ], + }); + + expect(bundleContains(searchResult1 as Bundle, patient1 as Patient)).toEqual(true); + expect(bundleContains(searchResult1 as Bundle, patient2 as Patient)).toEqual(false); + + // Greater than (newer than) or equal to 2 seconds ago should return both patients + const searchResult2 = await systemRepo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'name', + operator: Operator.EQUALS, + value: family, }, - }, - { - outcome: { text: 'Breast Cancer' }, - probabilityDecimal: 0.000838, - whenRange: { - low: { value: 63, unit: 'years' }, - high: { value: 67, unit: 'years' }, + { + code: '_lastUpdated', + operator: Operator.GREATER_THAN_OR_EQUALS, + value: nowMinus2Seconds.toISOString(), }, - }, - ], - }; + ], + }); - await systemRepo.createResource(riskAssessment); - const result = await systemRepo.search({ - resourceType: 'RiskAssessment', - filters: [ - { code: 'identifier', operator: Operator.EQUALS, value: ident }, - { code: 'probability', operator: Operator.GREATER_THAN, value: '0.0005' }, - ], - }); - expect(result.entry).toHaveLength(1); - })); + expect(bundleContains(searchResult2 as Bundle, patient1 as Patient)).toEqual(true); + expect(bundleContains(searchResult2 as Bundle, patient2 as Patient)).toEqual(true); - test('Disjunction with lookup tables', () => - withTestContext(async () => { - const n1 = randomUUID(); - const n2 = randomUUID(); + // Less than (older than) to 1 seconds ago should only return patient 2 + const searchResult3 = await systemRepo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'name', + operator: Operator.EQUALS, + value: family, + }, + { + code: '_lastUpdated', + operator: Operator.GREATER_THAN, + value: nowMinus3Seconds.toISOString(), + }, + { + code: '_lastUpdated', + operator: Operator.LESS_THAN, + value: nowMinus1Second.toISOString(), + }, + ], + }); - const p1 = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ family: n1 }], - }); + expect(bundleContains(searchResult3 as Bundle, patient1 as Patient)).toEqual(false); + expect(bundleContains(searchResult3 as Bundle, patient2 as Patient)).toEqual(true); - const p2 = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ family: n2 }], - }); + // Less than (older than) or equal to 1 seconds ago should return both patients + const searchResult4 = await systemRepo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'name', + operator: Operator.EQUALS, + value: family, + }, + { + code: '_lastUpdated', + operator: Operator.GREATER_THAN, + value: nowMinus3Seconds.toISOString(), + }, + { + code: '_lastUpdated', + operator: Operator.LESS_THAN_OR_EQUALS, + value: nowMinus1Second.toISOString(), + }, + ], + }); - const result = await systemRepo.search({ - resourceType: 'Patient', - filters: [{ code: '_filter', operator: Operator.EQUALS, value: `name co "${n1}" or name co "${n2}"` }], - }); + expect(bundleContains(searchResult4 as Bundle, patient1 as Patient)).toEqual(true); + expect(bundleContains(searchResult4 as Bundle, patient2 as Patient)).toEqual(true); + })); - expect(result.entry).toHaveLength(2); - expect(bundleContains(result, p1)).toBe(true); - expect(bundleContains(result, p2)).toBe(true); - })); + test('Sort by _lastUpdated', () => + withTestContext(async () => { + const project = randomUUID(); - test('Sort by unknown search parameter', async () => { - try { - await systemRepo.search({ - resourceType: 'Patient', - sortRules: [{ code: 'xyz' }], - }); - } catch (err) { - const outcome = normalizeOperationOutcome(err); - expect(outcome.issue?.[0]?.details?.text).toBe('Unknown search parameter: xyz'); - } - }); + const patient1 = await systemRepo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice1'], family: 'Smith1' }], + meta: { + lastUpdated: '2020-01-01T00:00:00.000Z', + project, + }, + }); + expect(patient1).toBeDefined(); - test('Date range search', () => - withTestContext(async () => { - const ident = randomUUID(); - - const measureReport = await systemRepo.createResource({ - resourceType: 'MeasureReport', - status: 'complete', - type: 'individual', - measure: 'http://example.com', - identifier: [{ value: ident }], - period: { - start: '2020-01-01T12:00:00.000Z', - end: '2020-01-15T12:00:00.000Z', - }, - }); - expect(measureReport).toBeDefined(); + const patient2 = await systemRepo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice2'], family: 'Smith2' }], + meta: { + lastUpdated: '2020-01-02T00:00:00.000Z', + project, + }, + }); + expect(patient2).toBeDefined(); - async function searchByPeriod(operator: Operator, value: string): Promise { - const result = await systemRepo.searchOne({ - resourceType: 'MeasureReport', + const bundle3 = await systemRepo.search({ + resourceType: 'Patient', filters: [ - { code: 'identifier', operator: Operator.EQUALS, value: ident }, - { code: 'period', operator, value }, + { + code: '_project', + operator: Operator.EQUALS, + value: project, + }, + ], + sortRules: [ + { + code: '_lastUpdated', + descending: false, + }, ], }); - return !!result; - } + expect(bundle3.entry?.length).toEqual(2); + expect(bundle3.entry?.[0]?.resource?.id).toEqual(patient1.id); + expect(bundle3.entry?.[1]?.resource?.id).toEqual(patient2.id); - expect(await searchByPeriod(Operator.EQUALS, '2019-12-31')).toBe(false); - expect(await searchByPeriod(Operator.EQUALS, '2020-01-01')).toBe(true); - expect(await searchByPeriod(Operator.EQUALS, '2020-01-02')).toBe(true); - expect(await searchByPeriod(Operator.EQUALS, '2020-01-15')).toBe(true); - expect(await searchByPeriod(Operator.EQUALS, '2020-01-16')).toBe(false); - - expect(await searchByPeriod(Operator.EQUALS, '2020-01-01T11:59:59.000Z')).toBe(false); - expect(await searchByPeriod(Operator.EQUALS, '2020-01-01T12:00:00.000Z')).toBe(true); - expect(await searchByPeriod(Operator.EQUALS, '2020-01-15T11:59:59.000Z')).toBe(true); - expect(await searchByPeriod(Operator.EQUALS, '2020-01-15T12:00:00.000Z')).toBe(true); - expect(await searchByPeriod(Operator.EQUALS, '2020-01-15T12:00:01.000Z')).toBe(false); - - expect(await searchByPeriod(Operator.NOT_EQUALS, '2019-12-31')).toBe(true); - expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-01')).toBe(false); - expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-02')).toBe(false); - expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-15')).toBe(false); - expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-16')).toBe(true); - - expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-01T11:59:59.000Z')).toBe(true); - expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-01T12:00:00.000Z')).toBe(false); - expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-15T11:59:59.000Z')).toBe(false); - expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-15T12:00:00.000Z')).toBe(false); - expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-15T12:00:01.000Z')).toBe(true); - - expect(await searchByPeriod(Operator.LESS_THAN, '2019-12-31')).toBe(false); - expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-01')).toBe(true); - expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-02')).toBe(true); - expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-15')).toBe(true); - expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-16')).toBe(true); - - expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-01T11:59:59.000Z')).toBe(false); - expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-01T12:00:00.000Z')).toBe(false); - expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-15T11:59:59.000Z')).toBe(true); - expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-15T12:00:00.000Z')).toBe(true); - expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-15T12:00:01.000Z')).toBe(true); - - expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2019-12-31')).toBe(false); - expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-01')).toBe(true); - expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-02')).toBe(true); - expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-15')).toBe(true); - expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-16')).toBe(true); - - expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-01T11:59:59.000Z')).toBe(false); - expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-01T12:00:00.000Z')).toBe(true); - expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-15T11:59:59.000Z')).toBe(true); - expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-15T12:00:00.000Z')).toBe(true); - expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-15T12:00:01.000Z')).toBe(true); - - expect(await searchByPeriod(Operator.GREATER_THAN, '2019-12-31')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-01')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-02')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-15')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-16')).toBe(false); - - expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-01T11:59:59.000Z')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-01T12:00:00.000Z')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-15T11:59:59.000Z')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-15T12:00:00.000Z')).toBe(false); - expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-15T12:00:01.000Z')).toBe(false); - - expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2019-12-31')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-01')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-02')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-15')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-16')).toBe(false); - - expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-01T11:59:59.000Z')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-01T12:00:00.000Z')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-15T11:59:59.000Z')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-15T12:00:00.000Z')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-15T12:00:01.000Z')).toBe(false); - - expect(await searchByPeriod(Operator.STARTS_AFTER, '2019-12-31')).toBe(true); - expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-01')).toBe(false); - expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-02')).toBe(false); - expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-15')).toBe(false); - expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-16')).toBe(false); - - expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-01T11:59:59.000Z')).toBe(true); - expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-01T12:00:00.000Z')).toBe(false); - expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-15T11:59:59.000Z')).toBe(false); - expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-15T12:00:00.000Z')).toBe(false); - expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-15T12:00:01.000Z')).toBe(false); - - expect(await searchByPeriod(Operator.ENDS_BEFORE, '2019-12-31')).toBe(false); - expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-01')).toBe(false); - expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-02')).toBe(false); - expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-15')).toBe(false); - expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-16')).toBe(true); - - expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-01T11:59:59.000Z')).toBe(false); - expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-01T12:00:00.000Z')).toBe(false); - expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-15T11:59:59.000Z')).toBe(false); - expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-15T12:00:00.000Z')).toBe(false); - expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-15T12:00:01.000Z')).toBe(true); - })); + const bundle4 = await systemRepo.search({ + resourceType: 'Patient', + filters: [ + { + code: '_project', + operator: Operator.EQUALS, + value: project, + }, + ], + sortRules: [ + { + code: '_lastUpdated', + descending: true, + }, + ], + }); + expect(bundle4.entry?.length).toEqual(2); + expect(bundle4.entry?.[0]?.resource?.id).toEqual(patient2.id); + expect(bundle4.entry?.[1]?.resource?.id).toEqual(patient1.id); + })); + }); }); From d89db56b8b23d9abec4f72e771677c0885b73a47 Mon Sep 17 00:00:00 2001 From: dillonstreator Date: Thu, 8 Feb 2024 14:00:59 -0600 Subject: [PATCH 17/81] fix-3885 propagate `traceId` through asynchronous jobs (#3886) * support `Sentry-Trace` header * update doc * traceparent parser allowing some divergence from the spec and maintain full traceparent in logs * allow some grace on flags format * feedback * fix-3885 propagate traceId through asynchronous jobs * propagate traceId to vmcontext and lambda bot executions * fix tests --------- Co-authored-by: Cody Ebberson --- packages/server/src/context.ts | 4 +- .../server/src/fhir/operations/execute.ts | 7 +- packages/server/src/test.setup.ts | 4 +- packages/server/src/traceparent.test.ts | 10 +- packages/server/src/workers/download.test.ts | 80 ++-- packages/server/src/workers/download.ts | 32 +- .../server/src/workers/subscription.test.ts | 424 +++++++++--------- packages/server/src/workers/subscription.ts | 29 +- 8 files changed, 329 insertions(+), 261 deletions(-) diff --git a/packages/server/src/context.ts b/packages/server/src/context.ts index 7445308685..a29716fbad 100644 --- a/packages/server/src/context.ts +++ b/packages/server/src/context.ts @@ -59,10 +59,10 @@ export class AuthenticatedRequestContext extends RequestContext { this.repo.close(); } - static system(): AuthenticatedRequestContext { + static system(ctx?: { requestId?: string; traceId?: string }): AuthenticatedRequestContext { const systemLogger = new Logger(write, undefined, LogLevel.ERROR); return new AuthenticatedRequestContext( - new RequestContext('', ''), + new RequestContext(ctx?.requestId ?? '', ctx?.traceId ?? ''), {} as unknown as Login, {} as unknown as Project, {} as unknown as ProjectMembership, diff --git a/packages/server/src/fhir/operations/execute.ts b/packages/server/src/fhir/operations/execute.ts index 925bcaf62c..bab04ec337 100644 --- a/packages/server/src/fhir/operations/execute.ts +++ b/packages/server/src/fhir/operations/execute.ts @@ -56,6 +56,7 @@ export interface BotExecutionRequest { readonly remoteAddress?: string; readonly forwardedFor?: string; readonly requestTime?: string; + readonly traceId?: string; } export interface BotExecutionResult { @@ -269,7 +270,7 @@ async function writeBotInputToStorage(request: BotExecutionRequest): Promise { - const { bot, runAs, input, contentType } = request; + const { bot, runAs, input, contentType, traceId } = request; const config = getConfig(); const accessToken = await getBotAccessToken(runAs); const secrets = await getBotSecrets(bot); @@ -282,6 +283,7 @@ async function runInLambda(request: BotExecutionRequest): Promise { - const { bot, runAs, input, contentType } = request; + const { bot, runAs, input, contentType, traceId } = request; const config = getConfig(); if (!config.vmContextBotsEnabled) { @@ -409,6 +411,7 @@ async function runInVmContext(request: BotExecutionRequest): Promise(fn: () => T): T { - return requestContextStore.run(AuthenticatedRequestContext.system(), fn); +export function withTestContext(fn: () => T, ctx?: { requestId?: string; traceId?: string }): T { + return requestContextStore.run(AuthenticatedRequestContext.system(ctx), fn); } diff --git a/packages/server/src/traceparent.test.ts b/packages/server/src/traceparent.test.ts index 7cc00023eb..54f16a6a15 100644 --- a/packages/server/src/traceparent.test.ts +++ b/packages/server/src/traceparent.test.ts @@ -12,23 +12,23 @@ describe('parseTraceparent', () => { expect(parseTraceparent(`${tp.version}-${tp.traceId}-${tp.parentId}-${tp.flags}`)).toEqual(tp); }); - it('allow missing version', () => { + it('allows missing version', () => { expect(parseTraceparent(`${tp.traceId}-${tp.parentId}-${tp.flags}`)).toEqual({ ...tp, version: undefined }); }); - it('allow missing flags', () => { + it('allows missing flags', () => { expect(parseTraceparent(`${tp.version}-${tp.traceId}-${tp.parentId}`)).toEqual({ ...tp, flags: undefined }); }); - it('allow missing version and flags', () => { + it('allows missing version and flags', () => { expect(parseTraceparent(`${tp.traceId}-${tp.parentId}`)).toEqual({ ...tp, version: undefined, flags: undefined }); }); - it('allow 1 character for flags', () => { + it('allows 1 character for flags', () => { expect(parseTraceparent(`${tp.traceId}-${tp.parentId}-1`)).toEqual({ ...tp, version: undefined, flags: '1' }); }); - it('no more than 2 characters for flags', () => { + it('returns null for more than 2 characters for flags', () => { expect(parseTraceparent(`${tp.traceId}-${tp.parentId}-001`)).toEqual(null); }); diff --git a/packages/server/src/workers/download.test.ts b/packages/server/src/workers/download.test.ts index 2cd882e7b4..0f8e929eed 100644 --- a/packages/server/src/workers/download.test.ts +++ b/packages/server/src/workers/download.test.ts @@ -37,45 +37,53 @@ describe('Download Worker', () => { }); test('Download external URL', () => - withTestContext(async () => { - const url = 'https://example.com/download'; - - const queue = getDownloadQueue() as any; - queue.add.mockClear(); - - const media = await repo.createResource({ - resourceType: 'Media', - status: 'completed', - content: { - contentType: ContentType.TEXT, - url, - }, - }); - expect(media).toBeDefined(); - expect(queue.add).toHaveBeenCalled(); - - const body = new Readable(); - body.push('foo'); - body.push(null); - - (fetch as unknown as jest.Mock).mockImplementation(() => ({ - status: 200, - headers: { - get(name: string): string | undefined { - return { - 'content-disposition': 'attachment; filename=download', - 'content-type': ContentType.TEXT, - }[name]; + withTestContext( + async () => { + const url = 'https://example.com/download'; + + const queue = getDownloadQueue() as any; + queue.add.mockClear(); + + const media = await repo.createResource({ + resourceType: 'Media', + status: 'completed', + content: { + contentType: ContentType.TEXT, + url, }, - }, - body, - })); + }); + expect(media).toBeDefined(); + expect(queue.add).toHaveBeenCalled(); + + const body = new Readable(); + body.push('foo'); + body.push(null); + + (fetch as unknown as jest.Mock).mockImplementation(() => ({ + status: 200, + headers: { + get(name: string): string | undefined { + return { + 'content-disposition': 'attachment; filename=download', + 'content-type': ContentType.TEXT, + }[name]; + }, + }, + body, + })); - const job = { id: 1, data: queue.add.mock.calls[0][1] } as unknown as Job; - await execDownloadJob(job); + const job = { id: 1, data: queue.add.mock.calls[0][1] } as unknown as Job; + await execDownloadJob(job); - expect(fetch).toHaveBeenCalledWith(url); - })); + expect(fetch).toHaveBeenCalledWith(url, { + headers: { + 'x-trace-id': '00-12345678901234567890123456789012-3456789012345678-01', + traceparent: '00-12345678901234567890123456789012-3456789012345678-01', + }, + }); + }, + { traceId: '00-12345678901234567890123456789012-3456789012345678-01' } + )); test('Ignore media missing URL', () => withTestContext(async () => { diff --git a/packages/server/src/workers/download.ts b/packages/server/src/workers/download.ts index 28a3838b87..3bb3da9858 100644 --- a/packages/server/src/workers/download.ts +++ b/packages/server/src/workers/download.ts @@ -4,10 +4,11 @@ import { Job, Queue, QueueBaseOptions, Worker } from 'bullmq'; import fetch from 'node-fetch'; import { Readable } from 'stream'; import { getConfig, MedplumServerConfig } from '../config'; -import { getRequestContext } from '../context'; +import { getRequestContext, RequestContext, requestContextStore } from '../context'; import { getSystemRepo } from '../fhir/repo'; import { getBinaryStorage } from '../fhir/storage'; import { globalLogger } from '../logger'; +import { parseTraceparent } from '../traceparent'; /* * The download worker inspects resources, @@ -24,6 +25,8 @@ export interface DownloadJobData { readonly resourceType: string; readonly id: string; readonly url: string; + readonly requestId: string; + readonly traceId: string; } const queueName = 'DownloadQueue'; @@ -53,10 +56,15 @@ export function initDownloadWorker(config: MedplumServerConfig): void { }, }); - worker = new Worker(queueName, execDownloadJob, { - ...defaultOptions, - ...config.bullmq, - }); + worker = new Worker( + queueName, + (job) => + requestContextStore.run(new RequestContext(job.data.requestId, job.data.traceId), () => execDownloadJob(job)), + { + ...defaultOptions, + ...config.bullmq, + } + ); worker.on('completed', (job) => globalLogger.info(`Completed job ${job.id} successfully`)); worker.on('failed', (job, err) => globalLogger.info(`Failed job ${job?.id} with ${err}`)); } @@ -102,12 +110,15 @@ export function getDownloadQueue(): Queue | undefined { * @param resource - The resource that was created or updated. */ export async function addDownloadJobs(resource: Resource): Promise { + const ctx = getRequestContext(); for (const attachment of getAttachments(resource)) { if (isExternalUrl(attachment.url)) { await addDownloadJobData({ resourceType: resource.resourceType, id: resource.id as string, url: attachment.url, + requestId: ctx.requestId, + traceId: ctx.traceId, }); } } @@ -172,9 +183,18 @@ export async function execDownloadJob(job: Job): Promise return; } + const headers: HeadersInit = {}; + const traceId = job.data.traceId; + headers['x-trace-id'] = traceId; + if (parseTraceparent(traceId)) { + headers['traceparent'] = traceId; + } + try { ctx.logger.info('Requesting content at: ' + url); - const response = await fetch(url); + const response = await fetch(url, { + headers, + }); ctx.logger.info('Received status: ' + response.status); if (response.status >= 400) { diff --git a/packages/server/src/workers/subscription.test.ts b/packages/server/src/workers/subscription.test.ts index 20a2f181cf..b1b1c65441 100644 --- a/packages/server/src/workers/subscription.test.ts +++ b/packages/server/src/workers/subscription.test.ts @@ -182,49 +182,54 @@ describe('Subscription Worker', () => { })); test('Send subscription with custom headers', () => - withTestContext(async () => { - const url = 'https://example.com/subscription'; - - const subscription = await repo.createResource({ - resourceType: 'Subscription', - reason: 'test', - status: 'active', - criteria: 'Patient', - channel: { - type: 'rest-hook', - endpoint: url, - header: ['Authorization: Basic xyz'], - }, - }); - expect(subscription).toBeDefined(); - - const queue = getSubscriptionQueue() as any; - queue.add.mockClear(); - - const patient = await repo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice'], family: 'Smith' }], - }); - expect(patient).toBeDefined(); - expect(queue.add).toHaveBeenCalled(); - - (fetch as unknown as jest.Mock).mockImplementation(() => ({ status: 200 })); - - const job = { id: 1, data: queue.add.mock.calls[0][1] } as unknown as Job; - await execSubscriptionJob(job); - - expect(fetch).toHaveBeenCalledWith( - url, - expect.objectContaining({ - method: 'POST', - body: stringify(patient), - headers: { - 'Content-Type': ContentType.FHIR_JSON, - Authorization: 'Basic xyz', + withTestContext( + async () => { + const url = 'https://example.com/subscription'; + + const subscription = await repo.createResource({ + resourceType: 'Subscription', + reason: 'test', + status: 'active', + criteria: 'Patient', + channel: { + type: 'rest-hook', + endpoint: url, + header: ['Authorization: Basic xyz'], }, - }) - ); - })); + }); + expect(subscription).toBeDefined(); + + const queue = getSubscriptionQueue() as any; + queue.add.mockClear(); + + const patient = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family: 'Smith' }], + }); + expect(patient).toBeDefined(); + expect(queue.add).toHaveBeenCalled(); + + (fetch as unknown as jest.Mock).mockImplementation(() => ({ status: 200 })); + + const job = { id: 1, data: queue.add.mock.calls[0][1] } as unknown as Job; + await execSubscriptionJob(job); + + expect(fetch).toHaveBeenCalledWith( + url, + expect.objectContaining({ + method: 'POST', + body: stringify(patient), + headers: { + 'Content-Type': ContentType.FHIR_JSON, + Authorization: 'Basic xyz', + 'x-trace-id': '00-12345678901234567890123456789012-3456789012345678-01', + traceparent: '00-12345678901234567890123456789012-3456789012345678-01', + }, + }) + ); + }, + { traceId: '00-12345678901234567890123456789012-3456789012345678-01' } + )); test('Create-only subscription', () => withTestContext(async () => { @@ -291,173 +296,188 @@ describe('Subscription Worker', () => { })); test('Delete-only subscription', () => - withTestContext(async () => { - const url = 'https://example.com/subscription'; - - const subscription = await repo.createResource({ - resourceType: 'Subscription', - reason: 'test', - status: 'active', - criteria: 'Patient', - channel: { - type: 'rest-hook', - endpoint: url, - }, - extension: [ - { - url: 'https://medplum.com/fhir/StructureDefinition/subscription-supported-interaction', - valueCode: 'delete', + withTestContext( + async () => { + const url = 'https://example.com/subscription'; + + const subscription = await repo.createResource({ + resourceType: 'Subscription', + reason: 'test', + status: 'active', + criteria: 'Patient', + channel: { + type: 'rest-hook', + endpoint: url, }, - ], - }); - expect(subscription).toBeDefined(); - - // Clear the queue - const queue = getSubscriptionQueue() as any; - queue.add.mockClear(); - - // Create the patient - const patient = await repo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice'], family: 'Smith' }], - }); - expect(patient).toBeDefined(); - - // Create should trigger the subscription - expect(queue.add).not.toHaveBeenCalled(); - - // Update the patient - await repo.updateResource({ ...patient, active: true }); - - // Update should not trigger the subscription - expect(queue.add).not.toHaveBeenCalled(); - - // Delete the patient - await repo.deleteResource('Patient', patient.id as string); - - expect(queue.add).toHaveBeenCalled(); - const job = { id: 1, data: queue.add.mock.calls[0][1] } as unknown as Job; - await execSubscriptionJob(job); - expect(fetch).toHaveBeenCalledWith( - url, - expect.objectContaining({ - method: 'POST', - body: '{}', - headers: { - 'Content-Type': ContentType.FHIR_JSON, - 'X-Medplum-Deleted-Resource': `Patient/${patient.id}`, - }, - }) - ); - })); + extension: [ + { + url: 'https://medplum.com/fhir/StructureDefinition/subscription-supported-interaction', + valueCode: 'delete', + }, + ], + }); + expect(subscription).toBeDefined(); + + // Clear the queue + const queue = getSubscriptionQueue() as any; + queue.add.mockClear(); + + // Create the patient + const patient = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family: 'Smith' }], + }); + expect(patient).toBeDefined(); + + // Create should trigger the subscription + expect(queue.add).not.toHaveBeenCalled(); + + // Update the patient + await repo.updateResource({ ...patient, active: true }); + + // Update should not trigger the subscription + expect(queue.add).not.toHaveBeenCalled(); + + // Delete the patient + await repo.deleteResource('Patient', patient.id as string); + + expect(queue.add).toHaveBeenCalled(); + const job = { id: 1, data: queue.add.mock.calls[0][1] } as unknown as Job; + await execSubscriptionJob(job); + expect(fetch).toHaveBeenCalledWith( + url, + expect.objectContaining({ + method: 'POST', + body: '{}', + headers: { + 'Content-Type': ContentType.FHIR_JSON, + 'X-Medplum-Deleted-Resource': `Patient/${patient.id}`, + 'x-trace-id': '00-12345678901234567890123456789012-3456789012345678-01', + traceparent: '00-12345678901234567890123456789012-3456789012345678-01', + }, + }) + ); + }, + { traceId: '00-12345678901234567890123456789012-3456789012345678-01' } + )); test('Send subscriptions with signature', () => - withTestContext(async () => { - const url = 'https://example.com/subscription'; - const secret = '0123456789'; - - const subscription = await repo.createResource({ - resourceType: 'Subscription', - reason: 'test', - status: 'active', - criteria: 'Patient', - channel: { - type: 'rest-hook', - endpoint: url, - }, - extension: [ - { - url: 'https://www.medplum.com/fhir/StructureDefinition/subscription-secret', - valueString: secret, + withTestContext( + async () => { + const url = 'https://example.com/subscription'; + const secret = '0123456789'; + + const subscription = await repo.createResource({ + resourceType: 'Subscription', + reason: 'test', + status: 'active', + criteria: 'Patient', + channel: { + type: 'rest-hook', + endpoint: url, }, - ], - }); - expect(subscription).toBeDefined(); - - const queue = getSubscriptionQueue() as any; - queue.add.mockClear(); - - const patient = await repo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice'], family: 'Smith' }], - }); - expect(patient).toBeDefined(); - expect(queue.add).toHaveBeenCalled(); - - (fetch as unknown as jest.Mock).mockImplementation(() => ({ status: 200 })); - - const body = stringify(patient); - const signature = createHmac('sha256', secret).update(body).digest('hex'); - - const job = { id: 1, data: queue.add.mock.calls[0][1] } as unknown as Job; - await execSubscriptionJob(job); - - expect(fetch).toHaveBeenCalledWith( - url, - expect.objectContaining({ - method: 'POST', - body, - headers: { - 'Content-Type': ContentType.FHIR_JSON, - 'X-Signature': signature, - }, - }) - ); - })); + extension: [ + { + url: 'https://www.medplum.com/fhir/StructureDefinition/subscription-secret', + valueString: secret, + }, + ], + }); + expect(subscription).toBeDefined(); + + const queue = getSubscriptionQueue() as any; + queue.add.mockClear(); + + const patient = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family: 'Smith' }], + }); + expect(patient).toBeDefined(); + expect(queue.add).toHaveBeenCalled(); + + (fetch as unknown as jest.Mock).mockImplementation(() => ({ status: 200 })); + + const body = stringify(patient); + const signature = createHmac('sha256', secret).update(body).digest('hex'); + + const job = { id: 1, data: queue.add.mock.calls[0][1] } as unknown as Job; + await execSubscriptionJob(job); + + expect(fetch).toHaveBeenCalledWith( + url, + expect.objectContaining({ + method: 'POST', + body, + headers: { + 'Content-Type': ContentType.FHIR_JSON, + 'X-Signature': signature, + 'x-trace-id': '00-12345678901234567890123456789012-3456789012345678-01', + traceparent: '00-12345678901234567890123456789012-3456789012345678-01', + }, + }) + ); + }, + { traceId: '00-12345678901234567890123456789012-3456789012345678-01' } + )); test('Send subscriptions with legacy signature extension', () => - withTestContext(async () => { - const url = 'https://example.com/subscription'; - const secret = '0123456789'; - - const subscription = await repo.createResource({ - resourceType: 'Subscription', - reason: 'test', - status: 'active', - criteria: 'Patient', - channel: { - type: 'rest-hook', - endpoint: url, - }, - extension: [ - { - url: 'https://www.medplum.com/fhir/StructureDefinition-subscriptionSecret', - valueString: secret, - }, - ], - }); - expect(subscription).toBeDefined(); - - const queue = getSubscriptionQueue() as any; - queue.add.mockClear(); - - const patient = await repo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice'], family: 'Smith' }], - }); - expect(patient).toBeDefined(); - expect(queue.add).toHaveBeenCalled(); - - (fetch as unknown as jest.Mock).mockImplementation(() => ({ status: 200 })); - - const body = stringify(patient); - const signature = createHmac('sha256', secret).update(body).digest('hex'); - - const job = { id: 1, data: queue.add.mock.calls[0][1] } as unknown as Job; - await execSubscriptionJob(job); - - expect(fetch).toHaveBeenCalledWith( - url, - expect.objectContaining({ - method: 'POST', - body, - headers: { - 'Content-Type': ContentType.FHIR_JSON, - 'X-Signature': signature, + withTestContext( + async () => { + const url = 'https://example.com/subscription'; + const secret = '0123456789'; + + const subscription = await repo.createResource({ + resourceType: 'Subscription', + reason: 'test', + status: 'active', + criteria: 'Patient', + channel: { + type: 'rest-hook', + endpoint: url, }, - }) - ); - })); + extension: [ + { + url: 'https://www.medplum.com/fhir/StructureDefinition-subscriptionSecret', + valueString: secret, + }, + ], + }); + expect(subscription).toBeDefined(); + + const queue = getSubscriptionQueue() as any; + queue.add.mockClear(); + + const patient = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family: 'Smith' }], + }); + expect(patient).toBeDefined(); + expect(queue.add).toHaveBeenCalled(); + + (fetch as unknown as jest.Mock).mockImplementation(() => ({ status: 200 })); + + const body = stringify(patient); + const signature = createHmac('sha256', secret).update(body).digest('hex'); + + const job = { id: 1, data: queue.add.mock.calls[0][1] } as unknown as Job; + await execSubscriptionJob(job); + + expect(fetch).toHaveBeenCalledWith( + url, + expect.objectContaining({ + method: 'POST', + body, + headers: { + 'Content-Type': ContentType.FHIR_JSON, + 'X-Signature': signature, + 'x-trace-id': '00-12345678901234567890123456789012-3456789012345678-01', + traceparent: '00-12345678901234567890123456789012-3456789012345678-01', + }, + }) + ); + }, + { traceId: '00-12345678901234567890123456789012-3456789012345678-01' } + )); test('Ignore non-subscription subscriptions', () => withTestContext(async () => { diff --git a/packages/server/src/workers/subscription.ts b/packages/server/src/workers/subscription.ts index a66a994803..e322acff4b 100644 --- a/packages/server/src/workers/subscription.ts +++ b/packages/server/src/workers/subscription.ts @@ -18,7 +18,7 @@ import { createHmac } from 'crypto'; import fetch, { HeadersInit } from 'node-fetch'; import { URL } from 'url'; import { MedplumServerConfig } from '../config'; -import { getRequestContext } from '../context'; +import { getRequestContext, RequestContext, requestContextStore } from '../context'; import { executeBot } from '../fhir/operations/execute'; import { getSystemRepo, Repository } from '../fhir/repo'; import { globalLogger } from '../logger'; @@ -27,6 +27,7 @@ import { createSubEventNotification } from '../subscriptions/websockets'; import { AuditEventOutcome } from '../util/auditevent'; import { BackgroundJobContext, BackgroundJobInteraction } from './context'; import { createAuditEvent, findProjectMembership, isFhirCriteriaMet, isJobSuccessful } from './utils'; +import { parseTraceparent } from '../traceparent'; /** * The upper limit on the number of times a job can be retried. @@ -55,6 +56,8 @@ export interface SubscriptionJobData { readonly versionId: string; readonly interaction: 'create' | 'update' | 'delete'; readonly requestTime: string; + readonly requestId: string; + readonly traceId: string; } const queueName = 'SubscriptionQueue'; @@ -84,10 +87,15 @@ export function initSubscriptionWorker(config: MedplumServerConfig): void { }, }); - worker = new Worker(queueName, execSubscriptionJob, { - ...defaultOptions, - ...config.bullmq, - }); + worker = new Worker( + queueName, + (job) => + requestContextStore.run(new RequestContext(job.data.requestId, job.data.traceId), () => execSubscriptionJob(job)), + { + ...defaultOptions, + ...config.bullmq, + } + ); worker.on('completed', (job) => globalLogger.info(`Completed job ${job.id} successfully`)); worker.on('failed', (job, err) => globalLogger.info(`Failed job ${job?.id} with ${err}`)); } @@ -152,6 +160,8 @@ export async function addSubscriptionJobs(resource: Resource, context: Backgroun versionId: resource.meta?.versionId as string, interaction: context.interaction, requestTime, + requestId: ctx.requestId, + traceId: ctx.traceId, }); } } @@ -404,6 +414,11 @@ async function sendRestHook( if (interaction === 'delete') { headers['X-Medplum-Deleted-Resource'] = `${resource.resourceType}/${resource.id}`; } + const traceId = job.data.traceId; + headers['x-trace-id'] = traceId; + if (parseTraceparent(traceId)) { + headers['traceparent'] = traceId; + } const body = interaction === 'delete' ? '{}' : stringify(resource); let error: Error | undefined = undefined; @@ -487,10 +502,11 @@ async function execBot( interaction: BackgroundJobInteraction, requestTime: string ): Promise { + const ctx = getRequestContext(); const url = subscription.channel?.endpoint as string; if (!url) { // This can happen if a user updates the Subscription after the job is created. - getRequestContext().logger.debug(`Ignore rest hook missing URL`); + ctx.logger.debug(`Ignore rest hook missing URL`); return; } @@ -517,6 +533,7 @@ async function execBot( input: interaction === 'delete' ? { deletedResource: resource } : resource, contentType: ContentType.FHIR_JSON, requestTime, + traceId: ctx.traceId, }); } From baac25cc3ac70412739255e295be9e061155115f Mon Sep 17 00:00:00 2001 From: Rahul Agarwal Date: Thu, 8 Feb 2024 13:09:26 -0800 Subject: [PATCH 18/81] Bugfix: Search Control Pagination End (#3887) * Added option to override request handling in MockClient * Fix bug for pagination end with large resource counts * Fixes based on feedback * Test Search control vs. jest mocks * Fixed pagination end math --- .../src/SearchControl/SearchControl.test.tsx | 104 +++++++++++++++++- .../react/src/SearchControl/SearchControl.tsx | 9 +- 2 files changed, 103 insertions(+), 10 deletions(-) diff --git a/packages/react/src/SearchControl/SearchControl.test.tsx b/packages/react/src/SearchControl/SearchControl.test.tsx index 3497a86ff4..7a0c50cdfe 100644 --- a/packages/react/src/SearchControl/SearchControl.test.tsx +++ b/packages/react/src/SearchControl/SearchControl.test.tsx @@ -1,8 +1,9 @@ -import { Operator } from '@medplum/core'; -import { MockClient } from '@medplum/mock'; -import { act, fireEvent, render, screen, waitFor } from '../test-utils/render'; -import { MemoryRouter } from 'react-router-dom'; +import { Operator, SearchRequest } from '@medplum/core'; +import { Bundle } from '@medplum/fhirtypes'; +import { HomerSimpson, MockClient } from '@medplum/mock'; import { MedplumProvider } from '@medplum/react-hooks'; +import { MemoryRouter } from 'react-router-dom'; +import { act, fireEvent, render, screen, waitFor } from '../test-utils/render'; import { SearchControl, SearchControlProps } from './SearchControl'; describe('SearchControl', () => { @@ -17,11 +18,15 @@ describe('SearchControl', () => { jest.useRealTimers(); }); - async function setup(args: SearchControlProps): Promise { + async function setup(args: SearchControlProps, returnVal?: Bundle): Promise { + const medplum = new MockClient(); + if (returnVal) { + medplum.search = jest.fn().mockResolvedValue(returnVal); + } await act(async () => { render( - + @@ -874,4 +879,91 @@ describe('SearchControl', () => { await waitFor(() => screen.getByText('Homer Simpson')); expect(onLoad).toHaveBeenCalled(); }); + + describe('Pagination', () => { + const onLoad = jest.fn(); + const search: SearchRequest = { + resourceType: 'Patient', + count: 20, + offset: 0, + filters: [ + { + code: 'name', + operator: Operator.EQUALS, + value: 'Simpson', + }, + ], + fields: ['id', '_lastUpdated', 'name'], + }; + test('Single Page', async () => { + const props: SearchControlProps = { + search, + onLoad, + }; + await setup(props, { + resourceType: 'Bundle', + type: 'searchset', + total: 5, + entry: [{ resource: HomerSimpson }, ...Array(4).fill({ resourceType: 'Patient' })], + }); + await waitFor(() => screen.getByText('Homer Simpson')); + const element = screen.getByTestId('count-display'); + expect(element.textContent).toBe('1-5 of 5'); + }); + + test('Multiple Pages', async () => { + const props: SearchControlProps = { + search, + onLoad, + }; + await setup(props, { + resourceType: 'Bundle', + type: 'searchset', + total: 40, + entry: [{ resource: HomerSimpson }, ...Array(19).fill({ resourceType: 'Patient' })], + }); + await waitFor(() => screen.getByText('Homer Simpson')); + const element = screen.getByTestId('count-display'); + expect(element.textContent).toBe('1-20 of 40'); + }); + + test('Large Estimated Count', async () => { + const props: SearchControlProps = { + search, + onLoad, + }; + + await setup(props, { + resourceType: 'Bundle', + type: 'searchset', + total: 403091, + entry: [{ resource: HomerSimpson }, ...Array(19).fill({ resourceType: 'Patient' })], + }); + await waitFor(() => screen.getByText('Homer Simpson')); + const element = screen.getByTestId('count-display'); + expect(element.textContent).toBe('1-20 of 403,091'); + }); + + test('Large Estimated Count w/ High Offset', async () => { + const props: SearchControlProps = { + search: { ...search, offset: 200000, count: 20 }, + onLoad, + }; + + await setup(props, { + resourceType: 'Bundle', + type: 'searchset', + total: 403091, + entry: [{ resource: HomerSimpson }, ...Array(19).fill({ resourceType: 'Patient' })], + link: [ + { + relation: 'next', + url: '', + }, + ], + }); + await waitFor(() => screen.getByText('Homer Simpson')); + expect(screen.getByTestId('count-display').textContent).toBe('200,001-200,020 of 403,091'); + }); + }); }); diff --git a/packages/react/src/SearchControl/SearchControl.tsx b/packages/react/src/SearchControl/SearchControl.tsx index 73ed42b903..8a6c3091f1 100644 --- a/packages/react/src/SearchControl/SearchControl.tsx +++ b/packages/react/src/SearchControl/SearchControl.tsx @@ -333,8 +333,8 @@ export function SearchControl(props: SearchControlProps): JSX.Element { {lastResult && ( - - {getStart(search, lastResult)}-{getEnd(search, lastResult)} + + {getStart(search, lastResult).toLocaleString()}-{getEnd(search, lastResult).toLocaleString()} {lastResult.total !== undefined && ` of ${search.total === 'estimate' ? '~' : ''}${lastResult.total?.toLocaleString()}`} @@ -577,7 +577,8 @@ function getPage(search: SearchRequest): number { function getTotalPages(search: SearchRequest, lastResult: Bundle): number { const pageSize = search.count ?? DEFAULT_SEARCH_COUNT; - return Math.ceil(getTotal(search, lastResult) / pageSize); + const total = getTotal(search, lastResult); + return Math.ceil(total / pageSize); } function getStart(search: SearchRequest, lastResult: Bundle): number { @@ -585,7 +586,7 @@ function getStart(search: SearchRequest, lastResult: Bundle): number { } function getEnd(search: SearchRequest, lastResult: Bundle): number { - return Math.min(getTotal(search, lastResult), ((search.offset ?? 0) + 1) * (search.count ?? DEFAULT_SEARCH_COUNT)); + return getStart(search, lastResult) + (lastResult.entry?.length ?? 0) - 1; } function getTotal(search: SearchRequest, lastResult: Bundle): number { From e78feb732e49172ccf669602407eb9efb2399031 Mon Sep 17 00:00:00 2001 From: Medplum Bot <152649536+medplumbot@users.noreply.github.com> Date: Thu, 8 Feb 2024 13:33:57 -0800 Subject: [PATCH 19/81] Release Version 3.0.3 (#3911) fix-3885 propagate `traceId` through asynchronous jobs (#3886) Expand profile operation (#3875) feat: Support OperationDefinition inputs for patient-everything (#3908) Remove TURBO_REMOTE_ONLY from build step (#3907) Server config setting for accurate count threshold (#3902) Remove global systemRepo (#3884) Maintain full `traceparent` header value for `traceId` (#3877) fix-3807 rectify fhirpath `exists` and `empty` (#3808) Fix return documentation for client read methods (#3891) Document how to switch to super admin project in projects (#3873) Refactor elements context and slicing logic (#3869) Dependency upgrades (#3874) No longer need to build core for @medplum/react npm run dev (#3871) Fixes to ResourceDiffTable with arrays (#3870) feat(agent): add `ping` via `Agent/$push` (#3846) Fix token search with pipe in value (#3867) Revert nested transactions (#3866) Evict connection on rollback (#3865) --- examples/foomedical/package.json | 12 +- examples/medplum-chart-demo/package.json | 10 +- examples/medplum-demo-bots/package.json | 12 +- examples/medplum-fhircast-demo/package.json | 10 +- examples/medplum-hello-world/package.json | 10 +- examples/medplum-live-chat-demo/package.json | 10 +- examples/medplum-nextjs-demo/package.json | 8 +- .../medplum-react-native-example/package.json | 8 +- examples/medplum-task-demo/package.json | 12 +- .../package.json | 14 +- package-lock.json | 155 +++++++++--------- package.json | 2 +- packages/agent/package.json | 2 +- packages/app/package.json | 2 +- packages/bot-layer/package.json | 2 +- packages/cdk/package.json | 2 +- packages/cli/package.json | 2 +- packages/core/package.json | 2 +- packages/definitions/package.json | 2 +- packages/docs/package.json | 2 +- packages/eslint-config/package.json | 2 +- packages/examples/package.json | 2 +- packages/expo-polyfills/package.json | 2 +- packages/fhir-router/package.json | 2 +- packages/fhirtypes/package.json | 2 +- packages/generator/package.json | 2 +- packages/graphiql/package.json | 2 +- packages/health-gorilla/package.json | 2 +- packages/hl7/package.json | 2 +- packages/mock/package.json | 2 +- packages/react-hooks/package.json | 2 +- packages/react/package.json | 2 +- packages/server/package.json | 2 +- sonar-project.properties | 2 +- 34 files changed, 155 insertions(+), 152 deletions(-) diff --git a/examples/foomedical/package.json b/examples/foomedical/package.json index 503253328f..56eb480c4f 100644 --- a/examples/foomedical/package.json +++ b/examples/foomedical/package.json @@ -1,6 +1,6 @@ { "name": "foomedical", - "version": "3.0.2", + "version": "3.0.3", "type": "module", "scripts": { "build": "tsc && vite build", @@ -28,11 +28,11 @@ "@mantine/core": "7.5.1", "@mantine/hooks": "7.5.1", "@mantine/notifications": "7.5.1", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/mock": "3.0.2", - "@medplum/react": "3.0.2", + "@medplum/core": "3.0.3", + "@medplum/eslint-config": "3.0.3", + "@medplum/fhirtypes": "3.0.3", + "@medplum/mock": "3.0.3", + "@medplum/react": "3.0.3", "@tabler/icons-react": "2.46.0", "@testing-library/jest-dom": "6.4.1", "@testing-library/react": "14.2.0", diff --git a/examples/medplum-chart-demo/package.json b/examples/medplum-chart-demo/package.json index 6e584605b6..4fe2985a5b 100644 --- a/examples/medplum-chart-demo/package.json +++ b/examples/medplum-chart-demo/package.json @@ -1,6 +1,6 @@ { "name": "medplum-chart-demo", - "version": "3.0.2", + "version": "3.0.3", "private": true, "type": "module", "scripts": { @@ -22,10 +22,10 @@ "@mantine/core": "7.5.1", "@mantine/hooks": "7.5.1", "@mantine/notifications": "7.5.1", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/react": "3.0.2", + "@medplum/core": "3.0.3", + "@medplum/eslint-config": "3.0.3", + "@medplum/fhirtypes": "3.0.3", + "@medplum/react": "3.0.3", "@tabler/icons-react": "2.46.0", "@types/node": "20.11.16", "@types/react": "18.2.51", diff --git a/examples/medplum-demo-bots/package.json b/examples/medplum-demo-bots/package.json index 7b6f6a89a4..67eb73a111 100644 --- a/examples/medplum-demo-bots/package.json +++ b/examples/medplum-demo-bots/package.json @@ -1,6 +1,6 @@ { "name": "medplum-demo-bots", - "version": "3.0.2", + "version": "3.0.3", "description": "Medplum Demo Bots", "license": "Apache-2.0", "author": "Medplum ", @@ -25,11 +25,11 @@ ] }, "devDependencies": { - "@medplum/cli": "3.0.2", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/mock": "3.0.2", + "@medplum/cli": "3.0.3", + "@medplum/core": "3.0.3", + "@medplum/eslint-config": "3.0.3", + "@medplum/fhirtypes": "3.0.3", + "@medplum/mock": "3.0.3", "@types/node": "20.11.16", "@types/node-fetch": "2.6.11", "@types/ssh2-sftp-client": "9.0.3", diff --git a/examples/medplum-fhircast-demo/package.json b/examples/medplum-fhircast-demo/package.json index eb95ef2069..f6dd9b27fc 100644 --- a/examples/medplum-fhircast-demo/package.json +++ b/examples/medplum-fhircast-demo/package.json @@ -1,6 +1,6 @@ { "name": "medplum-fhircast-demo", - "version": "3.0.2", + "version": "3.0.3", "private": true, "type": "module", "scripts": { @@ -18,10 +18,10 @@ "@mantine/core": "7.5.1", "@mantine/hooks": "7.5.1", "@mantine/notifications": "7.5.1", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/react": "3.0.2", + "@medplum/core": "3.0.3", + "@medplum/eslint-config": "3.0.3", + "@medplum/fhirtypes": "3.0.3", + "@medplum/react": "3.0.3", "@tabler/icons-react": "2.46.0", "@types/react": "18.2.51", "@types/react-dom": "18.2.18", diff --git a/examples/medplum-hello-world/package.json b/examples/medplum-hello-world/package.json index c8c7396246..d95f7e3411 100644 --- a/examples/medplum-hello-world/package.json +++ b/examples/medplum-hello-world/package.json @@ -1,6 +1,6 @@ { "name": "medplum-hello-world", - "version": "3.0.2", + "version": "3.0.3", "private": true, "type": "module", "scripts": { @@ -22,10 +22,10 @@ "@mantine/core": "7.5.1", "@mantine/hooks": "7.5.1", "@mantine/notifications": "7.5.1", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/react": "3.0.2", + "@medplum/core": "3.0.3", + "@medplum/eslint-config": "3.0.3", + "@medplum/fhirtypes": "3.0.3", + "@medplum/react": "3.0.3", "@tabler/icons-react": "2.46.0", "@types/node": "20.11.16", "@types/react": "18.2.51", diff --git a/examples/medplum-live-chat-demo/package.json b/examples/medplum-live-chat-demo/package.json index e11975b307..ab0ba22e7f 100644 --- a/examples/medplum-live-chat-demo/package.json +++ b/examples/medplum-live-chat-demo/package.json @@ -1,6 +1,6 @@ { "name": "medplum-live-chat-demo", - "version": "3.0.2", + "version": "3.0.3", "private": true, "type": "module", "scripts": { @@ -22,10 +22,10 @@ "@mantine/core": "7.5.1", "@mantine/hooks": "7.5.1", "@mantine/notifications": "7.5.1", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/react": "3.0.2", + "@medplum/core": "3.0.3", + "@medplum/eslint-config": "3.0.3", + "@medplum/fhirtypes": "3.0.3", + "@medplum/react": "3.0.3", "@tabler/icons-react": "2.46.0", "@types/node": "20.11.16", "@types/react": "18.2.51", diff --git a/examples/medplum-nextjs-demo/package.json b/examples/medplum-nextjs-demo/package.json index 28b5c37801..7874925b17 100644 --- a/examples/medplum-nextjs-demo/package.json +++ b/examples/medplum-nextjs-demo/package.json @@ -1,6 +1,6 @@ { "name": "medplum-nextjs-demo", - "version": "3.0.2", + "version": "3.0.3", "private": true, "type": "module", "scripts": { @@ -13,15 +13,15 @@ "@mantine/core": "7.5.1", "@mantine/hooks": "7.5.1", "@mantine/notifications": "7.5.1", - "@medplum/core": "3.0.2", - "@medplum/react": "3.0.2", + "@medplum/core": "3.0.3", + "@medplum/react": "3.0.3", "next": "14.1.0", "react": "18.2.0", "react-dom": "18.2.0", "rfc6902": "5.1.1" }, "devDependencies": { - "@medplum/fhirtypes": "3.0.2", + "@medplum/fhirtypes": "3.0.3", "@types/node": "20.11.16", "@types/react": "18.2.51", "@types/react-dom": "18.2.18", diff --git a/examples/medplum-react-native-example/package.json b/examples/medplum-react-native-example/package.json index 68109e0071..769722216e 100644 --- a/examples/medplum-react-native-example/package.json +++ b/examples/medplum-react-native-example/package.json @@ -1,6 +1,6 @@ { "name": "medplum-react-native-example", - "version": "3.0.2", + "version": "3.0.3", "main": "src/main.ts", "scripts": { "android": "expo start --android", @@ -20,9 +20,9 @@ }, "dependencies": { "@expo/webpack-config": "19.0.1", - "@medplum/core": "3.0.2", - "@medplum/expo-polyfills": "3.0.2", - "@medplum/react-hooks": "3.0.2", + "@medplum/core": "3.0.3", + "@medplum/expo-polyfills": "3.0.3", + "@medplum/react-hooks": "3.0.3", "expo": "50.0.4", "expo-status-bar": "1.11.1", "react": "18.2.0", diff --git a/examples/medplum-task-demo/package.json b/examples/medplum-task-demo/package.json index f30b3ede7d..33304bbf78 100644 --- a/examples/medplum-task-demo/package.json +++ b/examples/medplum-task-demo/package.json @@ -1,6 +1,6 @@ { "name": "medplum-task-demo", - "version": "3.0.2", + "version": "3.0.3", "private": true, "type": "module", "scripts": { @@ -25,11 +25,11 @@ "@mantine/core": "7.5.1", "@mantine/hooks": "7.5.1", "@mantine/notifications": "7.5.1", - "@medplum/core": "3.0.2", - "@medplum/definitions": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/react": "3.0.2", + "@medplum/core": "3.0.3", + "@medplum/definitions": "3.0.3", + "@medplum/eslint-config": "3.0.3", + "@medplum/fhirtypes": "3.0.3", + "@medplum/react": "3.0.3", "@tabler/icons-react": "2.46.0", "@types/node": "20.11.16", "@types/react": "18.2.51", diff --git a/examples/medplum-websocket-subscriptions-demo/package.json b/examples/medplum-websocket-subscriptions-demo/package.json index 4fe8cb8d88..7cca4c543b 100644 --- a/examples/medplum-websocket-subscriptions-demo/package.json +++ b/examples/medplum-websocket-subscriptions-demo/package.json @@ -1,6 +1,6 @@ { "name": "medplum-websocket-subscriptions-demo", - "version": "3.0.2", + "version": "3.0.3", "private": true, "type": "module", "scripts": { @@ -23,12 +23,12 @@ "@mantine/core": "7.5.1", "@mantine/hooks": "7.5.1", "@mantine/notifications": "7.5.1", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhir-router": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/mock": "3.0.2", - "@medplum/react": "3.0.2", + "@medplum/core": "3.0.3", + "@medplum/eslint-config": "3.0.3", + "@medplum/fhir-router": "3.0.3", + "@medplum/fhirtypes": "3.0.3", + "@medplum/mock": "3.0.3", + "@medplum/react": "3.0.3", "@tabler/icons-react": "2.46.0", "@types/node": "20.11.16", "@types/react": "18.2.51", diff --git a/package-lock.json b/package-lock.json index 0bb12c585f..db9b073641 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "root", - "version": "3.0.2", + "version": "3.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "root", - "version": "3.0.2", + "version": "3.0.3", "workspaces": [ "packages/*", "examples/*" @@ -45,7 +45,7 @@ } }, "examples/foomedical": { - "version": "3.0.2", + "version": "3.0.3", "devDependencies": { "@babel/core": "7.23.9", "@babel/preset-env": "7.23.9", @@ -54,11 +54,11 @@ "@mantine/core": "7.5.1", "@mantine/hooks": "7.5.1", "@mantine/notifications": "7.5.1", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/mock": "3.0.2", - "@medplum/react": "3.0.2", + "@medplum/core": "3.0.3", + "@medplum/eslint-config": "3.0.3", + "@medplum/fhirtypes": "3.0.3", + "@medplum/mock": "3.0.3", + "@medplum/react": "3.0.3", "@tabler/icons-react": "2.46.0", "@testing-library/jest-dom": "6.4.1", "@testing-library/react": "14.2.0", @@ -172,15 +172,15 @@ } }, "examples/medplum-chart-demo": { - "version": "3.0.2", + "version": "3.0.3", "devDependencies": { "@mantine/core": "7.5.1", "@mantine/hooks": "7.5.1", "@mantine/notifications": "7.5.1", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/react": "3.0.2", + "@medplum/core": "3.0.3", + "@medplum/eslint-config": "3.0.3", + "@medplum/fhirtypes": "3.0.3", + "@medplum/react": "3.0.3", "@tabler/icons-react": "2.46.0", "@types/node": "20.11.16", "@types/react": "18.2.51", @@ -283,14 +283,14 @@ } }, "examples/medplum-demo-bots": { - "version": "3.0.2", + "version": "3.0.3", "license": "Apache-2.0", "devDependencies": { - "@medplum/cli": "3.0.2", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/mock": "3.0.2", + "@medplum/cli": "3.0.3", + "@medplum/core": "3.0.3", + "@medplum/eslint-config": "3.0.3", + "@medplum/fhirtypes": "3.0.3", + "@medplum/mock": "3.0.3", "@types/node": "20.11.16", "@types/node-fetch": "2.6.11", "@types/ssh2-sftp-client": "9.0.3", @@ -308,15 +308,15 @@ } }, "examples/medplum-fhircast-demo": { - "version": "3.0.2", + "version": "3.0.3", "devDependencies": { "@mantine/core": "7.5.1", "@mantine/hooks": "7.5.1", "@mantine/notifications": "7.5.1", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/react": "3.0.2", + "@medplum/core": "3.0.3", + "@medplum/eslint-config": "3.0.3", + "@medplum/fhirtypes": "3.0.3", + "@medplum/react": "3.0.3", "@tabler/icons-react": "2.46.0", "@types/react": "18.2.51", "@types/react-dom": "18.2.18", @@ -418,15 +418,15 @@ } }, "examples/medplum-hello-world": { - "version": "3.0.2", + "version": "3.0.3", "devDependencies": { "@mantine/core": "7.5.1", "@mantine/hooks": "7.5.1", "@mantine/notifications": "7.5.1", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/react": "3.0.2", + "@medplum/core": "3.0.3", + "@medplum/eslint-config": "3.0.3", + "@medplum/fhirtypes": "3.0.3", + "@medplum/react": "3.0.3", "@tabler/icons-react": "2.46.0", "@types/node": "20.11.16", "@types/react": "18.2.51", @@ -529,15 +529,15 @@ } }, "examples/medplum-live-chat-demo": { - "version": "3.0.2", + "version": "3.0.3", "devDependencies": { "@mantine/core": "7.5.1", "@mantine/hooks": "7.5.1", "@mantine/notifications": "7.5.1", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/react": "3.0.2", + "@medplum/core": "3.0.3", + "@medplum/eslint-config": "3.0.3", + "@medplum/fhirtypes": "3.0.3", + "@medplum/react": "3.0.3", "@tabler/icons-react": "2.46.0", "@types/node": "20.11.16", "@types/react": "18.2.51", @@ -640,20 +640,20 @@ } }, "examples/medplum-nextjs-demo": { - "version": "3.0.2", + "version": "3.0.3", "dependencies": { "@mantine/core": "7.5.1", "@mantine/hooks": "7.5.1", "@mantine/notifications": "7.5.1", - "@medplum/core": "3.0.2", - "@medplum/react": "3.0.2", + "@medplum/core": "3.0.3", + "@medplum/react": "3.0.3", "next": "14.1.0", "react": "18.2.0", "react-dom": "18.2.0", "rfc6902": "5.1.1" }, "devDependencies": { - "@medplum/fhirtypes": "3.0.2", + "@medplum/fhirtypes": "3.0.3", "@types/node": "20.11.16", "@types/react": "18.2.51", "@types/react-dom": "18.2.18", @@ -665,12 +665,12 @@ } }, "examples/medplum-react-native-example": { - "version": "3.0.2", + "version": "3.0.3", "dependencies": { "@expo/webpack-config": "19.0.1", - "@medplum/core": "3.0.2", - "@medplum/expo-polyfills": "3.0.2", - "@medplum/react-hooks": "3.0.2", + "@medplum/core": "3.0.3", + "@medplum/expo-polyfills": "3.0.3", + "@medplum/react-hooks": "3.0.3", "expo": "50.0.4", "expo-status-bar": "1.11.1", "react": "18.2.0", @@ -684,16 +684,16 @@ } }, "examples/medplum-task-demo": { - "version": "3.0.2", + "version": "3.0.3", "devDependencies": { "@mantine/core": "7.5.1", "@mantine/hooks": "7.5.1", "@mantine/notifications": "7.5.1", - "@medplum/core": "3.0.2", - "@medplum/definitions": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/react": "3.0.2", + "@medplum/core": "3.0.3", + "@medplum/definitions": "3.0.3", + "@medplum/eslint-config": "3.0.3", + "@medplum/fhirtypes": "3.0.3", + "@medplum/react": "3.0.3", "@tabler/icons-react": "2.46.0", "@types/node": "20.11.16", "@types/react": "18.2.51", @@ -796,18 +796,18 @@ } }, "examples/medplum-websocket-subscriptions-demo": { - "version": "3.0.2", + "version": "3.0.3", "devDependencies": { "@emotion/react": "11.11.3", "@mantine/core": "7.5.1", "@mantine/hooks": "7.5.1", "@mantine/notifications": "7.5.1", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhir-router": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/mock": "3.0.2", - "@medplum/react": "3.0.2", + "@medplum/core": "3.0.3", + "@medplum/eslint-config": "3.0.3", + "@medplum/fhir-router": "3.0.3", + "@medplum/fhirtypes": "3.0.3", + "@medplum/mock": "3.0.3", + "@medplum/react": "3.0.3", "@tabler/icons-react": "2.46.0", "@types/node": "20.11.16", "@types/react": "18.2.51", @@ -32336,6 +32336,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -32349,6 +32350,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -42697,6 +42699,7 @@ "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -57981,7 +57984,7 @@ }, "packages/agent": { "name": "@medplum/agent", - "version": "3.0.2", + "version": "3.0.3", "license": "Apache-2.0", "dependencies": { "@medplum/core": "*", @@ -58005,7 +58008,7 @@ }, "packages/app": { "name": "@medplum/app", - "version": "3.0.2", + "version": "3.0.3", "license": "Apache-2.0", "devDependencies": { "@mantine/core": "7.5.1", @@ -58124,7 +58127,7 @@ }, "packages/bot-layer": { "name": "@medplum/bot-layer", - "version": "3.0.2", + "version": "3.0.3", "license": "Apache-2.0", "dependencies": { "@medplum/core": "*", @@ -58152,7 +58155,7 @@ }, "packages/cdk": { "name": "@medplum/cdk", - "version": "3.0.2", + "version": "3.0.3", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.502.0", @@ -58169,7 +58172,7 @@ }, "packages/cli": { "name": "@medplum/cli", - "version": "3.0.2", + "version": "3.0.3", "license": "Apache-2.0", "dependencies": { "@aws-sdk/client-acm": "3.504.0", @@ -58212,7 +58215,7 @@ }, "packages/core": { "name": "@medplum/core", - "version": "3.0.2", + "version": "3.0.3", "license": "Apache-2.0", "devDependencies": { "@medplum/definitions": "*", @@ -58233,7 +58236,7 @@ }, "packages/definitions": { "name": "@medplum/definitions", - "version": "3.0.2", + "version": "3.0.3", "license": "Apache-2.0", "engines": { "node": ">=18.0.0" @@ -58241,7 +58244,7 @@ }, "packages/docs": { "name": "@medplum/docs", - "version": "3.0.2", + "version": "3.0.3", "license": "Apache-2.0", "devDependencies": { "@docusaurus/core": "3.1.1", @@ -58281,7 +58284,7 @@ }, "packages/eslint-config": { "name": "@medplum/eslint-config", - "version": "3.0.2", + "version": "3.0.3", "license": "Apache-2.0", "devDependencies": { "@typescript-eslint/eslint-plugin": "6.20.0", @@ -58306,7 +58309,7 @@ }, "packages/examples": { "name": "@medplum/examples", - "version": "3.0.2", + "version": "3.0.3", "license": "Apache-2.0", "devDependencies": { "@jest/globals": "29.7.0", @@ -58321,7 +58324,7 @@ }, "packages/expo-polyfills": { "name": "@medplum/expo-polyfills", - "version": "3.0.2", + "version": "3.0.3", "license": "Apache-2.0", "dependencies": { "base-64": "1.0.0", @@ -58352,7 +58355,7 @@ }, "packages/fhir-router": { "name": "@medplum/fhir-router", - "version": "3.0.2", + "version": "3.0.3", "license": "Apache-2.0", "dependencies": { "@medplum/core": "*", @@ -58368,7 +58371,7 @@ }, "packages/fhirtypes": { "name": "@medplum/fhirtypes", - "version": "3.0.2", + "version": "3.0.3", "license": "Apache-2.0", "engines": { "node": ">=18.0.0" @@ -58376,7 +58379,7 @@ }, "packages/generator": { "name": "@medplum/generator", - "version": "3.0.2", + "version": "3.0.3", "license": "Apache-2.0", "devDependencies": { "@medplum/core": "*", @@ -58421,7 +58424,7 @@ }, "packages/graphiql": { "name": "@medplum/graphiql", - "version": "3.0.2", + "version": "3.0.3", "license": "Apache-2.0", "devDependencies": { "@graphiql/react": "0.20.2", @@ -58535,7 +58538,7 @@ }, "packages/health-gorilla": { "name": "@medplum/health-gorilla", - "version": "3.0.2", + "version": "3.0.3", "license": "Apache-2.0", "dependencies": { "@medplum/core": "*", @@ -58550,7 +58553,7 @@ }, "packages/hl7": { "name": "@medplum/hl7", - "version": "3.0.2", + "version": "3.0.3", "license": "Apache-2.0", "dependencies": { "@medplum/core": "*" @@ -58564,7 +58567,7 @@ }, "packages/mock": { "name": "@medplum/mock", - "version": "3.0.2", + "version": "3.0.3", "license": "Apache-2.0", "dependencies": { "@medplum/core": "*", @@ -58583,7 +58586,7 @@ }, "packages/react": { "name": "@medplum/react", - "version": "3.0.2", + "version": "3.0.3", "license": "Apache-2.0", "devDependencies": { "@mantine/core": "7.5.1", @@ -58654,7 +58657,7 @@ }, "packages/react-hooks": { "name": "@medplum/react-hooks", - "version": "3.0.2", + "version": "3.0.3", "license": "Apache-2.0", "devDependencies": { "@medplum/core": "*", @@ -58734,7 +58737,7 @@ }, "packages/server": { "name": "@medplum/server", - "version": "3.0.2", + "version": "3.0.3", "license": "Apache-2.0", "dependencies": { "@aws-sdk/client-cloudwatch-logs": "3.504.0", diff --git a/package.json b/package.json index 3dec839b2d..e56a03a32a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "3.0.2", + "version": "3.0.3", "private": true, "workspaces": [ "packages/*", diff --git a/packages/agent/package.json b/packages/agent/package.json index dba4b3fc12..7c6964c597 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/agent", - "version": "3.0.2", + "version": "3.0.3", "description": "Medplum Agent", "homepage": "https://www.medplum.com/", "bugs": { diff --git a/packages/app/package.json b/packages/app/package.json index 309a55b5b0..785acb9bb4 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/app", - "version": "3.0.2", + "version": "3.0.3", "description": "Medplum App", "homepage": "https://www.medplum.com/", "bugs": { diff --git a/packages/bot-layer/package.json b/packages/bot-layer/package.json index 2256928aa7..496bbf75a7 100644 --- a/packages/bot-layer/package.json +++ b/packages/bot-layer/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/bot-layer", - "version": "3.0.2", + "version": "3.0.3", "description": "Medplum Bot Lambda Layer", "keywords": [ "medplum", diff --git a/packages/cdk/package.json b/packages/cdk/package.json index 89ad9131a6..ad317fe50a 100644 --- a/packages/cdk/package.json +++ b/packages/cdk/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/cdk", - "version": "3.0.2", + "version": "3.0.3", "description": "Medplum CDK Infra as Code", "homepage": "https://www.medplum.com/", "bugs": { diff --git a/packages/cli/package.json b/packages/cli/package.json index 0b8ce6a536..e95a6b91f1 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/cli", - "version": "3.0.2", + "version": "3.0.3", "description": "Medplum Command Line Interface", "keywords": [ "medplum", diff --git a/packages/core/package.json b/packages/core/package.json index 36d5ac5549..763e9e0ae1 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/core", - "version": "3.0.2", + "version": "3.0.3", "description": "Medplum TS/JS Library", "keywords": [ "medplum", diff --git a/packages/definitions/package.json b/packages/definitions/package.json index f858a7051d..76a661a0b7 100644 --- a/packages/definitions/package.json +++ b/packages/definitions/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/definitions", - "version": "3.0.2", + "version": "3.0.3", "description": "Medplum Data Definitions", "keywords": [ "medplum", diff --git a/packages/docs/package.json b/packages/docs/package.json index 3bfe3b8fa1..2da22b8dab 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/docs", - "version": "3.0.2", + "version": "3.0.3", "description": "Medplum Docs", "homepage": "https://www.medplum.com/", "bugs": { diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index a0544418c1..46dfa23740 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/eslint-config", - "version": "3.0.2", + "version": "3.0.3", "description": "Shared ESLint configuration for Medplum projects", "keywords": [ "eslint", diff --git a/packages/examples/package.json b/packages/examples/package.json index 91b3733f56..eeba3bc98a 100644 --- a/packages/examples/package.json +++ b/packages/examples/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/examples", - "version": "3.0.2", + "version": "3.0.3", "description": "Medplum Code Examples", "homepage": "https://www.medplum.com/", "bugs": { diff --git a/packages/expo-polyfills/package.json b/packages/expo-polyfills/package.json index 041085ee53..1fb7bcb969 100644 --- a/packages/expo-polyfills/package.json +++ b/packages/expo-polyfills/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/expo-polyfills", - "version": "3.0.2", + "version": "3.0.3", "description": "A module for polyfilling the minimum necessary web APIs for using the Medplum client on React Native", "keywords": [ "react-native", diff --git a/packages/fhir-router/package.json b/packages/fhir-router/package.json index 5ea93ef63f..3141e0ddc2 100644 --- a/packages/fhir-router/package.json +++ b/packages/fhir-router/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/fhir-router", - "version": "3.0.2", + "version": "3.0.3", "description": "Medplum FHIR Router", "keywords": [ "medplum", diff --git a/packages/fhirtypes/package.json b/packages/fhirtypes/package.json index e3ad91c34e..8f98e70545 100644 --- a/packages/fhirtypes/package.json +++ b/packages/fhirtypes/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/fhirtypes", - "version": "3.0.2", + "version": "3.0.3", "description": "Medplum FHIR Type Definitions", "keywords": [ "medplum", diff --git a/packages/generator/package.json b/packages/generator/package.json index 4586a326c9..f5fa166f2f 100644 --- a/packages/generator/package.json +++ b/packages/generator/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/generator", - "version": "3.0.2", + "version": "3.0.3", "description": "Medplum Code Generator", "homepage": "https://www.medplum.com/", "repository": { diff --git a/packages/graphiql/package.json b/packages/graphiql/package.json index 15483f5f3e..5152dd84b5 100644 --- a/packages/graphiql/package.json +++ b/packages/graphiql/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/graphiql", - "version": "3.0.2", + "version": "3.0.3", "description": "Medplum GraphiQL", "homepage": "https://www.medplum.com/", "bugs": { diff --git a/packages/health-gorilla/package.json b/packages/health-gorilla/package.json index 0f0a235785..c08d9fd6a6 100644 --- a/packages/health-gorilla/package.json +++ b/packages/health-gorilla/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/health-gorilla", - "version": "3.0.2", + "version": "3.0.3", "description": "Medplum Health Gorilla SDK", "homepage": "https://www.medplum.com/", "bugs": { diff --git a/packages/hl7/package.json b/packages/hl7/package.json index 5c199c8c48..d8e4d2ac6c 100644 --- a/packages/hl7/package.json +++ b/packages/hl7/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/hl7", - "version": "3.0.2", + "version": "3.0.3", "description": "Medplum HL7 Utilities", "keywords": [ "medplum", diff --git a/packages/mock/package.json b/packages/mock/package.json index a9729dc7f9..2f7ba91fc9 100644 --- a/packages/mock/package.json +++ b/packages/mock/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/mock", - "version": "3.0.2", + "version": "3.0.3", "description": "Medplum Mock Client", "keywords": [ "medplum", diff --git a/packages/react-hooks/package.json b/packages/react-hooks/package.json index 2254047b30..0c4a01a8a3 100644 --- a/packages/react-hooks/package.json +++ b/packages/react-hooks/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/react-hooks", - "version": "3.0.2", + "version": "3.0.3", "description": "Medplum React Hooks Library", "keywords": [ "medplum", diff --git a/packages/react/package.json b/packages/react/package.json index de8f3389c4..4553fbd3b4 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/react", - "version": "3.0.2", + "version": "3.0.3", "description": "Medplum React Component Library", "keywords": [ "medplum", diff --git a/packages/server/package.json b/packages/server/package.json index b46e808f3f..bdf042e82c 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/server", - "version": "3.0.2", + "version": "3.0.3", "description": "Medplum Server", "homepage": "https://www.medplum.com/", "bugs": { diff --git a/sonar-project.properties b/sonar-project.properties index 2f4aa4b1ef..a7ebbe09b5 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,7 +1,7 @@ sonar.organization=medplum sonar.projectKey=medplum_medplum sonar.projectName=Medplum -sonar.projectVersion=3.0.2 +sonar.projectVersion=3.0.3 sonar.sources=packages sonar.sourceEncoding=UTF-8 sonar.exclusions=**/node_modules/**,\ From 6ae909459b2439b353b2c4e4f7817ce17e84d274 Mon Sep 17 00:00:00 2001 From: Matt Willer Date: Thu, 8 Feb 2024 15:43:19 -0800 Subject: [PATCH 20/81] Enhance request logging (#3915) * Enhance request logging * Update medplum.config.json --- packages/server/src/app.ts | 2 +- packages/server/src/auth/login.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/server/src/app.ts b/packages/server/src/app.ts index 99f9ea4813..b8880682bc 100644 --- a/packages/server/src/app.ts +++ b/packages/server/src/app.ts @@ -245,7 +245,7 @@ const loggingMiddleware = (req: Request, res: Response, next: NextFunction): voi duration: `${duration} ms`, ip: req.ip, method: req.method, - path: req.path, + path: req.originalUrl, profile: userProfile, projectId, receivedAt: start, diff --git a/packages/server/src/auth/login.ts b/packages/server/src/auth/login.ts index 00f17a4c13..18d328d8d5 100644 --- a/packages/server/src/auth/login.ts +++ b/packages/server/src/auth/login.ts @@ -5,6 +5,7 @@ import { body } from 'express-validator'; import { tryLogin } from '../oauth/utils'; import { makeValidationMiddleware } from '../util/validator'; import { getProjectIdByClientId, sendLoginResult } from './utils'; +import { getRequestContext } from '../context'; export const loginValidator = makeValidationMiddleware([ body('email').isEmail().withMessage('Valid email address is required'), @@ -41,5 +42,8 @@ export async function loginHandler(req: Request, res: Response): Promise { userAgent: req.get('User-Agent'), allowNoMembership: req.body.projectId === 'new', }); + + getRequestContext().logger.info('Login success', { email: req.body.email, projectId }); + await sendLoginResult(res, login); } From d49bd93bbd3cfa0fa1f3fc420917694807753844 Mon Sep 17 00:00:00 2001 From: Matt Willer Date: Fri, 9 Feb 2024 15:02:46 -0800 Subject: [PATCH 21/81] Enable linking between Projects (#3909) * Add fields to Project resource * Enable repo over multiple projects * Add test case * Add reference field target type * Restrict Project linking to Super Admins * Fix test * PR feedback * Add comment --- .../dist/fhir/r4/profiles-medplum.json | 26 +++++ packages/fhirtypes/dist/Project.d.ts | 16 +++ packages/server/src/agent/websockets.ts | 4 +- packages/server/src/fhir/accesspolicy.test.ts | 110 +++++++++++++----- packages/server/src/fhir/accesspolicy.ts | 27 +++-- packages/server/src/fhir/job.test.ts | 2 +- .../src/fhir/operations/graphql.test.ts | 2 +- packages/server/src/fhir/references.test.ts | 5 +- packages/server/src/fhir/repo.test.ts | 33 ++++-- packages/server/src/fhir/repo.ts | 56 ++++----- packages/server/src/fhir/search.test.ts | 4 +- packages/server/src/oauth/middleware.ts | 8 +- .../src/subscriptions/websockets.test.ts | 4 +- packages/server/src/workers/cron.test.ts | 4 +- .../server/src/workers/subscription.test.ts | 4 +- 15 files changed, 204 insertions(+), 101 deletions(-) diff --git a/packages/definitions/dist/fhir/r4/profiles-medplum.json b/packages/definitions/dist/fhir/r4/profiles-medplum.json index 788f35e71b..428765e600 100644 --- a/packages/definitions/dist/fhir/r4/profiles-medplum.json +++ b/packages/definitions/dist/fhir/r4/profiles-medplum.json @@ -381,6 +381,32 @@ "min" : 0, "max" : "1" } + }, + { + "id": "Project.link", + "path": "Project.link", + "definition": "Linked Projects whose contents are made available to this one", + "min": 0, + "max": "*", + "type": [{"code": "BackboneElement"}], + "base": { + "path": "Project.link", + "min": 0, + "max": "*" + } + }, + { + "id": "Project.link.project", + "path": "Project.link.project", + "definition": "A reference to the Project to be linked into this one", + "min": 1, + "max": "1", + "type": [{"code": "Reference", "targetProfile": ["Project"]}], + "base": { + "path": "Project.link.project", + "min": 1, + "max": "1" + } } ] } diff --git a/packages/fhirtypes/dist/Project.d.ts b/packages/fhirtypes/dist/Project.d.ts index f4c0cce598..dadc42983c 100644 --- a/packages/fhirtypes/dist/Project.d.ts +++ b/packages/fhirtypes/dist/Project.d.ts @@ -97,6 +97,22 @@ export interface Project { * Web application or web site that is associated with the project. */ site?: ProjectSite[]; + + /** + * Linked Projects whose contents are made available to this one + */ + link?: ProjectLink[]; +} + +/** + * Linked Projects whose contents are made available to this one + */ +export interface ProjectLink { + + /** + * A reference to the Project to be linked into this one + */ + project: Reference; } /** diff --git a/packages/server/src/agent/websockets.ts b/packages/server/src/agent/websockets.ts index 7531bf762e..482054a889 100644 --- a/packages/server/src/agent/websockets.ts +++ b/packages/server/src/agent/websockets.ts @@ -104,7 +104,7 @@ export async function handleAgentConnection(socket: ws.WebSocket, request: Incom agentId = command.agentId; const { login, project, membership } = await getLoginForAccessToken(command.accessToken); - const repo = await getRepoForLogin(login, membership, project.strictMode, true, project.checkReferencesOnWrite); + const repo = await getRepoForLogin(login, membership, project, true); const agent = await repo.readResource('Agent', agentId); // Connect to Redis @@ -149,7 +149,7 @@ export async function handleAgentConnection(socket: ws.WebSocket, request: Incom } const { login, project, membership } = await getLoginForAccessToken(command.accessToken); - const repo = await getRepoForLogin(login, membership, project.strictMode, true, project.checkReferencesOnWrite); + const repo = await getRepoForLogin(login, membership, project, true); const agent = await repo.readResource('Agent', agentId); const channel = agent?.channel?.find((c) => c.name === command.channel); if (!channel) { diff --git a/packages/server/src/fhir/accesspolicy.test.ts b/packages/server/src/fhir/accesspolicy.test.ts index 36a5aa4832..e85b69600a 100644 --- a/packages/server/src/fhir/accesspolicy.test.ts +++ b/packages/server/src/fhir/accesspolicy.test.ts @@ -34,6 +34,7 @@ import { getRepoForLogin } from './accesspolicy'; import { getSystemRepo, Repository } from './repo'; describe('AccessPolicy', () => { + let testProject: Project; const systemRepo = getSystemRepo(); beforeAll(async () => { @@ -41,6 +42,13 @@ describe('AccessPolicy', () => { await initAppServices(config); }); + beforeEach(() => { + testProject = { + resourceType: 'Project', + id: randomUUID(), + }; + }); + afterAll(async () => { await shutdownApp(); }); @@ -655,7 +663,6 @@ describe('AccessPolicy', () => { test('ClientApplication with account restriction', () => withTestContext(async () => { - const project = randomUUID(); const account = 'Organization/' + randomUUID(); // Create the access policy @@ -705,12 +712,13 @@ describe('AccessPolicy', () => { { resourceType: 'ProjectMembership', project: { - reference: 'Project/' + project, + reference: 'Project/' + testProject.id, }, profile: createReference(clientApplication as ClientApplication), accessPolicy: createReference(accessPolicy), user: createReference(clientApplication), - } + }, + testProject ); // Create a Patient using the ClientApplication @@ -780,8 +788,6 @@ describe('AccessPolicy', () => { test('ClientApplication with access policy', () => withTestContext(async () => { - const project = randomUUID(); - // Create the access policy const accessPolicy = await systemRepo.createResource({ resourceType: 'AccessPolicy', @@ -811,12 +817,13 @@ describe('AccessPolicy', () => { { resourceType: 'ProjectMembership', project: { - reference: 'Project/' + project, + reference: 'Project/' + testProject.id, }, profile: createReference(clientApplication as ClientApplication), accessPolicy: createReference(accessPolicy), user: createReference(clientApplication), - } + }, + testProject ); // Create a Patient using the ClientApplication @@ -1439,10 +1446,9 @@ describe('AccessPolicy', () => { test('Compound parameterized access policy', () => withTestContext(async () => { - const project = randomUUID(); const adminRepo = new Repository({ author: { reference: 'Practitioner/' + randomUUID() }, - project, + projects: [testProject.id as string], strictMode: true, extendedMode: true, }); @@ -1467,7 +1473,7 @@ describe('AccessPolicy', () => { const membership = await systemRepo.createResource({ resourceType: 'ProjectMembership', user: { reference: 'User/' + randomUUID() }, - project: { reference: 'Project/' + project }, + project: { reference: 'Project/' + testProject.id }, profile: { reference: 'Practitioner/' + randomUUID() }, access: [ { @@ -1481,7 +1487,7 @@ describe('AccessPolicy', () => { ], }); - const repo2 = await getRepoForLogin({ resourceType: 'Login' } as Login, membership); + const repo2 = await getRepoForLogin({ resourceType: 'Login' } as Login, membership, testProject); const check1 = await repo2.readResource('Patient', p1.id as string); expect(check1.id).toBe(p1.id); @@ -1499,10 +1505,9 @@ describe('AccessPolicy', () => { test('String parameters', () => withTestContext(async () => { - const project = randomUUID(); const adminRepo = new Repository({ author: { reference: 'Practitioner/' + randomUUID() }, - project, + projects: [testProject.id as string], strictMode: true, extendedMode: true, }); @@ -1523,7 +1528,7 @@ describe('AccessPolicy', () => { const membership = await systemRepo.createResource({ resourceType: 'ProjectMembership', user: { reference: 'User/' + randomUUID() }, - project: { reference: 'Project/' + project }, + project: { reference: 'Project/' + testProject.id }, profile: { reference: 'Practitioner/' + randomUUID() }, access: [ { @@ -1533,7 +1538,7 @@ describe('AccessPolicy', () => { ], }); - const repo2 = await getRepoForLogin({ resourceType: 'Login' } as Login, membership); + const repo2 = await getRepoForLogin({ resourceType: 'Login' } as Login, membership, testProject); const check1 = await repo2.readResource('Task', t1.id as string); expect(check1.id).toBe(t1.id); @@ -1552,7 +1557,7 @@ describe('AccessPolicy', () => { const adminRepo = new Repository({ author: { reference: 'Practitioner/' + randomUUID() }, - project: project.id, + projects: [project.id as string], strictMode: true, extendedMode: true, }); @@ -1595,7 +1600,7 @@ describe('AccessPolicy', () => { sendEmail: false, }); - const repo2 = await getRepoForLogin({ resourceType: 'Login' } as Login, membership, true, true, true); + const repo2 = await getRepoForLogin({ resourceType: 'Login' } as Login, membership, project, true); const check1 = await repo2.readResource('Patient', patient.id as string); expect(check1.id).toBe(patient.id); @@ -1635,7 +1640,7 @@ describe('AccessPolicy', () => { admin: true, }); - const repo2 = await getRepoForLogin({ resourceType: 'Login' } as Login, membership, true, true); + const repo2 = await getRepoForLogin({ resourceType: 'Login' } as Login, membership, project, true); const check1 = await repo2.readResource('Project', project.id as string); expect(check1.id).toEqual(project.id); @@ -1709,8 +1714,8 @@ describe('AccessPolicy', () => { admin: false, }); - const adminRepo = await getRepoForLogin({ resourceType: 'Login' } as Login, adminMembership, true, true); - const nonAdminRepo = await getRepoForLogin({ resourceType: 'Login' } as Login, nonAdminMembership, true, true); + const adminRepo = await getRepoForLogin({ resourceType: 'Login' } as Login, adminMembership, project, true); + const nonAdminRepo = await getRepoForLogin({ resourceType: 'Login' } as Login, nonAdminMembership, project, true); const account1 = randomUUID(); const account2 = randomUUID(); @@ -1777,7 +1782,7 @@ describe('AccessPolicy', () => { }); const repo = new Repository({ author: { reference: 'Practitioner/' + randomUUID() }, - project: project.id, + projects: [project.id as string], projectAdmin: true, strictMode: true, extendedMode: true, @@ -1843,17 +1848,17 @@ describe('AccessPolicy', () => { const project1 = await systemRepo.createResource({ resourceType: 'Project', name: 'Test1' }); const repo1 = new Repository({ author: { reference: 'Practitioner/' + randomUUID() }, - project: project1.id, + projects: [project1.id as string], projectAdmin: true, strictMode: true, extendedMode: true, checkReferencesOnWrite: true, }); - const project2 = await systemRepo.createResource({ resourceType: 'Project', name: 'Test1' }); + const project2 = await systemRepo.createResource({ resourceType: 'Project', name: 'Test2' }); const repo2 = new Repository({ author: { reference: 'Practitioner/' + randomUUID() }, - project: project2.id, + projects: [project2.id as string], projectAdmin: true, strictMode: true, extendedMode: true, @@ -1907,7 +1912,7 @@ describe('AccessPolicy', () => { }); // Get a repo for the user - const repo = await getRepoForLogin(login, updatedMembership, true, true, true); + const repo = await getRepoForLogin(login, updatedMembership, project, true); // Try to search for StructureDefinitions, should succeed const bundle1 = await repo.search({ resourceType: 'StructureDefinition' }); @@ -1939,7 +1944,7 @@ describe('AccessPolicy', () => { withTestContext(async () => { const repo = new Repository({ author: { reference: 'Practitioner/' + randomUUID() }, - project: randomUUID(), + projects: [testProject.id as string], projectAdmin: true, strictMode: true, extendedMode: true, @@ -1971,4 +1976,57 @@ describe('AccessPolicy', () => { expect(outcome.issue?.[0]?.code).toEqual('forbidden'); } })); + + test('Repo with multiple Projects', async () => + withTestContext(async () => { + const patientData: Patient = { + resourceType: 'Patient', + }; + + const project1 = await systemRepo.createResource({ resourceType: 'Project', name: 'Test1' }); + const repo1 = new Repository({ + author: { reference: 'Practitioner/' + randomUUID() }, + projects: [project1.id as string], + projectAdmin: true, + strictMode: true, + extendedMode: true, + checkReferencesOnWrite: true, + }); + + const project2 = await systemRepo.createResource({ resourceType: 'Project', name: 'Test2' }); + const repo2 = new Repository({ + author: { reference: 'Practitioner/' + randomUUID() }, + projects: [project2.id as string, project1.id as string], + projectAdmin: true, + strictMode: true, + extendedMode: true, + checkReferencesOnWrite: true, + }); + + const patient1 = await repo1.createResource(patientData); + const patient2 = await repo2.createResource(patientData); + + await expect(repo1.readResource('Patient', patient1.id as string)).resolves.toEqual(patient1); + await expect(repo1.readResource('Patient', patient2.id as string)).rejects.toBeInstanceOf(Error); + await expect(repo2.readResource('Patient', patient1.id as string)).resolves.toEqual(patient1); + await expect(repo2.readResource('Patient', patient2.id as string)).resolves.toEqual(patient2); + })); + + test('Project Admin cannot link Projects', async () => + withTestContext(async () => { + const { project, membership, login } = await registerNew({ + firstName: 'Link', + lastName: 'Test', + projectName: 'Project link test', + email: randomUUID() + '@example.com', + password: randomUUID(), + }); + expect(project.link).toBeUndefined(); + const repo = await getRepoForLogin(login, membership, project, true); + + project.link = [{ project: { reference: 'Project/foo' } }, { project: { reference: 'Project/bar' } }]; + + const updatedProject = await repo.updateResource(project); + expect(updatedProject.link).toBeUndefined(); + })); }); diff --git a/packages/server/src/fhir/accesspolicy.ts b/packages/server/src/fhir/accesspolicy.ts index 3b3fb19611..61f1ec070f 100644 --- a/packages/server/src/fhir/accesspolicy.ts +++ b/packages/server/src/fhir/accesspolicy.ts @@ -4,6 +4,7 @@ import { AccessPolicyIpAccessRule, AccessPolicyResource, Login, + Project, ProjectMembership, ProjectMembershipAccess, Reference, @@ -18,30 +19,38 @@ import { applySmartScopes } from './smart'; * This method ensures that the repository is setup correctly. * @param login - The user login. * @param membership - The active project membership. - * @param strictMode - Optional flag to enable strict mode for in-depth FHIR schema validation. + * @param project - The Project the current user is a member of. * @param extendedMode - Optional flag to enable extended mode for custom Medplum properties. - * @param checkReferencesOnWrite - Optional flag to enable reference checking on write. * @returns A repository configured for the login details. */ export async function getRepoForLogin( login: Login, membership: ProjectMembership, - strictMode?: boolean, - extendedMode?: boolean, - checkReferencesOnWrite?: boolean + project: Project, + extendedMode?: boolean ): Promise { const accessPolicy = await getAccessPolicyForLogin(login, membership); + let allowedProjects: string[] | undefined; + if (project.id) { + allowedProjects = [project.id]; + } + if (project.link && allowedProjects?.length) { + for (const link of project.link) { + allowedProjects.push(resolveId(link.project) as string); + } + } + return new Repository({ - project: resolveId(membership.project) as string, + projects: allowedProjects, author: membership.profile as Reference, remoteAddress: login.remoteAddress, superAdmin: login.superAdmin, projectAdmin: membership.admin, accessPolicy, - strictMode, + strictMode: project.strictMode, extendedMode, - checkReferencesOnWrite, + checkReferencesOnWrite: project.checkReferencesOnWrite, }); } @@ -212,7 +221,7 @@ function applyProjectAdminAccessPolicy( accessPolicy.resource.push({ resourceType: 'Project', criteria: `Project?_id=${resolveId(membership.project)}`, - readonlyFields: ['superAdmin', 'features'], + readonlyFields: ['superAdmin', 'features', 'link'], }); accessPolicy.resource.push({ diff --git a/packages/server/src/fhir/job.test.ts b/packages/server/src/fhir/job.test.ts index a73c12d3dd..7b990293c9 100644 --- a/packages/server/src/fhir/job.test.ts +++ b/packages/server/src/fhir/job.test.ts @@ -28,7 +28,7 @@ describe('Job status', () => { accessToken = testProject.accessToken; asyncJobManager = new AsyncJobExecutor( new Repository({ - project: testProject.project.id as string, + projects: [testProject.project.id as string], author: { reference: 'User/' + randomUUID() }, }) ); diff --git a/packages/server/src/fhir/operations/graphql.test.ts b/packages/server/src/fhir/operations/graphql.test.ts index 45d9d10868..95c2749039 100644 --- a/packages/server/src/fhir/operations/graphql.test.ts +++ b/packages/server/src/fhir/operations/graphql.test.ts @@ -38,7 +38,7 @@ describe('GraphQL', () => { const aliceRepo = new Repository({ author: createReference(aliceRegistration.profile), - project: aliceRegistration.project.id as string, + projects: [aliceRegistration.project.id as string], }); // Create a profile picture diff --git a/packages/server/src/fhir/references.test.ts b/packages/server/src/fhir/references.test.ts index e5a4ee6b4d..07431d4e26 100644 --- a/packages/server/src/fhir/references.test.ts +++ b/packages/server/src/fhir/references.test.ts @@ -19,15 +19,16 @@ describe('Reference checks', () => { test('Check references on write', () => withTestContext(async () => { - const { membership } = await registerNew({ + const { membership, project } = await registerNew({ firstName: randomUUID(), lastName: randomUUID(), projectName: randomUUID(), email: randomUUID() + '@example.com', password: randomUUID(), }); + project.checkReferencesOnWrite = true; - const repo = await getRepoForLogin({ resourceType: 'Login' } as Login, membership, true, true, true); + const repo = await getRepoForLogin({ resourceType: 'Login' } as Login, membership, project, true); const patient = await repo.createResource({ resourceType: 'Patient', diff --git a/packages/server/src/fhir/repo.test.ts b/packages/server/src/fhir/repo.test.ts index 2b9b0c6268..cca31d7648 100644 --- a/packages/server/src/fhir/repo.test.ts +++ b/packages/server/src/fhir/repo.test.ts @@ -6,6 +6,7 @@ import { Observation, OperationOutcome, Patient, + Project, ProjectMembership, Questionnaire, ResourceType, @@ -26,6 +27,10 @@ jest.mock('hibp'); jest.mock('ioredis'); describe('FHIR Repo', () => { + const testProject: Project = { + resourceType: 'Project', + id: randomUUID(), + }; const systemRepo = getSystemRepo(); beforeAll(async () => { @@ -39,7 +44,11 @@ describe('FHIR Repo', () => { test('getRepoForLogin', async () => { await expect(() => - getRepoForLogin({ resourceType: 'Login' } as Login, { resourceType: 'ProjectMembership' } as ProjectMembership) + getRepoForLogin( + { resourceType: 'Login' } as Login, + { resourceType: 'ProjectMembership' } as ProjectMembership, + testProject + ) ).rejects.toThrow('Invalid author reference'); }); @@ -172,7 +181,7 @@ describe('FHIR Repo', () => { const projectId = randomUUID(); const repo = new Repository({ extendedMode: true, - project: projectId, + projects: [projectId], author: { reference: clientApp, }, @@ -233,7 +242,7 @@ describe('FHIR Repo', () => { const author = 'Practitioner/' + randomUUID(); const repo = new Repository({ - project: randomUUID(), + projects: [randomUUID()], extendedMode: true, author: { reference: author, @@ -449,7 +458,7 @@ describe('FHIR Repo', () => { const result1 = await registerNew(registration1); expect(result1.profile).toBeDefined(); - const repo1 = await getRepoForLogin({ resourceType: 'Login' } as Login, result1.membership); + const repo1 = await getRepoForLogin({ resourceType: 'Login' } as Login, result1.membership, result1.project); const patient1 = await repo1.createResource({ resourceType: 'Patient', }); @@ -472,7 +481,7 @@ describe('FHIR Repo', () => { const result2 = await registerNew(registration2); expect(result2.profile).toBeDefined(); - const repo2 = await getRepoForLogin({ resourceType: 'Login' } as Login, result2.membership); + const repo2 = await getRepoForLogin({ resourceType: 'Login' } as Login, result2.membership, result2.project); try { await repo2.readResource('Patient', patient1.id as string); fail('Should have thrown'); @@ -516,7 +525,7 @@ describe('FHIR Repo', () => { test('Reindex resource type as non-admin', async () => { const repo = new Repository({ - project: randomUUID(), + projects: [randomUUID()], author: { reference: 'Practitioner/' + randomUUID(), }, @@ -532,7 +541,7 @@ describe('FHIR Repo', () => { test('Reindex resource as non-admin', async () => { const repo = new Repository({ - project: randomUUID(), + projects: [randomUUID()], author: { reference: 'Practitioner/' + randomUUID(), }, @@ -561,7 +570,7 @@ describe('FHIR Repo', () => { test('Rebuild compartments as non-admin', async () => { const repo = new Repository({ - project: randomUUID(), + projects: [randomUUID()], author: { reference: 'Practitioner/' + randomUUID(), }, @@ -724,7 +733,7 @@ describe('FHIR Repo', () => { const author = 'Practitioner/' + randomUUID(); const repo = new Repository({ - project: randomUUID(), + projects: [randomUUID()], extendedMode: true, author: { reference: author, @@ -744,7 +753,7 @@ describe('FHIR Repo', () => { const author = 'Practitioner/' + randomUUID(); const repo = new Repository({ - project: randomUUID(), + projects: [randomUUID()], extendedMode: true, author: { reference: author, @@ -764,7 +773,7 @@ describe('FHIR Repo', () => { const author = 'Practitioner/' + randomUUID(); const repo = new Repository({ - project: randomUUID(), + projects: [randomUUID()], extendedMode: true, author: { reference: author, @@ -914,7 +923,7 @@ describe('FHIR Repo', () => { const repo = new Repository({ extendedMode: true, strictMode: true, - project: projectId, + projects: [projectId], author: { reference: clientApp, }, diff --git a/packages/server/src/fhir/repo.ts b/packages/server/src/fhir/repo.ts index e33cd7ae38..feddb355a4 100644 --- a/packages/server/src/fhir/repo.ts +++ b/packages/server/src/fhir/repo.ts @@ -107,11 +107,14 @@ export interface RepositoryContext { remoteAddress?: string; /** - * The current project reference. - * This should be the ID/UUID of the current project. + * Projects that the Repository is allowed to access. + * This should include the ID/UUID of the current project, but may also include other accessory Projects. + * If this is undefined, the current user is a server user (e.g. Super Admin) + * The usual case has two elements: the user's Project and the base R4 Project + * The user's "primary" Project will be the first element in the array (i.e. projects[0]) * This value will be included in every resource as meta.project. */ - project?: string; + projects?: string[]; /** * Optional compartment restriction. @@ -187,6 +190,7 @@ export class Repository extends BaseRepository implements FhirRepository { - const projectId = this.context.project; + const projectIds = this.context.projects; - if (projectId) { - // Try retrieving from cache - const cachedProfile = await getProfileCacheEntry(projectId, url); - if (cachedProfile) { - return cachedProfile.resource; - } + if (projectIds?.length) { + // Try loading from cache, using all available Project IDs + const cacheKeys = projectIds.map((id) => getProfileCacheKey(id, url)); + const results = await getRedis().mget(...cacheKeys); + const cachedProfile = results.find(Boolean) as string | undefined; + return cachedProfile ? (JSON.parse(cachedProfile) as CacheEntry).resource : undefined; } // Fall back to loading from the DB; descending version sort approximates version resolution for some cases @@ -630,9 +634,9 @@ export class Repository extends BaseRepository implements FhirRepository | undefined> { - const cachedValue = await getRedis().get(getProfileCacheKey(projectId, url)); - return cachedValue ? (JSON.parse(cachedValue) as CacheEntry) : undefined; -} - /** * Writes a FHIR profile cache entry to Redis. * @param projectId - The project ID. diff --git a/packages/server/src/fhir/search.test.ts b/packages/server/src/fhir/search.test.ts index bf40fc433b..30227db13c 100644 --- a/packages/server/src/fhir/search.test.ts +++ b/packages/server/src/fhir/search.test.ts @@ -56,9 +56,9 @@ describe('FHIR Search', () => { beforeAll(async () => { const config = await loadTestConfig(); await initAppServices(config); - const testProject = await createTestProject(); + const { project } = await createTestProject(); repo = new Repository({ - project: testProject.project.id as string, + projects: [project.id as string], author: { reference: 'User/' + randomUUID() }, }); }); diff --git a/packages/server/src/oauth/middleware.ts b/packages/server/src/oauth/middleware.ts index 8bc374be75..e391561336 100644 --- a/packages/server/src/oauth/middleware.ts +++ b/packages/server/src/oauth/middleware.ts @@ -18,13 +18,7 @@ export function authenticateRequest(req: Request, res: Response, next: NextFunct return authenticateTokenImpl(req) .then(async ({ login, project, membership, accessToken }) => { const ctx = getRequestContext(); - const repo = await getRepoForLogin( - login, - membership, - project.strictMode, - isExtendedMode(req), - project.checkReferencesOnWrite - ); + const repo = await getRepoForLogin(login, membership, project, isExtendedMode(req)); requestContextStore.run( new AuthenticatedRequestContext(ctx, login, project, membership, repo, undefined, accessToken), () => next() diff --git a/packages/server/src/subscriptions/websockets.test.ts b/packages/server/src/subscriptions/websockets.test.ts index bb5fbdcadf..1ced8d4cfe 100644 --- a/packages/server/src/subscriptions/websockets.test.ts +++ b/packages/server/src/subscriptions/websockets.test.ts @@ -52,7 +52,7 @@ describe('WebSockets Subscriptions', () => { repo = new Repository({ extendedMode: true, - project: project.id, + projects: [project.id as string], author: { reference: 'ClientApplication/' + randomUUID(), }, @@ -260,7 +260,7 @@ describe('Subscription Heartbeat', () => { repo = new Repository({ extendedMode: true, - project: project.id, + projects: [project.id as string], author: { reference: 'ClientApplication/' + randomUUID(), }, diff --git a/packages/server/src/workers/cron.test.ts b/packages/server/src/workers/cron.test.ts index b02c6c794a..6c1cfc81d9 100644 --- a/packages/server/src/workers/cron.test.ts +++ b/packages/server/src/workers/cron.test.ts @@ -24,7 +24,7 @@ describe('Cron Worker', () => { botProject = botProjectDetails.project; botRepo = new Repository({ extendedMode: true, - project: botProjectDetails.project.id, + projects: [botProjectDetails.project.id as string], author: createReference(botProjectDetails.client), }); }); @@ -180,7 +180,7 @@ describe('Cron Worker', () => { const repo = new Repository({ extendedMode: true, - project: testProject.id, + projects: [testProject.id as string], author: { reference: 'ClientApplication/' + randomUUID(), }, diff --git a/packages/server/src/workers/subscription.test.ts b/packages/server/src/workers/subscription.test.ts index b1b1c65441..917ed3800e 100644 --- a/packages/server/src/workers/subscription.test.ts +++ b/packages/server/src/workers/subscription.test.ts @@ -59,7 +59,7 @@ describe('Subscription Worker', () => { repo = new Repository({ extendedMode: true, - project: testProject.id, + projects: [testProject.id as string], author: { reference: 'ClientApplication/' + randomUUID(), }, @@ -69,7 +69,7 @@ describe('Subscription Worker', () => { const botProjectDetails = await createTestProject(); botRepo = new Repository({ extendedMode: true, - project: botProjectDetails.project.id, + projects: [botProjectDetails.project.id as string], author: createReference(botProjectDetails.client), }); }); From 76e5a325031c25c70b8e25b6dbc0fa5aadd3a659 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Sat, 10 Feb 2024 13:19:53 -0800 Subject: [PATCH 22/81] Gravatar photos (#3921) * Gravatar photos * Up nodejs max memory --- packages/server/src/auth/utils.ts | 25 ++++++++++++++++++++++++- scripts/build.sh | 2 +- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/server/src/auth/utils.ts b/packages/server/src/auth/utils.ts index 7def7390f9..4b9a567e7a 100644 --- a/packages/server/src/auth/utils.ts +++ b/packages/server/src/auth/utils.ts @@ -6,10 +6,20 @@ import { ProfileResource, resolveId, } from '@medplum/core'; -import { ContactPoint, Login, OperationOutcome, Project, ProjectMembership, Reference, User } from '@medplum/fhirtypes'; +import { + Attachment, + ContactPoint, + Login, + OperationOutcome, + Project, + ProjectMembership, + Reference, + User, +} from '@medplum/fhirtypes'; import bcrypt from 'bcryptjs'; import { Handler, NextFunction, Request, Response } from 'express'; import fetch from 'node-fetch'; +import { createHash } from 'node:crypto'; import { getConfig } from '../config'; import { getRequestContext } from '../context'; import { sendOutcome } from '../fhir/outcomes'; @@ -27,8 +37,10 @@ export async function createProfile( const ctx = getRequestContext(); ctx.logger.info('Creating profile', { resourceType, firstName, lastName }); let telecom: ContactPoint[] | undefined = undefined; + let photo: Attachment[] | undefined = undefined; if (email) { telecom = [{ system: 'email', use: 'work', value: email }]; + photo = [{ url: getGravatar(email), contentType: 'image/png', title: 'profile.png' }]; } const systemRepo = getSystemRepo(); @@ -44,6 +56,7 @@ export async function createProfile( }, ], telecom, + photo, } as ProfileResource); ctx.logger.info('Created profile', { id: result.id }); return result; @@ -259,3 +272,13 @@ export function validateRecaptcha(projectValidation?: (p: Project) => OperationO next(); }; } + +/** + * Returns the Gravatar URL for the given email address. + * @param email - The email address. + * @returns The Gravatar URL. + */ +function getGravatar(email: string): string { + const hash = createHash('sha256').update(email.trim().toLowerCase()).digest('hex'); + return `https://gravatar.com/avatar/${hash}?s=256&r=pg&d=retro`; +} diff --git a/scripts/build.sh b/scripts/build.sh index 35693e8b2f..3505e523f5 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -9,7 +9,7 @@ set -e set -x # Set node options -export NODE_OPTIONS='--max-old-space-size=5120' +export NODE_OPTIONS='--max-old-space-size=8192' # Diagnostics node --version From 8b7f1ee8c92b35b72da6e1b6039f6ae66d5eef5d Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Sat, 10 Feb 2024 13:20:07 -0800 Subject: [PATCH 23/81] Fix attachment diff display (#3922) --- .../ResourceDiffTable.test.tsx | 25 +++++++++++++++++++ .../ResourceDiffTable/ResourceDiffTable.tsx | 9 +++++++ 2 files changed, 34 insertions(+) diff --git a/packages/react/src/ResourceDiffTable/ResourceDiffTable.test.tsx b/packages/react/src/ResourceDiffTable/ResourceDiffTable.test.tsx index 9d5f7c345c..b241270a1b 100644 --- a/packages/react/src/ResourceDiffTable/ResourceDiffTable.test.tsx +++ b/packages/react/src/ResourceDiffTable/ResourceDiffTable.test.tsx @@ -205,4 +205,29 @@ describe('ResourceDiffTable', () => { const operations = screen.getAllByText('Replace identifier'); expect(operations).toHaveLength(1); }); + + test('Change attachment URL', async () => { + const original: Patient = { + resourceType: 'Patient', + id: '123', + meta: { versionId: '456' }, + name: [{ family: 'Smith', given: ['John'] }], + photo: [{ url: 'http://example.com/foo.jpg' }], + }; + + const revised: Patient = { + ...original, + meta: { versionId: '457' }, + photo: [{ url: 'http://example.com/bar.jpg' }], + }; + + await act(async () => { + setup({ original, revised }); + }); + + await waitFor(() => screen.getByText('Replace photo[0]')); + + // There should be 2 download links + expect(screen.getAllByText('Download')).toHaveLength(2); + }); }); diff --git a/packages/react/src/ResourceDiffTable/ResourceDiffTable.tsx b/packages/react/src/ResourceDiffTable/ResourceDiffTable.tsx index c2c97a5e86..7b1f48a967 100644 --- a/packages/react/src/ResourceDiffTable/ResourceDiffTable.tsx +++ b/packages/react/src/ResourceDiffTable/ResourceDiffTable.tsx @@ -148,6 +148,15 @@ function jsonPathToFhirPath(path: string): string { result += part; } } + + // For attachments, remove the .url suffix + // Note that not all ".url" properties are attachments, but it is the common case. + // If the property is not an attachment, the diff will simply render the parent element, + // which is still fine. + if (result.endsWith('.url')) { + result = result.replace(/\.url$/, ''); + } + return result; } From 23da95745c75385a15dc380b452d36feb9c756dd Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Sat, 10 Feb 2024 13:20:18 -0800 Subject: [PATCH 24/81] Fixes #3506 - default bot runtime config setting (#3923) * Fixes #3506 - default bot runtime config setting * Up nodejs max memory --- .../docs/docs/self-hosting/config-settings.md | 81 ++++++++++--------- packages/server/medplum.config.json | 2 + packages/server/src/admin/bot.ts | 3 +- packages/server/src/config.ts | 2 + 4 files changed, 47 insertions(+), 41 deletions(-) diff --git a/packages/docs/docs/self-hosting/config-settings.md b/packages/docs/docs/self-hosting/config-settings.md index 5956b6a8c3..0b4c191756 100644 --- a/packages/docs/docs/self-hosting/config-settings.md +++ b/packages/docs/docs/self-hosting/config-settings.md @@ -109,46 +109,47 @@ For example, if your environment name is "prod", then the "baseUrl" parameter na You will also be prompted for a parameter "Type". The default option is "String". Medplum supports both "String" and "SecureString". "SecureString" is recommended for security and compliance purposes. -| Key | Description | Required | Created by | Default | -| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------- | ----------------------------------- | -| `port` | The port number that the API server binds to inside the Docker image. By default, you should use `8103`. In some cases, you may need to use `5000`. | yes | `init` | `8103` | -| `baseUrl` | The fully qualified base URL of the API server including a trailing slash. For example, `https://api.example.com/`. | yes | `init` | | -| `issuer` | The JWK issuer. By default, Medplum server uses built in OAuth, so `issuer` should be the same as `baseUrl`. | | | `baseUrl` | -| `jwksUrl` | The JWKS URL. By default, Medplum server uses built in OAuth, so `jwksUrl` should be `baseUrl` + `.well-known/jwks.json`. | | | `baseUrl` + `.well-known/jwks.json` | -| `authorizeUrl` | The OAuth authorize URL. By default, Medplum server uses built in OAuth, so `authorizeUrl` should be `baseUrl` + `oauth2/authorize`. | | | `baseUrl` + `oauth2/authorize` | -| `tokenUrl` | The OAuth token URL. By default, Medplum server uses built in OAuth, so `tokenUrl` should be `baseUrl` + `oauth2/token`. | | | `baseUrl` + `oauth2/token` | -| `userInfoUrl` | The OAuth userinfo URL. By default, Medplum server uses built in OAuth, so `userInfoUrl` should be `baseUrl` + `oauth2/userinfo`. | | | `baseUrl` + `oauth2/userinfo` | -| `appBaseUrl` | The fully qualified URL of the user-facing app. This is used for CORS and system generated emails. For example, `https://app.example.com/`. | yes | `init` | | -| `logLevel` | Verbosity of logging: `'NONE'`, `'ERROR'`, `'WARN'`, `'INFO'`, `'DEBUG'` | | | `'INFO'` | -| `binaryStorage` | Where to store binary contents. This should be the CDK config `storageBucketName` with `s3:` prefix. For example, `s3:medplum-storage`. | yes | `init` | | -| `storageBaseUrl` | The fully qualified base URL of the binary storage. This should be the CDK config `storageDomainName` with `https://` prefix. For example, `https://storage.medplum.com/binary/`. | yes | `init` | | -| `signingKeyId` | The AWS key ID of the CloudFront signing key that you created before. | yes | `cdk` | | -| `signingKey` | The private key of the CloudFront signing key. | yes | `init` | | -| `signingKeyPassphrase` | The passphrase of the CloudFront signing key. | yes | `init` | | -| `allowedOrigins` | Optional comma separated list of allowed origins for [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) requests. `appBaseUrl` is included automatically. See [Setting Up CORS](/docs/self-hosting/setting-up-cors) for more details. | | | | -| `supportEmail` | The email address to use when sending system generated messages. This email address must be registered in AWS SES. | yes | `init` | | -| `googleClientId` | If using Google Authentication, this is the Google Client ID. | | | | -| `googleClientSecret` | If using Google Authentication, this is the Google Client Secret. | | | | -| `recaptchaSiteKey` | If using reCAPTCHA, this is the reCAPTCHA site key. | | | | -| `recaptchaSecretKey` | If using reCAPTCHA, this is the reCAPTCHA secret key. | | | | -| `botLambdaRoleArn` | If using Medplum Bots, this is the ARN of the [Lambda execution role](https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html). | | `cdk` | | -| `botLambdaLayerName` | If using Medplum Bots, this is the name of the [Lambda layer](https://docs.aws.amazon.com/lambda/latest/dg/invocation-layers.html). For example, `medplum-bot-layer`. | | | `medplum-bot-layer` | -| `database` | The database connection details as a JSON object. Only available when using JSON config file. See [AWS Secrets](#aws-secrets). | | | | -| `DatabaseSecrets` | The AWS Secret ID containing database connection details (created automatically by CDK). Only available when using AWS Parameter Store config. See [AWS Secrets](#aws-secrets). | | `cdk` | | -| `redis` | The redis connection details as a JSON object. Only available when using JSON config file. See [AWS Secrets](#aws-secrets). | | | | -| `RedisSecrets` | The AWS Secret ID containing Redis connection details (created automatically by CDK). Only available when using AWS Parameter Store config. See [AWS Secrets](#aws-secrets). | | `cdk` | | -| `logRequests` | Optional flag to log individual HTTP requests. | | | `false` | -| `saveAuditEvents` | Optional flag to save `AuditEvent` resources for all auth and RESTful operations in the database. | | | `false` | -| `logAuditEvents` | Optional flag to log `AuditEvent` resources for all auth and RESTful operations to the logger. | | | `false` | -| `auditEventLogGroup` | Optional AWS CloudWatch Log Group name for `AuditEvent` logs. If not specified, `AuditEvent` logs use the default logger. | | | | -| `auditEventLogStream` | Optional AWS CloudWatch Log Stream name for `AuditEvent` logs. Only applies if `auditEventLogGroup` is set. Uses `os.hostname()` as the default. | | | `os.hostname()` | -| `registerEnabled` | Optional flag whether new user registration is enabled. | | | `true` | -| `maxJsonSize` | Maximum JSON size for API calls. String is parsed with the [bytes](https://www.npmjs.com/package/bytes) library. Default is `1mb`. | | | `1mb` | -| `smtp` | Optional SMTP email settings to use SMTP for email. See [Sending SMTP Emails](/docs/self-hosting/sendgrid) for more details. | | | | -| `awsRegion` | The AWS Region identifier. | | `cdk` | `us-east-1` | -| `otlpMetricsEndpoint` | Optional OTLP metrics endpoint for OpenTelemetry. For example, `http://localhost:4318/v1/metrics`. See [OpenTelemetry](/docs/self-hosting/opentelemetry) for more details. | | | | -| `otlpTraceEndpoint` | Optional OTLP trace endpoint for OpenTelemetry. For example, `http://localhost:4318/v1/traces`. See [OpenTelemetry](/docs/self-hosting/opentelemetry) for more details. | | | | -| `accurateCountThreshold` | Optional threshold for accurate count queries. The server will always perform an estimate count first (to protect database performance), and an accurate count if the estimate is below this threshold. | | | `1000000` | +| Key | Description | Required | Created by | Default | +| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------- | ----------------------------------- | +| `port` | The port number that the API server binds to inside the Docker image. By default, you should use `8103`. In some cases, you may need to use `5000`. | yes | `init` | `8103` | +| `baseUrl` | The fully qualified base URL of the API server including a trailing slash. For example, `https://api.example.com/`. | yes | `init` | | +| `issuer` | The JWK issuer. By default, Medplum server uses built in OAuth, so `issuer` should be the same as `baseUrl`. | | | `baseUrl` | +| `jwksUrl` | The JWKS URL. By default, Medplum server uses built in OAuth, so `jwksUrl` should be `baseUrl` + `.well-known/jwks.json`. | | | `baseUrl` + `.well-known/jwks.json` | +| `authorizeUrl` | The OAuth authorize URL. By default, Medplum server uses built in OAuth, so `authorizeUrl` should be `baseUrl` + `oauth2/authorize`. | | | `baseUrl` + `oauth2/authorize` | +| `tokenUrl` | The OAuth token URL. By default, Medplum server uses built in OAuth, so `tokenUrl` should be `baseUrl` + `oauth2/token`. | | | `baseUrl` + `oauth2/token` | +| `userInfoUrl` | The OAuth userinfo URL. By default, Medplum server uses built in OAuth, so `userInfoUrl` should be `baseUrl` + `oauth2/userinfo`. | | | `baseUrl` + `oauth2/userinfo` | +| `appBaseUrl` | The fully qualified URL of the user-facing app. This is used for CORS and system generated emails. For example, `https://app.example.com/`. | yes | `init` | | +| `logLevel` | Verbosity of logging: `'NONE'`, `'ERROR'`, `'WARN'`, `'INFO'`, `'DEBUG'` | | | `'INFO'` | +| `binaryStorage` | Where to store binary contents. This should be the CDK config `storageBucketName` with `s3:` prefix. For example, `s3:medplum-storage`. | yes | `init` | | +| `storageBaseUrl` | The fully qualified base URL of the binary storage. This should be the CDK config `storageDomainName` with `https://` prefix. For example, `https://storage.medplum.com/binary/`. | yes | `init` | | +| `signingKeyId` | The AWS key ID of the CloudFront signing key that you created before. | yes | `cdk` | | +| `signingKey` | The private key of the CloudFront signing key. | yes | `init` | | +| `signingKeyPassphrase` | The passphrase of the CloudFront signing key. | yes | `init` | | +| `allowedOrigins` | Optional comma separated list of allowed origins for [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) requests. `appBaseUrl` is included automatically. See [Setting Up CORS](/docs/self-hosting/setting-up-cors) for more details. | | | | +| `supportEmail` | The email address to use when sending system generated messages. This email address must be registered in AWS SES. | yes | `init` | | +| `googleClientId` | If using Google Authentication, this is the Google Client ID. | | | | +| `googleClientSecret` | If using Google Authentication, this is the Google Client Secret. | | | | +| `recaptchaSiteKey` | If using reCAPTCHA, this is the reCAPTCHA site key. | | | | +| `recaptchaSecretKey` | If using reCAPTCHA, this is the reCAPTCHA secret key. | | | | +| `botLambdaRoleArn` | If using Medplum Bots, this is the ARN of the [Lambda execution role](https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html). | | `cdk` | | +| `botLambdaLayerName` | If using Medplum Bots, this is the name of the [Lambda layer](https://docs.aws.amazon.com/lambda/latest/dg/invocation-layers.html). For example, `medplum-bot-layer`. | | | `medplum-bot-layer` | +| `database` | The database connection details as a JSON object. Only available when using JSON config file. See [AWS Secrets](#aws-secrets). | | | | +| `DatabaseSecrets` | The AWS Secret ID containing database connection details (created automatically by CDK). Only available when using AWS Parameter Store config. See [AWS Secrets](#aws-secrets). | | `cdk` | | +| `redis` | The redis connection details as a JSON object. Only available when using JSON config file. See [AWS Secrets](#aws-secrets). | | | | +| `RedisSecrets` | The AWS Secret ID containing Redis connection details (created automatically by CDK). Only available when using AWS Parameter Store config. See [AWS Secrets](#aws-secrets). | | `cdk` | | +| `logRequests` | Optional flag to log individual HTTP requests. | | | `false` | +| `saveAuditEvents` | Optional flag to save `AuditEvent` resources for all auth and RESTful operations in the database. | | | `false` | +| `logAuditEvents` | Optional flag to log `AuditEvent` resources for all auth and RESTful operations to the logger. | | | `false` | +| `auditEventLogGroup` | Optional AWS CloudWatch Log Group name for `AuditEvent` logs. If not specified, `AuditEvent` logs use the default logger. | | | | +| `auditEventLogStream` | Optional AWS CloudWatch Log Stream name for `AuditEvent` logs. Only applies if `auditEventLogGroup` is set. Uses `os.hostname()` as the default. | | | `os.hostname()` | +| `registerEnabled` | Optional flag whether new user registration is enabled. | | | `true` | +| `maxJsonSize` | Maximum JSON size for API calls. String is parsed with the [bytes](https://www.npmjs.com/package/bytes) library. Default is `1mb`. | | | `1mb` | +| `smtp` | Optional SMTP email settings to use SMTP for email. See [Sending SMTP Emails](/docs/self-hosting/sendgrid) for more details. | | | | +| `awsRegion` | The AWS Region identifier. | | `cdk` | `us-east-1` | +| `otlpMetricsEndpoint` | Optional OTLP metrics endpoint for OpenTelemetry. For example, `http://localhost:4318/v1/metrics`. See [OpenTelemetry](/docs/self-hosting/opentelemetry) for more details. | | | | +| `otlpTraceEndpoint` | Optional OTLP trace endpoint for OpenTelemetry. For example, `http://localhost:4318/v1/traces`. See [OpenTelemetry](/docs/self-hosting/opentelemetry) for more details. | | | | +| `accurateCountThreshold` | Optional threshold for accurate count queries. The server will always perform an estimate count first (to protect database performance), and an accurate count if the estimate is below this threshold. | | | `1000000` | +| `defaultBotRuntimeVersion` | Optional default bot runtime version. See [Bot runtime version](/docs/api/fhir/medplum/bot) for more details. | | | `awslambda` | :::tip Local Config To make changes to the server config after your first deploy, you must the edit parameter values _directly in AWS parameter store_ diff --git a/packages/server/medplum.config.json b/packages/server/medplum.config.json index 9903513fc2..2ef2451f46 100644 --- a/packages/server/medplum.config.json +++ b/packages/server/medplum.config.json @@ -22,6 +22,8 @@ "maxJsonSize": "1mb", "botLambdaRoleArn": "", "botLambdaLayerName": "medplum-bot-layer", + "vmContextBotsEnabled": true, + "defaultBotRuntimeVersion": "vmcontext", "allowedOrigins": "*", "introspectionEnabled": true, "database": { diff --git a/packages/server/src/admin/bot.ts b/packages/server/src/admin/bot.ts index 8350a7dfc9..a458dee35b 100644 --- a/packages/server/src/admin/bot.ts +++ b/packages/server/src/admin/bot.ts @@ -3,6 +3,7 @@ import { AccessPolicy, Binary, Bot, Project, ProjectMembership, Reference } from import { Request, Response } from 'express'; import { body } from 'express-validator'; import { Readable } from 'stream'; +import { getConfig } from '../config'; import { getAuthenticatedContext } from '../context'; import { Repository, getSystemRepo } from '../fhir/repo'; import { getBinaryStorage } from '../fhir/storage'; @@ -54,7 +55,7 @@ export async function createBot(repo: Repository, request: CreateBotRequest): Pr resourceType: 'Bot', name: request.name, description: request.description, - runtimeVersion: request.runtimeVersion ?? 'awslambda', + runtimeVersion: request.runtimeVersion ?? getConfig().defaultBotRuntimeVersion, sourceCode: { contentType, title: filename, diff --git a/packages/server/src/config.ts b/packages/server/src/config.ts index 6ce9c707ba..0c5f904b93 100644 --- a/packages/server/src/config.ts +++ b/packages/server/src/config.ts @@ -51,6 +51,7 @@ export interface MedplumServerConfig { heartbeatMilliseconds?: number; heartbeatEnabled?: boolean; accurateCountThreshold: number; + defaultBotRuntimeVersion: 'awslambda' | 'vmcontext'; /** @deprecated */ auditEventLogGroup?: string; @@ -296,6 +297,7 @@ function addDefaults(config: MedplumServerConfig): MedplumServerConfig { config.bullmq = { concurrency: 10, removeOnComplete: { count: 1 }, removeOnFail: { count: 1 }, ...config.bullmq }; config.shutdownTimeoutMilliseconds = config.shutdownTimeoutMilliseconds ?? 30000; config.accurateCountThreshold = config.accurateCountThreshold ?? 1000000; + config.defaultBotRuntimeVersion = config.defaultBotRuntimeVersion ?? 'awslambda'; return config; } From 3b1212c0ef4b46ac5e499957a692982996eee3d3 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Sat, 10 Feb 2024 13:20:30 -0800 Subject: [PATCH 25/81] Fixes #3904 - Added identifiers to Users and Projects (#3924) --- packages/app/src/admin/SuperAdminPage.tsx | 6 ++-- .../dist/fhir/r4/profiles-medplum.json | 32 +++++++++++++++++ .../fhir/r4/search-parameters-medplum.json | 34 +++++++++++++++++++ packages/fhirtypes/dist/Project.d.ts | 6 ++++ packages/fhirtypes/dist/User.d.ts | 6 ++++ .../SearchFilterEditor/SearchFilterEditor.tsx | 2 +- 6 files changed, 83 insertions(+), 3 deletions(-) diff --git a/packages/app/src/admin/SuperAdminPage.tsx b/packages/app/src/admin/SuperAdminPage.tsx index 19face2ecc..d8a2fd4c73 100644 --- a/packages/app/src/admin/SuperAdminPage.tsx +++ b/packages/app/src/admin/SuperAdminPage.tsx @@ -1,13 +1,13 @@ import { Button, Divider, NativeSelect, PasswordInput, Stack, TextInput, Title } from '@mantine/core'; import { notifications, showNotification } from '@mantine/notifications'; -import { forbidden, MedplumClient, normalizeErrorString } from '@medplum/core'; +import { MedplumClient, forbidden, normalizeErrorString } from '@medplum/core'; import { - convertLocalToIso, DateTimeInput, Document, Form, FormSection, OperationOutcomeAlert, + convertLocalToIso, useMedplum, } from '@medplum/react'; import { IconCheck, IconX } from '@tabler/icons-react'; @@ -187,6 +187,7 @@ function startAsyncJob(medplum: MedplumClient, title: string, url: string, body? title, message: 'Done', icon: , + loading: false, autoClose: false, withCloseButton: true, }); @@ -198,6 +199,7 @@ function startAsyncJob(medplum: MedplumClient, title: string, url: string, body? title, message: normalizeErrorString(err), icon: , + loading: false, autoClose: false, withCloseButton: true, }); diff --git a/packages/definitions/dist/fhir/r4/profiles-medplum.json b/packages/definitions/dist/fhir/r4/profiles-medplum.json index 428765e600..cb3dc1cc2d 100644 --- a/packages/definitions/dist/fhir/r4/profiles-medplum.json +++ b/packages/definitions/dist/fhir/r4/profiles-medplum.json @@ -94,6 +94,22 @@ "code" : "code" }] }, + { + "id" : "Project.identifier", + "path" : "Project.identifier", + "short" : "An identifier for this project", + "definition" : "An identifier for this project.", + "min" : 0, + "max" : "*", + "type" : [{ + "code" : "Identifier" + }], + "base" : { + "path" : "Project.identifier", + "min" : 0, + "max" : "*" + } + }, { "id" : "Project.name", "path" : "Project.name", @@ -734,6 +750,22 @@ "code" : "code" }] }, + { + "id" : "User.identifier", + "path" : "User.identifier", + "short" : "An identifier for this user", + "definition" : "An identifier for this user.", + "min" : 0, + "max" : "*", + "type" : [{ + "code" : "Identifier" + }], + "base" : { + "path" : "User.identifier", + "min" : 0, + "max" : "*" + } + }, { "id" : "User.firstName", "path" : "User.firstName", diff --git a/packages/definitions/dist/fhir/r4/search-parameters-medplum.json b/packages/definitions/dist/fhir/r4/search-parameters-medplum.json index ecabbb1037..278822f360 100644 --- a/packages/definitions/dist/fhir/r4/search-parameters-medplum.json +++ b/packages/definitions/dist/fhir/r4/search-parameters-medplum.json @@ -3,6 +3,23 @@ "id": "medplumSearchParams", "type": "collection", "entry": [ + { + "fullUrl": "https://medplum.com/fhir/SearchParameter/Project-identifier", + "resource": { + "resourceType": "SearchParameter", + "id": "Project-identifier", + "url": "https://medplum.com/fhir/SearchParameter/Project-identifier", + "version": "4.0.1", + "name": "identifier", + "status": "draft", + "publisher": "Medplum", + "description": "The identifier of the project", + "code": "identifier", + "base": ["Project"], + "type": "token", + "expression": "Project.identifier" + } + }, { "fullUrl": "https://medplum.com/fhir/SearchParameter/Project-name", "resource": { @@ -212,6 +229,23 @@ "expression": "JsonWebKey.active" } }, + { + "fullUrl": "https://medplum.com/fhir/SearchParameter/User-identifier", + "resource": { + "resourceType": "SearchParameter", + "id": "User-identifier", + "url": "https://medplum.com/fhir/SearchParameter/User-identifier", + "version": "4.0.1", + "name": "identifier", + "status": "draft", + "publisher": "Medplum", + "description": "The identifier of the user", + "code": "identifier", + "base": ["User"], + "type": "token", + "expression": "User.identifier" + } + }, { "fullUrl": "https://medplum.com/fhir/SearchParameter/User-email", "resource": { diff --git a/packages/fhirtypes/dist/Project.d.ts b/packages/fhirtypes/dist/Project.d.ts index dadc42983c..a52d82e35d 100644 --- a/packages/fhirtypes/dist/Project.d.ts +++ b/packages/fhirtypes/dist/Project.d.ts @@ -4,6 +4,7 @@ */ import { AccessPolicy } from './AccessPolicy'; +import { Identifier } from './Identifier'; import { Meta } from './Meta'; import { Reference } from './Reference'; import { User } from './User'; @@ -44,6 +45,11 @@ export interface Project { */ language?: string; + /** + * An identifier for this project. + */ + identifier?: Identifier[]; + /** * A name associated with the Project. */ diff --git a/packages/fhirtypes/dist/User.d.ts b/packages/fhirtypes/dist/User.d.ts index 0987b05198..fde151c585 100644 --- a/packages/fhirtypes/dist/User.d.ts +++ b/packages/fhirtypes/dist/User.d.ts @@ -3,6 +3,7 @@ * Do not edit manually. */ +import { Identifier } from './Identifier'; import { Meta } from './Meta'; import { Project } from './Project'; import { Reference } from './Reference'; @@ -43,6 +44,11 @@ export interface User { */ language?: string; + /** + * An identifier for this user. + */ + identifier?: Identifier[]; + /** * The first name or given name of the user. This is the value as entered * when the user is created. It is used to populate the profile resource. diff --git a/packages/react/src/SearchFilterEditor/SearchFilterEditor.tsx b/packages/react/src/SearchFilterEditor/SearchFilterEditor.tsx index 6c8885192f..7c43b857fd 100644 --- a/packages/react/src/SearchFilterEditor/SearchFilterEditor.tsx +++ b/packages/react/src/SearchFilterEditor/SearchFilterEditor.tsx @@ -129,7 +129,7 @@ function FilterRowDisplay(props: FilterRowDisplayProps): JSX.Element | null { - From 60f171f28bf152dc3563d29d1deb677295cea9b3 Mon Sep 17 00:00:00 2001 From: Medplum Bot <152649536+medplumbot@users.noreply.github.com> Date: Sat, 10 Feb 2024 17:01:37 -0800 Subject: [PATCH 26/81] Dependency upgrades (#3925) * Dependency upgrades * Remove duplicate audience option --------- Co-authored-by: Cody Ebberson --- examples/foomedical/package.json | 24 +- examples/medplum-chart-demo/package.json | 20 +- examples/medplum-demo-bots/package.json | 4 +- examples/medplum-fhircast-demo/package.json | 18 +- examples/medplum-hello-world/package.json | 20 +- examples/medplum-live-chat-demo/package.json | 20 +- examples/medplum-nextjs-demo/package.json | 14 +- .../medplum-react-native-example/package.json | 4 +- examples/medplum-task-demo/package.json | 20 +- .../package.json | 18 +- package-lock.json | 3296 +++++++++-------- package.json | 12 +- packages/app/package.json | 24 +- packages/bot-layer/package.json | 2 +- packages/cdk/package.json | 10 +- packages/cli/package.json | 20 +- packages/cli/src/util/command.ts | 1 - packages/docs/package.json | 4 +- packages/eslint-config/package.json | 6 +- packages/expo-polyfills/package.json | 4 +- packages/graphiql/package.json | 16 +- packages/mock/package.json | 2 +- packages/react-hooks/package.json | 10 +- packages/react/package.json | 36 +- packages/server/package.json | 28 +- 25 files changed, 1831 insertions(+), 1802 deletions(-) diff --git a/examples/foomedical/package.json b/examples/foomedical/package.json index 56eb480c4f..82101c210a 100644 --- a/examples/foomedical/package.json +++ b/examples/foomedical/package.json @@ -25,21 +25,21 @@ "@babel/preset-env": "7.23.9", "@babel/preset-react": "7.23.3", "@babel/preset-typescript": "7.23.3", - "@mantine/core": "7.5.1", - "@mantine/hooks": "7.5.1", - "@mantine/notifications": "7.5.1", + "@mantine/core": "7.5.2", + "@mantine/hooks": "7.5.2", + "@mantine/notifications": "7.5.2", "@medplum/core": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/mock": "3.0.3", "@medplum/react": "3.0.3", - "@tabler/icons-react": "2.46.0", - "@testing-library/jest-dom": "6.4.1", - "@testing-library/react": "14.2.0", + "@tabler/icons-react": "2.47.0", + "@testing-library/jest-dom": "6.4.2", + "@testing-library/react": "14.2.1", "@types/jest": "29.5.12", - "@types/node": "20.11.16", - "@types/react": "18.2.51", - "@types/react-dom": "18.2.18", + "@types/node": "20.11.17", + "@types/react": "18.2.55", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "babel-jest": "29.7.0", "c8": "9.1.0", @@ -48,13 +48,13 @@ "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", "jest-transform-stub": "2.0.0", - "postcss": "8.4.33", + "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-chartjs-2": "5.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.0", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.1" } } diff --git a/examples/medplum-chart-demo/package.json b/examples/medplum-chart-demo/package.json index 4fe2985a5b..8c3bea61f0 100644 --- a/examples/medplum-chart-demo/package.json +++ b/examples/medplum-chart-demo/package.json @@ -19,24 +19,24 @@ ] }, "devDependencies": { - "@mantine/core": "7.5.1", - "@mantine/hooks": "7.5.1", - "@mantine/notifications": "7.5.1", + "@mantine/core": "7.5.2", + "@mantine/hooks": "7.5.2", + "@mantine/notifications": "7.5.2", "@medplum/core": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/react": "3.0.3", - "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.16", - "@types/react": "18.2.51", - "@types/react-dom": "18.2.18", + "@tabler/icons-react": "2.47.0", + "@types/node": "20.11.17", + "@types/react": "18.2.55", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", - "postcss": "8.4.33", + "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.0", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.1" } } diff --git a/examples/medplum-demo-bots/package.json b/examples/medplum-demo-bots/package.json index 67eb73a111..7275d7f7c9 100644 --- a/examples/medplum-demo-bots/package.json +++ b/examples/medplum-demo-bots/package.json @@ -30,7 +30,7 @@ "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/mock": "3.0.3", - "@types/node": "20.11.16", + "@types/node": "20.11.17", "@types/node-fetch": "2.6.11", "@types/ssh2-sftp-client": "9.0.3", "@vitest/coverage-v8": "1.2.2", @@ -41,7 +41,7 @@ "node-fetch": "2.7.0", "pdfmake": "0.2.9", "ssh2-sftp-client": "10.0.3", - "stripe": "14.14.0", + "stripe": "14.16.0", "typescript": "5.3.3", "vitest": "1.2.2" } diff --git a/examples/medplum-fhircast-demo/package.json b/examples/medplum-fhircast-demo/package.json index f6dd9b27fc..f90de0476c 100644 --- a/examples/medplum-fhircast-demo/package.json +++ b/examples/medplum-fhircast-demo/package.json @@ -15,23 +15,23 @@ ] }, "devDependencies": { - "@mantine/core": "7.5.1", - "@mantine/hooks": "7.5.1", - "@mantine/notifications": "7.5.1", + "@mantine/core": "7.5.2", + "@mantine/hooks": "7.5.2", + "@mantine/notifications": "7.5.2", "@medplum/core": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/react": "3.0.3", - "@tabler/icons-react": "2.46.0", - "@types/react": "18.2.51", - "@types/react-dom": "18.2.18", + "@tabler/icons-react": "2.47.0", + "@types/react": "18.2.55", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", - "postcss": "8.4.33", + "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.0", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.1" } } diff --git a/examples/medplum-hello-world/package.json b/examples/medplum-hello-world/package.json index d95f7e3411..8e63e6ca03 100644 --- a/examples/medplum-hello-world/package.json +++ b/examples/medplum-hello-world/package.json @@ -19,24 +19,24 @@ ] }, "devDependencies": { - "@mantine/core": "7.5.1", - "@mantine/hooks": "7.5.1", - "@mantine/notifications": "7.5.1", + "@mantine/core": "7.5.2", + "@mantine/hooks": "7.5.2", + "@mantine/notifications": "7.5.2", "@medplum/core": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/react": "3.0.3", - "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.16", - "@types/react": "18.2.51", - "@types/react-dom": "18.2.18", + "@tabler/icons-react": "2.47.0", + "@types/node": "20.11.17", + "@types/react": "18.2.55", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", - "postcss": "8.4.33", + "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.0", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.1" } } diff --git a/examples/medplum-live-chat-demo/package.json b/examples/medplum-live-chat-demo/package.json index ab0ba22e7f..ffc0864c77 100644 --- a/examples/medplum-live-chat-demo/package.json +++ b/examples/medplum-live-chat-demo/package.json @@ -19,24 +19,24 @@ ] }, "devDependencies": { - "@mantine/core": "7.5.1", - "@mantine/hooks": "7.5.1", - "@mantine/notifications": "7.5.1", + "@mantine/core": "7.5.2", + "@mantine/hooks": "7.5.2", + "@mantine/notifications": "7.5.2", "@medplum/core": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/react": "3.0.3", - "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.16", - "@types/react": "18.2.51", - "@types/react-dom": "18.2.18", + "@tabler/icons-react": "2.47.0", + "@types/node": "20.11.17", + "@types/react": "18.2.55", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", - "postcss": "8.4.33", + "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.0", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.1" } } diff --git a/examples/medplum-nextjs-demo/package.json b/examples/medplum-nextjs-demo/package.json index 7874925b17..57ef1b3c66 100644 --- a/examples/medplum-nextjs-demo/package.json +++ b/examples/medplum-nextjs-demo/package.json @@ -10,9 +10,9 @@ "start": "next start" }, "dependencies": { - "@mantine/core": "7.5.1", - "@mantine/hooks": "7.5.1", - "@mantine/notifications": "7.5.1", + "@mantine/core": "7.5.2", + "@mantine/hooks": "7.5.2", + "@mantine/notifications": "7.5.2", "@medplum/core": "3.0.3", "@medplum/react": "3.0.3", "next": "14.1.0", @@ -22,12 +22,12 @@ }, "devDependencies": { "@medplum/fhirtypes": "3.0.3", - "@types/node": "20.11.16", - "@types/react": "18.2.51", - "@types/react-dom": "18.2.18", + "@types/node": "20.11.17", + "@types/react": "18.2.55", + "@types/react-dom": "18.2.19", "eslint": "8.56.0", "eslint-config-next": "14.1.0", - "postcss": "8.4.33", + "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "typescript": "5.3.3" } diff --git a/examples/medplum-react-native-example/package.json b/examples/medplum-react-native-example/package.json index 769722216e..f53088adea 100644 --- a/examples/medplum-react-native-example/package.json +++ b/examples/medplum-react-native-example/package.json @@ -23,11 +23,11 @@ "@medplum/core": "3.0.3", "@medplum/expo-polyfills": "3.0.3", "@medplum/react-hooks": "3.0.3", - "expo": "50.0.4", + "expo": "50.0.6", "expo-status-bar": "1.11.1", "react": "18.2.0", "react-dom": "18.2.0", - "react-native": "0.73.3", + "react-native": "0.73.4", "react-native-web": "0.19.10" }, "devDependencies": { diff --git a/examples/medplum-task-demo/package.json b/examples/medplum-task-demo/package.json index 33304bbf78..34929ab3e6 100644 --- a/examples/medplum-task-demo/package.json +++ b/examples/medplum-task-demo/package.json @@ -22,25 +22,25 @@ ] }, "devDependencies": { - "@mantine/core": "7.5.1", - "@mantine/hooks": "7.5.1", - "@mantine/notifications": "7.5.1", + "@mantine/core": "7.5.2", + "@mantine/hooks": "7.5.2", + "@mantine/notifications": "7.5.2", "@medplum/core": "3.0.3", "@medplum/definitions": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/react": "3.0.3", - "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.16", - "@types/react": "18.2.51", - "@types/react-dom": "18.2.18", + "@tabler/icons-react": "2.47.0", + "@types/node": "20.11.17", + "@types/react": "18.2.55", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", - "postcss": "8.4.33", + "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.0", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.1" } } diff --git a/examples/medplum-websocket-subscriptions-demo/package.json b/examples/medplum-websocket-subscriptions-demo/package.json index 7cca4c543b..316f998bcd 100644 --- a/examples/medplum-websocket-subscriptions-demo/package.json +++ b/examples/medplum-websocket-subscriptions-demo/package.json @@ -20,24 +20,24 @@ }, "devDependencies": { "@emotion/react": "11.11.3", - "@mantine/core": "7.5.1", - "@mantine/hooks": "7.5.1", - "@mantine/notifications": "7.5.1", + "@mantine/core": "7.5.2", + "@mantine/hooks": "7.5.2", + "@mantine/notifications": "7.5.2", "@medplum/core": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhir-router": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/mock": "3.0.3", "@medplum/react": "3.0.3", - "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.16", - "@types/react": "18.2.51", - "@types/react-dom": "18.2.18", + "@tabler/icons-react": "2.47.0", + "@types/node": "20.11.17", + "@types/react": "18.2.55", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.0", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.1" } } diff --git a/package-lock.json b/package-lock.json index db9b073641..fde88b27cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,10 +17,10 @@ "@babel/preset-react": "7.23.3", "@babel/preset-typescript": "7.23.3", "@cyclonedx/cyclonedx-npm": "1.16.1", - "@microsoft/api-documenter": "7.23.20", - "@microsoft/api-extractor": "7.39.4", + "@microsoft/api-documenter": "7.23.23", + "@microsoft/api-extractor": "7.40.1", "@types/jest": "29.5.12", - "@types/node": "20.11.16", + "@types/node": "20.11.17", "babel-jest": "29.7.0", "babel-preset-vite": "1.1.3", "cross-env": "7.0.3", @@ -29,15 +29,15 @@ "identity-obj-proxy": "3.0.0", "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", - "npm-check-updates": "16.14.14", + "npm-check-updates": "16.14.15", "nyc": "15.1.0", - "prettier": "3.2.4", + "prettier": "3.2.5", "rimraf": "5.0.5", "sort-package-json": "2.7.0", "source-map-explorer": "2.5.3", "ts-node": "10.9.2", "tslib": "2.6.2", - "turbo": "1.12.2", + "turbo": "1.12.3", "typescript": "5.3.3" }, "engines": { @@ -51,21 +51,21 @@ "@babel/preset-env": "7.23.9", "@babel/preset-react": "7.23.3", "@babel/preset-typescript": "7.23.3", - "@mantine/core": "7.5.1", - "@mantine/hooks": "7.5.1", - "@mantine/notifications": "7.5.1", + "@mantine/core": "7.5.2", + "@mantine/hooks": "7.5.2", + "@mantine/notifications": "7.5.2", "@medplum/core": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/mock": "3.0.3", "@medplum/react": "3.0.3", - "@tabler/icons-react": "2.46.0", - "@testing-library/jest-dom": "6.4.1", - "@testing-library/react": "14.2.0", + "@tabler/icons-react": "2.47.0", + "@testing-library/jest-dom": "6.4.2", + "@testing-library/react": "14.2.1", "@types/jest": "29.5.12", - "@types/node": "20.11.16", - "@types/react": "18.2.51", - "@types/react-dom": "18.2.18", + "@types/node": "20.11.17", + "@types/react": "18.2.55", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "babel-jest": "29.7.0", "c8": "9.1.0", @@ -74,20 +74,20 @@ "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", "jest-transform-stub": "2.0.0", - "postcss": "8.4.33", + "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-chartjs-2": "5.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.0", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.1" } }, "examples/foomedical/node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", + "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -100,30 +100,30 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", + "@rollup/rollup-android-arm-eabi": "4.10.0", + "@rollup/rollup-android-arm64": "4.10.0", + "@rollup/rollup-darwin-arm64": "4.10.0", + "@rollup/rollup-darwin-x64": "4.10.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", + "@rollup/rollup-linux-arm64-gnu": "4.10.0", + "@rollup/rollup-linux-arm64-musl": "4.10.0", + "@rollup/rollup-linux-riscv64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-musl": "4.10.0", + "@rollup/rollup-win32-arm64-msvc": "4.10.0", + "@rollup/rollup-win32-ia32-msvc": "4.10.0", + "@rollup/rollup-win32-x64-msvc": "4.10.0", "fsevents": "~2.3.2" } }, "examples/foomedical/node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", + "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", "dev": true, "dependencies": { "esbuild": "^0.19.3", - "postcss": "^8.4.32", + "postcss": "^8.4.35", "rollup": "^4.2.0" }, "bin": { @@ -174,31 +174,31 @@ "examples/medplum-chart-demo": { "version": "3.0.3", "devDependencies": { - "@mantine/core": "7.5.1", - "@mantine/hooks": "7.5.1", - "@mantine/notifications": "7.5.1", + "@mantine/core": "7.5.2", + "@mantine/hooks": "7.5.2", + "@mantine/notifications": "7.5.2", "@medplum/core": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/react": "3.0.3", - "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.16", - "@types/react": "18.2.51", - "@types/react-dom": "18.2.18", + "@tabler/icons-react": "2.47.0", + "@types/node": "20.11.17", + "@types/react": "18.2.55", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", - "postcss": "8.4.33", + "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.0", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.1" } }, "examples/medplum-chart-demo/node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", + "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -211,30 +211,30 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", + "@rollup/rollup-android-arm-eabi": "4.10.0", + "@rollup/rollup-android-arm64": "4.10.0", + "@rollup/rollup-darwin-arm64": "4.10.0", + "@rollup/rollup-darwin-x64": "4.10.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", + "@rollup/rollup-linux-arm64-gnu": "4.10.0", + "@rollup/rollup-linux-arm64-musl": "4.10.0", + "@rollup/rollup-linux-riscv64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-musl": "4.10.0", + "@rollup/rollup-win32-arm64-msvc": "4.10.0", + "@rollup/rollup-win32-ia32-msvc": "4.10.0", + "@rollup/rollup-win32-x64-msvc": "4.10.0", "fsevents": "~2.3.2" } }, "examples/medplum-chart-demo/node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", + "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", "dev": true, "dependencies": { "esbuild": "^0.19.3", - "postcss": "^8.4.32", + "postcss": "^8.4.35", "rollup": "^4.2.0" }, "bin": { @@ -291,7 +291,7 @@ "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/mock": "3.0.3", - "@types/node": "20.11.16", + "@types/node": "20.11.17", "@types/node-fetch": "2.6.11", "@types/ssh2-sftp-client": "9.0.3", "@vitest/coverage-v8": "1.2.2", @@ -302,7 +302,7 @@ "node-fetch": "2.7.0", "pdfmake": "0.2.9", "ssh2-sftp-client": "10.0.3", - "stripe": "14.14.0", + "stripe": "14.16.0", "typescript": "5.3.3", "vitest": "1.2.2" } @@ -310,30 +310,30 @@ "examples/medplum-fhircast-demo": { "version": "3.0.3", "devDependencies": { - "@mantine/core": "7.5.1", - "@mantine/hooks": "7.5.1", - "@mantine/notifications": "7.5.1", + "@mantine/core": "7.5.2", + "@mantine/hooks": "7.5.2", + "@mantine/notifications": "7.5.2", "@medplum/core": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/react": "3.0.3", - "@tabler/icons-react": "2.46.0", - "@types/react": "18.2.51", - "@types/react-dom": "18.2.18", + "@tabler/icons-react": "2.47.0", + "@types/react": "18.2.55", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", - "postcss": "8.4.33", + "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.0", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.1" } }, "examples/medplum-fhircast-demo/node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", + "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -346,30 +346,30 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", + "@rollup/rollup-android-arm-eabi": "4.10.0", + "@rollup/rollup-android-arm64": "4.10.0", + "@rollup/rollup-darwin-arm64": "4.10.0", + "@rollup/rollup-darwin-x64": "4.10.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", + "@rollup/rollup-linux-arm64-gnu": "4.10.0", + "@rollup/rollup-linux-arm64-musl": "4.10.0", + "@rollup/rollup-linux-riscv64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-musl": "4.10.0", + "@rollup/rollup-win32-arm64-msvc": "4.10.0", + "@rollup/rollup-win32-ia32-msvc": "4.10.0", + "@rollup/rollup-win32-x64-msvc": "4.10.0", "fsevents": "~2.3.2" } }, "examples/medplum-fhircast-demo/node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", + "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", "dev": true, "dependencies": { "esbuild": "^0.19.3", - "postcss": "^8.4.32", + "postcss": "^8.4.35", "rollup": "^4.2.0" }, "bin": { @@ -420,31 +420,31 @@ "examples/medplum-hello-world": { "version": "3.0.3", "devDependencies": { - "@mantine/core": "7.5.1", - "@mantine/hooks": "7.5.1", - "@mantine/notifications": "7.5.1", + "@mantine/core": "7.5.2", + "@mantine/hooks": "7.5.2", + "@mantine/notifications": "7.5.2", "@medplum/core": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/react": "3.0.3", - "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.16", - "@types/react": "18.2.51", - "@types/react-dom": "18.2.18", + "@tabler/icons-react": "2.47.0", + "@types/node": "20.11.17", + "@types/react": "18.2.55", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", - "postcss": "8.4.33", + "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.0", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.1" } }, "examples/medplum-hello-world/node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", + "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -457,30 +457,30 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", + "@rollup/rollup-android-arm-eabi": "4.10.0", + "@rollup/rollup-android-arm64": "4.10.0", + "@rollup/rollup-darwin-arm64": "4.10.0", + "@rollup/rollup-darwin-x64": "4.10.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", + "@rollup/rollup-linux-arm64-gnu": "4.10.0", + "@rollup/rollup-linux-arm64-musl": "4.10.0", + "@rollup/rollup-linux-riscv64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-musl": "4.10.0", + "@rollup/rollup-win32-arm64-msvc": "4.10.0", + "@rollup/rollup-win32-ia32-msvc": "4.10.0", + "@rollup/rollup-win32-x64-msvc": "4.10.0", "fsevents": "~2.3.2" } }, "examples/medplum-hello-world/node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", + "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", "dev": true, "dependencies": { "esbuild": "^0.19.3", - "postcss": "^8.4.32", + "postcss": "^8.4.35", "rollup": "^4.2.0" }, "bin": { @@ -531,31 +531,31 @@ "examples/medplum-live-chat-demo": { "version": "3.0.3", "devDependencies": { - "@mantine/core": "7.5.1", - "@mantine/hooks": "7.5.1", - "@mantine/notifications": "7.5.1", + "@mantine/core": "7.5.2", + "@mantine/hooks": "7.5.2", + "@mantine/notifications": "7.5.2", "@medplum/core": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/react": "3.0.3", - "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.16", - "@types/react": "18.2.51", - "@types/react-dom": "18.2.18", + "@tabler/icons-react": "2.47.0", + "@types/node": "20.11.17", + "@types/react": "18.2.55", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", - "postcss": "8.4.33", + "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.0", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.1" } }, "examples/medplum-live-chat-demo/node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", + "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -568,30 +568,30 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", + "@rollup/rollup-android-arm-eabi": "4.10.0", + "@rollup/rollup-android-arm64": "4.10.0", + "@rollup/rollup-darwin-arm64": "4.10.0", + "@rollup/rollup-darwin-x64": "4.10.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", + "@rollup/rollup-linux-arm64-gnu": "4.10.0", + "@rollup/rollup-linux-arm64-musl": "4.10.0", + "@rollup/rollup-linux-riscv64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-musl": "4.10.0", + "@rollup/rollup-win32-arm64-msvc": "4.10.0", + "@rollup/rollup-win32-ia32-msvc": "4.10.0", + "@rollup/rollup-win32-x64-msvc": "4.10.0", "fsevents": "~2.3.2" } }, "examples/medplum-live-chat-demo/node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", + "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", "dev": true, "dependencies": { "esbuild": "^0.19.3", - "postcss": "^8.4.32", + "postcss": "^8.4.35", "rollup": "^4.2.0" }, "bin": { @@ -642,9 +642,9 @@ "examples/medplum-nextjs-demo": { "version": "3.0.3", "dependencies": { - "@mantine/core": "7.5.1", - "@mantine/hooks": "7.5.1", - "@mantine/notifications": "7.5.1", + "@mantine/core": "7.5.2", + "@mantine/hooks": "7.5.2", + "@mantine/notifications": "7.5.2", "@medplum/core": "3.0.3", "@medplum/react": "3.0.3", "next": "14.1.0", @@ -654,12 +654,12 @@ }, "devDependencies": { "@medplum/fhirtypes": "3.0.3", - "@types/node": "20.11.16", - "@types/react": "18.2.51", - "@types/react-dom": "18.2.18", + "@types/node": "20.11.17", + "@types/react": "18.2.55", + "@types/react-dom": "18.2.19", "eslint": "8.56.0", "eslint-config-next": "14.1.0", - "postcss": "8.4.33", + "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "typescript": "5.3.3" } @@ -671,11 +671,11 @@ "@medplum/core": "3.0.3", "@medplum/expo-polyfills": "3.0.3", "@medplum/react-hooks": "3.0.3", - "expo": "50.0.4", + "expo": "50.0.6", "expo-status-bar": "1.11.1", "react": "18.2.0", "react-dom": "18.2.0", - "react-native": "0.73.3", + "react-native": "0.73.4", "react-native-web": "0.19.10" }, "devDependencies": { @@ -686,32 +686,32 @@ "examples/medplum-task-demo": { "version": "3.0.3", "devDependencies": { - "@mantine/core": "7.5.1", - "@mantine/hooks": "7.5.1", - "@mantine/notifications": "7.5.1", + "@mantine/core": "7.5.2", + "@mantine/hooks": "7.5.2", + "@mantine/notifications": "7.5.2", "@medplum/core": "3.0.3", "@medplum/definitions": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/react": "3.0.3", - "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.16", - "@types/react": "18.2.51", - "@types/react-dom": "18.2.18", + "@tabler/icons-react": "2.47.0", + "@types/node": "20.11.17", + "@types/react": "18.2.55", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", - "postcss": "8.4.33", + "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.0", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.1" } }, "examples/medplum-task-demo/node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", + "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -724,30 +724,30 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", + "@rollup/rollup-android-arm-eabi": "4.10.0", + "@rollup/rollup-android-arm64": "4.10.0", + "@rollup/rollup-darwin-arm64": "4.10.0", + "@rollup/rollup-darwin-x64": "4.10.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", + "@rollup/rollup-linux-arm64-gnu": "4.10.0", + "@rollup/rollup-linux-arm64-musl": "4.10.0", + "@rollup/rollup-linux-riscv64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-musl": "4.10.0", + "@rollup/rollup-win32-arm64-msvc": "4.10.0", + "@rollup/rollup-win32-ia32-msvc": "4.10.0", + "@rollup/rollup-win32-x64-msvc": "4.10.0", "fsevents": "~2.3.2" } }, "examples/medplum-task-demo/node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", + "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", "dev": true, "dependencies": { "esbuild": "^0.19.3", - "postcss": "^8.4.32", + "postcss": "^8.4.35", "rollup": "^4.2.0" }, "bin": { @@ -799,31 +799,31 @@ "version": "3.0.3", "devDependencies": { "@emotion/react": "11.11.3", - "@mantine/core": "7.5.1", - "@mantine/hooks": "7.5.1", - "@mantine/notifications": "7.5.1", + "@mantine/core": "7.5.2", + "@mantine/hooks": "7.5.2", + "@mantine/notifications": "7.5.2", "@medplum/core": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhir-router": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/mock": "3.0.3", "@medplum/react": "3.0.3", - "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.16", - "@types/react": "18.2.51", - "@types/react-dom": "18.2.18", + "@tabler/icons-react": "2.47.0", + "@types/node": "20.11.17", + "@types/react": "18.2.55", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.0", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.1" } }, "examples/medplum-websocket-subscriptions-demo/node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", + "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -836,30 +836,30 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", + "@rollup/rollup-android-arm-eabi": "4.10.0", + "@rollup/rollup-android-arm64": "4.10.0", + "@rollup/rollup-darwin-arm64": "4.10.0", + "@rollup/rollup-darwin-x64": "4.10.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", + "@rollup/rollup-linux-arm64-gnu": "4.10.0", + "@rollup/rollup-linux-arm64-musl": "4.10.0", + "@rollup/rollup-linux-riscv64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-musl": "4.10.0", + "@rollup/rollup-win32-arm64-msvc": "4.10.0", + "@rollup/rollup-win32-ia32-msvc": "4.10.0", + "@rollup/rollup-win32-x64-msvc": "4.10.0", "fsevents": "~2.3.2" } }, "examples/medplum-websocket-subscriptions-demo/node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", + "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", "dev": true, "dependencies": { "esbuild": "^0.19.3", - "postcss": "^8.4.32", + "postcss": "^8.4.35", "rollup": "^4.2.0" }, "bin": { @@ -1267,25 +1267,25 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@aws-sdk/client-acm": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-acm/-/client-acm-3.504.0.tgz", - "integrity": "sha512-5Yybn8RreBrPSWgQETxg/9twEx9Ho5xCmAxxmyTAuzudBlxN/D4oJtZnGAARXTpjM27NqJo5tpEfizoePzJvcw==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-acm/-/client-acm-3.511.0.tgz", + "integrity": "sha512-0PdcCshsfqUZWhIHRX7fo3JG92IrQGkGYapgRJpY3gYlFF0CteQ+gFpVv6aZ9j4eIZ0iubozFDvrRmxq6TEvcw==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.504.0", - "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.504.0", - "@aws-sdk/middleware-host-header": "3.502.0", - "@aws-sdk/middleware-logger": "3.502.0", - "@aws-sdk/middleware-recursion-detection": "3.502.0", - "@aws-sdk/middleware-signing": "3.502.0", - "@aws-sdk/middleware-user-agent": "3.502.0", - "@aws-sdk/region-config-resolver": "3.502.0", - "@aws-sdk/types": "3.502.0", - "@aws-sdk/util-endpoints": "3.502.0", - "@aws-sdk/util-user-agent-browser": "3.502.0", - "@aws-sdk/util-user-agent-node": "3.502.0", + "@aws-sdk/client-sts": "3.511.0", + "@aws-sdk/core": "3.511.0", + "@aws-sdk/credential-provider-node": "3.511.0", + "@aws-sdk/middleware-host-header": "3.511.0", + "@aws-sdk/middleware-logger": "3.511.0", + "@aws-sdk/middleware-recursion-detection": "3.511.0", + "@aws-sdk/middleware-signing": "3.511.0", + "@aws-sdk/middleware-user-agent": "3.511.0", + "@aws-sdk/region-config-resolver": "3.511.0", + "@aws-sdk/types": "3.511.0", + "@aws-sdk/util-endpoints": "3.511.0", + "@aws-sdk/util-user-agent-browser": "3.511.0", + "@aws-sdk/util-user-agent-node": "3.511.0", "@smithy/config-resolver": "^2.1.1", "@smithy/core": "^1.3.1", "@smithy/fetch-http-handler": "^2.4.1", @@ -1318,25 +1318,25 @@ } }, "node_modules/@aws-sdk/client-cloudformation": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudformation/-/client-cloudformation-3.504.0.tgz", - "integrity": "sha512-qmejnayn5p9MX5WRO/304hymAFhKeV+kmi1U90qE1tVURMKGf45QC5ndIXwsu39G65qKekhWwyKwtJfGOaTGtg==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudformation/-/client-cloudformation-3.511.0.tgz", + "integrity": "sha512-FR41v2xzMGXKM+QiV+3XktKuCR6VwOvhoRjbCwECIhrvOaUGdXi0XMVCD1aSFocwxDN5aa96piD7gexkXRF91A==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.504.0", - "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.504.0", - "@aws-sdk/middleware-host-header": "3.502.0", - "@aws-sdk/middleware-logger": "3.502.0", - "@aws-sdk/middleware-recursion-detection": "3.502.0", - "@aws-sdk/middleware-signing": "3.502.0", - "@aws-sdk/middleware-user-agent": "3.502.0", - "@aws-sdk/region-config-resolver": "3.502.0", - "@aws-sdk/types": "3.502.0", - "@aws-sdk/util-endpoints": "3.502.0", - "@aws-sdk/util-user-agent-browser": "3.502.0", - "@aws-sdk/util-user-agent-node": "3.502.0", + "@aws-sdk/client-sts": "3.511.0", + "@aws-sdk/core": "3.511.0", + "@aws-sdk/credential-provider-node": "3.511.0", + "@aws-sdk/middleware-host-header": "3.511.0", + "@aws-sdk/middleware-logger": "3.511.0", + "@aws-sdk/middleware-recursion-detection": "3.511.0", + "@aws-sdk/middleware-signing": "3.511.0", + "@aws-sdk/middleware-user-agent": "3.511.0", + "@aws-sdk/region-config-resolver": "3.511.0", + "@aws-sdk/types": "3.511.0", + "@aws-sdk/util-endpoints": "3.511.0", + "@aws-sdk/util-user-agent-browser": "3.511.0", + "@aws-sdk/util-user-agent-node": "3.511.0", "@smithy/config-resolver": "^2.1.1", "@smithy/core": "^1.3.1", "@smithy/fetch-http-handler": "^2.4.1", @@ -1371,25 +1371,25 @@ } }, "node_modules/@aws-sdk/client-cloudfront": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudfront/-/client-cloudfront-3.504.0.tgz", - "integrity": "sha512-lBwHYHeNv+zD8oRBTB3lder6fgAe3ZMpfApopC/7Futt1jCmuv81cM6ots6VJ7+J5TaNeYQpc8+wA4DqFc1bsw==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudfront/-/client-cloudfront-3.511.0.tgz", + "integrity": "sha512-5tqS6OZ+59N3tpJBxSQgg9+n8yZfcKtSVzIPHLtNmVVLYLxf/We4QYlOj0dZK+GlsfR2MDR//Qwlm9cFxoVrIA==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.504.0", - "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.504.0", - "@aws-sdk/middleware-host-header": "3.502.0", - "@aws-sdk/middleware-logger": "3.502.0", - "@aws-sdk/middleware-recursion-detection": "3.502.0", - "@aws-sdk/middleware-signing": "3.502.0", - "@aws-sdk/middleware-user-agent": "3.502.0", - "@aws-sdk/region-config-resolver": "3.502.0", - "@aws-sdk/types": "3.502.0", - "@aws-sdk/util-endpoints": "3.502.0", - "@aws-sdk/util-user-agent-browser": "3.502.0", - "@aws-sdk/util-user-agent-node": "3.502.0", + "@aws-sdk/client-sts": "3.511.0", + "@aws-sdk/core": "3.511.0", + "@aws-sdk/credential-provider-node": "3.511.0", + "@aws-sdk/middleware-host-header": "3.511.0", + "@aws-sdk/middleware-logger": "3.511.0", + "@aws-sdk/middleware-recursion-detection": "3.511.0", + "@aws-sdk/middleware-signing": "3.511.0", + "@aws-sdk/middleware-user-agent": "3.511.0", + "@aws-sdk/region-config-resolver": "3.511.0", + "@aws-sdk/types": "3.511.0", + "@aws-sdk/util-endpoints": "3.511.0", + "@aws-sdk/util-user-agent-browser": "3.511.0", + "@aws-sdk/util-user-agent-node": "3.511.0", "@aws-sdk/xml-builder": "3.496.0", "@smithy/config-resolver": "^2.1.1", "@smithy/core": "^1.3.1", @@ -1425,25 +1425,25 @@ } }, "node_modules/@aws-sdk/client-cloudwatch-logs": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.504.0.tgz", - "integrity": "sha512-pXpRHHs5DZcmX2Q/CHN641+plvGhwN57FSgRcEU9wnsheAuadqhVCJkke2StPJ50U06UOQYW5YFGsv2F3FfNCQ==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.511.0.tgz", + "integrity": "sha512-MS5EiKfmEpo/y6bKBQEJZUppYrh3k/yCj67ckC+9/xzLzeT7XSRt3eM4MqNTVKt1HHWJLufuEE655hX4bJ2D+Q==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.504.0", - "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.504.0", - "@aws-sdk/middleware-host-header": "3.502.0", - "@aws-sdk/middleware-logger": "3.502.0", - "@aws-sdk/middleware-recursion-detection": "3.502.0", - "@aws-sdk/middleware-signing": "3.502.0", - "@aws-sdk/middleware-user-agent": "3.502.0", - "@aws-sdk/region-config-resolver": "3.502.0", - "@aws-sdk/types": "3.502.0", - "@aws-sdk/util-endpoints": "3.502.0", - "@aws-sdk/util-user-agent-browser": "3.502.0", - "@aws-sdk/util-user-agent-node": "3.502.0", + "@aws-sdk/client-sts": "3.511.0", + "@aws-sdk/core": "3.511.0", + "@aws-sdk/credential-provider-node": "3.511.0", + "@aws-sdk/middleware-host-header": "3.511.0", + "@aws-sdk/middleware-logger": "3.511.0", + "@aws-sdk/middleware-recursion-detection": "3.511.0", + "@aws-sdk/middleware-signing": "3.511.0", + "@aws-sdk/middleware-user-agent": "3.511.0", + "@aws-sdk/region-config-resolver": "3.511.0", + "@aws-sdk/types": "3.511.0", + "@aws-sdk/util-endpoints": "3.511.0", + "@aws-sdk/util-user-agent-browser": "3.511.0", + "@aws-sdk/util-user-agent-node": "3.511.0", "@smithy/config-resolver": "^2.1.1", "@smithy/core": "^1.3.1", "@smithy/eventstream-serde-browser": "^2.1.1", @@ -1479,25 +1479,25 @@ } }, "node_modules/@aws-sdk/client-ecs": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ecs/-/client-ecs-3.504.0.tgz", - "integrity": "sha512-l7WQKOv2LnxOJrxrCBOg16L/bZRRwItEzUJV/qLpbe4j2dHHRksGJZU+Pw4jIzY25WIh8scp7Y5XqYG3r7tPRQ==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ecs/-/client-ecs-3.511.0.tgz", + "integrity": "sha512-23NTodvp8P5B0RzanSYhUXsSxLr1m4r1hTQfZ/WbRz04vD1F339urxE5wmS6U8GAu+HqB/KldgGaTevc2Ik33A==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.504.0", - "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.504.0", - "@aws-sdk/middleware-host-header": "3.502.0", - "@aws-sdk/middleware-logger": "3.502.0", - "@aws-sdk/middleware-recursion-detection": "3.502.0", - "@aws-sdk/middleware-signing": "3.502.0", - "@aws-sdk/middleware-user-agent": "3.502.0", - "@aws-sdk/region-config-resolver": "3.502.0", - "@aws-sdk/types": "3.502.0", - "@aws-sdk/util-endpoints": "3.502.0", - "@aws-sdk/util-user-agent-browser": "3.502.0", - "@aws-sdk/util-user-agent-node": "3.502.0", + "@aws-sdk/client-sts": "3.511.0", + "@aws-sdk/core": "3.511.0", + "@aws-sdk/credential-provider-node": "3.511.0", + "@aws-sdk/middleware-host-header": "3.511.0", + "@aws-sdk/middleware-logger": "3.511.0", + "@aws-sdk/middleware-recursion-detection": "3.511.0", + "@aws-sdk/middleware-signing": "3.511.0", + "@aws-sdk/middleware-user-agent": "3.511.0", + "@aws-sdk/region-config-resolver": "3.511.0", + "@aws-sdk/types": "3.511.0", + "@aws-sdk/util-endpoints": "3.511.0", + "@aws-sdk/util-user-agent-browser": "3.511.0", + "@aws-sdk/util-user-agent-node": "3.511.0", "@smithy/config-resolver": "^2.1.1", "@smithy/core": "^1.3.1", "@smithy/fetch-http-handler": "^2.4.1", @@ -1531,25 +1531,25 @@ } }, "node_modules/@aws-sdk/client-lambda": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.504.0.tgz", - "integrity": "sha512-YRhJ4tkNGTauLZZBUUUj63xlu3LAZTdqtRV0n2wYeI/ylT869YwOW41glJmMKy0kqG+u/p4YgVIyDjGZk5aLVQ==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.511.0.tgz", + "integrity": "sha512-zJVszZcdEOit+hxqZc3sb0MsgpYfbygjKNYsWTSj4YMjZBT+bzSqi/4dE/VJPGvvchU61jJLnqafJXc6GBPVxA==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.504.0", - "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.504.0", - "@aws-sdk/middleware-host-header": "3.502.0", - "@aws-sdk/middleware-logger": "3.502.0", - "@aws-sdk/middleware-recursion-detection": "3.502.0", - "@aws-sdk/middleware-signing": "3.502.0", - "@aws-sdk/middleware-user-agent": "3.502.0", - "@aws-sdk/region-config-resolver": "3.502.0", - "@aws-sdk/types": "3.502.0", - "@aws-sdk/util-endpoints": "3.502.0", - "@aws-sdk/util-user-agent-browser": "3.502.0", - "@aws-sdk/util-user-agent-node": "3.502.0", + "@aws-sdk/client-sts": "3.511.0", + "@aws-sdk/core": "3.511.0", + "@aws-sdk/credential-provider-node": "3.511.0", + "@aws-sdk/middleware-host-header": "3.511.0", + "@aws-sdk/middleware-logger": "3.511.0", + "@aws-sdk/middleware-recursion-detection": "3.511.0", + "@aws-sdk/middleware-signing": "3.511.0", + "@aws-sdk/middleware-user-agent": "3.511.0", + "@aws-sdk/region-config-resolver": "3.511.0", + "@aws-sdk/types": "3.511.0", + "@aws-sdk/util-endpoints": "3.511.0", + "@aws-sdk/util-user-agent-browser": "3.511.0", + "@aws-sdk/util-user-agent-node": "3.511.0", "@smithy/config-resolver": "^2.1.1", "@smithy/core": "^1.3.1", "@smithy/eventstream-serde-browser": "^2.1.1", @@ -1586,33 +1586,33 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.504.0.tgz", - "integrity": "sha512-J8xPsnk7EDwalFSaDxPFNT2+x99nG2uQTpsLXAV3bWbT1nD/JZ+fase9GqxM11v6WngzqRvTQg26ljMn5hQSKA==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.511.0.tgz", + "integrity": "sha512-IRUYev0KNKa5rQrpULE9IhJW6dhgGQWBmAJI+OyITHMu3uGvVHDqWKqnShV0IfMJWg1y37I3juFJ1KAti8jyHw==", "dependencies": { "@aws-crypto/sha1-browser": "3.0.0", "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.504.0", - "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.504.0", - "@aws-sdk/middleware-bucket-endpoint": "3.502.0", - "@aws-sdk/middleware-expect-continue": "3.502.0", - "@aws-sdk/middleware-flexible-checksums": "3.502.0", - "@aws-sdk/middleware-host-header": "3.502.0", - "@aws-sdk/middleware-location-constraint": "3.502.0", - "@aws-sdk/middleware-logger": "3.502.0", - "@aws-sdk/middleware-recursion-detection": "3.502.0", - "@aws-sdk/middleware-sdk-s3": "3.502.0", - "@aws-sdk/middleware-signing": "3.502.0", - "@aws-sdk/middleware-ssec": "3.502.0", - "@aws-sdk/middleware-user-agent": "3.502.0", - "@aws-sdk/region-config-resolver": "3.502.0", - "@aws-sdk/signature-v4-multi-region": "3.502.0", - "@aws-sdk/types": "3.502.0", - "@aws-sdk/util-endpoints": "3.502.0", - "@aws-sdk/util-user-agent-browser": "3.502.0", - "@aws-sdk/util-user-agent-node": "3.502.0", + "@aws-sdk/client-sts": "3.511.0", + "@aws-sdk/core": "3.511.0", + "@aws-sdk/credential-provider-node": "3.511.0", + "@aws-sdk/middleware-bucket-endpoint": "3.511.0", + "@aws-sdk/middleware-expect-continue": "3.511.0", + "@aws-sdk/middleware-flexible-checksums": "3.511.0", + "@aws-sdk/middleware-host-header": "3.511.0", + "@aws-sdk/middleware-location-constraint": "3.511.0", + "@aws-sdk/middleware-logger": "3.511.0", + "@aws-sdk/middleware-recursion-detection": "3.511.0", + "@aws-sdk/middleware-sdk-s3": "3.511.0", + "@aws-sdk/middleware-signing": "3.511.0", + "@aws-sdk/middleware-ssec": "3.511.0", + "@aws-sdk/middleware-user-agent": "3.511.0", + "@aws-sdk/region-config-resolver": "3.511.0", + "@aws-sdk/signature-v4-multi-region": "3.511.0", + "@aws-sdk/types": "3.511.0", + "@aws-sdk/util-endpoints": "3.511.0", + "@aws-sdk/util-user-agent-browser": "3.511.0", + "@aws-sdk/util-user-agent-node": "3.511.0", "@aws-sdk/xml-builder": "3.496.0", "@smithy/config-resolver": "^2.1.1", "@smithy/core": "^1.3.1", @@ -1654,25 +1654,25 @@ } }, "node_modules/@aws-sdk/client-secrets-manager": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.504.0.tgz", - "integrity": "sha512-JPwsYfQMjs5t74JmA4r1AjpiOG/LEw74d4a8vEdSy3pe2lhl/sSsxSdQtbI30wlJJramngtLNZjxn2+BGDphbg==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.511.0.tgz", + "integrity": "sha512-HI03pQK6EyBunogeGWAylSZU866yv5vOEr7cku8UGNUxDt5yP+xctXusdYIggl4jIRTwaqg/0IGmZDs8mXTFtg==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.504.0", - "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.504.0", - "@aws-sdk/middleware-host-header": "3.502.0", - "@aws-sdk/middleware-logger": "3.502.0", - "@aws-sdk/middleware-recursion-detection": "3.502.0", - "@aws-sdk/middleware-signing": "3.502.0", - "@aws-sdk/middleware-user-agent": "3.502.0", - "@aws-sdk/region-config-resolver": "3.502.0", - "@aws-sdk/types": "3.502.0", - "@aws-sdk/util-endpoints": "3.502.0", - "@aws-sdk/util-user-agent-browser": "3.502.0", - "@aws-sdk/util-user-agent-node": "3.502.0", + "@aws-sdk/client-sts": "3.511.0", + "@aws-sdk/core": "3.511.0", + "@aws-sdk/credential-provider-node": "3.511.0", + "@aws-sdk/middleware-host-header": "3.511.0", + "@aws-sdk/middleware-logger": "3.511.0", + "@aws-sdk/middleware-recursion-detection": "3.511.0", + "@aws-sdk/middleware-signing": "3.511.0", + "@aws-sdk/middleware-user-agent": "3.511.0", + "@aws-sdk/region-config-resolver": "3.511.0", + "@aws-sdk/types": "3.511.0", + "@aws-sdk/util-endpoints": "3.511.0", + "@aws-sdk/util-user-agent-browser": "3.511.0", + "@aws-sdk/util-user-agent-node": "3.511.0", "@smithy/config-resolver": "^2.1.1", "@smithy/core": "^1.3.1", "@smithy/fetch-http-handler": "^2.4.1", @@ -1705,25 +1705,25 @@ } }, "node_modules/@aws-sdk/client-sesv2": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.504.0.tgz", - "integrity": "sha512-vMFJAMMol5u2JueLbSVDhIz4ntBARJ8hvYDmpnJ7yZwLUqaZ1TnLPQQni/gRm8ujIyp557xraGaAVPFWDEb+XA==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.511.0.tgz", + "integrity": "sha512-unp8zqN6QLQ2ypz0f++ydC7a+rrJe+kNISI9wlGmQ32UDlrvh2nl8YBi44ZvhzmRSpN9idvy/WYvq5L1ly5FVA==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.504.0", - "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.504.0", - "@aws-sdk/middleware-host-header": "3.502.0", - "@aws-sdk/middleware-logger": "3.502.0", - "@aws-sdk/middleware-recursion-detection": "3.502.0", - "@aws-sdk/middleware-signing": "3.502.0", - "@aws-sdk/middleware-user-agent": "3.502.0", - "@aws-sdk/region-config-resolver": "3.502.0", - "@aws-sdk/types": "3.502.0", - "@aws-sdk/util-endpoints": "3.502.0", - "@aws-sdk/util-user-agent-browser": "3.502.0", - "@aws-sdk/util-user-agent-node": "3.502.0", + "@aws-sdk/client-sts": "3.511.0", + "@aws-sdk/core": "3.511.0", + "@aws-sdk/credential-provider-node": "3.511.0", + "@aws-sdk/middleware-host-header": "3.511.0", + "@aws-sdk/middleware-logger": "3.511.0", + "@aws-sdk/middleware-recursion-detection": "3.511.0", + "@aws-sdk/middleware-signing": "3.511.0", + "@aws-sdk/middleware-user-agent": "3.511.0", + "@aws-sdk/region-config-resolver": "3.511.0", + "@aws-sdk/types": "3.511.0", + "@aws-sdk/util-endpoints": "3.511.0", + "@aws-sdk/util-user-agent-browser": "3.511.0", + "@aws-sdk/util-user-agent-node": "3.511.0", "@smithy/config-resolver": "^2.1.1", "@smithy/core": "^1.3.1", "@smithy/fetch-http-handler": "^2.4.1", @@ -1755,25 +1755,25 @@ } }, "node_modules/@aws-sdk/client-ssm": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ssm/-/client-ssm-3.504.0.tgz", - "integrity": "sha512-HaqPsRds/1F8MSy0xM8iwsjSUlSoKsYsyi4DVwZ/C29t6iunAnxx+tcQ9U7pK3usMGWCcsAcJdJp2VksmWCmFQ==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ssm/-/client-ssm-3.511.0.tgz", + "integrity": "sha512-L7LUER0n/9yVMuCzDTY90p9a1n/kln1J720WZ3OQuckLbs7U2ibdY66SE0dxVriVP85Yq9ihnGr4dammB8PW5w==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.504.0", - "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.504.0", - "@aws-sdk/middleware-host-header": "3.502.0", - "@aws-sdk/middleware-logger": "3.502.0", - "@aws-sdk/middleware-recursion-detection": "3.502.0", - "@aws-sdk/middleware-signing": "3.502.0", - "@aws-sdk/middleware-user-agent": "3.502.0", - "@aws-sdk/region-config-resolver": "3.502.0", - "@aws-sdk/types": "3.502.0", - "@aws-sdk/util-endpoints": "3.502.0", - "@aws-sdk/util-user-agent-browser": "3.502.0", - "@aws-sdk/util-user-agent-node": "3.502.0", + "@aws-sdk/client-sts": "3.511.0", + "@aws-sdk/core": "3.511.0", + "@aws-sdk/credential-provider-node": "3.511.0", + "@aws-sdk/middleware-host-header": "3.511.0", + "@aws-sdk/middleware-logger": "3.511.0", + "@aws-sdk/middleware-recursion-detection": "3.511.0", + "@aws-sdk/middleware-signing": "3.511.0", + "@aws-sdk/middleware-user-agent": "3.511.0", + "@aws-sdk/region-config-resolver": "3.511.0", + "@aws-sdk/types": "3.511.0", + "@aws-sdk/util-endpoints": "3.511.0", + "@aws-sdk/util-user-agent-browser": "3.511.0", + "@aws-sdk/util-user-agent-node": "3.511.0", "@smithy/config-resolver": "^2.1.1", "@smithy/core": "^1.3.1", "@smithy/fetch-http-handler": "^2.4.1", @@ -1807,22 +1807,22 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.502.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.502.0.tgz", - "integrity": "sha512-OZAYal1+PQgUUtWiHhRayDtX0OD+XpXHKAhjYgEIPbyhQaCMp3/Bq1xDX151piWXvXqXLJHFKb8DUEqzwGO9QA==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.511.0.tgz", + "integrity": "sha512-v1f5ZbuZWpad+fgTOpgFyIZT3A37wdqoSPh0hl+cKRu5kPsz96xCe9+UvLx+HdN2yJ/mV0UZcMq6ysj4xAGIEg==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.496.0", - "@aws-sdk/middleware-host-header": "3.502.0", - "@aws-sdk/middleware-logger": "3.502.0", - "@aws-sdk/middleware-recursion-detection": "3.502.0", - "@aws-sdk/middleware-user-agent": "3.502.0", - "@aws-sdk/region-config-resolver": "3.502.0", - "@aws-sdk/types": "3.502.0", - "@aws-sdk/util-endpoints": "3.502.0", - "@aws-sdk/util-user-agent-browser": "3.502.0", - "@aws-sdk/util-user-agent-node": "3.502.0", + "@aws-sdk/core": "3.511.0", + "@aws-sdk/middleware-host-header": "3.511.0", + "@aws-sdk/middleware-logger": "3.511.0", + "@aws-sdk/middleware-recursion-detection": "3.511.0", + "@aws-sdk/middleware-user-agent": "3.511.0", + "@aws-sdk/region-config-resolver": "3.511.0", + "@aws-sdk/types": "3.511.0", + "@aws-sdk/util-endpoints": "3.511.0", + "@aws-sdk/util-user-agent-browser": "3.511.0", + "@aws-sdk/util-user-agent-node": "3.511.0", "@smithy/config-resolver": "^2.1.1", "@smithy/core": "^1.3.1", "@smithy/fetch-http-handler": "^2.4.1", @@ -1854,24 +1854,24 @@ } }, "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.504.0.tgz", - "integrity": "sha512-ODA33/nm2srhV08EW0KZAP577UgV0qjyr7Xp2yEo8MXWL4ZqQZprk1c+QKBhjr4Djesrm0VPmSD/np0mtYP68A==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.511.0.tgz", + "integrity": "sha512-cITRRq54eTrq7ll9li+yYnLbNHKXG2P+ovdZSDiQ6LjCYBdcD4ela30qbs87Yye9YsopdslDzBhHHtrf5oiuMw==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.504.0", - "@aws-sdk/core": "3.496.0", - "@aws-sdk/middleware-host-header": "3.502.0", - "@aws-sdk/middleware-logger": "3.502.0", - "@aws-sdk/middleware-recursion-detection": "3.502.0", - "@aws-sdk/middleware-signing": "3.502.0", - "@aws-sdk/middleware-user-agent": "3.502.0", - "@aws-sdk/region-config-resolver": "3.502.0", - "@aws-sdk/types": "3.502.0", - "@aws-sdk/util-endpoints": "3.502.0", - "@aws-sdk/util-user-agent-browser": "3.502.0", - "@aws-sdk/util-user-agent-node": "3.502.0", + "@aws-sdk/client-sts": "3.511.0", + "@aws-sdk/core": "3.511.0", + "@aws-sdk/middleware-host-header": "3.511.0", + "@aws-sdk/middleware-logger": "3.511.0", + "@aws-sdk/middleware-recursion-detection": "3.511.0", + "@aws-sdk/middleware-signing": "3.511.0", + "@aws-sdk/middleware-user-agent": "3.511.0", + "@aws-sdk/region-config-resolver": "3.511.0", + "@aws-sdk/types": "3.511.0", + "@aws-sdk/util-endpoints": "3.511.0", + "@aws-sdk/util-user-agent-browser": "3.511.0", + "@aws-sdk/util-user-agent-node": "3.511.0", "@smithy/config-resolver": "^2.1.1", "@smithy/core": "^1.3.1", "@smithy/fetch-http-handler": "^2.4.1", @@ -1902,26 +1902,26 @@ "node": ">=14.0.0" }, "peerDependencies": { - "@aws-sdk/credential-provider-node": "^3.504.0" + "@aws-sdk/credential-provider-node": "^3.511.0" } }, "node_modules/@aws-sdk/client-sts": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.504.0.tgz", - "integrity": "sha512-IESs8FkL7B/uY+ml4wgoRkrr6xYo4PizcNw6JX17eveq1gRBCPKeGMjE6HTDOcIYZZ8rqz/UeuH3JD4UhrMOnA==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.511.0.tgz", + "integrity": "sha512-lwVEEXK+1auEwmBuTv35m2GvbxPthi8SjNUpU4pRetZPVbGhnhCN6H7JqeMDP6GLf81Io2eySXRsmLMt7l/fjg==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.496.0", - "@aws-sdk/middleware-host-header": "3.502.0", - "@aws-sdk/middleware-logger": "3.502.0", - "@aws-sdk/middleware-recursion-detection": "3.502.0", - "@aws-sdk/middleware-user-agent": "3.502.0", - "@aws-sdk/region-config-resolver": "3.502.0", - "@aws-sdk/types": "3.502.0", - "@aws-sdk/util-endpoints": "3.502.0", - "@aws-sdk/util-user-agent-browser": "3.502.0", - "@aws-sdk/util-user-agent-node": "3.502.0", + "@aws-sdk/core": "3.511.0", + "@aws-sdk/middleware-host-header": "3.511.0", + "@aws-sdk/middleware-logger": "3.511.0", + "@aws-sdk/middleware-recursion-detection": "3.511.0", + "@aws-sdk/middleware-user-agent": "3.511.0", + "@aws-sdk/region-config-resolver": "3.511.0", + "@aws-sdk/types": "3.511.0", + "@aws-sdk/util-endpoints": "3.511.0", + "@aws-sdk/util-user-agent-browser": "3.511.0", + "@aws-sdk/util-user-agent-node": "3.511.0", "@smithy/config-resolver": "^2.1.1", "@smithy/core": "^1.3.1", "@smithy/fetch-http-handler": "^2.4.1", @@ -1954,7 +1954,7 @@ "node": ">=14.0.0" }, "peerDependencies": { - "@aws-sdk/credential-provider-node": "^3.504.0" + "@aws-sdk/credential-provider-node": "^3.511.0" } }, "node_modules/@aws-sdk/cloudfront-signer": { @@ -1970,9 +1970,9 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.496.0.tgz", - "integrity": "sha512-yT+ug7Cw/3eJi7x2es0+46x12+cIJm5Xv+GPWsrTFD1TKgqO/VPEgfDtHFagDNbFmjNQA65Ygc/kEdIX9ICX/A==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.511.0.tgz", + "integrity": "sha512-0gbDvQhToyLxPyr/7KP6uavrBYKh7exld2lju1Lp65U61XgEjTVP/thJmHTvH4BAKGSqeIz/rrwJ0KrC8nwBtw==", "dependencies": { "@smithy/core": "^1.3.1", "@smithy/protocol-http": "^3.1.1", @@ -1986,11 +1986,11 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.502.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.502.0.tgz", - "integrity": "sha512-KIB8Ae1Z7domMU/jU4KiIgK4tmYgvuXlhR54ehwlVHxnEoFPoPuGHFZU7oFn79jhhSLUFQ1lRYMxP0cEwb7XeQ==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.511.0.tgz", + "integrity": "sha512-4VUsnLRox8YzxnZwnFrfZM4bL5KKLhsjjjX7oiuLyzFkhauI4HFYt7rTB8YNGphpqAg/Wzw5DBZfO3Bw1iR1HA==", "dependencies": { - "@aws-sdk/types": "3.502.0", + "@aws-sdk/types": "3.511.0", "@smithy/property-provider": "^2.1.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -2000,11 +2000,11 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.503.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.503.1.tgz", - "integrity": "sha512-rTdlFFGoPPFMF2YjtlfRuSgKI+XsF49u7d98255hySwhsbwd3Xp+utTTPquxP+CwDxMHbDlI7NxDzFiFdsoZug==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.511.0.tgz", + "integrity": "sha512-y83Gt8GPpgMe/lMFxIq+0G2rbzLTC6lhrDocHUzqcApLD6wet8Esy2iYckSRlJgYY+qsVAzpLrSMtt85DwRPTw==", "dependencies": { - "@aws-sdk/types": "3.502.0", + "@aws-sdk/types": "3.511.0", "@smithy/fetch-http-handler": "^2.4.1", "@smithy/node-http-handler": "^2.3.1", "@smithy/property-provider": "^2.1.1", @@ -2019,16 +2019,16 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.504.0.tgz", - "integrity": "sha512-ODICLXfr8xTUd3wweprH32Ge41yuBa+u3j0JUcLdTUO1N9ldczSMdo8zOPlP0z4doqD3xbnqMkjNQWgN/Q+5oQ==", - "dependencies": { - "@aws-sdk/client-sts": "3.504.0", - "@aws-sdk/credential-provider-env": "3.502.0", - "@aws-sdk/credential-provider-process": "3.502.0", - "@aws-sdk/credential-provider-sso": "3.504.0", - "@aws-sdk/credential-provider-web-identity": "3.504.0", - "@aws-sdk/types": "3.502.0", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.511.0.tgz", + "integrity": "sha512-AgIOCtYzm61jbTQCY/2Vf/yu7DeLG0TLZa05a3VVRN9XE4ERtEnMn7TdbxM+hS24MTX8xI0HbMcWxCBkXRIg9w==", + "dependencies": { + "@aws-sdk/client-sts": "3.511.0", + "@aws-sdk/credential-provider-env": "3.511.0", + "@aws-sdk/credential-provider-process": "3.511.0", + "@aws-sdk/credential-provider-sso": "3.511.0", + "@aws-sdk/credential-provider-web-identity": "3.511.0", + "@aws-sdk/types": "3.511.0", "@smithy/credential-provider-imds": "^2.2.1", "@smithy/property-provider": "^2.1.1", "@smithy/shared-ini-file-loader": "^2.3.1", @@ -2040,17 +2040,17 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.504.0.tgz", - "integrity": "sha512-6+V5hIh+tILmUjf2ZQWQINR3atxQVgH/bFrGdSR/sHSp/tEgw3m0xWL3IRslWU1e4/GtXrfg1iYnMknXy68Ikw==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.502.0", - "@aws-sdk/credential-provider-http": "3.503.1", - "@aws-sdk/credential-provider-ini": "3.504.0", - "@aws-sdk/credential-provider-process": "3.502.0", - "@aws-sdk/credential-provider-sso": "3.504.0", - "@aws-sdk/credential-provider-web-identity": "3.504.0", - "@aws-sdk/types": "3.502.0", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.511.0.tgz", + "integrity": "sha512-5JDZXsSluliJmxOF+lYYFgJdSKQfVLQyic5NxScHULTERGoEwEHMgucFGwJ9MV9FoINjNTQLfAiWlJL/kGkCEQ==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.511.0", + "@aws-sdk/credential-provider-http": "3.511.0", + "@aws-sdk/credential-provider-ini": "3.511.0", + "@aws-sdk/credential-provider-process": "3.511.0", + "@aws-sdk/credential-provider-sso": "3.511.0", + "@aws-sdk/credential-provider-web-identity": "3.511.0", + "@aws-sdk/types": "3.511.0", "@smithy/credential-provider-imds": "^2.2.1", "@smithy/property-provider": "^2.1.1", "@smithy/shared-ini-file-loader": "^2.3.1", @@ -2062,11 +2062,11 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.502.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.502.0.tgz", - "integrity": "sha512-fJJowOjQ4infYQX0E1J3xFVlmuwEYJAFk0Mo1qwafWmEthsBJs+6BR2RiWDELHKrSK35u4Pf3fu3RkYuCtmQFw==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.511.0.tgz", + "integrity": "sha512-88hLUPqcTwjSubPS+34ZfmglnKeLny8GbmZsyllk96l26PmDTAqo5RScSA8BWxL0l5pRRWGtcrFyts+oibHIuQ==", "dependencies": { - "@aws-sdk/types": "3.502.0", + "@aws-sdk/types": "3.511.0", "@smithy/property-provider": "^2.1.1", "@smithy/shared-ini-file-loader": "^2.3.1", "@smithy/types": "^2.9.1", @@ -2077,13 +2077,13 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.504.0.tgz", - "integrity": "sha512-4MgH2or2SjPzaxM08DCW+BjaX4DSsEGJlicHKmz6fh+w9JmLh750oXcTnbvgUeVz075jcs6qTKjvUcsdGM/t8Q==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.511.0.tgz", + "integrity": "sha512-aEei9UdXYEE2e0Htf28/IcuHcWk3VkUkpcg3KDR/AyzXA3i/kxmixtAgRmHOForC5CMqoJjzVPFUITNkAscyag==", "dependencies": { - "@aws-sdk/client-sso": "3.502.0", - "@aws-sdk/token-providers": "3.504.0", - "@aws-sdk/types": "3.502.0", + "@aws-sdk/client-sso": "3.511.0", + "@aws-sdk/token-providers": "3.511.0", + "@aws-sdk/types": "3.511.0", "@smithy/property-provider": "^2.1.1", "@smithy/shared-ini-file-loader": "^2.3.1", "@smithy/types": "^2.9.1", @@ -2094,12 +2094,12 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.504.0.tgz", - "integrity": "sha512-L1ljCvGpIEFdJk087ijf2ohg7HBclOeB1UgBxUBBzf4iPRZTQzd2chGaKj0hm2VVaXz7nglswJeURH5PFcS5oA==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.511.0.tgz", + "integrity": "sha512-/3XMyN7YYefAsES/sMMY5zZGRmZ5QJisJw798DdMYmYMsb1dt0Qy8kZTu+59ZzOiVIcznsjSTCEB81QmGtDKcA==", "dependencies": { - "@aws-sdk/client-sts": "3.504.0", - "@aws-sdk/types": "3.502.0", + "@aws-sdk/client-sts": "3.511.0", + "@aws-sdk/types": "3.511.0", "@smithy/property-provider": "^2.1.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -2109,9 +2109,9 @@ } }, "node_modules/@aws-sdk/lib-storage": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.504.0.tgz", - "integrity": "sha512-A2h/yHy+2JFhqiCL1vfSlKxLRIZyyQte58O8s0yAV/TDt7ElzeXMTVtCUvhcOrnjtdHKfh4F36jeZSh1ja/9HA==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.511.0.tgz", + "integrity": "sha512-inEbSyqzGxiQs8aEnkGdxw9ZDn370mRHOdE1TB/GvVe9buQVyZ2hQvOY5WBVOaIGDIxGpuUzVvr4o89XreU19w==", "dependencies": { "@smithy/abort-controller": "^2.1.1", "@smithy/middleware-endpoint": "^2.4.1", @@ -2129,11 +2129,11 @@ } }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.502.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.502.0.tgz", - "integrity": "sha512-mUSP2DUcjhO5zM2b21CvZ9AqwI8DaAeZA6NYHOxWGTV9BUxHcdGWXEjDkcVj9CQ0gvNwTtw6B5L/q52rVAnZbw==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.511.0.tgz", + "integrity": "sha512-G4dAAHPUZbpDCVBaCcAOlFoctO9lcecSs0EZYrvzQc/9d4XJvNWGd1C7GSdf204VPOCPZCjNpTkdWGm25r00wA==", "dependencies": { - "@aws-sdk/types": "3.502.0", + "@aws-sdk/types": "3.511.0", "@aws-sdk/util-arn-parser": "3.495.0", "@smithy/node-config-provider": "^2.2.1", "@smithy/protocol-http": "^3.1.1", @@ -2146,11 +2146,11 @@ } }, "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.502.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.502.0.tgz", - "integrity": "sha512-DxfAuBVuPSt8as9xP57o8ks6ySVSjwO2NNNAdpLwk4KhEAPYEpHlf2yWYorYLrS+dDmwfYgOhRNoguuBdCu6ow==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.511.0.tgz", + "integrity": "sha512-zjDzrJV9PFCkEqhNLKKK+9PB1vPveVZLJbcY71V3PZFvPII1bhlgwvI1e99MhEiaiH2a9I2PnS56bGwEKuNTrw==", "dependencies": { - "@aws-sdk/types": "3.502.0", + "@aws-sdk/types": "3.511.0", "@smithy/protocol-http": "^3.1.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -2160,13 +2160,13 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.502.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.502.0.tgz", - "integrity": "sha512-kCt2zQDFumz/LnJJJOSd2GW4dr8oT8YMJKgxC/pph3aRXoSHXRwhrMbFnQ8swEE9vjywxtcED8sym0b0tNhhoA==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.511.0.tgz", + "integrity": "sha512-oI8zULi6VXLXJ3zA6aCdbOoceSNOxGITosB7EKDsLllzAQFV1WlzmQCtjFY8DLLYZ521atgJNcVbzjxPQnrnJA==", "dependencies": { "@aws-crypto/crc32": "3.0.0", "@aws-crypto/crc32c": "3.0.0", - "@aws-sdk/types": "3.502.0", + "@aws-sdk/types": "3.511.0", "@smithy/is-array-buffer": "^2.1.1", "@smithy/protocol-http": "^3.1.1", "@smithy/types": "^2.9.1", @@ -2178,11 +2178,11 @@ } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.502.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.502.0.tgz", - "integrity": "sha512-EjnG0GTYXT/wJBmm5/mTjDcAkzU8L7wQjOzd3FTXuTCNNyvAvwrszbOj5FlarEw5XJBbQiZtBs+I5u9+zy560w==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.511.0.tgz", + "integrity": "sha512-DbBzQP/6woSHR/+g9dHN3YiYaLIqFw9u8lQFMxi3rT3hqITFVYLzzXtEaHjDD6/is56pNT84CIKbyJ6/gY5d1Q==", "dependencies": { - "@aws-sdk/types": "3.502.0", + "@aws-sdk/types": "3.511.0", "@smithy/protocol-http": "^3.1.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -2192,11 +2192,11 @@ } }, "node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.502.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.502.0.tgz", - "integrity": "sha512-fLRwPuTZvEWQkPjys03m3D6tYN4kf7zU6+c8mJxwvEg+yfBuv2RBsbd+Vn2bTisUjXvIg1kyBzONlpHoIyFneg==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.511.0.tgz", + "integrity": "sha512-PKHnOT3oBo41NELq3Esz3K9JuV1l9E+SrCcfr/07yU4EbqhS4UGPb22Yf5JakQu4fGbTFlAftcc8PXcE2zLr4g==", "dependencies": { - "@aws-sdk/types": "3.502.0", + "@aws-sdk/types": "3.511.0", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" }, @@ -2205,11 +2205,11 @@ } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.502.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.502.0.tgz", - "integrity": "sha512-FDyv6K4nCoHxbjLGS2H8ex8I0KDIiu4FJgVRPs140ZJy6gE5Pwxzv6YTzZGLMrnqcIs9gh065Lf6DjwMelZqaw==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.511.0.tgz", + "integrity": "sha512-EYU9dBlJXvQcCsM2Tfgi0NQoXrqovfDv/fDy8oGJgZFrgNuHDti8tdVVxeJTUJNEAF67xlDl5o+rWEkKthkYGQ==", "dependencies": { - "@aws-sdk/types": "3.502.0", + "@aws-sdk/types": "3.511.0", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" }, @@ -2218,11 +2218,11 @@ } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.502.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.502.0.tgz", - "integrity": "sha512-hvbyGJbxeuezxOu8VfFmcV4ql1hKXLxHTe5FNYfEBat2KaZXVhc1Hg+4TvB06/53p+E8J99Afmumkqbxs2esUA==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.511.0.tgz", + "integrity": "sha512-PlNPCV/6zpDVdNx1K69xDTh/wPNU4WyP4qa6hUo2/+4/PNG5HI9xbCWtpb4RjhdTRw6qDtkBNcPICHbtWx5aHg==", "dependencies": { - "@aws-sdk/types": "3.502.0", + "@aws-sdk/types": "3.511.0", "@smithy/protocol-http": "^3.1.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -2232,11 +2232,11 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.502.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.502.0.tgz", - "integrity": "sha512-GbGugrfyL5bNA/zw8iQll92yXBONfWSC8Ns00DtkOU1saPXp4/7WHtyyZGYdvPa73T1IsuZy9egpoYRBmRcd5Q==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.511.0.tgz", + "integrity": "sha512-SKJr8mKaqjcGpu0xxRPXZiKrJmyetDfgzvWuZ7QOgdnPa+6jk5fmEUTFoPb3VCarMkf8xo/l6cTZ5lei7Lbflw==", "dependencies": { - "@aws-sdk/types": "3.502.0", + "@aws-sdk/types": "3.511.0", "@aws-sdk/util-arn-parser": "3.495.0", "@smithy/node-config-provider": "^2.2.1", "@smithy/protocol-http": "^3.1.1", @@ -2251,11 +2251,11 @@ } }, "node_modules/@aws-sdk/middleware-signing": { - "version": "3.502.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.502.0.tgz", - "integrity": "sha512-4hF08vSzJ7L6sB+393gOFj3s2N6nLusYS0XrMW6wYNFU10IDdbf8Z3TZ7gysDJJHEGQPmTAesPEDBsasGWcMxg==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.511.0.tgz", + "integrity": "sha512-IMijFLfm+QQHD6NNDX9k3op9dpBSlWKnqjcMU38Tytl2nbqV4gktkarOK1exHAmH7CdoYR5BufVtBzbASNSF/A==", "dependencies": { - "@aws-sdk/types": "3.502.0", + "@aws-sdk/types": "3.511.0", "@smithy/property-provider": "^2.1.1", "@smithy/protocol-http": "^3.1.1", "@smithy/signature-v4": "^2.1.1", @@ -2268,11 +2268,11 @@ } }, "node_modules/@aws-sdk/middleware-ssec": { - "version": "3.502.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.502.0.tgz", - "integrity": "sha512-1nidVTIba6/aVjjzD/WNqWdzSyTrXOHO3Ddz2MGD8S1yGSrYz4iYaq4Bm/uosfdr8B1L0Ws0pjdRXrNfzSw/DQ==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.511.0.tgz", + "integrity": "sha512-8pfgBard9pj7oWJ79R6dbXHUGr7JPP/OmAsKBYZA0r/91a1XdFUDtRYZadstjcOv/X3QbeG3QqWOtNco+XgM7Q==", "dependencies": { - "@aws-sdk/types": "3.502.0", + "@aws-sdk/types": "3.511.0", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" }, @@ -2281,12 +2281,12 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.502.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.502.0.tgz", - "integrity": "sha512-TxbBZbRiXPH0AUxegqiNd9aM9zNSbfjtBs5MEfcBsweeT/B2O7K1EjP9+CkB8Xmk/5FLKhAKLr19b1TNoE27rw==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.511.0.tgz", + "integrity": "sha512-eLs+CxP2QCXh3tCGYCdAml3oyWj8MSIwKbH+8rKw0k/5vmY1YJDBy526whOxx61ivhz2e0muuijN4X5EZZ2Pnw==", "dependencies": { - "@aws-sdk/types": "3.502.0", - "@aws-sdk/util-endpoints": "3.502.0", + "@aws-sdk/types": "3.511.0", + "@aws-sdk/util-endpoints": "3.511.0", "@smithy/protocol-http": "^3.1.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -2296,11 +2296,11 @@ } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.502.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.502.0.tgz", - "integrity": "sha512-mxmsX2AGgnSM+Sah7mcQCIneOsJQNiLX0COwEttuf8eO+6cLMAZvVudH3BnWTfea4/A9nuri9DLCqBvEmPrilg==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.511.0.tgz", + "integrity": "sha512-RzBLSNaRd4iEkQyEGfiSNvSnWU/x23rsiFgA9tqYFA0Vqx7YmzSWC8QBUxpwybB8HkbbL9wNVKQqTbhI3mYneQ==", "dependencies": { - "@aws-sdk/types": "3.502.0", + "@aws-sdk/types": "3.511.0", "@smithy/node-config-provider": "^2.2.1", "@smithy/types": "^2.9.1", "@smithy/util-config-provider": "^2.2.1", @@ -2312,12 +2312,12 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.502.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.502.0.tgz", - "integrity": "sha512-NpOXtUXH0ZAgnyI3Y3s2fPrgwbsWoNMwdoXdFZvH0eDzzX80tim7Yuy6dzVA5zrxSzOYs1xjcOhM+4CmM0QZiw==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.511.0.tgz", + "integrity": "sha512-lwbU3LX5TpYu1DHBMH2Wz+2MWGccn5G3psu1Y9WTPc+1bubVQHWf8UD2lzON5L2QirT9tQheQjTke1u5JC7FTQ==", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.502.0", - "@aws-sdk/types": "3.502.0", + "@aws-sdk/middleware-sdk-s3": "3.511.0", + "@aws-sdk/types": "3.511.0", "@smithy/protocol-http": "^3.1.1", "@smithy/signature-v4": "^2.1.1", "@smithy/types": "^2.9.1", @@ -2328,12 +2328,12 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.504.0.tgz", - "integrity": "sha512-YIJWWsZi2ClUiILS1uh5L6VjmCUSTI6KKMuL9DkGjYqJ0aI6M8bd8fT9Wm7QmXCyjcArTgr/Atkhia4T7oKvzQ==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.511.0.tgz", + "integrity": "sha512-92dXjMHBJcRoUkJHc0Bvtsz7Sal8t6VASRJ5vfs5c2ZpTVgLpVnM4dBmwUgGUdnvHov0cZTXbbadTJ/qOWx5Zw==", "dependencies": { - "@aws-sdk/client-sso-oidc": "3.504.0", - "@aws-sdk/types": "3.502.0", + "@aws-sdk/client-sso-oidc": "3.511.0", + "@aws-sdk/types": "3.511.0", "@smithy/property-provider": "^2.1.1", "@smithy/shared-ini-file-loader": "^2.3.1", "@smithy/types": "^2.9.1", @@ -2344,9 +2344,9 @@ } }, "node_modules/@aws-sdk/types": { - "version": "3.502.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.502.0.tgz", - "integrity": "sha512-M0DSPYe/gXhwD2QHgoukaZv5oDxhW3FfvYIrJptyqUq3OnPJBcDbihHjrE0PBtfh/9kgMZT60/fQ2NVFANfa2g==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.511.0.tgz", + "integrity": "sha512-P03ufufxmkvd7nO46oOeEqYIMPJ8qMCKxAsfJk1JBVPQ1XctVntbail4/UFnrnzij8DTl4Mk/D62uGo7+RolXA==", "dependencies": { "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -2367,11 +2367,11 @@ } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.502.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.502.0.tgz", - "integrity": "sha512-6LKFlJPp2J24r1Kpfoz5ESQn+1v5fEjDB3mtUKRdpwarhm3syu7HbKlHCF3KbcCOyahobvLvhoedT78rJFEeeg==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.511.0.tgz", + "integrity": "sha512-J/5hsscJkg2pAOdLx1YKlyMCk5lFRxRxEtup9xipzOxVBlqOIE72Tuu31fbxSlF8XzO/AuCJcZL4m1v098K9oA==", "dependencies": { - "@aws-sdk/types": "3.502.0", + "@aws-sdk/types": "3.511.0", "@smithy/types": "^2.9.1", "@smithy/util-endpoints": "^1.1.1", "tslib": "^2.5.0" @@ -2392,22 +2392,22 @@ } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.502.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.502.0.tgz", - "integrity": "sha512-v8gKyCs2obXoIkLETAeEQ3AM+QmhHhst9xbM1cJtKUGsRlVIak/XyyD+kVE6kmMm1cjfudHpHKABWk9apQcIZQ==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.511.0.tgz", + "integrity": "sha512-5LuESdwtIcA10aHcX7pde7aCIijcyTPBXFuXmFlDTgm/naAayQxelQDpvgbzuzGLgePf8eTyyhDKhzwPZ2EqiQ==", "dependencies": { - "@aws-sdk/types": "3.502.0", + "@aws-sdk/types": "3.511.0", "@smithy/types": "^2.9.1", "bowser": "^2.11.0", "tslib": "^2.5.0" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.502.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.502.0.tgz", - "integrity": "sha512-9RjxpkGZKbTdl96tIJvAo+vZoz4P/cQh36SBUt9xfRfW0BtsaLyvSrvlR5wyUYhvRcC12Axqh/8JtnAPq//+Vw==", + "version": "3.511.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.511.0.tgz", + "integrity": "sha512-UopdlRvYY5mxlS4wwFv+QAWL6/T302wmoQj7i+RY+c/D3Ej3PKBb/mW3r2wEOgZLJmPpeeM1SYMk+rVmsW1rqw==", "dependencies": { - "@aws-sdk/types": "3.502.0", + "@aws-sdk/types": "3.511.0", "@smithy/node-config-provider": "^2.2.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -4596,9 +4596,9 @@ "peer": true }, "node_modules/@codemirror/view": { - "version": "6.23.1", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.23.1.tgz", - "integrity": "sha512-J2Xnn5lFYT1ZN/5ewEoMBCmLlL71lZ3mBdb7cUEuHhX2ESoSrNEucpsDXpX22EuTGm9LOgC9v4Z0wx+Ez8QmGA==", + "version": "6.24.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.24.0.tgz", + "integrity": "sha512-zK6m5pNkdhdJl8idPP1gA4N8JKTiSsOz8U/Iw+C1ChMwyLG7+MLiNXnH/wFuAk6KeGEe33/adOiAh5jMqee03w==", "dev": true, "peer": true, "dependencies": { @@ -5285,9 +5285,9 @@ } }, "node_modules/@docusaurus/core/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -6874,9 +6874,9 @@ "dev": true }, "node_modules/@es-joy/jsdoccomment": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.41.0.tgz", - "integrity": "sha512-aKUhyn1QI5Ksbqcr3fFJj16p99QdjUxXAEuFst1Z47DRyoiMwivIH9MV/ARcJOCXVjPfjITciej8ZD2O/6qUmw==", + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.42.0.tgz", + "integrity": "sha512-R1w57YlVA6+YE01wch3GPYn6bCsrOV3YW/5oGGE2tmX6JcL9Nr+b5IikrjMPF+v9CV3ay+obImEdsDhovhJrzw==", "dev": true, "dependencies": { "comment-parser": "1.4.1", @@ -7416,9 +7416,9 @@ } }, "node_modules/@expo/cli": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-0.17.3.tgz", - "integrity": "sha512-lIK8igsEQxTh4WuDlcEhE0wAJcDrAyjWDF00phdmwuSCpE5SaEXNlddOXvGxEVKPhUxHZUFo9NbfoQC+JVmkfA==", + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-0.17.5.tgz", + "integrity": "sha512-9cMquL/5bBfV73CbZcWipk3KZSo8mBqdgvkoWCtEtnnlm/879ayxzMWjVIgT5yV4w+X7+N6KkBSUIIj4t9Xqew==", "dependencies": { "@babel/runtime": "^7.20.0", "@expo/code-signing-certificates": "0.0.5", @@ -7486,6 +7486,7 @@ "send": "^0.18.0", "slugify": "^1.3.4", "source-map-support": "~0.5.21", + "stacktrace-parser": "^0.1.10", "structured-headers": "^0.4.1", "tar": "^6.0.5", "temp-dir": "^2.0.0", @@ -7993,9 +7994,9 @@ } }, "node_modules/@expo/cli/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -8317,9 +8318,9 @@ } }, "node_modules/@expo/config-plugins/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -8902,9 +8903,9 @@ } }, "node_modules/@expo/metro-config": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-0.17.3.tgz", - "integrity": "sha512-YW8ixbaz6yL7/Mg1rJJejiAAVQQKjGY1wXvT2Dh487r/r9/j1yE1YRS/oRY1yItYzbnHvO0p0jMnEGfiFYL3Tg==", + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-0.17.4.tgz", + "integrity": "sha512-PxqDMuVjXQeboa6Aj8kNj4iTxIpwpfoYlF803qOjf1LE1ePlREnWNwqy65ESCBnCmekYIO5hhm1Nksa+xCvuyg==", "dependencies": { "@babel/core": "^7.20.0", "@babel/generator": "^7.20.5", @@ -10227,9 +10228,9 @@ } }, "node_modules/@graphiql/react": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@graphiql/react/-/react-0.20.2.tgz", - "integrity": "sha512-/crAUlM+4iVHyNHVdiZjsTEqfMXBHfjEvrMwCwTVig6YXmCAVuaxqkD7NlDtrrPQArLGkABmf1Nw7ObRpby5lg==", + "version": "0.20.3", + "resolved": "https://registry.npmjs.org/@graphiql/react/-/react-0.20.3.tgz", + "integrity": "sha512-LHEiWQPABflTyRJZBZB50WSlrWER4RtlWg9XV1+D4yZQ3+6GbLM7X1zYf4D/TQ6AJB/vLZQHEnbhS0LuKcNqfA==", "dev": true, "dependencies": { "@graphiql/toolkit": "^0.9.1", @@ -10291,9 +10292,9 @@ } }, "node_modules/@grpc/grpc-js": { - "version": "1.9.14", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.14.tgz", - "integrity": "sha512-nOpuzZ2G3IuMFN+UPPpKrC6NsLmWsTqSsm66IRfnBt1D4pwTqE27lmbpcPM+l2Ua4gE7PfjRHI6uedAy7hoXUw==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.0.tgz", + "integrity": "sha512-tx+eoEsqkMkLCHR4OOplwNIaJ7SVZWzeVKzEMBz8VR+TbssgBYOP4a0P+KQiQ6LaTG4SGaIEu7YTS8xOmkOWLA==", "dependencies": { "@grpc/proto-loader": "^0.7.8", "@types/node": ">=12.12.47" @@ -11018,9 +11019,9 @@ } }, "node_modules/@jest/reporters/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -11468,9 +11469,9 @@ } }, "node_modules/@mantine/core": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.5.1.tgz", - "integrity": "sha512-V7apuQuRubqxTRXb1uxOM43K7tkLRzpbb1ONJ/sj8QRp/26bShkdYp7EVuSKyrQ8DQ5EGYyBBGyzBOQARh41gA==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.5.2.tgz", + "integrity": "sha512-e58qTiLEp9qLxQ5JZlPNykJWBR+oi0q2J8JPKTjTJD+4UM58uh9oL4wuMEdUyBgiYGvDc/cVYF+rftMpuCt+KQ==", "dependencies": { "@floating-ui/react": "^0.24.8", "clsx": "2.0.0", @@ -11480,53 +11481,53 @@ "type-fest": "^3.13.1" }, "peerDependencies": { - "@mantine/hooks": "7.5.1", + "@mantine/hooks": "7.5.2", "react": "^18.2.0", "react-dom": "^18.2.0" } }, "node_modules/@mantine/dropzone": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/@mantine/dropzone/-/dropzone-7.5.1.tgz", - "integrity": "sha512-pUQt7EwhDkTWbj4OLCbsvbkbp1vCXS6nGm8Y4pTrca1vR9S4+hilR6oULEY0UbQgHq+hfsWKDtnlhLkf45tJXA==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/@mantine/dropzone/-/dropzone-7.5.2.tgz", + "integrity": "sha512-YjzaBEppLhb5F+lKpBSVeIGneCdisC+0nSertcjjo7QXPc++NR+WbVuTiPMYZoUuRbpJ2GRRTFZndmK1FvO+7w==", "dev": true, "dependencies": { "react-dropzone-esm": "15.0.1" }, "peerDependencies": { - "@mantine/core": "7.5.1", - "@mantine/hooks": "7.5.1", + "@mantine/core": "7.5.2", + "@mantine/hooks": "7.5.2", "react": "^18.2.0", "react-dom": "^18.2.0" } }, "node_modules/@mantine/hooks": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.5.1.tgz", - "integrity": "sha512-LfrEOkX8U2KbkYAU5BMA7FPbMva/TSd65c45W35wHSx3iqYMsoPN9+Ll1zc/HT0XNFp73jGet9cU7VREbAl0/A==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.5.2.tgz", + "integrity": "sha512-4POujH5Xx84VB/xfM6EttDq725dqqL2DntoeMjHz3Ua94aK5Y0VSc1IwlETxCuF7yNV5/w/Z8kgeVVa5cDgrPg==", "peerDependencies": { "react": "^18.2.0" } }, "node_modules/@mantine/notifications": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-7.5.1.tgz", - "integrity": "sha512-IQDOAz+U9G6IkYXAXG9qL5EESmnhWV3JBJrxwBOPPdi1e9S/akQlsmABWS/voB9WFnOnbMbrkF067RVBA7W4dg==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-7.5.2.tgz", + "integrity": "sha512-wGRDeOG2NGng202k/vZPfkRJMHtflvIORYe4cJaQAd9EhP646xr/ji8zzdXeXvUO5PrKlBpu+K+HSF1bQQY4og==", "dependencies": { - "@mantine/store": "7.5.1", + "@mantine/store": "7.5.2", "react-transition-group": "4.4.5" }, "peerDependencies": { - "@mantine/core": "7.5.1", - "@mantine/hooks": "7.5.1", + "@mantine/core": "7.5.2", + "@mantine/hooks": "7.5.2", "react": "^18.2.0", "react-dom": "^18.2.0" } }, "node_modules/@mantine/store": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/@mantine/store/-/store-7.5.1.tgz", - "integrity": "sha512-sDaPXB3v9JlJghNTnRTFT2hC3HN6pdBcCXj0CqO/QrJgtRA7A3FxW+mnY7YQOaBxHJ1MIRr+zsv0Qy1f/pu1dw==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/@mantine/store/-/store-7.5.2.tgz", + "integrity": "sha512-au53HLLH/LfHLOR2Zb00TgG11xAXZNJb4jqsEhAr90axCIw6mlBDIAhbb+Yu6OwDaV7TscE/sonkUstmRjLh/w==", "peerDependencies": { "react": "^18.2.0" } @@ -11627,9 +11628,9 @@ } }, "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "optional": true, "dependencies": { @@ -11795,14 +11796,14 @@ "link": true }, "node_modules/@microsoft/api-documenter": { - "version": "7.23.20", - "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.23.20.tgz", - "integrity": "sha512-61V6sukyYZ5jQEdyvDFzInaIRTd0wgT2ECKPanr2ba0fc+Mien+KIr5shz9EAqJMZz0GifTnw9HmJqsfR688xA==", + "version": "7.23.23", + "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.23.23.tgz", + "integrity": "sha512-77upYNmm6h9+8hdNWL7R1+vW1QaExkwKuOuSWR2v3Tdk2JHCVf+s341jybgYEnxRhIQIIxtLeiwQ/xzMFOizwQ==", "dev": true, "dependencies": { - "@microsoft/api-extractor-model": "7.28.7", + "@microsoft/api-extractor-model": "7.28.9", "@microsoft/tsdoc": "0.14.2", - "@rushstack/node-core-library": "3.64.2", + "@rushstack/node-core-library": "3.66.0", "@rushstack/ts-command-line": "4.17.1", "colors": "~1.2.1", "js-yaml": "~3.13.1", @@ -11813,15 +11814,15 @@ } }, "node_modules/@microsoft/api-extractor": { - "version": "7.39.4", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.39.4.tgz", - "integrity": "sha512-6YvfkpbEqRQ0UPdVBc+lOiq7VlXi9kw8U3w+RcXCFDVc/UljlXU5l9fHEyuBAW1GGO2opUe+yf9OscWhoHANhg==", + "version": "7.40.1", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.40.1.tgz", + "integrity": "sha512-xHn2Zkh6s5JIjP94SG6VtIlIeRJcASgfZpDKV+bgoddMt1X4ujSZFOz7uEGNYNO7mEtdVOvpNKBpC4CDytD8KQ==", "dev": true, "dependencies": { - "@microsoft/api-extractor-model": "7.28.7", + "@microsoft/api-extractor-model": "7.28.9", "@microsoft/tsdoc": "0.14.2", "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "3.64.2", + "@rushstack/node-core-library": "3.66.0", "@rushstack/rig-package": "0.5.1", "@rushstack/ts-command-line": "4.17.1", "colors": "~1.2.1", @@ -11836,14 +11837,14 @@ } }, "node_modules/@microsoft/api-extractor-model": { - "version": "7.28.7", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.28.7.tgz", - "integrity": "sha512-4gCGGEQGHmbQmarnDcEWS2cjj0LtNuD3D6rh3ZcAyAYTkceAugAk2eyQHGdTcGX8w3qMjWCTU1TPb8xHnMM+Kg==", + "version": "7.28.9", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.28.9.tgz", + "integrity": "sha512-lM77dV+VO46MGp5lu4stUBnO3jyr+CrDzU+DtapcOQEZUqJxPYUoK5zjeD+gRZ9ckgGMZC94ch6FBkpmsjwQgw==", "dev": true, "dependencies": { "@microsoft/tsdoc": "0.14.2", "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "3.64.2" + "@rushstack/node-core-library": "3.66.0" } }, "node_modules/@microsoft/api-extractor/node_modules/lru-cache": { @@ -12294,9 +12295,9 @@ } }, "node_modules/@npmcli/fs/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -12343,9 +12344,9 @@ } }, "node_modules/@npmcli/git/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -12756,14 +12757,14 @@ } }, "node_modules/@opentelemetry/auto-instrumentations-node": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.41.0.tgz", - "integrity": "sha512-CmVVzphp1eb6Aenrr4B6TDsCNuDvut8NQbosTSdJFfnEX4+3szVQuaJquaicGI5tLvVxrgVbZAv52C7bJgMHkg==", + "version": "0.41.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.41.1.tgz", + "integrity": "sha512-gQG0mHlPVBQveuemqNYkrL4IB3T6v2e5sMiDI6FQzFWeLzzFrC04R4fY6AE1YmkhOyteCDpHL/U6CULP2mkt8w==", "dependencies": { "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/instrumentation-amqplib": "^0.34.0", "@opentelemetry/instrumentation-aws-lambda": "^0.38.0", - "@opentelemetry/instrumentation-aws-sdk": "^0.38.0", + "@opentelemetry/instrumentation-aws-sdk": "^0.38.1", "@opentelemetry/instrumentation-bunyan": "^0.35.0", "@opentelemetry/instrumentation-cassandra-driver": "^0.35.0", "@opentelemetry/instrumentation-connect": "^0.33.0", @@ -13001,9 +13002,9 @@ } }, "node_modules/@opentelemetry/instrumentation-aws-sdk": { - "version": "0.38.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.38.0.tgz", - "integrity": "sha512-q1R+evR1j8sqd5LG+I8fKqP5TAPJEEOmJTvtEDYCVCdtzukI0ABYN8SHEIgDgYZmGBDM0yvC6jH0GmoEwvYuMw==", + "version": "0.38.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.38.1.tgz", + "integrity": "sha512-/Tupb4UfVVkmcopq2H2nr2D/pHLF0riVw2biQQUJ/jGIkfrOgUMMKbShi2RQE4Zy8NFv3xaDn4/pNxzodLBy3w==", "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.48.0", @@ -13122,9 +13123,9 @@ } }, "node_modules/@opentelemetry/instrumentation-dns/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -13278,9 +13279,9 @@ } }, "node_modules/@opentelemetry/instrumentation-http/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -13682,9 +13683,9 @@ } }, "node_modules/@opentelemetry/instrumentation/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -14010,9 +14011,9 @@ } }, "node_modules/@opentelemetry/sdk-trace-node/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -15712,9 +15713,9 @@ } }, "node_modules/@react-native-community/cli-doctor/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -16260,9 +16261,9 @@ } }, "node_modules/@react-native-community/cli-tools/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -16414,9 +16415,9 @@ } }, "node_modules/@react-native-community/cli/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -16452,20 +16453,20 @@ } }, "node_modules/@react-native/babel-plugin-codegen": { - "version": "0.73.3", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.73.3.tgz", - "integrity": "sha512-+zQrDDbz6lB48LyzFHxNCgXDCBHH+oTRdXAjikRcBUdeG9St9ABbYFLtb799zSxLOrCqFVyXqhJR2vlgLLEbcg==", + "version": "0.73.4", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.73.4.tgz", + "integrity": "sha512-XzRd8MJGo4Zc5KsphDHBYJzS1ryOHg8I2gOZDAUCGcwLFhdyGu1zBNDJYH2GFyDrInn9TzAbRIf3d4O+eltXQQ==", "dependencies": { - "@react-native/codegen": "0.73.2" + "@react-native/codegen": "0.73.3" }, "engines": { "node": ">=18" } }, "node_modules/@react-native/babel-preset": { - "version": "0.73.20", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.73.20.tgz", - "integrity": "sha512-fU9NqkusbfFq71l4BWQfqqD/lLcLC0MZ++UYgieA3j8lIEppJTLVauv2RwtD2yltBkjebgYEC5Rwvt1l0MUBXw==", + "version": "0.73.21", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.73.21.tgz", + "integrity": "sha512-WlFttNnySKQMeujN09fRmrdWqh46QyJluM5jdtDNrkl/2Hx6N4XeDUGhABvConeK95OidVO7sFFf7sNebVXogA==", "dependencies": { "@babel/core": "^7.20.0", "@babel/plugin-proposal-async-generator-functions": "^7.0.0", @@ -16506,7 +16507,7 @@ "@babel/plugin-transform-typescript": "^7.5.0", "@babel/plugin-transform-unicode-regex": "^7.0.0", "@babel/template": "^7.0.0", - "@react-native/babel-plugin-codegen": "0.73.3", + "@react-native/babel-plugin-codegen": "0.73.4", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" }, @@ -16518,9 +16519,9 @@ } }, "node_modules/@react-native/codegen": { - "version": "0.73.2", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.73.2.tgz", - "integrity": "sha512-lfy8S7umhE3QLQG5ViC4wg5N1Z+E6RnaeIw8w1voroQsXXGPB72IBozh8dAHR3+ceTxIU0KX3A8OpJI8e1+HpQ==", + "version": "0.73.3", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.73.3.tgz", + "integrity": "sha512-sxslCAAb8kM06vGy9Jyh4TtvjhcP36k/rvj2QE2Jdhdm61KvfafCATSIsOfc0QvnduWFcpXUPvAVyYwuv7PYDg==", "dependencies": { "@babel/parser": "^7.20.0", "flow-parser": "^0.206.0", @@ -16738,14 +16739,14 @@ } }, "node_modules/@react-native/community-cli-plugin": { - "version": "0.73.14", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.73.14.tgz", - "integrity": "sha512-KzIwsTvAJrXPtwhGOSm+OcJH1B8TpY8cS4xxzu/e2qv3a2n4VLePHTPAfco1tmvekV8OHWvvD9JSIX7i2fB1gg==", + "version": "0.73.16", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.73.16.tgz", + "integrity": "sha512-eNH3v3qJJF6f0n/Dck90qfC9gVOR4coAXMTdYECO33GfgjTi+73vf/SBqlXw9HICH/RNZYGPM3wca4FRF7TYeQ==", "dependencies": { "@react-native-community/cli-server-api": "12.3.2", "@react-native-community/cli-tools": "12.3.2", "@react-native/dev-middleware": "0.73.7", - "@react-native/metro-babel-transformer": "0.73.14", + "@react-native/metro-babel-transformer": "0.73.15", "chalk": "^4.0.0", "execa": "^5.1.1", "metro": "^0.80.3", @@ -16879,12 +16880,12 @@ } }, "node_modules/@react-native/metro-babel-transformer": { - "version": "0.73.14", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.14.tgz", - "integrity": "sha512-5wLeYw/lormpSqYfI9H/geZ/EtPmi+x5qLkEit15Q/70hkzYo/M+aWztUtbOITfgTEOP8d6ybROzoGsqgyZLcw==", + "version": "0.73.15", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.15.tgz", + "integrity": "sha512-LlkSGaXCz+xdxc9819plmpsl4P4gZndoFtpjN3GMBIu6f7TBV0GVbyJAU4GE8fuAWPVSVL5ArOcdkWKSbI1klw==", "dependencies": { "@babel/core": "^7.20.0", - "@react-native/babel-preset": "0.73.20", + "@react-native/babel-preset": "0.73.21", "hermes-parser": "0.15.0", "nullthrows": "^1.1.1" }, @@ -16921,9 +16922,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.2.tgz", - "integrity": "sha512-ACXpdMM9hmKZww21yEqWwiLws/UPLhNKvimN8RrYSqPSvB3ov7sLvAcfvaxePeLvccTQKGdkDIhLYApZVDFuKg==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.0.tgz", + "integrity": "sha512-HOil5aFtme37dVQTB6M34G95kPM3MMuqSmIRVCC52eKV+Y/tGSqw9P3rWhlAx6A+mz+MoX+XxsGsNJbaI5qCgQ==", "dev": true, "engines": { "node": ">=14.0.0" @@ -17009,9 +17010,9 @@ "dev": true }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.6.tgz", - "integrity": "sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.10.0.tgz", + "integrity": "sha512-/MeDQmcD96nVoRumKUljsYOLqfv1YFJps+0pTrb2Z9Nl/w5qNUysMaWQsrd1mvAlNT4yza1iVyIu4Q4AgF6V3A==", "cpu": [ "arm" ], @@ -17022,9 +17023,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.6.tgz", - "integrity": "sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.10.0.tgz", + "integrity": "sha512-lvu0jK97mZDJdpZKDnZI93I0Om8lSDaiPx3OiCk0RXn3E8CMPJNS/wxjAvSJJzhhZpfjXsjLWL8LnS6qET4VNQ==", "cpu": [ "arm64" ], @@ -17035,9 +17036,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.6.tgz", - "integrity": "sha512-CqNNAyhRkTbo8VVZ5R85X73H3R5NX9ONnKbXuHisGWC0qRbTTxnF1U4V9NafzJbgGM0sHZpdO83pLPzq8uOZFw==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.10.0.tgz", + "integrity": "sha512-uFpayx8I8tyOvDkD7X6n0PriDRWxcqEjqgtlxnUA/G9oS93ur9aZ8c8BEpzFmsed1TH5WZNG5IONB8IiW90TQg==", "cpu": [ "arm64" ], @@ -17048,9 +17049,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.6.tgz", - "integrity": "sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.10.0.tgz", + "integrity": "sha512-nIdCX03qFKoR/MwQegQBK+qZoSpO3LESurVAC6s6jazLA1Mpmgzo3Nj3H1vydXp/JM29bkCiuF7tDuToj4+U9Q==", "cpu": [ "x64" ], @@ -17061,9 +17062,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.6.tgz", - "integrity": "sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.10.0.tgz", + "integrity": "sha512-Fz7a+y5sYhYZMQFRkOyCs4PLhICAnxRX/GnWYReaAoruUzuRtcf+Qnw+T0CoAWbHCuz2gBUwmWnUgQ67fb3FYw==", "cpu": [ "arm" ], @@ -17074,9 +17075,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.6.tgz", - "integrity": "sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.10.0.tgz", + "integrity": "sha512-yPtF9jIix88orwfTi0lJiqINnlWo6p93MtZEoaehZnmCzEmLL0eqjA3eGVeyQhMtxdV+Mlsgfwhh0+M/k1/V7Q==", "cpu": [ "arm64" ], @@ -17087,9 +17088,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.6.tgz", - "integrity": "sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.10.0.tgz", + "integrity": "sha512-9GW9yA30ib+vfFiwjX+N7PnjTnCMiUffhWj4vkG4ukYv1kJ4T9gHNg8zw+ChsOccM27G9yXrEtMScf1LaCuoWQ==", "cpu": [ "arm64" ], @@ -17100,9 +17101,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.6.tgz", - "integrity": "sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.10.0.tgz", + "integrity": "sha512-X1ES+V4bMq2ws5fF4zHornxebNxMXye0ZZjUrzOrf7UMx1d6wMQtfcchZ8SqUnQPPHdOyOLW6fTcUiFgHFadRA==", "cpu": [ "riscv64" ], @@ -17113,9 +17114,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.6.tgz", - "integrity": "sha512-HUNqM32dGzfBKuaDUBqFB7tP6VMN74eLZ33Q9Y1TBqRDn+qDonkAUyKWwF9BR9unV7QUzffLnz9GrnKvMqC/fw==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.10.0.tgz", + "integrity": "sha512-w/5OpT2EnI/Xvypw4FIhV34jmNqU5PZjZue2l2Y3ty1Ootm3SqhI+AmfhlUYGBTd9JnpneZCDnt3uNOiOBkMyw==", "cpu": [ "x64" ], @@ -17126,9 +17127,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.6.tgz", - "integrity": "sha512-ch7M+9Tr5R4FK40FHQk8VnML0Szi2KRujUgHXd/HjuH9ifH72GUmw6lStZBo3c3GB82vHa0ZoUfjfcM7JiiMrQ==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.10.0.tgz", + "integrity": "sha512-q/meftEe3QlwQiGYxD9rWwB21DoKQ9Q8wA40of/of6yGHhZuGfZO0c3WYkN9dNlopHlNT3mf5BPsUSxoPuVQaw==", "cpu": [ "x64" ], @@ -17139,9 +17140,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.6.tgz", - "integrity": "sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.10.0.tgz", + "integrity": "sha512-NrR6667wlUfP0BHaEIKgYM/2va+Oj+RjZSASbBMnszM9k+1AmliRjHc3lJIiOehtSSjqYiO7R6KLNrWOX+YNSQ==", "cpu": [ "arm64" ], @@ -17152,9 +17153,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.6.tgz", - "integrity": "sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.10.0.tgz", + "integrity": "sha512-FV0Tpt84LPYDduIDcXvEC7HKtyXxdvhdAOvOeWMWbQNulxViH2O07QXkT/FffX4FqEI02jEbCJbr+YcuKdyyMg==", "cpu": [ "ia32" ], @@ -17165,9 +17166,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.6.tgz", - "integrity": "sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.10.0.tgz", + "integrity": "sha512-OZoJd+o5TaTSQeFFQ6WjFCiltiYVjIdsXxwu/XZ8qRpsvMQr4UsVrE5UyT9RIvsnuF47DqkJKhhVZ2Q9YW9IpQ==", "cpu": [ "x64" ], @@ -17184,9 +17185,9 @@ "dev": true }, "node_modules/@rushstack/node-core-library": { - "version": "3.64.2", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.64.2.tgz", - "integrity": "sha512-n1S2VYEklONiwKpUyBq/Fym6yAsfsCXrqFabuOMcCuj4C+zW+HyaspSHXJCKqkMxfjviwe/c9+DUqvRWIvSN9Q==", + "version": "3.66.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.66.0.tgz", + "integrity": "sha512-nXyddNe3T9Ph14TrIfjtLZ+GDzC7HL/wF+ZKC18qmRVtz2xXLd1ZzreVgiAgGDwn8ZUWZ/7q//gQJk96iWjSrg==", "dev": true, "dependencies": { "colors": "~1.2.1", @@ -17475,9 +17476,9 @@ } }, "node_modules/@smithy/core": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-1.3.1.tgz", - "integrity": "sha512-tf+NIu9FkOh312b6M9G4D68is4Xr7qptzaZGZUREELF8ysE1yLKphqt7nsomjKZVwW7WE5pDDex9idowNGRQ/Q==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-1.3.2.tgz", + "integrity": "sha512-tYDmTp0f2TZVE18jAOH1PnmkngLQ+dOGUlMd1u67s87ieueNeyqhja6z/Z4MxhybEiXKOWFOmGjfTZWFxljwJw==", "dependencies": { "@smithy/middleware-endpoint": "^2.4.1", "@smithy/middleware-retry": "^2.1.1", @@ -17948,9 +17949,9 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.1.1.tgz", - "integrity": "sha512-tYVrc+w+jSBfBd267KDnvSGOh4NMz+wVH7v4CClDbkdPfnjvImBZsOURncT5jsFwR9KCuDyPoSZq4Pa6+eCUrA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.2.0.tgz", + "integrity": "sha512-iFJp/N4EtkanFpBUtSrrIbtOIBf69KNuve03ic1afhJ9/korDxdM0c6cCH4Ehj/smI9pDCfVv+bqT3xZjF2WaA==", "dependencies": { "@smithy/config-resolver": "^2.1.1", "@smithy/credential-provider-imds": "^2.2.1", @@ -18068,12 +18069,12 @@ } }, "node_modules/@storybook/addon-actions": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-7.6.12.tgz", - "integrity": "sha512-vK/H6K+AJ4ZSsCu/+MapYYI/xrynB6JoCOejt//flTigZOhwTWv7WXbmEeqGIIToXy0LA2IUZ1/kCjFXR0lEdQ==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-7.6.14.tgz", + "integrity": "sha512-hFVB/ejxBdE6J3wOEPSx6aFB51PqDfQ/YR4ik5GCGJb3cmUX7d/FY8zH0TKJLXcG/Hw3XoxNiEo5AaMVxtGVGA==", "dev": true, "dependencies": { - "@storybook/core-events": "7.6.12", + "@storybook/core-events": "7.6.14", "@storybook/global": "^5.0.0", "@types/uuid": "^9.0.1", "dequal": "^2.0.2", @@ -18099,9 +18100,9 @@ } }, "node_modules/@storybook/addon-backgrounds": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-7.6.12.tgz", - "integrity": "sha512-G14uN5lDXUtXw+dmEPaB6lpDpR9K25ssYuWWn8yYR44B1WMuD4kDgw0QGb0g+xYQj9R1TsalKEJHA4AuSYkVGQ==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-7.6.14.tgz", + "integrity": "sha512-R6OblK71iKIwpxTZQhuOpbktIT5pNrfMNe4/lkIP2F6Dv9HgHwvg95Bpt0ebHKlRvD7KNwj1whKjJh1fO3yLgQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0", @@ -18114,12 +18115,12 @@ } }, "node_modules/@storybook/addon-controls": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-7.6.12.tgz", - "integrity": "sha512-NX4KajscOsuXyYE3hhniF+y0E59E6rM0FgIaZ48P9c0DD+wDo8bAISHjZvmKXtDVajLk4/JySvByx1eN6V3hmA==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-7.6.14.tgz", + "integrity": "sha512-KJRPdzbXjitqCixMzMjkcRYJGIts9wrx2Qk7NCSXCbE0LDdT+U7//25luLp5DrRiPdqIVEQjNcLF10frljaA9g==", "dev": true, "dependencies": { - "@storybook/blocks": "7.6.12", + "@storybook/blocks": "7.6.14", "lodash": "^4.17.21", "ts-dedent": "^2.0.0" }, @@ -18129,26 +18130,26 @@ } }, "node_modules/@storybook/addon-docs": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-7.6.12.tgz", - "integrity": "sha512-AzMgnGYfEg+Z1ycJh8MEp44x1DfjRijKCVYNaPFT6o+TjN/9GBaAkV4ydxmQzMEMnnnh/0E9YeHO+ivBVSkNog==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-7.6.14.tgz", + "integrity": "sha512-fH3voEcHuJmMXNIT6Lxs5ve+dM6P74gwhdyMj21WIp8DnYM99RrmjvT1k/3+tGknL/7oGM+4Y2DLyy2KYFc6HQ==", "dev": true, "dependencies": { "@jest/transform": "^29.3.1", "@mdx-js/react": "^2.1.5", - "@storybook/blocks": "7.6.12", - "@storybook/client-logger": "7.6.12", - "@storybook/components": "7.6.12", - "@storybook/csf-plugin": "7.6.12", - "@storybook/csf-tools": "7.6.12", + "@storybook/blocks": "7.6.14", + "@storybook/client-logger": "7.6.14", + "@storybook/components": "7.6.14", + "@storybook/csf-plugin": "7.6.14", + "@storybook/csf-tools": "7.6.14", "@storybook/global": "^5.0.0", "@storybook/mdx2-csf": "^1.0.0", - "@storybook/node-logger": "7.6.12", - "@storybook/postinstall": "7.6.12", - "@storybook/preview-api": "7.6.12", - "@storybook/react-dom-shim": "7.6.12", - "@storybook/theming": "7.6.12", - "@storybook/types": "7.6.12", + "@storybook/node-logger": "7.6.14", + "@storybook/postinstall": "7.6.14", + "@storybook/preview-api": "7.6.14", + "@storybook/react-dom-shim": "7.6.14", + "@storybook/theming": "7.6.14", + "@storybook/types": "7.6.14", "fs-extra": "^11.1.0", "remark-external-links": "^8.0.0", "remark-slug": "^6.0.0", @@ -18216,24 +18217,24 @@ } }, "node_modules/@storybook/addon-essentials": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-7.6.12.tgz", - "integrity": "sha512-Pl6n+19QC/T+cuU8DZjCwILXVxrdRTivNxPOiy8SEX+jjR4H0uAfXC9+RXCPjRFn64t4j1K7oIyoNokEn39cNw==", - "dev": true, - "dependencies": { - "@storybook/addon-actions": "7.6.12", - "@storybook/addon-backgrounds": "7.6.12", - "@storybook/addon-controls": "7.6.12", - "@storybook/addon-docs": "7.6.12", - "@storybook/addon-highlight": "7.6.12", - "@storybook/addon-measure": "7.6.12", - "@storybook/addon-outline": "7.6.12", - "@storybook/addon-toolbars": "7.6.12", - "@storybook/addon-viewport": "7.6.12", - "@storybook/core-common": "7.6.12", - "@storybook/manager-api": "7.6.12", - "@storybook/node-logger": "7.6.12", - "@storybook/preview-api": "7.6.12", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-7.6.14.tgz", + "integrity": "sha512-1CcpLvzmvXyRhxbc2FgVbchpu7EMEeAjNY2lQ8ejn4cwLuIeWvYI61Cq4swiEmcEOEzi9Uvrq9q1bua9N1fPqw==", + "dev": true, + "dependencies": { + "@storybook/addon-actions": "7.6.14", + "@storybook/addon-backgrounds": "7.6.14", + "@storybook/addon-controls": "7.6.14", + "@storybook/addon-docs": "7.6.14", + "@storybook/addon-highlight": "7.6.14", + "@storybook/addon-measure": "7.6.14", + "@storybook/addon-outline": "7.6.14", + "@storybook/addon-toolbars": "7.6.14", + "@storybook/addon-viewport": "7.6.14", + "@storybook/core-common": "7.6.14", + "@storybook/manager-api": "7.6.14", + "@storybook/node-logger": "7.6.14", + "@storybook/preview-api": "7.6.14", "ts-dedent": "^2.0.0" }, "funding": { @@ -18246,9 +18247,9 @@ } }, "node_modules/@storybook/addon-highlight": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-7.6.12.tgz", - "integrity": "sha512-rWNEyBhwncXEDd9z7l67BLBIPqn0SRI/CJpZvCSF5KLWrVaoSEDF8INavmbikd1JBMcajJ28Ur6NsGj+eJjJiw==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-7.6.14.tgz", + "integrity": "sha512-VQTgLm6jPKN7DOhrx0mY5yrhQxOiidQt4yoazJTgzn+aV7zBFKn+GtF1W38QrnFtq5Mr8VJsEByEdtVCqMcmyw==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -18259,9 +18260,9 @@ } }, "node_modules/@storybook/addon-links": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-7.6.12.tgz", - "integrity": "sha512-rGwPYpZAANPrf2GaNi5t9zAjLF8PgzKizyBPltIXUtplxDg88ziXlDA1dhsuGDs4Kf0oXECyAHPw79JjkJQziA==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-7.6.14.tgz", + "integrity": "sha512-xzDWQEzntia9ArFQC95TEw/Tqp/cNFq0SSuQQ6d9/ryQczuSdRGFHRmEd99/92ufNgCGPaRZOB7sweiKG0bkzA==", "dev": true, "dependencies": { "@storybook/csf": "^0.1.2", @@ -18282,9 +18283,9 @@ } }, "node_modules/@storybook/addon-measure": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-7.6.12.tgz", - "integrity": "sha512-K3aKErr84V0eVK7t+wco5cSYDdeotwoXi4e7VLSa2cdUz0wanOb4R7v3kf6vxucUyp05Lv+yHkz9zsbwuezepA==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-7.6.14.tgz", + "integrity": "sha512-bRy3SEv4uf1csDe5H8Lg3wUDg1uMZo6/j2FwNjvUmW+vcasj3VsqPKQjT6KO+LjCXsQ1pIAHu1HcUh2v/Qoitw==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0", @@ -18296,9 +18297,9 @@ } }, "node_modules/@storybook/addon-outline": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-7.6.12.tgz", - "integrity": "sha512-r6eO4EKh+zwGUNjxe8v/44BhyV+JD3Dl9GYMutsFqbwYsoWHJaZmzHuyqeFBXwx2MEoixdWdIzNMP71+srQqvw==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-7.6.14.tgz", + "integrity": "sha512-RH3arZYMBBxoqif4pnKN8m8Vt8setpeh0kz6oA+Ilhf/Z8Wz5jWiYDvTL5WW3+E+XGLrIFwH87wmJLN0egKqtA==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0", @@ -18310,12 +18311,12 @@ } }, "node_modules/@storybook/addon-storysource": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/addon-storysource/-/addon-storysource-7.6.12.tgz", - "integrity": "sha512-30h9NrtdI4CWJO/7z14y6uvdAJWP/YLJi9j3GzxGF2pFVKI5qRymeQRsNvk3p0VgWvN5+E+OhoI1b1T9F3wvhw==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/addon-storysource/-/addon-storysource-7.6.14.tgz", + "integrity": "sha512-hDJ8MHokSH4CiUn6a5HoPUjTmDfkYiCqKkRgMbVkNIHaLNttfqqSCm8FLMZT/XEOw4RJeh8g4CqEu2NTCf63CA==", "dev": true, "dependencies": { - "@storybook/source-loader": "7.6.12", + "@storybook/source-loader": "7.6.14", "estraverse": "^5.2.0", "tiny-invariant": "^1.3.1" }, @@ -18325,9 +18326,9 @@ } }, "node_modules/@storybook/addon-toolbars": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-7.6.12.tgz", - "integrity": "sha512-TSwq8xO7fmS6GRTgJJa31OBzm+5zlgDYK2Q42jxFo/Vm10uMzCpjYJE6mIHpUDyjyBVQk6xxMMEcvo6no2eAWg==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-7.6.14.tgz", + "integrity": "sha512-/Zea9XgmxJp/5pQ+PKw+FGj2s2POIur/9uCUmLBWPDAMIW+kugOYZ/i8krrcHDPJ7nG2rtUJbeSliod9h2tpfw==", "dev": true, "funding": { "type": "opencollective", @@ -18335,9 +18336,9 @@ } }, "node_modules/@storybook/addon-viewport": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-7.6.12.tgz", - "integrity": "sha512-51zsBeoaEzq699SKDCe+GG/2PDAJKKJtpjqxIc4lDskogaCJSb3Ie8LyookHAKYgbi2qealVgK8zaP27KUj3Pg==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-7.6.14.tgz", + "integrity": "sha512-7GbJyXFP3QCZezUQ+75VdjBpyXWutdFY0YMM/3JTjU+Khutbph3RurMTi4dRiBndAIPXlReNm1AnnYX5w+jd9w==", "dev": true, "dependencies": { "memoizerific": "^1.11.3" @@ -18348,22 +18349,22 @@ } }, "node_modules/@storybook/blocks": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-7.6.12.tgz", - "integrity": "sha512-T47KOAjgZmhV+Ov59A70inE5edInh1Jh5w/5J5cjpk9a2p4uhd337SnK4B8J5YLhcM2lbKRWJjzIJ0nDZQTdnQ==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-7.6.14.tgz", + "integrity": "sha512-DZOSEWSNptAhaeNiOG0BqidJxqi/KaAZ2ZnlygpswDDT9vOCGoc7edZEgrq/i83M55KZFD4IXVLYFdfpjRcirQ==", "dev": true, "dependencies": { - "@storybook/channels": "7.6.12", - "@storybook/client-logger": "7.6.12", - "@storybook/components": "7.6.12", - "@storybook/core-events": "7.6.12", + "@storybook/channels": "7.6.14", + "@storybook/client-logger": "7.6.14", + "@storybook/components": "7.6.14", + "@storybook/core-events": "7.6.14", "@storybook/csf": "^0.1.2", - "@storybook/docs-tools": "7.6.12", + "@storybook/docs-tools": "7.6.14", "@storybook/global": "^5.0.0", - "@storybook/manager-api": "7.6.12", - "@storybook/preview-api": "7.6.12", - "@storybook/theming": "7.6.12", - "@storybook/types": "7.6.12", + "@storybook/manager-api": "7.6.14", + "@storybook/preview-api": "7.6.14", + "@storybook/theming": "7.6.14", + "@storybook/types": "7.6.14", "@types/lodash": "^4.14.167", "color-convert": "^2.0.1", "dequal": "^2.0.2", @@ -18387,15 +18388,15 @@ } }, "node_modules/@storybook/builder-manager": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/builder-manager/-/builder-manager-7.6.12.tgz", - "integrity": "sha512-AJFrtBj0R11OFwwz+2j+ivRzttWXT6LesSGoLnxown24EV9uLQoHtGb7GOA2GyzY5wjUJS9gQBPGHXjvQEfLJA==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/builder-manager/-/builder-manager-7.6.14.tgz", + "integrity": "sha512-pID/g2Bnr3tjmkh8c+O6TZei3f1TWHW/UWi/skNQ3wGJ+9dqJIK2vQY5SwnXBWkmJdUqGVXaW5BvzR8jjfpTxQ==", "dev": true, "dependencies": { "@fal-works/esbuild-plugin-global-externals": "^2.1.2", - "@storybook/core-common": "7.6.12", - "@storybook/manager": "7.6.12", - "@storybook/node-logger": "7.6.12", + "@storybook/core-common": "7.6.14", + "@storybook/manager": "7.6.14", + "@storybook/node-logger": "7.6.14", "@types/ejs": "^3.1.1", "@types/find-cache-dir": "^3.2.1", "@yarnpkg/esbuild-plugin-pnp": "^3.0.0-rc.10", @@ -18450,19 +18451,19 @@ } }, "node_modules/@storybook/builder-vite": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-7.6.12.tgz", - "integrity": "sha512-VJIn+XYVVhdJHHMEtYDnEyQQU4fRupugSFpP9XLYTRYgXPN9PSVey4vI/IyuHcHYINPba39UY2+8PW+5NgShxQ==", - "dev": true, - "dependencies": { - "@storybook/channels": "7.6.12", - "@storybook/client-logger": "7.6.12", - "@storybook/core-common": "7.6.12", - "@storybook/csf-plugin": "7.6.12", - "@storybook/node-logger": "7.6.12", - "@storybook/preview": "7.6.12", - "@storybook/preview-api": "7.6.12", - "@storybook/types": "7.6.12", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-7.6.14.tgz", + "integrity": "sha512-GhIuK0Xu+HZK4K3NW0PlPpY3wQQ6Ay8WQp9Ea8UZn+ixop4wAV+dLFEJ0B8fXrpSNqsmjUim7rIfMePzXkfucQ==", + "dev": true, + "dependencies": { + "@storybook/channels": "7.6.14", + "@storybook/client-logger": "7.6.14", + "@storybook/core-common": "7.6.14", + "@storybook/csf-plugin": "7.6.14", + "@storybook/node-logger": "7.6.14", + "@storybook/preview": "7.6.14", + "@storybook/preview-api": "7.6.14", + "@storybook/types": "7.6.14", "@types/find-cache-dir": "^3.2.1", "browser-assert": "^1.2.1", "es-module-lexer": "^0.9.3", @@ -18530,13 +18531,13 @@ } }, "node_modules/@storybook/channels": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.12.tgz", - "integrity": "sha512-TaPl5Y3lOoVi5kTLgKNRX8xh2sUPekH0Id1l4Ymw+lpgriEY6r60bmkZLysLG1GhlskpQ/da2+S2ap2ht8P2TQ==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.14.tgz", + "integrity": "sha512-tyrnnXTh7Ca6HbtzYtZGZmbUkC+eYPdot41+YDERMxXCnejd18BnsH/pyGW66GwgY079Q7uhdDFyM63ynZrt/A==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.12", - "@storybook/core-events": "7.6.12", + "@storybook/client-logger": "7.6.14", + "@storybook/core-events": "7.6.14", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -18548,23 +18549,23 @@ } }, "node_modules/@storybook/cli": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/cli/-/cli-7.6.12.tgz", - "integrity": "sha512-x4sG1oIVERxp+WnWUexVlgaJCFmML0kGi7a5qfx7z4vHMxCV/WG7g1q7mPS/kqStCGEiQdTciCqOEFqlMh9MLw==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/cli/-/cli-7.6.14.tgz", + "integrity": "sha512-2xqcGRPtj/OE+9ro92C5MFCT8VHdMCDDuZZRnmgPi83iqSZtYbO8xHZwz78j4TvmouHstOV1SedeWv0IsFIxLw==", "dev": true, "dependencies": { "@babel/core": "^7.23.2", "@babel/preset-env": "^7.23.2", "@babel/types": "^7.23.0", "@ndelangen/get-tarball": "^3.0.7", - "@storybook/codemod": "7.6.12", - "@storybook/core-common": "7.6.12", - "@storybook/core-events": "7.6.12", - "@storybook/core-server": "7.6.12", - "@storybook/csf-tools": "7.6.12", - "@storybook/node-logger": "7.6.12", - "@storybook/telemetry": "7.6.12", - "@storybook/types": "7.6.12", + "@storybook/codemod": "7.6.14", + "@storybook/core-common": "7.6.14", + "@storybook/core-events": "7.6.14", + "@storybook/core-server": "7.6.14", + "@storybook/csf-tools": "7.6.14", + "@storybook/node-logger": "7.6.14", + "@storybook/telemetry": "7.6.14", + "@storybook/types": "7.6.14", "@types/semver": "^7.3.4", "@yarnpkg/fslib": "2.10.3", "@yarnpkg/libzip": "2.3.0", @@ -18715,9 +18716,9 @@ } }, "node_modules/@storybook/cli/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -18757,9 +18758,9 @@ "dev": true }, "node_modules/@storybook/client-logger": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.12.tgz", - "integrity": "sha512-hiRv6dXsOttMPqm9SxEuFoAtDe9rs7TUS8XcO5rmJ9BgfwBJsYlHzAxXkazxmvlyZtKL7gMx6m8OYbCdZgUqtA==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.14.tgz", + "integrity": "sha512-rHa2hLU+80BN5E58Shf1g09YS6QEEOk5hwMuJ4WJfAypMDYPjnIsOYUboHClkCA9TDCH/iVhyRSPy83NWN2MZg==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -18770,18 +18771,18 @@ } }, "node_modules/@storybook/codemod": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-7.6.12.tgz", - "integrity": "sha512-4EI4Ah1cvz6gFkXOS/LGf23oN8LO6ABGpWwPQoMHpIV3wUkFWBwrKFUe/UAQZGptnM0VZRYx4grS82Hluw4XJA==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-7.6.14.tgz", + "integrity": "sha512-Sq/Q12KmvzaSUtmbtD26cEEGVmZLUA+iiNHbl0n65MMka6QBGG/VgSPvSgu+GEpKowbVoqfMpH4Ic16A6XsNFg==", "dev": true, "dependencies": { "@babel/core": "^7.23.2", "@babel/preset-env": "^7.23.2", "@babel/types": "^7.23.0", "@storybook/csf": "^0.1.2", - "@storybook/csf-tools": "7.6.12", - "@storybook/node-logger": "7.6.12", - "@storybook/types": "7.6.12", + "@storybook/csf-tools": "7.6.14", + "@storybook/node-logger": "7.6.14", + "@storybook/types": "7.6.14", "@types/cross-spawn": "^6.0.2", "cross-spawn": "^7.0.3", "globby": "^11.0.2", @@ -18811,18 +18812,18 @@ } }, "node_modules/@storybook/components": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/components/-/components-7.6.12.tgz", - "integrity": "sha512-PCijPqmlZd7qyTzNr+vD0Kf8sAI9vWJIaxbSjXwn/De3e63m4fsEcIf8FaUT8cMZ46AWZvaxaxX5km2u0UISJQ==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/components/-/components-7.6.14.tgz", + "integrity": "sha512-kukLj6B2xaIbKAq8E2WUcU0KZ+keuvIo0VcfrtSNHFbNvrNzHshajPC1dTO4NbgI3ey2SmD0rp71eh06TUQ9ng==", "dev": true, "dependencies": { "@radix-ui/react-select": "^1.2.2", "@radix-ui/react-toolbar": "^1.0.4", - "@storybook/client-logger": "7.6.12", + "@storybook/client-logger": "7.6.14", "@storybook/csf": "^0.1.2", "@storybook/global": "^5.0.0", - "@storybook/theming": "7.6.12", - "@storybook/types": "7.6.12", + "@storybook/theming": "7.6.14", + "@storybook/types": "7.6.14", "memoizerific": "^1.11.3", "use-resize-observer": "^9.1.0", "util-deprecate": "^1.0.2" @@ -18837,13 +18838,13 @@ } }, "node_modules/@storybook/core-client": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/core-client/-/core-client-7.6.12.tgz", - "integrity": "sha512-VzVp32tMZsCzM4UIqfvCoJF7N9mBf6dsAxh1/ZgViy75Fht78pGo3JwZXW8osMbFSRpmWD7fxlUM5S7TQOYQug==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/core-client/-/core-client-7.6.14.tgz", + "integrity": "sha512-2q+R6olHLS5GJBTZNdKscTKJ8YwKOatKx6QjktFTfxfLRfBfOGSepignYy8JnEGuU4iTOwBekmUDm5dWAUjnQg==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.12", - "@storybook/preview-api": "7.6.12" + "@storybook/client-logger": "7.6.14", + "@storybook/preview-api": "7.6.14" }, "funding": { "type": "opencollective", @@ -18851,14 +18852,14 @@ } }, "node_modules/@storybook/core-common": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.12.tgz", - "integrity": "sha512-kM9YiBBMM2x5v/oylL7gdO1PS4oehgJC21MivS9p5QZ8uuXKtCQ6UQvI3rzaV+1ZzUA4n+I8MyaMrNIQk8KDbw==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.14.tgz", + "integrity": "sha512-0CIfwdjY5+OO6B+WxeCx3fZou1wk50RU9hFOMGwJ2yj/5ilV06xVHt0HNrA2x37zaK7r370PjOuny0Xudba03g==", "dev": true, "dependencies": { - "@storybook/core-events": "7.6.12", - "@storybook/node-logger": "7.6.12", - "@storybook/types": "7.6.12", + "@storybook/core-events": "7.6.14", + "@storybook/node-logger": "7.6.14", + "@storybook/types": "7.6.14", "@types/find-cache-dir": "^3.2.1", "@types/node": "^18.0.0", "@types/node-fetch": "^2.6.4", @@ -18886,9 +18887,9 @@ } }, "node_modules/@storybook/core-common/node_modules/@types/node": { - "version": "18.19.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.14.tgz", - "integrity": "sha512-EnQ4Us2rmOS64nHDWr0XqAD8DsO6f3XR6lf9UIIrZQpUzPVdN/oPuEzfDWNHSyXLvoGgjuEm/sPwFGSSs35Wtg==", + "version": "18.19.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.15.tgz", + "integrity": "sha512-AMZ2UWx+woHNfM11PyAEQmfSxi05jm9OlkxczuHeEqmvwPkYj6MWv44gbzDPefYOLysTOFyI3ziiy2ONmUZfpA==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -18982,9 +18983,9 @@ } }, "node_modules/@storybook/core-events": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.12.tgz", - "integrity": "sha512-IO4cwk7bBCKH6lLnnIlHO9FwQXt/9CzLUAoZSY9msWsdPppCdKlw8ynJI5YarSNKDBUn8ArIfnRf0Mve0KQr9Q==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.14.tgz", + "integrity": "sha512-zuSMjOgju7WLFL+okTXVvOKKNzwqVGRVp5UhXeSikT4aXuVdpfepCfikkjntn12G1ybL7mfFCsBU2DV1lwwp6Q==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -18995,26 +18996,26 @@ } }, "node_modules/@storybook/core-server": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-7.6.12.tgz", - "integrity": "sha512-tjWifKsDnIc8pvbjVyQrOHef70Gcp93Bg3WwuysB8PGk7lcX2RD9zv44HNIyjxdOLSSv66IGKrOldEBL3hab4w==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-7.6.14.tgz", + "integrity": "sha512-OSUunvjXyUiyfGet8ZBz7/Lka6dSgbbVMH7lU6wELIYCd2ZUxU5HQMl9JPesl61wWB4L3JaWFAoMRaCVI7q0xQ==", "dev": true, "dependencies": { "@aw-web-design/x-default-browser": "1.4.126", "@discoveryjs/json-ext": "^0.5.3", - "@storybook/builder-manager": "7.6.12", - "@storybook/channels": "7.6.12", - "@storybook/core-common": "7.6.12", - "@storybook/core-events": "7.6.12", + "@storybook/builder-manager": "7.6.14", + "@storybook/channels": "7.6.14", + "@storybook/core-common": "7.6.14", + "@storybook/core-events": "7.6.14", "@storybook/csf": "^0.1.2", - "@storybook/csf-tools": "7.6.12", + "@storybook/csf-tools": "7.6.14", "@storybook/docs-mdx": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/manager": "7.6.12", - "@storybook/node-logger": "7.6.12", - "@storybook/preview-api": "7.6.12", - "@storybook/telemetry": "7.6.12", - "@storybook/types": "7.6.12", + "@storybook/manager": "7.6.14", + "@storybook/node-logger": "7.6.14", + "@storybook/preview-api": "7.6.14", + "@storybook/telemetry": "7.6.14", + "@storybook/types": "7.6.14", "@types/detect-port": "^1.3.0", "@types/node": "^18.0.0", "@types/pretty-hrtime": "^1.0.0", @@ -19048,9 +19049,9 @@ } }, "node_modules/@storybook/core-server/node_modules/@types/node": { - "version": "18.19.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.14.tgz", - "integrity": "sha512-EnQ4Us2rmOS64nHDWr0XqAD8DsO6f3XR6lf9UIIrZQpUzPVdN/oPuEzfDWNHSyXLvoGgjuEm/sPwFGSSs35Wtg==", + "version": "18.19.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.15.tgz", + "integrity": "sha512-AMZ2UWx+woHNfM11PyAEQmfSxi05jm9OlkxczuHeEqmvwPkYj6MWv44gbzDPefYOLysTOFyI3ziiy2ONmUZfpA==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -19135,9 +19136,9 @@ } }, "node_modules/@storybook/core-server/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -19186,12 +19187,12 @@ } }, "node_modules/@storybook/csf-plugin": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-7.6.12.tgz", - "integrity": "sha512-fe/84AyctJcrpH1F/tTBxKrbjv0ilmG3ZTwVcufEiAzupZuYjQ/0P+Pxs8m8VxiGJZZ1pWofFFDbYi+wERjamQ==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-7.6.14.tgz", + "integrity": "sha512-TYmtuLCzdWGy4/T6KYUBGdzRy/4cJzDQrDzWRWD7a+xcy1Z7wlKkXw+zWfxbNheEnxb146q5lIkRpvhevKgpGA==", "dev": true, "dependencies": { - "@storybook/csf-tools": "7.6.12", + "@storybook/csf-tools": "7.6.14", "unplugin": "^1.3.1" }, "funding": { @@ -19200,9 +19201,9 @@ } }, "node_modules/@storybook/csf-tools": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.12.tgz", - "integrity": "sha512-MdhkYYxSW5I6Jpk34gTkAZsuj9sxe0xdyeUQpNa8CgJxG43F+ehZ6scW/IPjoSG9gCXBUJMekq26UrmbVfsLCQ==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.14.tgz", + "integrity": "sha512-s7XFIi823HhcKxTqHY/uU1QZCujLBjFt6OJa5y3XvwIMoLJWZtuT1PF/QPR0K7iYb9gQnGHwO9lZBfMraUywrQ==", "dev": true, "dependencies": { "@babel/generator": "^7.23.0", @@ -19210,7 +19211,7 @@ "@babel/traverse": "^7.23.2", "@babel/types": "^7.23.0", "@storybook/csf": "^0.1.2", - "@storybook/types": "7.6.12", + "@storybook/types": "7.6.14", "fs-extra": "^11.1.0", "recast": "^0.23.1", "ts-dedent": "^2.0.0" @@ -19274,14 +19275,14 @@ "dev": true }, "node_modules/@storybook/docs-tools": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/docs-tools/-/docs-tools-7.6.12.tgz", - "integrity": "sha512-nY2lqEDTd/fR/D91ZLlIp+boSuJtkb8DqHW7pECy61rJqzGq4QpepRaWjQDKnGTgPItrsPfTPOu6iXvXNK07Ow==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/docs-tools/-/docs-tools-7.6.14.tgz", + "integrity": "sha512-8FCuVnty2d74cgF+qjhI/LTbGlf3mvu1OkKpLMp9xqouPy3X+yo9N8mpe2tIhgpRMTDzDScIeIBUpLrIpjHaXA==", "dev": true, "dependencies": { - "@storybook/core-common": "7.6.12", - "@storybook/preview-api": "7.6.12", - "@storybook/types": "7.6.12", + "@storybook/core-common": "7.6.14", + "@storybook/preview-api": "7.6.14", + "@storybook/types": "7.6.14", "@types/doctrine": "^0.0.3", "assert": "^2.1.0", "doctrine": "^3.0.0", @@ -19299,9 +19300,9 @@ "dev": true }, "node_modules/@storybook/manager": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/manager/-/manager-7.6.12.tgz", - "integrity": "sha512-WMWvswJHGiqJFJb98WQMQfZQhLuVtmci4y/VJGQ/Jnq1nJQs76BCtaeGiHcsYmRaAP1HMI4DbzuTSEgca146xw==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/manager/-/manager-7.6.14.tgz", + "integrity": "sha512-lgowunC/pm2y6d+3j7UJ/CkHpWC0o+nZ9b7mDbkJ6PmezW5Hpy83kbeCxbwRGosYoPQ0izBzVB5ZqGgKrNNDjA==", "dev": true, "funding": { "type": "opencollective", @@ -19309,19 +19310,19 @@ } }, "node_modules/@storybook/manager-api": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.6.12.tgz", - "integrity": "sha512-XA5KQpY44d6mlqt0AlesZ7fsPpm1PCpoV+nRGFBR0YtF6RdPFvrPyHhlGgLkJC4xSyb2YJmLKn8cERSluAcEgQ==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.6.14.tgz", + "integrity": "sha512-kZbcudrpQaYgUCrnBumDBPOvaEcvFBrZjM5v3AvMenVMXTjwlAHF8mZswE/ptpDsico2iSN96nMhd97OyaAuqA==", "dev": true, "dependencies": { - "@storybook/channels": "7.6.12", - "@storybook/client-logger": "7.6.12", - "@storybook/core-events": "7.6.12", + "@storybook/channels": "7.6.14", + "@storybook/client-logger": "7.6.14", + "@storybook/core-events": "7.6.14", "@storybook/csf": "^0.1.2", "@storybook/global": "^5.0.0", - "@storybook/router": "7.6.12", - "@storybook/theming": "7.6.12", - "@storybook/types": "7.6.12", + "@storybook/router": "7.6.14", + "@storybook/theming": "7.6.14", + "@storybook/types": "7.6.14", "dequal": "^2.0.2", "lodash": "^4.17.21", "memoizerific": "^1.11.3", @@ -19341,9 +19342,9 @@ "dev": true }, "node_modules/@storybook/node-logger": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.12.tgz", - "integrity": "sha512-iS44/EjfF6hLecKzICmcpQoB9bmVi4tXx5gVXnbI5ZyziBibRQcg/U191Njl7wY2ScN/RCQOr8lh5k57rI3Prg==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.14.tgz", + "integrity": "sha512-prKUMGxGzeX3epdlin1UU6M1//CoAJM1GrffrFeNntnPr3h6GMTgxNzl85flUhWd4ky/wjC/36dGOI8QRYVtoA==", "dev": true, "funding": { "type": "opencollective", @@ -19351,9 +19352,9 @@ } }, "node_modules/@storybook/postinstall": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/postinstall/-/postinstall-7.6.12.tgz", - "integrity": "sha512-uR0mDPxLzPaouCNrLp8vID8lATVTOtG7HB6lfjjzMdE3sN6MLmK9n2z2nXjb5DRRxOFWMeE1/4Age1/Ml2tnmA==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/postinstall/-/postinstall-7.6.14.tgz", + "integrity": "sha512-ya3e5jvW1eSw4l3lhiGH2g+Gk8py2Tr3PW5ecnH/x1rD8Tt43OHXRQqiFfl7QzOudHxQGKQsO3lhWe8FJXvdbA==", "dev": true, "funding": { "type": "opencollective", @@ -19361,9 +19362,9 @@ } }, "node_modules/@storybook/preview": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/preview/-/preview-7.6.12.tgz", - "integrity": "sha512-7vbeqQY3X+FCt/ccgCuBmj4rkbQebLHGEBAt8elcX0E2pr7SGW57lWhnasU3jeMaz7tNrkcs0gfl4hyVRWUHDg==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/preview/-/preview-7.6.14.tgz", + "integrity": "sha512-6Y873pNsJBQuCeR3YDMlRgRW+4Tf+Rj4VdujjvRw/H7ES1+pO8qgcI3VJCcoxqDY9ZNPT/riLh8YOddpLNCgNg==", "dev": true, "funding": { "type": "opencollective", @@ -19371,17 +19372,17 @@ } }, "node_modules/@storybook/preview-api": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.6.12.tgz", - "integrity": "sha512-uSzeMSLnCRROjiofJP0F0niLWL+sboQ5ktHW6BAYoPwprumXduPxKBUVEZNxMbVYoAz9v/kEZmaLauh8LRP2Hg==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.6.14.tgz", + "integrity": "sha512-CnUEkTUK3ei3vw4Ypa9EOxEO9lCKc3HvVHxXu4z6Caoe/hRUc10Q6Nj1A7brqok1QLZ304qc715XdYFMahDhyA==", "dev": true, "dependencies": { - "@storybook/channels": "7.6.12", - "@storybook/client-logger": "7.6.12", - "@storybook/core-events": "7.6.12", + "@storybook/channels": "7.6.14", + "@storybook/client-logger": "7.6.14", + "@storybook/core-events": "7.6.14", "@storybook/csf": "^0.1.2", "@storybook/global": "^5.0.0", - "@storybook/types": "7.6.12", + "@storybook/types": "7.6.14", "@types/qs": "^6.9.5", "dequal": "^2.0.2", "lodash": "^4.17.21", @@ -19397,18 +19398,18 @@ } }, "node_modules/@storybook/react": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/react/-/react-7.6.12.tgz", - "integrity": "sha512-ITDRGi79Qg+z1kGYv+yyJESz/5AsJVdBTMO7tr1qV7gmHElkASt6UR8SBSqKgePOnYgy3k/1PLfbzOs6G4OgYQ==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-7.6.14.tgz", + "integrity": "sha512-esWjMgVkYaIyS4ZvEkTrHUDLu9KkTE+wyiyRBINoZLeczAw1YHI5iNqKDMOAN+pOyCyM6iEYSZasAzsJTAFWYA==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.12", - "@storybook/core-client": "7.6.12", - "@storybook/docs-tools": "7.6.12", + "@storybook/client-logger": "7.6.14", + "@storybook/core-client": "7.6.14", + "@storybook/docs-tools": "7.6.14", "@storybook/global": "^5.0.0", - "@storybook/preview-api": "7.6.12", - "@storybook/react-dom-shim": "7.6.12", - "@storybook/types": "7.6.12", + "@storybook/preview-api": "7.6.14", + "@storybook/react-dom-shim": "7.6.14", + "@storybook/types": "7.6.14", "@types/escodegen": "^0.0.6", "@types/estree": "^0.0.51", "@types/node": "^18.0.0", @@ -19443,9 +19444,9 @@ } }, "node_modules/@storybook/react-dom-shim": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-7.6.12.tgz", - "integrity": "sha512-P8eu/s/RQlc/7Yvr260lqNa6rttxIYiPUuHQBu9oCacwkpB3Xep2R/PUY2CifRHqlDhaOINO/Z79oGZl4EBQRQ==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-7.6.14.tgz", + "integrity": "sha512-Ldmc2tKj1N3vNYZpI791xgTbk0XdqJDm1a09fSRM4CeBu4BI7M9IjnNS4cHNdTeqtK9MbCSzCr1nxfxNCtrtiA==", "dev": true, "funding": { "type": "opencollective", @@ -19457,15 +19458,15 @@ } }, "node_modules/@storybook/react-vite": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-7.6.12.tgz", - "integrity": "sha512-kQjCWmTcHuZM1Mlt1QjpYNXP1TxfkSDFWC36fSEUC0q48wzyjUEZs6YraxZu0YE+zXK+X4tmaZhz8pUPgV3gLw==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-7.6.14.tgz", + "integrity": "sha512-KYJtXWCVZbDnSpiqKe2zmt3CDXbc7JRGoLVWB3T+/SBl7aaOmAcFxVRM7yDPMLxVzs9GTNH3gCj2CD64LZMHjg==", "dev": true, "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "0.3.0", "@rollup/pluginutils": "^5.0.2", - "@storybook/builder-vite": "7.6.12", - "@storybook/react": "7.6.12", + "@storybook/builder-vite": "7.6.14", + "@storybook/react": "7.6.14", "@vitejs/plugin-react": "^3.0.1", "magic-string": "^0.30.0", "react-docgen": "^7.0.0" @@ -19521,9 +19522,9 @@ "dev": true }, "node_modules/@storybook/react/node_modules/@types/node": { - "version": "18.19.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.14.tgz", - "integrity": "sha512-EnQ4Us2rmOS64nHDWr0XqAD8DsO6f3XR6lf9UIIrZQpUzPVdN/oPuEzfDWNHSyXLvoGgjuEm/sPwFGSSs35Wtg==", + "version": "18.19.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.15.tgz", + "integrity": "sha512-AMZ2UWx+woHNfM11PyAEQmfSxi05jm9OlkxczuHeEqmvwPkYj6MWv44gbzDPefYOLysTOFyI3ziiy2ONmUZfpA==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -19542,12 +19543,12 @@ } }, "node_modules/@storybook/router": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.6.12.tgz", - "integrity": "sha512-1fqscJbePFJXhapqiv7fAIIqAvouSsdPnqWjJGJrUMR6JBtRYMcrb3MnDeqi9OYnU73r65BrQBPtSzWM8nP0LQ==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.6.14.tgz", + "integrity": "sha512-eVD7jVZeM8mppEtHsvkKIEN92stsdbiXDHG49iNVnw+ojOSjJ1HR8+Pm8wy5Cc2pcyoZEHeU356kaP9gXOhuOQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.12", + "@storybook/client-logger": "7.6.14", "memoizerific": "^1.11.3", "qs": "^6.10.0" }, @@ -19557,13 +19558,13 @@ } }, "node_modules/@storybook/source-loader": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/source-loader/-/source-loader-7.6.12.tgz", - "integrity": "sha512-fGmE9E4lJ+YCGNTnxQ/HtQnI21lufkfeUDBH4jKGf2TLPFBJWjpqaRwUZu5mVEvfMlNx9MnU+ZG4ISPFjlBHRg==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/source-loader/-/source-loader-7.6.14.tgz", + "integrity": "sha512-Ii/APRpuqkve1NQhAX9QKzcsvp/TkhkkLUZK2PCsRaxAZkSLPqK/TrsmctOACtkkg5HP+Mb9I7LHytHFsnhfZg==", "dev": true, "dependencies": { "@storybook/csf": "^0.1.2", - "@storybook/types": "7.6.12", + "@storybook/types": "7.6.14", "estraverse": "^5.2.0", "lodash": "^4.17.21", "prettier": "^2.8.0" @@ -19589,14 +19590,14 @@ } }, "node_modules/@storybook/telemetry": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/telemetry/-/telemetry-7.6.12.tgz", - "integrity": "sha512-eBG3sLb9CZ05pyK2JXBvnaAsxDzbZH57VyhtphhuZmx0DqF/78qIoHs9ebRJpJWV0sL5rtT9vIq8QXpQhDHLWg==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/telemetry/-/telemetry-7.6.14.tgz", + "integrity": "sha512-F+a9Q4dHCpuBLQmB05DOLosU8p1Otj3Vd+/5EF9QUFSn4C64z1gmMc3jzF3iUgktY53HdoUqR871w3GoOJ7g9A==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.12", - "@storybook/core-common": "7.6.12", - "@storybook/csf-tools": "7.6.12", + "@storybook/client-logger": "7.6.14", + "@storybook/core-common": "7.6.14", + "@storybook/csf-tools": "7.6.14", "chalk": "^4.1.0", "detect-package-manager": "^2.0.1", "fetch-retry": "^5.0.2", @@ -19696,13 +19697,13 @@ } }, "node_modules/@storybook/theming": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.6.12.tgz", - "integrity": "sha512-P4zoMKlSYbNrWJjQROuz+DZSDEpdf3TUvk203EqBRdElqw2EMHcqZ8+0HGPFfVHpqEj05+B9Mr6R/Z/BURj0lw==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.6.14.tgz", + "integrity": "sha512-jpryYjBAGLkFauSyNEoflSfYqO3srn98llNxhgxpc1P1ocmOzeDwdg7PUWDI9DCuJC+OWaXa1zzLO6uRLyEJAQ==", "dev": true, "dependencies": { "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@storybook/client-logger": "7.6.12", + "@storybook/client-logger": "7.6.14", "@storybook/global": "^5.0.0", "memoizerific": "^1.11.3" }, @@ -19716,12 +19717,12 @@ } }, "node_modules/@storybook/types": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.12.tgz", - "integrity": "sha512-Wsbd+NS10/2yMHQ/26rXHflXam0hm2qufTFiHOX6VXZWxij3slRU88Fnwzp+1QSyjXb0qkEr8dOx7aG00+ItVw==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.14.tgz", + "integrity": "sha512-sJ3qn45M2XLXlOi+wkhXK5xsXbSVzi8YGrusux//DttI3s8wCP3BQSnEgZkBiEktloxPferINHT1er8/9UK7Xw==", "dev": true, "dependencies": { - "@storybook/channels": "7.6.12", + "@storybook/channels": "7.6.14", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -20009,9 +20010,9 @@ } }, "node_modules/@tabler/icons": { - "version": "2.46.0", - "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-2.46.0.tgz", - "integrity": "sha512-Q5G8Pj5IO+Uhc6pszpu5/hGYY018JwEzzvmuqr+gKJtfIvAHA3umpwUilMRLEy89p+WCP+YsDhicMhfBCCv1qA==", + "version": "2.47.0", + "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-2.47.0.tgz", + "integrity": "sha512-4w5evLh+7FUUiA1GucvGj2ReX2TvOjEr4ejXdwL/bsjoSkof6r1gQmzqI+VHrE2CpJpB3al7bCTulOkFa/RcyA==", "dev": true, "funding": { "type": "github", @@ -20019,12 +20020,12 @@ } }, "node_modules/@tabler/icons-react": { - "version": "2.46.0", - "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-2.46.0.tgz", - "integrity": "sha512-X8MRxuslIOFqMjAo+GvUZDpjlOwNYNJTuOsHXf/NBvVI6ygqUf0FUNsDLLA5fQ6k6KtRwxMlgGB+eR8ZG1UP0g==", + "version": "2.47.0", + "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-2.47.0.tgz", + "integrity": "sha512-iqly2FvCF/qUbgmvS8E40rVeYY7laltc5GUjRxQj59DuX0x/6CpKHTXt86YlI2whg4czvd/c8Ce8YR08uEku0g==", "dev": true, "dependencies": { - "@tabler/icons": "2.46.0", + "@tabler/icons": "2.47.0", "prop-types": "^15.7.2" }, "funding": { @@ -20036,9 +20037,9 @@ } }, "node_modules/@tanstack/react-virtual": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.0.2.tgz", - "integrity": "sha512-9XbRLPKgnhMwwmuQMnJMv+5a9sitGNCSEtf/AZXzmJdesYk7XsjYHaEDny+IrJzvPNwZliIIDwCRiaUqR3zzCA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.0.4.tgz", + "integrity": "sha512-tiqKW/e2MJVCr7/pRUXulpkyxllaOclkHNfhKTo4pmHjJIqnhMfwIjc1Q1R0Un3PI3kQywywu/791c8z9u0qeA==", "dev": true, "dependencies": { "@tanstack/virtual-core": "3.0.0" @@ -20175,9 +20176,9 @@ } }, "node_modules/@testing-library/jest-dom": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.4.1.tgz", - "integrity": "sha512-Z7qMM3J2Zw5H/nC2/5CYx5YcuaD56JmDFKNIozZ89VIo6o6Y9FMhssics4e2madEKYDNEpZz3+glPGz0yWMOag==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.4.2.tgz", + "integrity": "sha512-CzqH0AFymEMG48CpzXFriYYkOjk6ZGPCLMhW9e9jg3KMCn5OfJecF8GtGW7yGfR/IgCe3SX8BSwjdzI6BBbZLw==", "dev": true, "dependencies": { "@adobe/css-tools": "^4.3.2", @@ -20275,9 +20276,9 @@ } }, "node_modules/@testing-library/react": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.2.0.tgz", - "integrity": "sha512-7uBnPHyOG6nDGCzv8SLeJbSa33ZoYw7swYpSLIgJvBALdq7l9zPNk33om4USrxy1lKTxXaVfufzLmq83WNfWIw==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.2.1.tgz", + "integrity": "sha512-sGdjws32ai5TLerhvzThYFbpnF9XtL65Cjf+gB0Dhr29BGqK+mAeN7SURSdu+eqgET4ANcWoC7FQpkaiGvBr+A==", "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", @@ -20710,9 +20711,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.42", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.42.tgz", - "integrity": "sha512-ckM3jm2bf/MfB3+spLPWYPUH573plBFwpOhqQ2WottxYV85j1HQFlxmnTq57X1yHY9awZPig06hL/cLMgNWHIQ==", + "version": "4.17.43", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz", + "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==", "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -21027,9 +21028,9 @@ } }, "node_modules/@types/node": { - "version": "20.11.16", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz", - "integrity": "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==", + "version": "20.11.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.17.tgz", + "integrity": "sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==", "dependencies": { "undici-types": "~5.26.4" } @@ -21083,18 +21084,18 @@ "dev": true }, "node_modules/@types/pdfkit": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/@types/pdfkit/-/pdfkit-0.13.3.tgz", - "integrity": "sha512-CN0prCV0n1HssBg34izaTAcWRmq0916SnsmpTsRqIxlbdS6QkDYsZ5/cm6/a5V2MO3501fbZHkv9DLjJCh9W4Q==", + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/@types/pdfkit/-/pdfkit-0.13.4.tgz", + "integrity": "sha512-ixGNDHYJCCKuamY305wbfYSphZ2WPe8FPkjn8oF4fHV+PgPV4V+hecPh2VOS2h4RNtpSB3zQcR4sCpNvvrEb1A==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/pdfmake": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@types/pdfmake/-/pdfmake-0.2.8.tgz", - "integrity": "sha512-9HavCBXKri7lhfwnM4qK012ru2qGYXvV1BVgYuNwa+vX6KFfI2Pfd0YoJ2l8m2UhE2yd8d1KuIBku6+9igDr+Q==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@types/pdfmake/-/pdfmake-0.2.9.tgz", + "integrity": "sha512-uLDKEH3A1fnCd/qXYJB2OnKkkjfdC1oc6HYVYBKxsyN1UsJL/8Lt67T6WFo3umkL+5Zd74M2IYcOf5kwwd9x9w==", "dev": true, "dependencies": { "@types/node": "*", @@ -21158,9 +21159,9 @@ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" }, "node_modules/@types/react": { - "version": "18.2.51", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.51.tgz", - "integrity": "sha512-XeoMaU4CzyjdRr3c4IQQtiH7Rpo18V07rYZUucEZQwOUEtGgTXv7e6igQiQ+xnV6MbMe1qjEmKdgMNnfppnXfg==", + "version": "18.2.55", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.55.tgz", + "integrity": "sha512-Y2Tz5P4yz23brwm2d7jNon39qoAtMMmalOQv6+fEFt1mT+FcM3D841wDpoUvFXhaYenuROCy3FZYqdTjM7qVyA==", "devOptional": true, "dependencies": { "@types/prop-types": "*", @@ -21169,9 +21170,9 @@ } }, "node_modules/@types/react-dom": { - "version": "18.2.18", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz", - "integrity": "sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.19.tgz", + "integrity": "sha512-aZvQL6uUbIJpjZk4U8JZGbau9KDeAwMfmhyWorxgBkqDIEf6ROjRozcmPIicqsUwPUjbkDfHKgGee1Lq65APcA==", "dev": true, "dependencies": { "@types/react": "*" @@ -21331,9 +21332,9 @@ } }, "node_modules/@types/ssh2/node_modules/@types/node": { - "version": "18.19.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.14.tgz", - "integrity": "sha512-EnQ4Us2rmOS64nHDWr0XqAD8DsO6f3XR6lf9UIIrZQpUzPVdN/oPuEzfDWNHSyXLvoGgjuEm/sPwFGSSs35Wtg==", + "version": "18.19.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.15.tgz", + "integrity": "sha512-AMZ2UWx+woHNfM11PyAEQmfSxi05jm9OlkxczuHeEqmvwPkYj6MWv44gbzDPefYOLysTOFyI3ziiy2ONmUZfpA==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -21449,9 +21450,9 @@ "dev": true }, "node_modules/@types/validator": { - "version": "13.11.8", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.8.tgz", - "integrity": "sha512-c/hzNDBh7eRF+KbCf+OoZxKbnkpaK/cKp9iLQWqB7muXtM+MtL9SUUH8vCFcLn6dH1Qm05jiexK0ofWY7TfOhQ==", + "version": "13.11.9", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.9.tgz", + "integrity": "sha512-FCTsikRozryfayPuiI46QzH3fnrOoctTjvOYZkho9BTFLCOZ2rgZJHMOVgCOfttjPJcgOx52EpkY0CMfy87MIw==", "dev": true }, "node_modules/@types/ws": { @@ -21476,16 +21477,16 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.20.0.tgz", - "integrity": "sha512-fTwGQUnjhoYHeSF6m5pWNkzmDDdsKELYrOBxhjMrofPqCkoC2k3B2wvGHFxa1CTIqkEn88nlW1HVMztjo2K8Hg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.20.0", - "@typescript-eslint/type-utils": "6.20.0", - "@typescript-eslint/utils": "6.20.0", - "@typescript-eslint/visitor-keys": "6.20.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -21523,9 +21524,9 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -21544,15 +21545,15 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.20.0.tgz", - "integrity": "sha512-bYerPDF/H5v6V76MdMYhjwmwgMA+jlPVqjSDq2cRqMi8bP5sR3Z+RLOiOMad3nsnmDVmn2gAFCyNgh/dIrfP/w==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.20.0", - "@typescript-eslint/types": "6.20.0", - "@typescript-eslint/typescript-estree": "6.20.0", - "@typescript-eslint/visitor-keys": "6.20.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "engines": { @@ -21572,13 +21573,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.20.0.tgz", - "integrity": "sha512-p4rvHQRDTI1tGGMDFQm+GtxP1ZHyAh64WANVoyEcNMpaTFn3ox/3CcgtIlELnRfKzSs/DwYlDccJEtr3O6qBvA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.20.0", - "@typescript-eslint/visitor-keys": "6.20.0" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -21589,13 +21590,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.20.0.tgz", - "integrity": "sha512-qnSobiJQb1F5JjN0YDRPHruQTrX7ICsmltXhkV536mp4idGAYrIyr47zF/JmkJtEcAVnIz4gUYJ7gOZa6SmN4g==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.20.0", - "@typescript-eslint/utils": "6.20.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -21616,9 +21617,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.20.0.tgz", - "integrity": "sha512-MM9mfZMAhiN4cOEcUOEx+0HmuaW3WBfukBZPCfwSqFnQy0grXYtngKCqpQN339X3RrwtzspWJrpbrupKYUSBXQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -21629,13 +21630,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.20.0.tgz", - "integrity": "sha512-RnRya9q5m6YYSpBN7IzKu9FmLcYtErkDkc8/dKv81I9QiLLtVBHrjz+Ev/crAqgMNW2FCsoZF4g2QUylMnJz+g==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.20.0", - "@typescript-eslint/visitor-keys": "6.20.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -21669,9 +21670,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -21690,17 +21691,17 @@ "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.20.0.tgz", - "integrity": "sha512-/EKuw+kRu2vAqCoDwDCBtDRU6CTKbUmwwI7SH7AashZ+W+7o8eiyy6V2cdOqN49KsTcASWsC5QeghYuRDTyOOg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.20.0", - "@typescript-eslint/types": "6.20.0", - "@typescript-eslint/typescript-estree": "6.20.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", "semver": "^7.5.4" }, "engines": { @@ -21727,9 +21728,9 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -21748,12 +21749,12 @@ "dev": true }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.20.0.tgz", - "integrity": "sha512-E8Cp98kRe4gKHjJD4NExXKz/zOJ1A2hhZc+IMVD6i7w4yjIvh6VyuRI0gRtxAsXtoC35uGMaQ9rjI2zJaXDEAw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.20.0", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -22702,13 +22703,16 @@ } }, "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -22759,17 +22763,36 @@ "node": ">=0.10.0" } }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", - "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", + "node_modules/array.prototype.filter": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz", + "integrity": "sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.2.1" + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.4.tgz", + "integrity": "sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -22815,30 +22838,31 @@ } }, "node_modules/array.prototype.tosorted": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.2.tgz", - "integrity": "sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz", + "integrity": "sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.2.1" + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.1.0", + "es-shim-unscopables": "^1.0.2" } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", - "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", "dev": true, "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "is-array-buffer": "^3.0.2", + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", "is-shared-array-buffer": "^1.0.2" }, "engines": { @@ -23094,9 +23118,9 @@ } }, "node_modules/aws-cdk": { - "version": "2.125.0", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.125.0.tgz", - "integrity": "sha512-6qFtaDPzhddhwIbCpqBjMePzZS7bfthGFQYfcwF1OhqMv2f3VpHQQ0f7kz4UxXJXUIR5BbgCnlpawH3c0aNzKw==", + "version": "2.127.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.127.0.tgz", + "integrity": "sha512-0yPiN+/VFVc/NpOryO+1S7b4DBgRSs4JdQ64jhV4QbwaoWZo7KISxdN2cK4pmcVH67BSNCJCjjlf10cYhmMvwA==", "bin": { "cdk": "bin/cdk" }, @@ -23108,9 +23132,9 @@ } }, "node_modules/aws-cdk-lib": { - "version": "2.125.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.125.0.tgz", - "integrity": "sha512-yRcHuvpPYHuvffeJCnTSIqo6y+Qjeuf+BKmr/oyMcMhyfIzcGFFhh+ZQRCTYIJTfTyU6nh73TLhsZ4TmzFuBBA==", + "version": "2.127.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.127.0.tgz", + "integrity": "sha512-pEdp2TqgNLYY+kAo68oVzMDEHJevYoRArZJoH+bjM9YTwqRJJiwF1k6tc78e3jca4sCNDZAgX2ytOgqW6lVTWQ==", "bundleDependencies": [ "@balena/dockerignore", "case", @@ -23130,7 +23154,7 @@ "@balena/dockerignore": "^1.0.2", "case": "1.6.3", "fs-extra": "^11.2.0", - "ignore": "^5.3.0", + "ignore": "^5.3.1", "jsonschema": "^1.4.1", "minimatch": "^3.1.2", "punycode": "^2.3.1", @@ -23267,7 +23291,7 @@ "license": "ISC" }, "node_modules/aws-cdk-lib/node_modules/ignore": { - "version": "5.3.0", + "version": "5.3.1", "inBundle": true, "license": "MIT", "engines": { @@ -24914,9 +24938,9 @@ } }, "node_modules/builtins/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -24935,9 +24959,9 @@ "dev": true }, "node_modules/bullmq": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.1.6.tgz", - "integrity": "sha512-VkLfig+xm4U3hc4QChzuuAy0NGQ9dfPB8o54hmcZHCX9ofp0Zn6bEY+W3Ytkk76eYwPAgXfywDBlAb2Unjl1Rg==", + "version": "5.1.10", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.1.10.tgz", + "integrity": "sha512-53CpB/ALaFYTJof7lqRNvKlJyYejMCOo6Jv1IbQxustKqZGRKqbaD2o8FSfaSWsJtGTN7bHjVbvxVD0g8EUtgQ==", "dependencies": { "cron-parser": "^4.6.0", "glob": "^8.0.3", @@ -24991,9 +25015,9 @@ } }, "node_modules/bullmq/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -25176,13 +25200,17 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.6.tgz", + "integrity": "sha512-Mj50FLHtlsoVfRfnHaZvyrooHcrlceNZdL/QBvJJVd9Ta55qCQK0gs4ss2oZDeV9zFCs6ewzYgVE5yfVmfFpVg==", "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.3", + "set-function-length": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -25268,9 +25296,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001582", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001582.tgz", - "integrity": "sha512-vsJG3V5vgfduaQGVxL53uSX/HUzxyr2eA8xCo36OLal7sRcSZbibJtLeh0qja4sFOr/QQGt4opB4tOy+eOgAxg==", + "version": "1.0.30001585", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001585.tgz", + "integrity": "sha512-yr2BWR1yLXQ8fMpdS/4ZZXpseBgE7o4g41x3a6AJOqZuOi+iE/WdJYAuZ6Y95i4Ohd2Y+9MzIWRR+uGABH4s3Q==", "funding": [ { "type": "opencollective", @@ -25297,11 +25325,11 @@ } }, "node_modules/cdk": { - "version": "2.125.0", - "resolved": "https://registry.npmjs.org/cdk/-/cdk-2.125.0.tgz", - "integrity": "sha512-sSfchANTWXlztwSXJRV2j6dr/3ECwho4orFUmgmdr+4VZirOpREPPr3Uc+3Dao1U08C3O/SjvmBpYDDsIciVcw==", + "version": "2.127.0", + "resolved": "https://registry.npmjs.org/cdk/-/cdk-2.127.0.tgz", + "integrity": "sha512-HHH/miv1r8My2vub7GP0JKyWKz50oiDrmrJfPmG0KXTPD4FSczGEmVrbIC5eN5asvf8exRvVcIoH0ShahavwQA==", "dependencies": { - "aws-cdk": "2.125.0" + "aws-cdk": "2.127.0" }, "bin": { "cdk": "bin/cdk" @@ -25311,18 +25339,18 @@ } }, "node_modules/cdk-nag": { - "version": "2.28.27", - "resolved": "https://registry.npmjs.org/cdk-nag/-/cdk-nag-2.28.27.tgz", - "integrity": "sha512-b9QnWZ6KPoFZtowMxYPlsdTEoMrOfg0jRx09gr4525uHlO6gWf+eNJtXUmQRYcBgaWF05a9vGBnbq+QKoslbNw==", + "version": "2.28.30", + "resolved": "https://registry.npmjs.org/cdk-nag/-/cdk-nag-2.28.30.tgz", + "integrity": "sha512-aGwztQoMikzErJRUp/obeGDt3x+OAmJLEDGuHou/5CSsXTd45siZrrFEMeNL7VVoeLIN3TaQGjdNlcytPDwGqA==", "peerDependencies": { "aws-cdk-lib": "^2.116.0", "constructs": "^10.0.5" } }, "node_modules/cdk-serverless-clamscan": { - "version": "2.6.88", - "resolved": "https://registry.npmjs.org/cdk-serverless-clamscan/-/cdk-serverless-clamscan-2.6.88.tgz", - "integrity": "sha512-tX27dMwoUEsrJ/87HpfSBo2mwMaFXZMuGhKA5FCVnTcrv37TVARizp6S/zaLeGaNjboGMl/b2RUzF9rfxeUHVg==", + "version": "2.6.91", + "resolved": "https://registry.npmjs.org/cdk-serverless-clamscan/-/cdk-serverless-clamscan-2.6.91.tgz", + "integrity": "sha512-n758zaRt166NIXMRsrp7ppgnD67IcSh/HuHnKa/QF0OWSceny61w9zju9FPWU2B1rG8k5LCWRApGnx/tSKxPKw==", "bin": { "0": "assets" }, @@ -25502,15 +25530,9 @@ } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -25523,6 +25545,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -27043,9 +27068,9 @@ } }, "node_modules/css-loader/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -27357,33 +27382,6 @@ "cytoscape": "^3.2.0" } }, - "node_modules/cytoscape-fcose": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", - "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", - "dev": true, - "dependencies": { - "cose-base": "^2.2.0" - }, - "peerDependencies": { - "cytoscape": "^3.2.0" - } - }, - "node_modules/cytoscape-fcose/node_modules/cose-base": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", - "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", - "dev": true, - "dependencies": { - "layout-base": "^2.0.0" - } - }, - "node_modules/cytoscape-fcose/node_modules/layout-base": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", - "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", - "dev": true - }, "node_modules/d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -28240,13 +28238,14 @@ } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.2.tgz", + "integrity": "sha512-SRtsSqsDbgpJBbW3pABMCOt6rQyeM8s8RiyeSN8jYG8sYmt/kGJejbydttUsnDs1tadr19tvhT4ShwMyoqAm4g==", "dependencies": { - "get-intrinsic": "^1.2.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.2", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -28760,14 +28759,14 @@ } }, "node_modules/dotenv": { - "version": "16.4.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.1.tgz", - "integrity": "sha512-CjA3y+Dr3FyFDOAMnxZEGtnW9KBR2M0JvvUtXNW+dYJL5ROWxP9DUHCwgFqpMk0OXCc0ljhaNTr2w/kutYIcHQ==", + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.2.tgz", + "integrity": "sha512-rZSSFxke7d9nYQ5NeMIwp5PP+f8wXgKNljpOb7KtH6SKW1cEqcXAz9VSJYVLKe7Jhup/gUYOkaeSVyK8GJ+nBg==", "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" + "url": "https://dotenvx.com" } }, "node_modules/dotenv-expand": { @@ -28849,9 +28848,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.653", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.653.tgz", - "integrity": "sha512-wA2A2LQCqnEwQAvwADQq3KpMpNwgAUBnRmrFgRzHnPhbQUFArTR32Ab46f4p0MovDLcg4uqd4nCsN2hTltslpA==" + "version": "1.4.665", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.665.tgz", + "integrity": "sha512-UpyCWObBoD+nSZgOC2ToaIdZB0r9GhqT2WahPKiSki6ckkSuKhQNso8V2PrFcHBMleI/eqbKgVQgVC4Wni4ilw==" }, "node_modules/elkjs": { "version": "0.9.1", @@ -28991,9 +28990,9 @@ } }, "node_modules/envinfo": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", - "integrity": "sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg==", + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.1.tgz", + "integrity": "sha512-8PiZgZNIB4q/Lw4AhOvAfB/ityHAd2bli3lESSWmWSzSsl5dKpy5N1d1Rfkd2teq/g9xN90lc6o98DOjMeYHpg==", "bin": { "envinfo": "dist/cli.js" }, @@ -29092,6 +29091,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "dev": true + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-get-iterator": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", @@ -29113,25 +29126,29 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz", - "integrity": "sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==", + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.16.tgz", + "integrity": "sha512-CREG2A9Vq7bpDRnldhFcMKuKArvkZtsH6Y0DHOHVg49qhf+LD8uEdUM3OkOAICv0EziGtDEnQtqY2/mfBILpFw==", "dev": true, "dependencies": { "asynciterator.prototype": "^1.0.0", - "call-bind": "^1.0.2", + "call-bind": "^1.0.6", "define-properties": "^1.2.1", - "es-abstract": "^1.22.1", - "es-set-tostringtag": "^2.0.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.2", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", "globalthis": "^1.0.3", - "has-property-descriptors": "^1.0.0", + "has-property-descriptors": "^1.0.1", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", + "internal-slot": "^1.0.7", "iterator.prototype": "^1.1.2", - "safe-array-concat": "^1.0.1" + "safe-array-concat": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/es-module-lexer": { @@ -29326,9 +29343,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "engines": { "node": ">=6" } @@ -29608,19 +29625,19 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "48.0.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.0.4.tgz", - "integrity": "sha512-A0cH+5svWPXzGZszBjXA1t0aAqVGS+/x3i02KFmb73rU0iMLnadEcVWcD/dGBZHIfAMKr3YpWh58f6wn4N909w==", + "version": "48.0.6", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.0.6.tgz", + "integrity": "sha512-LgwXOX6TWxxFYcbdVe+BJ94Kl/pgjSPYHLzqEdAMXTA1BH9WDx7iJ+9/iDajPF64LtzWX8C1mCfpbMZjJGhAOw==", "dev": true, "dependencies": { - "@es-joy/jsdoccomment": "~0.41.0", + "@es-joy/jsdoccomment": "~0.42.0", "are-docs-informative": "^0.0.2", "comment-parser": "1.4.1", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", "esquery": "^1.5.0", "is-builtin-module": "^3.2.1", - "semver": "^7.5.4", + "semver": "^7.6.0", "spdx-expression-parse": "^4.0.0" }, "engines": { @@ -29643,9 +29660,9 @@ } }, "node_modules/eslint-plugin-jsdoc/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -29783,9 +29800,9 @@ } }, "node_modules/eslint-plugin-json-files/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -30545,22 +30562,22 @@ } }, "node_modules/expo": { - "version": "50.0.4", - "resolved": "https://registry.npmjs.org/expo/-/expo-50.0.4.tgz", - "integrity": "sha512-8QWBvYZyKFd7pHxbtri8/ZITBR19QbrW2IkezAhs3ZOHR2kluSgNfyo9ojAe7GnOnE8hCB6Xe83Dbm0R3Ealhw==", + "version": "50.0.6", + "resolved": "https://registry.npmjs.org/expo/-/expo-50.0.6.tgz", + "integrity": "sha512-CVg0h9bmYeTWtw4EOL0HKNL+zu84YZl5nLWRPKrcpt8jox1VQQAYmvJGMdM5gSRxq5CFNLlWGxq9O8Zvfi1SOQ==", "dependencies": { "@babel/runtime": "^7.20.0", - "@expo/cli": "0.17.3", + "@expo/cli": "0.17.5", "@expo/config": "8.5.4", "@expo/config-plugins": "7.8.4", - "@expo/metro-config": "0.17.3", + "@expo/metro-config": "0.17.4", "@expo/vector-icons": "^14.0.0", "babel-preset-expo": "~10.0.1", "expo-asset": "~9.0.2", - "expo-file-system": "~16.0.5", + "expo-file-system": "~16.0.6", "expo-font": "~11.10.2", "expo-keep-awake": "~12.8.2", - "expo-modules-autolinking": "1.10.2", + "expo-modules-autolinking": "1.10.3", "expo-modules-core": "1.11.8", "fbemitter": "^3.0.0", "whatwg-url-without-unicode": "8.0.0-3" @@ -30606,9 +30623,9 @@ } }, "node_modules/expo-file-system": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-16.0.5.tgz", - "integrity": "sha512-JpKMbKfwTaMCbwUwq7MwcSbPR7r+IqZEL3RFam3ClPHDtKLnlEoywREeaDsWjSZb7dS25hG3WqXspfTuugCDvg==", + "version": "16.0.6", + "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-16.0.6.tgz", + "integrity": "sha512-ATCHL7nEg2WwKeamW/SDTR9jBEqM5wncFq594ftKS5QFmhKIrX48d9jyPFGnNq+6h8AGPg4QKh2KCA4OY49L4g==", "peerDependencies": { "expo": "*" } @@ -30633,9 +30650,9 @@ } }, "node_modules/expo-modules-autolinking": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-1.10.2.tgz", - "integrity": "sha512-OEeoz0+zGx5EJwGtDm9pSywCr+gUCaisZV0mNkK7V3fuRl+EVPBSsI+957JwAc4ZxVps95jy28eLcRRtQ33yVg==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-1.10.3.tgz", + "integrity": "sha512-pn4n2Dl4iRh/zUeiChjRIe1C7EqOw1qhccr85viQV7W6l5vgRpY0osE51ij5LKg/kJmGRcJfs12+PwbdTplbKw==", "dependencies": { "@expo/config": "~8.5.0", "chalk": "^4.1.0", @@ -31309,9 +31326,9 @@ } }, "node_modules/fastq": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.0.tgz", - "integrity": "sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dependencies": { "reusify": "^1.0.4" } @@ -31408,9 +31425,9 @@ "dev": true }, "node_modules/fflate": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.1.tgz", - "integrity": "sha512-/exOvEuc+/iaUm105QIiOt4LpBdMTWsXxqR0HDF35vx3fmaKzw7354gTilCh5rkzEt8WYyG//ku3h3nRmd7CHQ==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", "dev": true }, "node_modules/fhirpath": { @@ -31833,9 +31850,9 @@ "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==" }, "node_modules/flow-parser": { - "version": "0.227.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.227.0.tgz", - "integrity": "sha512-nOygtGKcX/siZK/lFzpfdHEfOkfGcTW7rNroR1Zsz6T/JxSahPALXVt5qVHq/fgvMJuv096BTKbgxN3PzVBaDA==", + "version": "0.228.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.228.0.tgz", + "integrity": "sha512-xPWkzCO07AnS8X+fQFpWm+tJ+C7aeaiVzJ+rSepbkCXUvUJ6l6squEl63axoMcixyH4wLjmypOzq/+zTD0O93w==", "dev": true, "engines": { "node": ">=0.4.0" @@ -32124,9 +32141,9 @@ } }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -32336,7 +32353,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -32350,7 +32366,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -32667,15 +32682,19 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -32749,13 +32768,14 @@ } }, "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" }, "engines": { "node": ">= 0.4" @@ -33048,12 +33068,12 @@ "dev": true }, "node_modules/graphiql": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/graphiql/-/graphiql-3.1.0.tgz", - "integrity": "sha512-1l2PecYNvFYYNSYq+4vIJOACXkP60Kod0E0SnKu+2f0Ux/npFNr3TfwJLZs7eKqqSh0KODmorvHi/XBP46Ua7A==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/graphiql/-/graphiql-3.1.1.tgz", + "integrity": "sha512-FMNa981Wj8JBJJRTdryNyrVteigS8B7q+Q1fh1rW4IsFPaXNIs1VMs8kwqIZ8zERj4Fc64Ea750g3n6r2w9Zcg==", "dev": true, "dependencies": { - "@graphiql/react": "^0.20.2", + "@graphiql/react": "^0.20.3", "@graphiql/toolkit": "^0.9.1", "graphql-language-service": "^5.2.0", "markdown-it": "^12.2.0" @@ -33252,9 +33272,12 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.1.tgz", - "integrity": "sha512-6J4rC9ROz0UkOpjn0BRtSSqlewDTDYJNQvy8N8RSrPCduUWId1o9BQPEVII/KKBqRk/ZIQff1YbRkUDCH2N5Sg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, "engines": { "node": ">= 0.4" }, @@ -34402,12 +34425,12 @@ } }, "node_modules/internal-slot": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", - "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2", + "es-errors": "^1.3.0", "hasown": "^2.0.0", "side-channel": "^1.0.4" }, @@ -34556,14 +34579,16 @@ } }, "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -35165,11 +35190,11 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", "dependencies": { - "which-typed-array": "^1.1.11" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -35462,9 +35487,9 @@ } }, "node_modules/istanbul-lib-report/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -36236,9 +36261,9 @@ } }, "node_modules/jest-expo": { - "version": "50.0.1", - "resolved": "https://registry.npmjs.org/jest-expo/-/jest-expo-50.0.1.tgz", - "integrity": "sha512-osvA63UDLJ/v7MG9UHjU7WJ0oZ0Krq9UhXxm2s6rdOlnt85ARocyMU57RC0T0yzPN47C9Ref45sEeOIxoV4Mzg==", + "version": "50.0.2", + "resolved": "https://registry.npmjs.org/jest-expo/-/jest-expo-50.0.2.tgz", + "integrity": "sha512-g9Vq4Cpndp6M+bWGNJfyxw+iiZm7o1XpaOEHgtyC1evdy4B9IsEWql1Y2xBH7uD79FwSKhaIz+xCQHZNhnSlAg==", "dev": true, "dependencies": { "@expo/config": "~8.5.0", @@ -36884,9 +36909,9 @@ } }, "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -37390,9 +37415,9 @@ "integrity": "sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ==" }, "node_modules/jose": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/jose/-/jose-5.2.0.tgz", - "integrity": "sha512-oW3PCnvyrcm1HMvGTzqjxxfnEs9EoFOFWi2HsEGhlFVOXxTE3K9GKWVMFoFw06yPUqwpvEWic1BmtUZBI/tIjw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.2.1.tgz", + "integrity": "sha512-qiaQhtQRw6YrOaOj0v59h3R6hUY9NvxBmmnMfKemkqYmBB0tEc97NbLP7ix44VP5p9/0YHG8Vyhzuo5YBNwviA==", "funding": { "url": "https://github.com/sponsors/panva" } @@ -37851,9 +37876,9 @@ } }, "node_modules/jsonwebtoken/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -38902,9 +38927,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.6", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.6.tgz", - "integrity": "sha512-n62qCLbPjNjyo+owKtveQxZFZTBm+Ms6YoGD23Wew6Vw337PElFNifQpknPruVRQV57kVShPnLGo9vWxVhpPvA==", + "version": "0.30.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", + "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -39505,9 +39530,9 @@ } }, "node_modules/mdast-util-mdx-jsx": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.0.0.tgz", - "integrity": "sha512-XZuPPzQNBPAlaqsTTgRrcJnyFbSOBovSadFgbFu8SnuNgm+6Bdx1K+IWoitsmj6Lq6MNtI+ytOqwN70n//NaBA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.0.tgz", + "integrity": "sha512-A8AJHlR7/wPQ3+Jre1+1rq040fX9A4Q1jG8JxmSNp/PLPHg80A6475wxTp3KzHpApFH6yWxFotHrJQA3dXP6/w==", "dev": true, "dependencies": { "@types/estree-jsx": "^1.0.0", @@ -39745,17 +39770,16 @@ } }, "node_modules/mermaid": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.7.0.tgz", - "integrity": "sha512-PsvGupPCkN1vemAAjScyw4pw34p4/0dZkSrqvAB26hUvJulOWGIwt35FZWmT9wPIi4r0QLa5X0PB4YLIGn0/YQ==", + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.8.0.tgz", + "integrity": "sha512-9CzfSreRjdDJxX796+jW4zjEq0DVw5xVF0nWsqff8OTbrt+ml0TZ5PyYUjjUZJa2NYxYJZZXewEquxGiM8qZEA==", "dev": true, "dependencies": { "@braintree/sanitize-url": "^6.0.1", "@types/d3-scale": "^4.0.3", "@types/d3-scale-chromatic": "^3.0.0", - "cytoscape": "^3.23.0", + "cytoscape": "^3.28.1", "cytoscape-cose-bilkent": "^4.1.0", - "cytoscape-fcose": "^2.1.0", "d3": "^7.4.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.10", @@ -42699,7 +42723,6 @@ "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -43423,9 +43446,9 @@ } }, "node_modules/node-abi/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -43520,9 +43543,9 @@ } }, "node_modules/node-fetch-native": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.1.tgz", - "integrity": "sha512-bW9T/uJDPAJB2YNYEpWzE54U5O3MQidXsOyTfnbKYtTtFexRvGzb1waphBN4ZwP6EcIvYYEOwW0b72BpAqydTw==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.2.tgz", + "integrity": "sha512-69mtXOFZ6hSkYiXAVB5SqaRvrbITC/NPyqv7yuu/qw0nmgPyYbIMYYNIDhNtwPrzk0ptrimrLz/hhjvm4w5Z+w==", "dev": true }, "node_modules/node-fetch/node_modules/tr46": { @@ -43908,9 +43931,9 @@ } }, "node_modules/node-gyp/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -44181,9 +44204,9 @@ } }, "node_modules/normalize-package-data/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -44242,9 +44265,9 @@ } }, "node_modules/npm-check-updates": { - "version": "16.14.14", - "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-16.14.14.tgz", - "integrity": "sha512-Y3ajS/Ep40jM489rLBdz9jehn/BMil5s9fA4PSr2ZJxxSmtLWCSmRqsI2IEZ9Nb3MTMu8a3s7kBs0l+JbjdkTA==", + "version": "16.14.15", + "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-16.14.15.tgz", + "integrity": "sha512-WH0wJ9j6CP7Azl+LLCxWAYqroT2IX02kRIzgK/fg0rPpMbETgHITWBdOPtrv521xmA3JMgeNsQ62zvVtS/nCmQ==", "dev": true, "dependencies": { "chalk": "^5.3.0", @@ -44352,9 +44375,9 @@ } }, "node_modules/npm-check-updates/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -44431,9 +44454,9 @@ } }, "node_modules/npm-install-checks/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -44497,9 +44520,9 @@ } }, "node_modules/npm-package-arg/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -44569,9 +44592,9 @@ } }, "node_modules/npm-pick-manifest/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -45223,15 +45246,16 @@ } }, "node_modules/object.groupby": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", - "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.2.tgz", + "integrity": "sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1" + "array.prototype.filter": "^1.0.3", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.0.0" } }, "node_modules/object.hasown": { @@ -45681,9 +45705,9 @@ } }, "node_modules/package-json/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -46493,9 +46517,9 @@ } }, "node_modules/pkg-fetch/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -46856,9 +46880,9 @@ } }, "node_modules/polished": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/polished/-/polished-4.2.2.tgz", - "integrity": "sha512-Sz2Lkdxz6F2Pgnpi9U5Ng/WdWAUZxmHrNPoVlm3aAemxoy2Qy7LGjQg4uf8qKelDAUW94F4np3iH2YPf2qefcQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz", + "integrity": "sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==", "dev": true, "dependencies": { "@babel/runtime": "^7.17.8" @@ -46880,9 +46904,9 @@ } }, "node_modules/postcss": { - "version": "8.4.33", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", - "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", "funding": [ { "type": "opencollective", @@ -47063,9 +47087,9 @@ } }, "node_modules/postcss-loader/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -47768,9 +47792,9 @@ } }, "node_modules/prettier": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", - "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -48843,9 +48867,9 @@ } }, "node_modules/react-intersection-observer": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.6.0.tgz", - "integrity": "sha512-ZJCi6aOY4kIulpUhWRNIFTyMUKOmSA25iw8sKH07fhlqWTaWD5CWvWarqH4N31EyjCFKsgetvr/amRpnuEWzRg==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.8.0.tgz", + "integrity": "sha512-wXHvMQUsTagh3X0Z6jDtGkIXc3VVCd2tjDRYR9kII3GKrZr0XF0xtpfdamo2n8BSF+zzfeeBVOTjxZWpBp9X0g==", "dev": true, "peerDependencies": { "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", @@ -48905,17 +48929,17 @@ } }, "node_modules/react-native": { - "version": "0.73.3", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.73.3.tgz", - "integrity": "sha512-RSQDtT2DNUcmB4IgmW9NhRb5wqvXFl6DI2NEJmt0ps2OrVHpoA8Tkq+lkFOA/fvPscJKtFKEHFBDSR5UHR3PUw==", + "version": "0.73.4", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.73.4.tgz", + "integrity": "sha512-VtS+Yr6OOTIuJGDECIYWzNU8QpJjASQYvMtfa/Hvm/2/h5GdB6W9H9TOmh13x07Lj4AOhNMx3XSsz6TdrO4jIg==", "dependencies": { "@jest/create-cache-key-function": "^29.6.3", "@react-native-community/cli": "12.3.2", "@react-native-community/cli-platform-android": "12.3.2", "@react-native-community/cli-platform-ios": "12.3.2", "@react-native/assets-registry": "0.73.1", - "@react-native/codegen": "0.73.2", - "@react-native/community-cli-plugin": "0.73.14", + "@react-native/codegen": "0.73.3", + "@react-native/community-cli-plugin": "0.73.16", "@react-native/gradle-plugin": "0.73.4", "@react-native/js-polyfills": "0.73.1", "@react-native/normalize-colors": "0.73.2", @@ -49230,13 +49254,13 @@ } }, "node_modules/react-router-dom": { - "version": "6.21.3", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.21.3.tgz", - "integrity": "sha512-kNzubk7n4YHSrErzjLK72j0B5i969GsuCGazRl3G6j1zqZBLjuSlYBdVdkDOgzGdPIffUOc9nmgiadTEVoq91g==", + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.0.tgz", + "integrity": "sha512-z2w+M4tH5wlcLmH3BMMOMdrtrJ9T3oJJNsAlBJbwk+8Syxd5WFJ7J5dxMEW0/GEXD1BBis4uXRrNIz3mORr0ag==", "dev": true, "dependencies": { - "@remix-run/router": "1.14.2", - "react-router": "6.21.3" + "@remix-run/router": "1.15.0", + "react-router": "6.22.0" }, "engines": { "node": ">=14.0.0" @@ -49247,12 +49271,12 @@ } }, "node_modules/react-router-dom/node_modules/react-router": { - "version": "6.21.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.3.tgz", - "integrity": "sha512-a0H638ZXULv1OdkmiK6s6itNhoy33ywxmUFT/xtSoVyf9VnC7n7+VT4LjVzdIHSaF5TIh9ylUgxMXksHTgGrKg==", + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.0.tgz", + "integrity": "sha512-q2yemJeg6gw/YixRlRnVx6IRJWZD6fonnfZhN1JIOhV2iJCPeRNSH3V1ISwHf+JWcESzLC3BOLD1T07tmO5dmg==", "dev": true, "dependencies": { - "@remix-run/router": "1.14.2" + "@remix-run/router": "1.15.0" }, "engines": { "node": ">=14.0.0" @@ -49444,9 +49468,9 @@ } }, "node_modules/read-package-json/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -49747,15 +49771,16 @@ } }, "node_modules/reflect.getprototypeof": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", - "integrity": "sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.5.tgz", + "integrity": "sha512-62wgfC8dJWrmxv44CA36pLDnP6KKl3Vhxb7PL+8+qrrFMMoJij4vgiMP8zV4O8+CBMXY1mHxI5fITGHXFHVmQQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.0.0", + "get-intrinsic": "^1.2.3", "globalthis": "^1.0.3", "which-builtin-type": "^1.1.3" }, @@ -50642,13 +50667,13 @@ "optional": true }, "node_modules/safe-regex-test": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.2.tgz", - "integrity": "sha512-83S9w6eFq12BBIJYvjMux6/dkirb8+4zJRA9cxNBVb7Wq5fJBW+Xze48WqR8pxua7bDuAaaAxtVVd4Idjp1dBQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", "dev": true, "dependencies": { - "call-bind": "^1.0.5", - "get-intrinsic": "^1.2.2", + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", "is-regex": "^1.1.4" }, "engines": { @@ -50823,9 +50848,9 @@ } }, "node_modules/semver-diff/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -51105,13 +51130,14 @@ "dev": true }, "node_modules/set-function-length": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", - "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", + "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", "dependencies": { - "define-data-property": "^1.1.1", + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.2", + "get-intrinsic": "^1.2.3", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.1" }, @@ -51286,13 +51312,17 @@ "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz", + "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -52167,9 +52197,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", - "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==", + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", + "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", "dev": true }, "node_modules/spdy": { @@ -52598,12 +52628,12 @@ "dev": true }, "node_modules/storybook": { - "version": "7.6.12", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-7.6.12.tgz", - "integrity": "sha512-zcH9CwIsE8N4PX3he5vaJ3mTTWGxu7cxJ/ag9oja/k3N5/IvQjRyIU1TLkRVb28BB8gaLyorpnc4C4aLVGy4WQ==", + "version": "7.6.14", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-7.6.14.tgz", + "integrity": "sha512-4WMb/Dyzl4QzAd1X1b13cJXwynI7fGbT3qGy+X169hsXn6u73tlRcuPXrTsEO9a+rNBxZiBEBJf5poYxCH2j5Q==", "dev": true, "dependencies": { - "@storybook/cli": "7.6.12" + "@storybook/cli": "7.6.14" }, "bin": { "sb": "index.js", @@ -53027,9 +53057,9 @@ } }, "node_modules/stripe": { - "version": "14.14.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-14.14.0.tgz", - "integrity": "sha512-P6lvKHxgDzZXto9VMstG1ucv4Ls0U9nOoQhVZABXJ33kRD7zMwkBy5Y4c3BO59O230uSTkOFPLh87YxVzkp0Mg==", + "version": "14.16.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-14.16.0.tgz", + "integrity": "sha512-1gOr2LzafWV84cPIO5Md/QPh4XVPLKULVuRpBVOV3Plq3seiHmg/eeOktX+hDl8jpNZuORHYaUJGrNqrABLwdg==", "dev": true, "dependencies": { "@types/node": ">=8.1.0", @@ -53273,9 +53303,9 @@ } }, "node_modules/superagent/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -54028,9 +54058,9 @@ } }, "node_modules/tinyspy": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz", - "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", "dev": true, "engines": { "node": ">=14.0.0" @@ -54182,9 +54212,9 @@ } }, "node_modules/trough": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", - "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", "dev": true, "funding": { "type": "github", @@ -54192,12 +54222,12 @@ } }, "node_modules/ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz", + "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==", "dev": true, "engines": { - "node": ">=16.13.0" + "node": ">=16" }, "peerDependencies": { "typescript": ">=4.2.0" @@ -54273,9 +54303,9 @@ } }, "node_modules/ts-jest/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -54572,26 +54602,26 @@ } }, "node_modules/turbo": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/turbo/-/turbo-1.12.2.tgz", - "integrity": "sha512-BcoQjBZ+LJCMdjzWhzQflOinUjek28rWXj07aaaAQ8T3Ehs0JFSjIsXOm4qIbo52G4xk3gFVcUtJhh/QRADl7g==", + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/turbo/-/turbo-1.12.3.tgz", + "integrity": "sha512-a6q8I0TK9ohACYbkmxzG/JYPuDC4VCvfmXLTlf321qQ4BIAhoyaOj/O2g+zJ6L1vNYnZ82G4LrbMfgLLngbLsg==", "dev": true, "bin": { "turbo": "bin/turbo" }, "optionalDependencies": { - "turbo-darwin-64": "1.12.2", - "turbo-darwin-arm64": "1.12.2", - "turbo-linux-64": "1.12.2", - "turbo-linux-arm64": "1.12.2", - "turbo-windows-64": "1.12.2", - "turbo-windows-arm64": "1.12.2" + "turbo-darwin-64": "1.12.3", + "turbo-darwin-arm64": "1.12.3", + "turbo-linux-64": "1.12.3", + "turbo-linux-arm64": "1.12.3", + "turbo-windows-64": "1.12.3", + "turbo-windows-arm64": "1.12.3" } }, "node_modules/turbo-darwin-64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-1.12.2.tgz", - "integrity": "sha512-Aq/ePQ5KNx6XGwlZWTVTqpQYfysm1vkwkI6kAYgrX5DjMWn+tUXrSgNx4YNte0F+V4DQ7PtuWX+jRG0h0ZNg0A==", + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-1.12.3.tgz", + "integrity": "sha512-dDglIaux+A4jOnB9CDH69sujmrnuLJLrKw1t3J+if6ySlFuxSwC++gDq9TVuOZo2+S7lFkGh+x5ytn3wp+jE8Q==", "cpu": [ "x64" ], @@ -54602,9 +54632,9 @@ ] }, "node_modules/turbo-darwin-arm64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-1.12.2.tgz", - "integrity": "sha512-wTr+dqkwJo/eXE+4SPTSeNBKyyfQJhI6I9sKVlCSBmtaNEqoGNgdVzgMUdqrg9AIFzLIiKO+zhfskNaSWpVFow==", + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-1.12.3.tgz", + "integrity": "sha512-5TqqeujEyHMoVUWGzSzUl5ERSg7HDCdbU3gBs5ziWTpFRpeJ/+Y15kYyZJcMQcubRIH3Y1hL/yA5IhlGdgXOMA==", "cpu": [ "arm64" ], @@ -54615,9 +54645,9 @@ ] }, "node_modules/turbo-linux-64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-1.12.2.tgz", - "integrity": "sha512-BggBKrLojGarDaa2zBo+kUR3fmjpd6bLA8Unm3Aa2oJw0UvEi3Brd+w9lNsPZHXXQYBUzNUY2gCdxf3RteWb0g==", + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-1.12.3.tgz", + "integrity": "sha512-yUreU+/gq4vlBtcdyfjz7slwz4zM1RG8sSXvyHmAS+QXqSrGkegg4qLl2fRbv/c3EyA/XbfcZuD6tcrXkejr6g==", "cpu": [ "x64" ], @@ -54628,9 +54658,9 @@ ] }, "node_modules/turbo-linux-arm64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-1.12.2.tgz", - "integrity": "sha512-v/apSRvVuwYjq1D9MJFsHv2EpGd1S4VoSdZvVfW6FaM06L8CFZa92urNR1svdGYN28YVKwK9Ikc9qudC6t/d5A==", + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-1.12.3.tgz", + "integrity": "sha512-XRwAsp2eRSqZmaMVNrmHoKqofeJMuD87zmefZLTRAObh38hIwKgyl2QRsJIbteob5RN77yFbv3lAJ36UIY5h7w==", "cpu": [ "arm64" ], @@ -54641,9 +54671,9 @@ ] }, "node_modules/turbo-windows-64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-1.12.2.tgz", - "integrity": "sha512-3uDdwXcRGkgopYFdPDpxQiuQjfQ12Fxq0fhj+iGymav0eWA4W4wzYwSdlUp6rT22qOBIzaEsrIspRwx1DsMkNg==", + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-1.12.3.tgz", + "integrity": "sha512-CPnRfnUCtmFeShOtUdMCthySjmyHaoTyh9JueiYFvtCNeO3WfDMj63dpOQstQWHdJFYmIrIGfhAclcds9ePQYA==", "cpu": [ "x64" ], @@ -54654,9 +54684,9 @@ ] }, "node_modules/turbo-windows-arm64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-1.12.2.tgz", - "integrity": "sha512-zNIHnwtQfJSjFi7movwhPQh2rfrcKZ7Xv609EN1yX0gEp9GxooCUi2yNnBQ8wTqFjioA2M5hZtGJQ0RrKaEm/Q==", + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-1.12.3.tgz", + "integrity": "sha512-cYA/wlzvp4vlCNHYJ2AjNS3FLXWwUC/5CJompBkTeKFFB6AviE/iLkbIhFikCVSNXZk/3AGanpMUXIkt3bdlwg==", "cpu": [ "arm64" ], @@ -54720,14 +54750,14 @@ } }, "node_modules/typed-array-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", - "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.1.tgz", + "integrity": "sha512-RSqu1UEuSlrBhHTWC8O9FnPjOduNs4M7rJ4pRKoEjtx1zUNOPN2sSXHLDX+Y2WPbHIxbvg4JFo2DNAEfPIKWoQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "is-typed-array": "^1.1.10" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -54840,9 +54870,9 @@ "dev": true }, "node_modules/ufo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz", - "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.4.0.tgz", + "integrity": "sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==", "dev": true }, "node_modules/uglify-js": { @@ -55118,12 +55148,12 @@ } }, "node_modules/unplugin": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.6.0.tgz", - "integrity": "sha512-BfJEpWBu3aE/AyHx8VaNE/WgouoQxgH9baAiH82JjX8cqVyi3uJQstqwD5J+SZxIK326SZIhsSZlALXVBCknTQ==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.7.1.tgz", + "integrity": "sha512-JqzORDAPxxs8ErLV4x+LL7bk5pk3YlcWqpSNsIkAZj972KzFZLClc/ekppahKkOczGkwIG6ElFgdOgOlK4tXZw==", "dev": true, "dependencies": { - "acorn": "^8.11.2", + "acorn": "^8.11.3", "chokidar": "^3.5.3", "webpack-sources": "^3.2.3", "webpack-virtual-modules": "^0.6.1" @@ -55313,9 +55343,9 @@ } }, "node_modules/update-notifier/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -56021,9 +56051,9 @@ } }, "node_modules/vite-node/node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", + "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -56036,30 +56066,30 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", + "@rollup/rollup-android-arm-eabi": "4.10.0", + "@rollup/rollup-android-arm64": "4.10.0", + "@rollup/rollup-darwin-arm64": "4.10.0", + "@rollup/rollup-darwin-x64": "4.10.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", + "@rollup/rollup-linux-arm64-gnu": "4.10.0", + "@rollup/rollup-linux-arm64-musl": "4.10.0", + "@rollup/rollup-linux-riscv64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-musl": "4.10.0", + "@rollup/rollup-win32-arm64-msvc": "4.10.0", + "@rollup/rollup-win32-ia32-msvc": "4.10.0", + "@rollup/rollup-win32-x64-msvc": "4.10.0", "fsevents": "~2.3.2" } }, "node_modules/vite-node/node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", + "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", "dev": true, "dependencies": { "esbuild": "^0.19.3", - "postcss": "^8.4.32", + "postcss": "^8.4.35", "rollup": "^4.2.0" }, "bin": { @@ -56510,9 +56540,9 @@ } }, "node_modules/vitest/node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", + "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -56525,19 +56555,19 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", + "@rollup/rollup-android-arm-eabi": "4.10.0", + "@rollup/rollup-android-arm64": "4.10.0", + "@rollup/rollup-darwin-arm64": "4.10.0", + "@rollup/rollup-darwin-x64": "4.10.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", + "@rollup/rollup-linux-arm64-gnu": "4.10.0", + "@rollup/rollup-linux-arm64-musl": "4.10.0", + "@rollup/rollup-linux-riscv64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-musl": "4.10.0", + "@rollup/rollup-win32-arm64-msvc": "4.10.0", + "@rollup/rollup-win32-ia32-msvc": "4.10.0", + "@rollup/rollup-win32-x64-msvc": "4.10.0", "fsevents": "~2.3.2" } }, @@ -56554,13 +56584,13 @@ } }, "node_modules/vitest/node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", + "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", "dev": true, "dependencies": { "esbuild": "^0.19.3", - "postcss": "^8.4.32", + "postcss": "^8.4.35", "rollup": "^4.2.0" }, "bin": { @@ -57467,9 +57497,9 @@ } }, "node_modules/winston-transport": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.6.0.tgz", - "integrity": "sha512-wbBA9PbPAHxKiygo7ub7BYRiKxms0tpfU2ljtWzb3SjRjv5yl6Ozuy/TkXf00HTAt+Uylo3gSkNwzc4ME0wiIg==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.7.0.tgz", + "integrity": "sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==", "dependencies": { "logform": "^2.3.2", "readable-stream": "^3.6.0", @@ -58011,37 +58041,37 @@ "version": "3.0.3", "license": "Apache-2.0", "devDependencies": { - "@mantine/core": "7.5.1", - "@mantine/dropzone": "7.5.1", - "@mantine/hooks": "7.5.1", - "@mantine/notifications": "7.5.1", + "@mantine/core": "7.5.2", + "@mantine/dropzone": "7.5.2", + "@mantine/hooks": "7.5.2", + "@mantine/notifications": "7.5.2", "@medplum/core": "*", "@medplum/definitions": "*", "@medplum/fhirtypes": "*", "@medplum/mock": "*", "@medplum/react": "*", - "@tabler/icons-react": "2.46.0", - "@testing-library/jest-dom": "6.4.1", - "@testing-library/react": "14.2.0", - "@types/react": "18.2.51", - "@types/react-dom": "18.2.18", + "@tabler/icons-react": "2.47.0", + "@testing-library/jest-dom": "6.4.2", + "@testing-library/react": "14.2.1", + "@types/react": "18.2.55", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", - "postcss": "8.4.33", + "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.0", "rfc6902": "5.1.1", - "vite": "5.0.12" + "vite": "5.1.1" }, "engines": { "node": ">=18.0.0" } }, "packages/app/node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", + "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -58054,30 +58084,30 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", + "@rollup/rollup-android-arm-eabi": "4.10.0", + "@rollup/rollup-android-arm64": "4.10.0", + "@rollup/rollup-darwin-arm64": "4.10.0", + "@rollup/rollup-darwin-x64": "4.10.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", + "@rollup/rollup-linux-arm64-gnu": "4.10.0", + "@rollup/rollup-linux-arm64-musl": "4.10.0", + "@rollup/rollup-linux-riscv64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-musl": "4.10.0", + "@rollup/rollup-win32-arm64-msvc": "4.10.0", + "@rollup/rollup-win32-ia32-msvc": "4.10.0", + "@rollup/rollup-win32-x64-msvc": "4.10.0", "fsevents": "~2.3.2" } }, "packages/app/node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", + "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", "dev": true, "dependencies": { "esbuild": "^0.19.3", - "postcss": "^8.4.32", + "postcss": "^8.4.35", "rollup": "^4.2.0" }, "bin": { @@ -58132,7 +58162,7 @@ "dependencies": { "@medplum/core": "*", "form-data": "4.0.0", - "jose": "5.2.0", + "jose": "5.2.1", "node-fetch": "2.7.0", "pdfmake": "0.2.9", "ssh2": "1.15.0", @@ -58158,12 +58188,12 @@ "version": "3.0.3", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.502.0", + "@aws-sdk/types": "3.511.0", "@medplum/core": "*", - "aws-cdk-lib": "2.125.0", - "cdk": "2.125.0", - "cdk-nag": "2.28.27", - "cdk-serverless-clamscan": "2.6.88", + "aws-cdk-lib": "2.127.0", + "cdk": "2.127.0", + "cdk-nag": "2.28.30", + "cdk-serverless-clamscan": "2.6.91", "constructs": "10.3.0" }, "engines": { @@ -58175,19 +58205,19 @@ "version": "3.0.3", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-acm": "3.504.0", - "@aws-sdk/client-cloudformation": "3.504.0", - "@aws-sdk/client-cloudfront": "3.504.0", - "@aws-sdk/client-ecs": "3.504.0", - "@aws-sdk/client-s3": "3.504.0", - "@aws-sdk/client-ssm": "3.504.0", - "@aws-sdk/client-sts": "3.504.0", - "@aws-sdk/types": "3.502.0", + "@aws-sdk/client-acm": "3.511.0", + "@aws-sdk/client-cloudformation": "3.511.0", + "@aws-sdk/client-cloudfront": "3.511.0", + "@aws-sdk/client-ecs": "3.511.0", + "@aws-sdk/client-s3": "3.511.0", + "@aws-sdk/client-ssm": "3.511.0", + "@aws-sdk/client-sts": "3.511.0", + "@aws-sdk/types": "3.511.0", "@medplum/core": "*", "@medplum/hl7": "*", "aws-sdk-client-mock": "3.0.1", - "commander": "11.1.0", - "dotenv": "16.4.1", + "commander": "12.0.0", + "dotenv": "16.4.2", "fast-glob": "3.3.2", "node-fetch": "2.7.0", "tar": "6.2.0" @@ -58206,11 +58236,11 @@ } }, "packages/cli/node_modules/commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz", + "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==", "engines": { - "node": ">=16" + "node": ">=18" } }, "packages/core": { @@ -58264,8 +58294,8 @@ "raw-loader": "4.0.2", "react": "18.2.0", "react-dom": "18.2.0", - "react-intersection-observer": "9.6.0", - "react-router-dom": "6.21.3", + "react-intersection-observer": "9.8.0", + "react-router-dom": "6.22.0", "typescript": "5.3.3", "url-loader": "4.1.1" }, @@ -58287,10 +58317,10 @@ "version": "3.0.3", "license": "Apache-2.0", "devDependencies": { - "@typescript-eslint/eslint-plugin": "6.20.0", - "@typescript-eslint/parser": "6.20.0", + "@typescript-eslint/eslint-plugin": "6.21.0", + "@typescript-eslint/parser": "6.21.0", "eslint": "8.56.0", - "eslint-plugin-jsdoc": "48.0.4", + "eslint-plugin-jsdoc": "48.0.6", "eslint-plugin-json-files": "4.1.0", "eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-react-refresh": "0.4.5" @@ -58334,12 +58364,12 @@ "devDependencies": { "@medplum/core": "*", "@types/base-64": "1.0.2", - "@types/react": "18.2.51", + "@types/react": "18.2.55", "@types/text-encoding": "0.0.39", "esbuild": "0.20.0", "esbuild-node-externals": "1.12.0", "jest": "29.7.0", - "jest-expo": "50.0.1", + "jest-expo": "50.0.2", "rimraf": "5.0.5", "ts-jest": "29.1.2" }, @@ -58427,32 +58457,32 @@ "version": "3.0.3", "license": "Apache-2.0", "devDependencies": { - "@graphiql/react": "0.20.2", + "@graphiql/react": "0.20.3", "@graphiql/toolkit": "0.9.1", - "@mantine/core": "7.5.1", - "@mantine/hooks": "7.5.1", + "@mantine/core": "7.5.2", + "@mantine/hooks": "7.5.2", "@medplum/core": "*", "@medplum/fhirtypes": "*", "@medplum/react": "*", - "@types/react": "18.2.51", - "@types/react-dom": "18.2.18", - "graphiql": "3.1.0", + "@types/react": "18.2.55", + "@types/react-dom": "18.2.19", + "graphiql": "3.1.1", "graphql": "16.8.1", "graphql-ws": "5.14.3", - "postcss": "8.4.33", + "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "vite": "5.0.12" + "vite": "5.1.1" }, "engines": { "node": ">=18.0.0" } }, "packages/graphiql/node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", + "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -58465,30 +58495,30 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", + "@rollup/rollup-android-arm-eabi": "4.10.0", + "@rollup/rollup-android-arm64": "4.10.0", + "@rollup/rollup-darwin-arm64": "4.10.0", + "@rollup/rollup-darwin-x64": "4.10.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", + "@rollup/rollup-linux-arm64-gnu": "4.10.0", + "@rollup/rollup-linux-arm64-musl": "4.10.0", + "@rollup/rollup-linux-riscv64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-musl": "4.10.0", + "@rollup/rollup-win32-arm64-msvc": "4.10.0", + "@rollup/rollup-win32-ia32-msvc": "4.10.0", + "@rollup/rollup-win32-x64-msvc": "4.10.0", "fsevents": "~2.3.2" } }, "packages/graphiql/node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", + "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", "dev": true, "dependencies": { "esbuild": "^0.19.3", - "postcss": "^8.4.32", + "postcss": "^8.4.35", "rollup": "^4.2.0" }, "bin": { @@ -58578,7 +58608,7 @@ "rfc6902": "5.1.1" }, "devDependencies": { - "@types/pdfmake": "0.2.8" + "@types/pdfmake": "0.2.9" }, "engines": { "node": ">=18.0.0" @@ -58589,42 +58619,42 @@ "version": "3.0.3", "license": "Apache-2.0", "devDependencies": { - "@mantine/core": "7.5.1", - "@mantine/hooks": "7.5.1", - "@mantine/notifications": "7.5.1", + "@mantine/core": "7.5.2", + "@mantine/hooks": "7.5.2", + "@mantine/notifications": "7.5.2", "@medplum/core": "*", "@medplum/definitions": "*", "@medplum/fhirtypes": "*", "@medplum/mock": "*", "@medplum/react-hooks": "*", - "@storybook/addon-actions": "7.6.12", - "@storybook/addon-essentials": "7.6.12", - "@storybook/addon-links": "7.6.12", - "@storybook/addon-storysource": "7.6.12", - "@storybook/builder-vite": "7.6.12", - "@storybook/react": "7.6.12", - "@storybook/react-vite": "7.6.12", - "@tabler/icons-react": "2.46.0", + "@storybook/addon-actions": "7.6.14", + "@storybook/addon-essentials": "7.6.14", + "@storybook/addon-links": "7.6.14", + "@storybook/addon-storysource": "7.6.14", + "@storybook/builder-vite": "7.6.14", + "@storybook/react": "7.6.14", + "@storybook/react-vite": "7.6.14", + "@tabler/icons-react": "2.47.0", "@testing-library/dom": "9.3.4", - "@testing-library/jest-dom": "6.4.1", - "@testing-library/react": "14.2.0", + "@testing-library/jest-dom": "6.4.2", + "@testing-library/react": "14.2.1", "@testing-library/user-event": "14.5.2", "@types/jest": "29.5.12", - "@types/node": "20.11.16", - "@types/react": "18.2.51", - "@types/react-dom": "18.2.18", + "@types/node": "20.11.17", + "@types/react": "18.2.55", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "chromatic": "10.3.1", "jest": "29.7.0", "jest-each": "29.7.0", - "postcss": "8.4.33", + "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", "rfc6902": "5.1.1", "rimraf": "5.0.5", "sinon": "17.0.1", - "storybook": "7.6.12", + "storybook": "7.6.14", "typescript": "5.3.3", "vite-plugin-turbosnap": "^1.0.3" }, @@ -58665,12 +58695,12 @@ "@medplum/fhirtypes": "*", "@medplum/mock": "*", "@testing-library/dom": "9.3.4", - "@testing-library/jest-dom": "6.4.1", - "@testing-library/react": "14.2.0", + "@testing-library/jest-dom": "6.4.2", + "@testing-library/react": "14.2.1", "@types/jest": "29.5.12", - "@types/node": "20.11.16", - "@types/react": "18.2.51", - "@types/react-dom": "18.2.18", + "@types/node": "20.11.17", + "@types/react": "18.2.55", + "@types/react-dom": "18.2.19", "jest": "29.7.0", "jest-each": "29.7.0", "react": "18.2.0", @@ -58740,19 +58770,19 @@ "version": "3.0.3", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-cloudwatch-logs": "3.504.0", - "@aws-sdk/client-lambda": "3.504.0", - "@aws-sdk/client-s3": "3.504.0", - "@aws-sdk/client-secrets-manager": "3.504.0", - "@aws-sdk/client-sesv2": "3.504.0", - "@aws-sdk/client-ssm": "3.504.0", + "@aws-sdk/client-cloudwatch-logs": "3.511.0", + "@aws-sdk/client-lambda": "3.511.0", + "@aws-sdk/client-s3": "3.511.0", + "@aws-sdk/client-secrets-manager": "3.511.0", + "@aws-sdk/client-sesv2": "3.511.0", + "@aws-sdk/client-ssm": "3.511.0", "@aws-sdk/cloudfront-signer": "3.496.0", - "@aws-sdk/lib-storage": "3.504.0", - "@aws-sdk/types": "3.502.0", + "@aws-sdk/lib-storage": "3.511.0", + "@aws-sdk/types": "3.511.0", "@medplum/core": "*", "@medplum/definitions": "*", "@medplum/fhir-router": "*", - "@opentelemetry/auto-instrumentations-node": "0.41.0", + "@opentelemetry/auto-instrumentations-node": "0.41.1", "@opentelemetry/exporter-metrics-otlp-proto": "0.48.0", "@opentelemetry/exporter-trace-otlp-proto": "0.48.0", "@opentelemetry/sdk-metrics": "1.21.0", @@ -58761,14 +58791,14 @@ "@smithy/util-stream": "2.1.1", "bcryptjs": "2.4.3", "body-parser": "1.20.2", - "bullmq": "5.1.6", + "bullmq": "5.1.10", "bytes": "3.1.2", "compression": "1.7.4", "cookie-parser": "1.4.6", "cors": "2.8.5", "cron-validator": "1.3.1", "dataloader": "2.2.2", - "dotenv": "16.4.1", + "dotenv": "16.4.2", "express": "4.18.2", "express-rate-limit": "7.1.5", "express-validator": "7.0.1", @@ -58776,7 +58806,7 @@ "hibp": "14.0.3", "http-graceful-shutdown": "^3.1.13", "ioredis": "5.3.2", - "jose": "5.2.0", + "jose": "5.2.1", "jszip": "3.10.1", "node-fetch": "2.7.0", "nodemailer": "6.9.9", @@ -58803,7 +58833,7 @@ "@types/ioredis": "4.28.10", "@types/json-schema": "7.0.15", "@types/mailparser": "3.4.4", - "@types/node": "20.11.16", + "@types/node": "20.11.17", "@types/node-fetch": "2.6.11", "@types/nodemailer": "6.4.14", "@types/pg": "8.11.0", @@ -58812,7 +58842,7 @@ "@types/supertest": "6.0.2", "@types/ua-parser-js": "0.7.39", "@types/uuid": "9.0.8", - "@types/validator": "13.11.8", + "@types/validator": "13.11.9", "@types/ws": "8.5.10", "aws-sdk-client-mock": "3.0.1", "aws-sdk-client-mock-jest": "3.0.1", diff --git a/package.json b/package.json index e56a03a32a..a7027603bf 100644 --- a/package.json +++ b/package.json @@ -37,10 +37,10 @@ "@babel/preset-react": "7.23.3", "@babel/preset-typescript": "7.23.3", "@cyclonedx/cyclonedx-npm": "1.16.1", - "@microsoft/api-documenter": "7.23.20", - "@microsoft/api-extractor": "7.39.4", + "@microsoft/api-documenter": "7.23.23", + "@microsoft/api-extractor": "7.40.1", "@types/jest": "29.5.12", - "@types/node": "20.11.16", + "@types/node": "20.11.17", "babel-jest": "29.7.0", "babel-preset-vite": "1.1.3", "cross-env": "7.0.3", @@ -49,15 +49,15 @@ "identity-obj-proxy": "3.0.0", "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", - "npm-check-updates": "16.14.14", + "npm-check-updates": "16.14.15", "nyc": "15.1.0", - "prettier": "3.2.4", + "prettier": "3.2.5", "rimraf": "5.0.5", "sort-package-json": "2.7.0", "source-map-explorer": "2.5.3", "ts-node": "10.9.2", "tslib": "2.6.2", - "turbo": "1.12.2", + "turbo": "1.12.3", "typescript": "5.3.3" }, "packageManager": "npm@9.8.1", diff --git a/packages/app/package.json b/packages/app/package.json index 785acb9bb4..f5c1f89d4e 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -29,28 +29,28 @@ "last 1 Chrome versions" ], "devDependencies": { - "@mantine/core": "7.5.1", - "@mantine/dropzone": "7.5.1", - "@mantine/hooks": "7.5.1", - "@mantine/notifications": "7.5.1", + "@mantine/core": "7.5.2", + "@mantine/dropzone": "7.5.2", + "@mantine/hooks": "7.5.2", + "@mantine/notifications": "7.5.2", "@medplum/core": "*", "@medplum/definitions": "*", "@medplum/fhirtypes": "*", "@medplum/mock": "*", "@medplum/react": "*", - "@tabler/icons-react": "2.46.0", - "@testing-library/jest-dom": "6.4.1", - "@testing-library/react": "14.2.0", - "@types/react": "18.2.51", - "@types/react-dom": "18.2.18", + "@tabler/icons-react": "2.47.0", + "@testing-library/jest-dom": "6.4.2", + "@testing-library/react": "14.2.1", + "@types/react": "18.2.55", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", - "postcss": "8.4.33", + "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.0", "rfc6902": "5.1.1", - "vite": "5.0.12" + "vite": "5.1.1" }, "engines": { "node": ">=18.0.0" diff --git a/packages/bot-layer/package.json b/packages/bot-layer/package.json index 496bbf75a7..a96e6e6ac8 100644 --- a/packages/bot-layer/package.json +++ b/packages/bot-layer/package.json @@ -24,7 +24,7 @@ "dependencies": { "@medplum/core": "*", "form-data": "4.0.0", - "jose": "5.2.0", + "jose": "5.2.1", "node-fetch": "2.7.0", "pdfmake": "0.2.9", "ssh2": "1.15.0", diff --git a/packages/cdk/package.json b/packages/cdk/package.json index ad317fe50a..291300e9e0 100644 --- a/packages/cdk/package.json +++ b/packages/cdk/package.json @@ -23,12 +23,12 @@ "test": "jest --runInBand" }, "dependencies": { - "@aws-sdk/types": "3.502.0", + "@aws-sdk/types": "3.511.0", "@medplum/core": "*", - "aws-cdk-lib": "2.125.0", - "cdk": "2.125.0", - "cdk-nag": "2.28.27", - "cdk-serverless-clamscan": "2.6.88", + "aws-cdk-lib": "2.127.0", + "cdk": "2.127.0", + "cdk-nag": "2.28.30", + "cdk-serverless-clamscan": "2.6.91", "constructs": "10.3.0" }, "engines": { diff --git a/packages/cli/package.json b/packages/cli/package.json index e95a6b91f1..fd32f53cd6 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -41,19 +41,19 @@ "test": "jest" }, "dependencies": { - "@aws-sdk/client-acm": "3.504.0", - "@aws-sdk/client-cloudformation": "3.504.0", - "@aws-sdk/client-cloudfront": "3.504.0", - "@aws-sdk/client-ecs": "3.504.0", - "@aws-sdk/client-s3": "3.504.0", - "@aws-sdk/client-ssm": "3.504.0", - "@aws-sdk/client-sts": "3.504.0", - "@aws-sdk/types": "3.502.0", + "@aws-sdk/client-acm": "3.511.0", + "@aws-sdk/client-cloudformation": "3.511.0", + "@aws-sdk/client-cloudfront": "3.511.0", + "@aws-sdk/client-ecs": "3.511.0", + "@aws-sdk/client-s3": "3.511.0", + "@aws-sdk/client-ssm": "3.511.0", + "@aws-sdk/client-sts": "3.511.0", + "@aws-sdk/types": "3.511.0", "@medplum/core": "*", "@medplum/hl7": "*", "aws-sdk-client-mock": "3.0.1", - "commander": "11.1.0", - "dotenv": "16.4.1", + "commander": "12.0.0", + "dotenv": "16.4.2", "fast-glob": "3.3.2", "node-fetch": "2.7.0", "tar": "6.2.0" diff --git a/packages/cli/src/util/command.ts b/packages/cli/src/util/command.ts index 924312d7b6..adc4193276 100644 --- a/packages/cli/src/util/command.ts +++ b/packages/cli/src/util/command.ts @@ -15,7 +15,6 @@ export function createMedplumCommand(name: string): Command { .option('--audience ', 'Audience for JWT authentication') .option('--issuer ', 'Issuer for JWT authentication') .option('--private-key-path ', 'Private key path for JWT assertion') - .option('--audience ', 'Audience for JWT assertion') .option('-p, --profile ', 'Profile name') .option('-v --verbose', 'Verbose output') .addOption( diff --git a/packages/docs/package.json b/packages/docs/package.json index 2da22b8dab..f1c90646a6 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -57,8 +57,8 @@ "raw-loader": "4.0.2", "react": "18.2.0", "react-dom": "18.2.0", - "react-intersection-observer": "9.6.0", - "react-router-dom": "6.21.3", + "react-intersection-observer": "9.8.0", + "react-router-dom": "6.22.0", "typescript": "5.3.3", "url-loader": "4.1.1" }, diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 46dfa23740..ac71d49fdd 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -19,10 +19,10 @@ "author": "Medplum ", "main": "index.cjs", "devDependencies": { - "@typescript-eslint/eslint-plugin": "6.20.0", - "@typescript-eslint/parser": "6.20.0", + "@typescript-eslint/eslint-plugin": "6.21.0", + "@typescript-eslint/parser": "6.21.0", "eslint": "8.56.0", - "eslint-plugin-jsdoc": "48.0.4", + "eslint-plugin-jsdoc": "48.0.6", "eslint-plugin-json-files": "4.1.0", "eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-react-refresh": "0.4.5" diff --git a/packages/expo-polyfills/package.json b/packages/expo-polyfills/package.json index 1fb7bcb969..7635b031bb 100644 --- a/packages/expo-polyfills/package.json +++ b/packages/expo-polyfills/package.json @@ -49,12 +49,12 @@ "devDependencies": { "@medplum/core": "*", "@types/base-64": "1.0.2", - "@types/react": "18.2.51", + "@types/react": "18.2.55", "@types/text-encoding": "0.0.39", "esbuild": "0.20.0", "esbuild-node-externals": "1.12.0", "jest": "29.7.0", - "jest-expo": "50.0.1", + "jest-expo": "50.0.2", "rimraf": "5.0.5", "ts-jest": "29.1.2" }, diff --git a/packages/graphiql/package.json b/packages/graphiql/package.json index 5152dd84b5..4729ee18ac 100644 --- a/packages/graphiql/package.json +++ b/packages/graphiql/package.json @@ -23,23 +23,23 @@ "last 1 Chrome versions" ], "devDependencies": { - "@graphiql/react": "0.20.2", + "@graphiql/react": "0.20.3", "@graphiql/toolkit": "0.9.1", - "@mantine/core": "7.5.1", - "@mantine/hooks": "7.5.1", + "@mantine/core": "7.5.2", + "@mantine/hooks": "7.5.2", "@medplum/core": "*", "@medplum/fhirtypes": "*", "@medplum/react": "*", - "@types/react": "18.2.51", - "@types/react-dom": "18.2.18", - "graphiql": "3.1.0", + "@types/react": "18.2.55", + "@types/react-dom": "18.2.19", + "graphiql": "3.1.1", "graphql": "16.8.1", "graphql-ws": "5.14.3", - "postcss": "8.4.33", + "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "vite": "5.0.12" + "vite": "5.1.1" }, "engines": { "node": ">=18.0.0" diff --git a/packages/mock/package.json b/packages/mock/package.json index 2f7ba91fc9..a19e9127fb 100644 --- a/packages/mock/package.json +++ b/packages/mock/package.json @@ -61,7 +61,7 @@ "rfc6902": "5.1.1" }, "devDependencies": { - "@types/pdfmake": "0.2.8" + "@types/pdfmake": "0.2.9" }, "engines": { "node": ">=18.0.0" diff --git a/packages/react-hooks/package.json b/packages/react-hooks/package.json index 0c4a01a8a3..0115890bd7 100644 --- a/packages/react-hooks/package.json +++ b/packages/react-hooks/package.json @@ -62,12 +62,12 @@ "@medplum/fhirtypes": "*", "@medplum/mock": "*", "@testing-library/dom": "9.3.4", - "@testing-library/jest-dom": "6.4.1", - "@testing-library/react": "14.2.0", + "@testing-library/jest-dom": "6.4.2", + "@testing-library/react": "14.2.1", "@types/jest": "29.5.12", - "@types/node": "20.11.16", - "@types/react": "18.2.51", - "@types/react-dom": "18.2.18", + "@types/node": "20.11.17", + "@types/react": "18.2.55", + "@types/react-dom": "18.2.19", "jest": "29.7.0", "jest-each": "29.7.0", "react": "18.2.0", diff --git a/packages/react/package.json b/packages/react/package.json index 4553fbd3b4..398d22ffca 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -67,42 +67,42 @@ "test": "jest" }, "devDependencies": { - "@mantine/core": "7.5.1", - "@mantine/hooks": "7.5.1", - "@mantine/notifications": "7.5.1", + "@mantine/core": "7.5.2", + "@mantine/hooks": "7.5.2", + "@mantine/notifications": "7.5.2", "@medplum/core": "*", "@medplum/definitions": "*", "@medplum/fhirtypes": "*", "@medplum/mock": "*", "@medplum/react-hooks": "*", - "@storybook/addon-actions": "7.6.12", - "@storybook/addon-essentials": "7.6.12", - "@storybook/addon-links": "7.6.12", - "@storybook/addon-storysource": "7.6.12", - "@storybook/builder-vite": "7.6.12", - "@storybook/react": "7.6.12", - "@storybook/react-vite": "7.6.12", - "@tabler/icons-react": "2.46.0", + "@storybook/addon-actions": "7.6.14", + "@storybook/addon-essentials": "7.6.14", + "@storybook/addon-links": "7.6.14", + "@storybook/addon-storysource": "7.6.14", + "@storybook/builder-vite": "7.6.14", + "@storybook/react": "7.6.14", + "@storybook/react-vite": "7.6.14", + "@tabler/icons-react": "2.47.0", "@testing-library/dom": "9.3.4", - "@testing-library/jest-dom": "6.4.1", - "@testing-library/react": "14.2.0", + "@testing-library/jest-dom": "6.4.2", + "@testing-library/react": "14.2.1", "@testing-library/user-event": "14.5.2", "@types/jest": "29.5.12", - "@types/node": "20.11.16", - "@types/react": "18.2.51", - "@types/react-dom": "18.2.18", + "@types/node": "20.11.17", + "@types/react": "18.2.55", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "chromatic": "10.3.1", "jest": "29.7.0", "jest-each": "29.7.0", - "postcss": "8.4.33", + "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", "rfc6902": "5.1.1", "rimraf": "5.0.5", "sinon": "17.0.1", - "storybook": "7.6.12", + "storybook": "7.6.14", "typescript": "5.3.3", "vite-plugin-turbosnap": "^1.0.3" }, diff --git a/packages/server/package.json b/packages/server/package.json index bdf042e82c..9cd8bdb00d 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -21,19 +21,19 @@ "test": "jest --runInBand" }, "dependencies": { - "@aws-sdk/client-cloudwatch-logs": "3.504.0", - "@aws-sdk/client-lambda": "3.504.0", - "@aws-sdk/client-s3": "3.504.0", - "@aws-sdk/client-secrets-manager": "3.504.0", - "@aws-sdk/client-sesv2": "3.504.0", - "@aws-sdk/client-ssm": "3.504.0", + "@aws-sdk/client-cloudwatch-logs": "3.511.0", + "@aws-sdk/client-lambda": "3.511.0", + "@aws-sdk/client-s3": "3.511.0", + "@aws-sdk/client-secrets-manager": "3.511.0", + "@aws-sdk/client-sesv2": "3.511.0", + "@aws-sdk/client-ssm": "3.511.0", "@aws-sdk/cloudfront-signer": "3.496.0", - "@aws-sdk/lib-storage": "3.504.0", - "@aws-sdk/types": "3.502.0", + "@aws-sdk/lib-storage": "3.511.0", + "@aws-sdk/types": "3.511.0", "@medplum/core": "*", "@medplum/definitions": "*", "@medplum/fhir-router": "*", - "@opentelemetry/auto-instrumentations-node": "0.41.0", + "@opentelemetry/auto-instrumentations-node": "0.41.1", "@opentelemetry/exporter-metrics-otlp-proto": "0.48.0", "@opentelemetry/exporter-trace-otlp-proto": "0.48.0", "@opentelemetry/sdk-metrics": "1.21.0", @@ -42,14 +42,14 @@ "@smithy/util-stream": "2.1.1", "bcryptjs": "2.4.3", "body-parser": "1.20.2", - "bullmq": "5.1.6", + "bullmq": "5.1.10", "bytes": "3.1.2", "compression": "1.7.4", "cookie-parser": "1.4.6", "cors": "2.8.5", "cron-validator": "1.3.1", "dataloader": "2.2.2", - "dotenv": "16.4.1", + "dotenv": "16.4.2", "express": "4.18.2", "express-rate-limit": "7.1.5", "express-validator": "7.0.1", @@ -57,7 +57,7 @@ "hibp": "14.0.3", "http-graceful-shutdown": "^3.1.13", "ioredis": "5.3.2", - "jose": "5.2.0", + "jose": "5.2.1", "jszip": "3.10.1", "node-fetch": "2.7.0", "nodemailer": "6.9.9", @@ -84,7 +84,7 @@ "@types/ioredis": "4.28.10", "@types/json-schema": "7.0.15", "@types/mailparser": "3.4.4", - "@types/node": "20.11.16", + "@types/node": "20.11.17", "@types/node-fetch": "2.6.11", "@types/nodemailer": "6.4.14", "@types/pg": "8.11.0", @@ -93,7 +93,7 @@ "@types/supertest": "6.0.2", "@types/ua-parser-js": "0.7.39", "@types/uuid": "9.0.8", - "@types/validator": "13.11.8", + "@types/validator": "13.11.9", "@types/ws": "8.5.10", "aws-sdk-client-mock": "3.0.1", "aws-sdk-client-mock-jest": "3.0.1", From 1b5f8220362c56624cb75705dff2af51f8c25f23 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Sun, 11 Feb 2024 09:51:57 -0800 Subject: [PATCH 27/81] Fix turborepo for forks (#3926) * Fix turborepo for forks * Wrap in curlies * Fix github actions error * Try again --- .github/workflows/build.yml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b71d0ce6e7..58184b66c9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,9 +22,7 @@ jobs: env: NODE_VERSION: ${{ matrix.node-version }} PG_VERSION: ${{ matrix.pg-version }} - TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: ${{ secrets.TURBO_TEAM }} - TURBO_REMOTE_ONLY: ${{ secrets.TURBO_REMOTE_ONLY }} + SECRETS_TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} services: postgres: image: postgres:${{ matrix.pg-version }} @@ -58,6 +56,16 @@ jobs: ${{ runner.os }}-build-${{ env.cache-name }}- ${{ runner.os }}-build- ${{ runner.os }}- + - name: Setup TurboRepo + # Conditionally setup turborepo + # In the past, turborepo would silently ignore empty environment variables + # This is no longer the case, so we need to check if the secret is set + # You cannot use `if: ${{ secrets.TURBO_TOKEN != '' }}` because secrets are not available in the `if` condition + if: ${{ env.SECRETS_TURBO_TOKEN != '' }} + run: | + echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV + echo "TURBO_TEAM=${{ secrets.TURBO_TEAM }}" >> $GITHUB_ENV + echo "TURBO_REMOTE_ONLY=${{ secrets.TURBO_REMOTE_ONLY }}" >> $GITHUB_ENV - name: Build Project run: ./scripts/build.sh env: From da3049d8e0ccb7a90c47b673d643e641a1d9a2ca Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Sun, 11 Feb 2024 15:25:56 -0800 Subject: [PATCH 28/81] Fixes #3838 - github actions permissions (#3931) --- .github/workflows/build.yml | 2 ++ .github/workflows/prettier-fmt.yml | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 58184b66c9..3bf7c00e6d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,6 +15,8 @@ jobs: build: name: Build runs-on: ubuntu-latest + permissions: + pull-requests: write strategy: matrix: node-version: [18, 20] diff --git a/.github/workflows/prettier-fmt.yml b/.github/workflows/prettier-fmt.yml index 2841ae6004..78b4c61a42 100644 --- a/.github/workflows/prettier-fmt.yml +++ b/.github/workflows/prettier-fmt.yml @@ -15,6 +15,8 @@ jobs: prettier-fmt: name: prettier runs-on: ubuntu-latest + permissions: + pull-requests: write outputs: prettier_fmt_errs: ${{ steps.fmt.outputs.prettier_fmt_errs }} steps: @@ -97,5 +99,5 @@ jobs: echo "See: https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode" echo "" echo "https://github.com/medplum/medplum/commits/${{github.sha}}" - + exit 1 From fd0bb56d645c66929337a3f2b1b27c8f34e2183e Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Mon, 12 Feb 2024 06:56:21 -0800 Subject: [PATCH 29/81] Fixes #3708 - CareTeam.name and Group.name search params (#3934) --- .../fhir/r4/search-parameters-medplum.json | 34 +++++++++++++++++++ .../react/src/ResourceInput/ResourceInput.tsx | 4 ++- .../server/src/migrations/schema/index.ts | 1 + packages/server/src/migrations/schema/v60.ts | 13 +++++++ 4 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 packages/server/src/migrations/schema/v60.ts diff --git a/packages/definitions/dist/fhir/r4/search-parameters-medplum.json b/packages/definitions/dist/fhir/r4/search-parameters-medplum.json index 278822f360..31e0b65d49 100644 --- a/packages/definitions/dist/fhir/r4/search-parameters-medplum.json +++ b/packages/definitions/dist/fhir/r4/search-parameters-medplum.json @@ -607,6 +607,40 @@ "type": "number", "expression": "iif(priority = 'stat', 50, iif(priority = 'asap', 40, iif(priority = 'urgent', 30, iif(priority = 'routine', 20, 10))))" } + }, + { + "fullUrl": "https://medplum.com/fhir/SearchParameter/CareTeam-name", + "resource": { + "resourceType": "SearchParameter", + "id": "CareTeam-name", + "url": "https://medplum.com/fhir/SearchParameter/CareTeam-name", + "version": "4.0.1", + "name": "name", + "status": "draft", + "publisher": "Medplum", + "description": "The name of the care team", + "code": "name", + "base": ["CareTeam"], + "type": "string", + "expression": "CareTeam.name" + } + }, + { + "fullUrl": "https://medplum.com/fhir/SearchParameter/Group-name", + "resource": { + "resourceType": "SearchParameter", + "id": "Group-name", + "url": "https://medplum.com/fhir/SearchParameter/Group-name", + "version": "4.0.1", + "name": "name", + "status": "draft", + "publisher": "Medplum", + "description": "The name of the group", + "code": "name", + "base": ["Group"], + "type": "string", + "expression": "Group.name" + } } ] } diff --git a/packages/react/src/ResourceInput/ResourceInput.tsx b/packages/react/src/ResourceInput/ResourceInput.tsx index 336ebfe829..048ce3f586 100644 --- a/packages/react/src/ResourceInput/ResourceInput.tsx +++ b/packages/react/src/ResourceInput/ResourceInput.tsx @@ -1,9 +1,9 @@ +import { Group, Text } from '@mantine/core'; import { getDisplayString, getReferenceString } from '@medplum/core'; import { OperationOutcome, Patient, Reference, Resource } from '@medplum/fhirtypes'; import { useMedplum, useResource } from '@medplum/react-hooks'; import { forwardRef, useCallback, useState } from 'react'; import { AsyncAutocomplete, AsyncAutocompleteOption } from '../AsyncAutocomplete/AsyncAutocomplete'; -import { Group, Text } from '@mantine/core'; import { ResourceAvatar } from '../ResourceAvatar/ResourceAvatar'; /** @@ -28,6 +28,7 @@ const NAME_RESOURCE_TYPES = [ 'ActivityDefinition', 'Bot', 'CapabilityStatement', + 'CareTeam', 'ClientApplication', 'CodeSystem', 'CompartmentDefinition', @@ -39,6 +40,7 @@ const NAME_RESOURCE_TYPES = [ 'EvidenceVariable', 'ExampleScenario', 'GraphDefinition', + 'Group', 'HealthcareService', 'ImplementationGuide', 'InsurancePlan', diff --git a/packages/server/src/migrations/schema/index.ts b/packages/server/src/migrations/schema/index.ts index fbcee4eda6..8caa6227cd 100644 --- a/packages/server/src/migrations/schema/index.ts +++ b/packages/server/src/migrations/schema/index.ts @@ -58,3 +58,4 @@ export * as v56 from './v56'; export * as v57 from './v57'; export * as v58 from './v58'; export * as v59 from './v59'; +export * as v60 from './v60'; diff --git a/packages/server/src/migrations/schema/v60.ts b/packages/server/src/migrations/schema/v60.ts new file mode 100644 index 0000000000..a2c272f9cd --- /dev/null +++ b/packages/server/src/migrations/schema/v60.ts @@ -0,0 +1,13 @@ +/* + * Generated by @medplum/generator + * Do not edit manually. + */ + +import { PoolClient } from 'pg'; + +export async function run(client: PoolClient): Promise { + await client.query('ALTER TABLE IF EXISTS "CareTeam" ADD COLUMN IF NOT EXISTS "name" TEXT'); + await client.query('CREATE INDEX CONCURRENTLY IF NOT EXISTS CareTeam_name_idx ON "CareTeam" ("name")'); + await client.query('ALTER TABLE IF EXISTS "Group" ADD COLUMN IF NOT EXISTS "name" TEXT'); + await client.query('CREATE INDEX CONCURRENTLY IF NOT EXISTS Group_name_idx ON "Group" ("name")'); +} From 7b3453c1236ab5968601aef0f0f66d1362cbe078 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Mon, 12 Feb 2024 06:56:55 -0800 Subject: [PATCH 30/81] Fixes #3786 - startAsyncRequest and token refresh (#3932) * Fixes #3786 - startAsyncRequest and token refresh * Fixed tests * Cleanup --- packages/core/src/client-test-utils.ts | 27 ++-- packages/core/src/client.test.ts | 166 +++++++++++++++---------- packages/core/src/client.ts | 23 ++-- 3 files changed, 128 insertions(+), 88 deletions(-) diff --git a/packages/core/src/client-test-utils.ts b/packages/core/src/client-test-utils.ts index 765fd6294a..bb800e2976 100644 --- a/packages/core/src/client-test-utils.ts +++ b/packages/core/src/client-test-utils.ts @@ -12,12 +12,25 @@ export function mockFetch( return jest.fn((url: string, options?: any) => { const response = bodyFn(url, options); const responseStatus = isOperationOutcome(response) ? getStatus(response) : status; - return Promise.resolve({ - ok: responseStatus < 400, - status: responseStatus, - headers: { get: () => contentType }, - blob: () => Promise.resolve(response), - json: () => Promise.resolve(response), - }); + return Promise.resolve(mockFetchResponse(responseStatus, response, { 'content-type': contentType })); }); } + +export function mockFetchResponse(status: number, body: any, headers?: Record): Response { + const headersMap = new Map(); + if (headers) { + for (const [key, value] of Object.entries(headers)) { + headersMap.set(key, value); + } + } + if (!headersMap.has('content-type')) { + headersMap.set('content-type', ContentType.FHIR_JSON); + } + return { + ok: status < 400, + status, + headers: headersMap, + blob: () => Promise.resolve(body), + json: () => Promise.resolve(body), + } as unknown as Response; +} diff --git a/packages/core/src/client.test.ts b/packages/core/src/client.test.ts index f9c08d4a21..7d2118eab4 100644 --- a/packages/core/src/client.test.ts +++ b/packages/core/src/client.test.ts @@ -13,9 +13,9 @@ import { NewProjectRequest, NewUserRequest, } from './client'; -import { mockFetch } from './client-test-utils'; +import { mockFetch, mockFetchResponse } from './client-test-utils'; import { ContentType } from './contenttype'; -import { OperationOutcomeError, notFound, unauthorized } from './outcomes'; +import { OperationOutcomeError, accepted, forbidden, notFound, unauthorized } from './outcomes'; import { MockAsyncClientStorage } from './storage'; import { getDataType, isDataTypeLoaded, isProfileLoaded } from './typeschema/types'; import { ProfileResource, createReference } from './utils'; @@ -2440,84 +2440,32 @@ describe('Client', () => { let count = 0; fetch = jest.fn(async (url) => { if (url.includes('/$export?_since=200')) { - return { - status: 200, - headers: { get: () => ContentType.FHIR_JSON }, - json: jest.fn(async () => { - return { - resourceType: 'OperationOutcome', - id: 'accepted', - issue: [ - { - severity: 'information', - code: 'informational', - details: { - text: 'Accepted', - }, - }, - ], - }; - }), - }; + return mockFetchResponse(200, accepted('bulkdata/id/status'), { 'content-location': 'bulkdata/id/status' }); } if (url.includes('/$export')) { - return { - status: 202, - json: jest.fn(async () => { - return { - resourceType: 'OperationOutcome', - id: 'accepted', - issue: [ - { - severity: 'information', - code: 'informational', - details: { - text: 'Accepted', - }, - }, - ], - }; - }), - headers: { - get(name: string): string | undefined { - return { - 'content-type': ContentType.FHIR_JSON, - 'content-location': 'bulkdata/id/status', - }[name]; - }, - }, - }; + return mockFetchResponse(202, accepted('bulkdata/id/status'), { 'content-location': 'bulkdata/id/status' }); } if (url.includes('bulkdata/id/status')) { if (count < 1) { count++; - return { - status: 202, - json: jest.fn(async () => { - return {}; - }), - }; + return mockFetchResponse(202, {}); } } - return { - status: 200, - headers: { get: () => ContentType.FHIR_JSON }, - json: jest.fn(async () => ({ - transactionTime: '2023-05-18T22:55:31.280Z', - request: 'https://api.medplum.com/fhir/R4/$export?_type=Observation', - requiresAccessToken: false, - output: [ - { - type: 'ProjectMembership', - url: 'https://api.medplum.com/storage/TEST', - }, - ], - error: [], - })), - }; + return mockFetchResponse(200, { + transactionTime: '2023-05-18T22:55:31.280Z', + request: 'https://api.medplum.com/fhir/R4/$export?_type=Observation', + requiresAccessToken: false, + output: [ + { + type: 'ProjectMembership', + url: 'https://api.medplum.com/storage/TEST', + }, + ], + error: [], + }); }); }); @@ -2620,11 +2568,88 @@ describe('Client', () => { expect((err as Error).message).toBe('Not found'); } }); + + test('Poll after token refresh', async () => { + const clientId = randomUUID(); + const clientSecret = randomUUID(); + const statusUrl = 'status-' + randomUUID(); + const locationUrl = 'location-' + randomUUID(); + + const mockTokens = { + access_token: createFakeJwt({ client_id: clientId, login_id: '123' }), + refresh_token: createFakeJwt({ client_id: clientId }), + profile: { reference: 'Patient/123' }, + }; + + const mockMe = { + project: { resourceType: 'Project', id: '123' }, + membership: { resourceType: 'ProjectMembership', id: '123' }, + profile: { resouceType: 'Practitioner', id: '123' }, + config: { resourceType: 'UserConfiguration', id: '123' }, + accessPolicy: { resourceType: 'AccessPolicy', id: '123' }, + }; + + let count = 0; + + const mockFetch = async (url: string, options: any): Promise => { + count++; + switch (count) { + case 1: + // First, handle the initial startClientLogin client credentials flow + expect(options.method).toBe('POST'); + expect(url).toBe('https://api.medplum.com/oauth2/token'); + return mockFetchResponse(200, mockTokens); + case 2: + // MedplumClient will automatically fetch the user profile after token refresh + expect(options.method).toBe('GET'); + expect(url).toBe('https://api.medplum.com/auth/me'); + return mockFetchResponse(200, mockMe); + case 3: + // Next, handle the initial bulk export - mock an expired token response + expect(options.method).toBe('POST'); + expect(url).toBe('https://api.medplum.com/fhir/R4/$export'); + return mockFetchResponse(401, forbidden); + case 4: + // Now MedplumClient will try to automatically refresh the token + expect(options.method).toBe('POST'); + expect(url).toBe('https://api.medplum.com/oauth2/token'); + return mockFetchResponse(200, mockTokens); + case 5: + // And then MedplumClient will automatically fetch the user profile again + expect(options.method).toBe('GET'); + expect(url).toBe('https://api.medplum.com/auth/me'); + return mockFetchResponse(200, mockMe); + case 6: + // Ok, whew, we are refreshed, so we can finally get the bulk export + // However, the bulk export isn't "done", so return "Accepted" + expect(options.method).toBe('POST'); + expect(url).toBe('https://api.medplum.com/fhir/R4/$export'); + return mockFetchResponse(202, accepted(statusUrl)); + case 7: + // Report status complete, and send the location of the bulk export + expect(options.method).toBeUndefined(); + expect(url).toBe('https://api.medplum.com/' + statusUrl); + return mockFetchResponse(201, {}, { location: locationUrl }); + case 8: + // What a journey! Finally, we can get the contents of the bulk export + expect(options.method).toBeUndefined(); + expect(url).toBe('https://api.medplum.com/' + locationUrl); + return mockFetchResponse(200, { resourceType: 'Bundle' }); + } + throw new Error('Unexpected fetch call: ' + url); + }; + + const medplum = new MedplumClient({ fetch: mockFetch }); + await medplum.startClientLogin(clientId, clientSecret); + const result = await medplum.bulkExport(); + expect(result).toMatchObject({ resourceType: 'Bundle' }); + }); }); describe('Downloading resources', () => { const baseUrl = 'https://api.medplum.com/'; const fhirUrlPath = 'fhir/R4/'; + const accessToken = 'fake'; let fetch: FetchLike; let client: MedplumClient; @@ -2633,6 +2658,7 @@ describe('Client', () => { text: () => Promise.resolve(url), })); client = new MedplumClient({ fetch, baseUrl, fhirUrlPath }); + client.setAccessToken(accessToken); }); test('Downloading resources via URL', async () => { @@ -2642,6 +2668,7 @@ describe('Client', () => { expect.objectContaining({ headers: { Accept: DEFAULT_ACCEPT, + Authorization: `Bearer ${accessToken}`, 'X-Medplum': 'extended', }, }) @@ -2656,6 +2683,7 @@ describe('Client', () => { expect.objectContaining({ headers: { Accept: DEFAULT_ACCEPT, + Authorization: `Bearer ${accessToken}`, 'X-Medplum': 'extended', }, }) diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index e7370c6fc0..316693a263 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -2699,16 +2699,7 @@ export class MedplumClient extends EventTarget { const headers = options.headers as Record; headers['Prefer'] = 'respond-async'; - const response = await this.fetchWithRetry(url, options); - - if (response.status === 202) { - const contentLocation = await tryGetContentLocation(response); - if (contentLocation) { - return this.pollStatus(contentLocation); - } - } - - return this.parseResponse(response, 'POST', url); + return this.request('POST', url, options); } // @@ -2809,13 +2800,21 @@ export class MedplumClient extends EventTarget { throw new OperationOutcomeError(notFound); } - const contentLocation = response.headers.get('content-location'); + const contentLocation = await tryGetContentLocation(response); const redirectMode = options.redirect ?? this.options.redirect; if (response.status === 201 && contentLocation && redirectMode === 'follow') { // Follow redirect return this.request('GET', contentLocation, { ...options, body: undefined }); } + if ( + response.status === 202 && + contentLocation && + (options.headers as Record | undefined)?.['Prefer'] === 'respond-async' + ) { + return this.pollStatus(contentLocation); + } + let obj: any = undefined; if (isJson) { try { @@ -2894,7 +2893,7 @@ export class MedplumClient extends EventTarget { checkStatus = false; resultResponse = statusResponse; - if (statusResponse.status === 201) { + if (statusResponse.status === 200 || statusResponse.status === 201) { const contentLocation = await tryGetContentLocation(statusResponse); if (contentLocation) { resultResponse = await this.fetchWithRetry(contentLocation, fetchOptions); From 7e26d6d0086fecfe0bb31e2a2dcbf32f07d02de7 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Mon, 12 Feb 2024 08:46:30 -0800 Subject: [PATCH 31/81] Revert "Fixes #3786 - startAsyncRequest and token refresh (#3932)" This reverts commit 7b3453c1236ab5968601aef0f0f66d1362cbe078. --- packages/core/src/client-test-utils.ts | 27 ++-- packages/core/src/client.test.ts | 166 ++++++++++--------------- packages/core/src/client.ts | 23 ++-- 3 files changed, 88 insertions(+), 128 deletions(-) diff --git a/packages/core/src/client-test-utils.ts b/packages/core/src/client-test-utils.ts index bb800e2976..765fd6294a 100644 --- a/packages/core/src/client-test-utils.ts +++ b/packages/core/src/client-test-utils.ts @@ -12,25 +12,12 @@ export function mockFetch( return jest.fn((url: string, options?: any) => { const response = bodyFn(url, options); const responseStatus = isOperationOutcome(response) ? getStatus(response) : status; - return Promise.resolve(mockFetchResponse(responseStatus, response, { 'content-type': contentType })); + return Promise.resolve({ + ok: responseStatus < 400, + status: responseStatus, + headers: { get: () => contentType }, + blob: () => Promise.resolve(response), + json: () => Promise.resolve(response), + }); }); } - -export function mockFetchResponse(status: number, body: any, headers?: Record): Response { - const headersMap = new Map(); - if (headers) { - for (const [key, value] of Object.entries(headers)) { - headersMap.set(key, value); - } - } - if (!headersMap.has('content-type')) { - headersMap.set('content-type', ContentType.FHIR_JSON); - } - return { - ok: status < 400, - status, - headers: headersMap, - blob: () => Promise.resolve(body), - json: () => Promise.resolve(body), - } as unknown as Response; -} diff --git a/packages/core/src/client.test.ts b/packages/core/src/client.test.ts index 7d2118eab4..f9c08d4a21 100644 --- a/packages/core/src/client.test.ts +++ b/packages/core/src/client.test.ts @@ -13,9 +13,9 @@ import { NewProjectRequest, NewUserRequest, } from './client'; -import { mockFetch, mockFetchResponse } from './client-test-utils'; +import { mockFetch } from './client-test-utils'; import { ContentType } from './contenttype'; -import { OperationOutcomeError, accepted, forbidden, notFound, unauthorized } from './outcomes'; +import { OperationOutcomeError, notFound, unauthorized } from './outcomes'; import { MockAsyncClientStorage } from './storage'; import { getDataType, isDataTypeLoaded, isProfileLoaded } from './typeschema/types'; import { ProfileResource, createReference } from './utils'; @@ -2440,32 +2440,84 @@ describe('Client', () => { let count = 0; fetch = jest.fn(async (url) => { if (url.includes('/$export?_since=200')) { - return mockFetchResponse(200, accepted('bulkdata/id/status'), { 'content-location': 'bulkdata/id/status' }); + return { + status: 200, + headers: { get: () => ContentType.FHIR_JSON }, + json: jest.fn(async () => { + return { + resourceType: 'OperationOutcome', + id: 'accepted', + issue: [ + { + severity: 'information', + code: 'informational', + details: { + text: 'Accepted', + }, + }, + ], + }; + }), + }; } if (url.includes('/$export')) { - return mockFetchResponse(202, accepted('bulkdata/id/status'), { 'content-location': 'bulkdata/id/status' }); + return { + status: 202, + json: jest.fn(async () => { + return { + resourceType: 'OperationOutcome', + id: 'accepted', + issue: [ + { + severity: 'information', + code: 'informational', + details: { + text: 'Accepted', + }, + }, + ], + }; + }), + headers: { + get(name: string): string | undefined { + return { + 'content-type': ContentType.FHIR_JSON, + 'content-location': 'bulkdata/id/status', + }[name]; + }, + }, + }; } if (url.includes('bulkdata/id/status')) { if (count < 1) { count++; - return mockFetchResponse(202, {}); + return { + status: 202, + json: jest.fn(async () => { + return {}; + }), + }; } } - return mockFetchResponse(200, { - transactionTime: '2023-05-18T22:55:31.280Z', - request: 'https://api.medplum.com/fhir/R4/$export?_type=Observation', - requiresAccessToken: false, - output: [ - { - type: 'ProjectMembership', - url: 'https://api.medplum.com/storage/TEST', - }, - ], - error: [], - }); + return { + status: 200, + headers: { get: () => ContentType.FHIR_JSON }, + json: jest.fn(async () => ({ + transactionTime: '2023-05-18T22:55:31.280Z', + request: 'https://api.medplum.com/fhir/R4/$export?_type=Observation', + requiresAccessToken: false, + output: [ + { + type: 'ProjectMembership', + url: 'https://api.medplum.com/storage/TEST', + }, + ], + error: [], + })), + }; }); }); @@ -2568,88 +2620,11 @@ describe('Client', () => { expect((err as Error).message).toBe('Not found'); } }); - - test('Poll after token refresh', async () => { - const clientId = randomUUID(); - const clientSecret = randomUUID(); - const statusUrl = 'status-' + randomUUID(); - const locationUrl = 'location-' + randomUUID(); - - const mockTokens = { - access_token: createFakeJwt({ client_id: clientId, login_id: '123' }), - refresh_token: createFakeJwt({ client_id: clientId }), - profile: { reference: 'Patient/123' }, - }; - - const mockMe = { - project: { resourceType: 'Project', id: '123' }, - membership: { resourceType: 'ProjectMembership', id: '123' }, - profile: { resouceType: 'Practitioner', id: '123' }, - config: { resourceType: 'UserConfiguration', id: '123' }, - accessPolicy: { resourceType: 'AccessPolicy', id: '123' }, - }; - - let count = 0; - - const mockFetch = async (url: string, options: any): Promise => { - count++; - switch (count) { - case 1: - // First, handle the initial startClientLogin client credentials flow - expect(options.method).toBe('POST'); - expect(url).toBe('https://api.medplum.com/oauth2/token'); - return mockFetchResponse(200, mockTokens); - case 2: - // MedplumClient will automatically fetch the user profile after token refresh - expect(options.method).toBe('GET'); - expect(url).toBe('https://api.medplum.com/auth/me'); - return mockFetchResponse(200, mockMe); - case 3: - // Next, handle the initial bulk export - mock an expired token response - expect(options.method).toBe('POST'); - expect(url).toBe('https://api.medplum.com/fhir/R4/$export'); - return mockFetchResponse(401, forbidden); - case 4: - // Now MedplumClient will try to automatically refresh the token - expect(options.method).toBe('POST'); - expect(url).toBe('https://api.medplum.com/oauth2/token'); - return mockFetchResponse(200, mockTokens); - case 5: - // And then MedplumClient will automatically fetch the user profile again - expect(options.method).toBe('GET'); - expect(url).toBe('https://api.medplum.com/auth/me'); - return mockFetchResponse(200, mockMe); - case 6: - // Ok, whew, we are refreshed, so we can finally get the bulk export - // However, the bulk export isn't "done", so return "Accepted" - expect(options.method).toBe('POST'); - expect(url).toBe('https://api.medplum.com/fhir/R4/$export'); - return mockFetchResponse(202, accepted(statusUrl)); - case 7: - // Report status complete, and send the location of the bulk export - expect(options.method).toBeUndefined(); - expect(url).toBe('https://api.medplum.com/' + statusUrl); - return mockFetchResponse(201, {}, { location: locationUrl }); - case 8: - // What a journey! Finally, we can get the contents of the bulk export - expect(options.method).toBeUndefined(); - expect(url).toBe('https://api.medplum.com/' + locationUrl); - return mockFetchResponse(200, { resourceType: 'Bundle' }); - } - throw new Error('Unexpected fetch call: ' + url); - }; - - const medplum = new MedplumClient({ fetch: mockFetch }); - await medplum.startClientLogin(clientId, clientSecret); - const result = await medplum.bulkExport(); - expect(result).toMatchObject({ resourceType: 'Bundle' }); - }); }); describe('Downloading resources', () => { const baseUrl = 'https://api.medplum.com/'; const fhirUrlPath = 'fhir/R4/'; - const accessToken = 'fake'; let fetch: FetchLike; let client: MedplumClient; @@ -2658,7 +2633,6 @@ describe('Client', () => { text: () => Promise.resolve(url), })); client = new MedplumClient({ fetch, baseUrl, fhirUrlPath }); - client.setAccessToken(accessToken); }); test('Downloading resources via URL', async () => { @@ -2668,7 +2642,6 @@ describe('Client', () => { expect.objectContaining({ headers: { Accept: DEFAULT_ACCEPT, - Authorization: `Bearer ${accessToken}`, 'X-Medplum': 'extended', }, }) @@ -2683,7 +2656,6 @@ describe('Client', () => { expect.objectContaining({ headers: { Accept: DEFAULT_ACCEPT, - Authorization: `Bearer ${accessToken}`, 'X-Medplum': 'extended', }, }) diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 316693a263..e7370c6fc0 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -2699,7 +2699,16 @@ export class MedplumClient extends EventTarget { const headers = options.headers as Record; headers['Prefer'] = 'respond-async'; - return this.request('POST', url, options); + const response = await this.fetchWithRetry(url, options); + + if (response.status === 202) { + const contentLocation = await tryGetContentLocation(response); + if (contentLocation) { + return this.pollStatus(contentLocation); + } + } + + return this.parseResponse(response, 'POST', url); } // @@ -2800,21 +2809,13 @@ export class MedplumClient extends EventTarget { throw new OperationOutcomeError(notFound); } - const contentLocation = await tryGetContentLocation(response); + const contentLocation = response.headers.get('content-location'); const redirectMode = options.redirect ?? this.options.redirect; if (response.status === 201 && contentLocation && redirectMode === 'follow') { // Follow redirect return this.request('GET', contentLocation, { ...options, body: undefined }); } - if ( - response.status === 202 && - contentLocation && - (options.headers as Record | undefined)?.['Prefer'] === 'respond-async' - ) { - return this.pollStatus(contentLocation); - } - let obj: any = undefined; if (isJson) { try { @@ -2893,7 +2894,7 @@ export class MedplumClient extends EventTarget { checkStatus = false; resultResponse = statusResponse; - if (statusResponse.status === 200 || statusResponse.status === 201) { + if (statusResponse.status === 201) { const contentLocation = await tryGetContentLocation(statusResponse); if (contentLocation) { resultResponse = await this.fetchWithRetry(contentLocation, fetchOptions); From b8bb30292103880857eabb3e4a821d0a2c96e6a0 Mon Sep 17 00:00:00 2001 From: Rahul Agarwal Date: Mon, 12 Feb 2024 12:45:52 -0800 Subject: [PATCH 32/81] Added DoseSpot FAQ page (#3933) * Added DoseSpot FAQ page * Split out e-prescribe and dosespot documentation * Fix broken links --- packages/docs/docs/integration/dosespot.md | 85 ++++++++++++++++++++ packages/docs/docs/medications/e-prescibe.md | 45 +++++++++++ 2 files changed, 130 insertions(+) create mode 100644 packages/docs/docs/integration/dosespot.md create mode 100644 packages/docs/docs/medications/e-prescibe.md diff --git a/packages/docs/docs/integration/dosespot.md b/packages/docs/docs/integration/dosespot.md new file mode 100644 index 0000000000..3d034854a8 --- /dev/null +++ b/packages/docs/docs/integration/dosespot.md @@ -0,0 +1,85 @@ +# DoseSpot + +Medplum has partnered with [DoseSpot](https://www.dosespot.com/), a leader in e-prescription (eRx) technology, to offer prescription ordering services through the Medplum platform. + +This collaboration enables healthcare professionals to seamlessly place medication orders using DoseSpot's eRx interface, which Medplum integrates through an embedded iFrame. This approach ensures a secure, safe, and efficient, experience for clinicians, and offers functionality such as: + +- Clinician identity proofing +- Drug-drug interactions +- Patient coverage benefits checks + +## DoseSpot FAQ + +### Who is qualified to use the eRx feature at Medplum? + +The eRx feature at Medplum is available to professionals authorized to prescribe medication in the United States, contingent upon integration with our partner platform, DoseSpot. Approval from DoseSpot is required for access. + +The feature is user-specific; hence, only approved users can issue prescriptions. Proxy access can be granted to other team members upon designation. + +### Why does DoseSpot request credit card information? + +DoseSpot partners with Experian to verify prescribers' identities. This is a one time check that is performed during the prescriber's first prescription in Medplum. + +Credit card details may be requested by Medplum's eRx partner for identity verification purposes, to ensure the security and integrity of prescribing privileges within the platform. + +This procedure is integral to the DoseSpot's identity proofing protocol. Such identity verification measures are widely adopted to prevent fraud and confirm the authenticity of an individual's identity. To accomplish this, the prescriber's credit card details are cross-referenced with Experian data to ensure a match between the prescriber's provided information and that associated with the credit card and report. + +**Importantly, the credit card is neither charged nor stored in Medplum.** Moreover, this process involves a soft credit check, which does not impact the prescriber's credit rating or borrowing capabilities + +### What distinguishes a "Proxy" user from a "Prescriber" ? + +A "Prescriber" is a user authorized to directly issue prescriptions, holding the necessary credentials and permissions. + +A "Proxy" user, however, acts as an assistant or delegate, performing tasks on behalf of a Prescriber but does not have the authority to finalize prescriptions without review and approval by a Prescriber. + +### What guidelines exist for prescribing to minors through Medplum? {#prescribing-to-minors} + +Surescripts requires e-Prescriptions sent for pediatric patients (defined as ages 18 and younger – up until their 19th birthday) to include a **height and weight**. This is a firm requirement across all prescribers; it was implemented in September of 2021 as part of the Surescripts update to SCRIPT Standard v2017071 which followed NCPDP regulations to maintain compliance. + +Client's 18 years old and under are required to have a documented height & weight on file otherwise eRX cannot be accessed. This requirement cannot be bypassed. Please ensure that any pediatric patients have their date of birth documented on their `Patient` resource, as well as update height and weight documented according to the [US Core Guidelines](/docs/fhir-datastore/understanding-uscdi-dataclasses) + +### How do "Refill" and "Reorder" differ within Medplum? + +A "Refill" refers to authorizing additional quantities of a medication under an existing prescription, is initiated by the pharmacy. + +A "Reorder" involves creating a new prescription order for a medication previously prescribed, and is initiated by the prescriber. + +### How does one initiate an Electronic Prior Authorization (ePA) with Medplum's eRx service? + +When formulary data indicates a need for ePA based on insurance, the clinician can start the process within the eRx interface. This option becomes available after a prescription is marked as pending, accessible through the medication's action menu. Note that this process is independent of the patient's coverage details, which are auto-populated from external sources when available. + +### Does Medplum support prescription submissions to pharmacies across all 50 states? + +Yes. Medplum utilizes the Surescripts Pharmacy network through our partnership with our eRx provider, DoseSpot, ensuring nationwide coverage. + +### Are there limits on a patient's choice of pharmacy? + +No. Patients are free to choose any pharmacy within the SureScripts network. + +### Why might a pharmacy not appear in Medplum's search? + +A pharmacy might not be found due to reasons such as it not being registered within the network Medplum uses, data entry errors, or it being outside the service area of our eRx integration partner. + +To verify that a pharmacy exists in the SureScripts database, you can navigate to to https://surescripts.com/network-connections and scroll to the section "Locate E-Prescribing Pharmacies." + +### How does Medplum collect and manage patient insurance details? + +Patient insurance details within Medplum's eRx system are pulled by DoseSpot from from Surescripts, which matches insurance information based on patient demographics: name, gender, and date of birth. + +The SureScripts database covers approximately 95% of U.S. pharmacies. When patients enter information through their pharmacy, their data is uploaded to SureScripts. + +It's important to note that insurance information stored directly in Medplum **does not integrate with Surescripts**, nor can it be manually entered into the eRx system. + +### What constitutes a transmission error in Medplum's eRx service? + +A transmission error occurs when there's a failure in sending a prescription from Medplum's eRx system to a pharmacy, due to issues like incorrect pharmacy details, network problems, or data mismatches. + +### Does Medplum offer drug references or educational materials for patients? + +The DoseSpot eRx interface, provides access to drug monographs when a prescription is placed. These monographs can be accessed by clicking on the drug name when viewing pending prescriptions. + +### Can custom instructions be added to prescription notes in Medplum? + +Drop-down fields within the Medplum eRx interface cannot be customized. However, prescribers can use the free text instructions field for adding notes. + +Similarly, the patient notes field can not be auto-populated at this time. diff --git a/packages/docs/docs/medications/e-prescibe.md b/packages/docs/docs/medications/e-prescibe.md new file mode 100644 index 0000000000..fdca5a91e5 --- /dev/null +++ b/packages/docs/docs/medications/e-prescibe.md @@ -0,0 +1,45 @@ +# E-Prescribe (eRx) + +Medplum's flexible architecture allows for integration with any e-prescribe(eRx) provider through the use of [Bots](/docs/bots), offering customizable e-prescription solutions. + +To provide an integrated prescribing experience out of the box, Medplum offers DoseSpot as its default, first-party eRx provider . While DoseSpot serves as our default solution, [Medplum Bots](/docs/bots) can be used to integrate other prescribing solutions. + +Visit our [DoseSpot integrations page](/docs/integration/dosespot) to learn more about our DoseSpot integration. + +## E-Prescribe FAQ + +### Who is qualified to use the eRx feature at Medplum? + +The eRx feature at Medplum is available to professionals authorized to prescribe medication in the United States. The feature is user-specific; hence, only approved users can issue prescriptions. + +### Is it possible to prescribe controlled substances through Medplum? + +No. Currently, Medplum's eRx integration does not allow prescription of controlled substances. If controlled substances are important for your care model, please contact our support team at [support@medplum.com](mailto:support@medplum.com). + +### Are there other limits on which medications can be prescribed? + +No. Beyond restrictions on controlled substances, prescriber has free choice of medication when prescribing. + +### What guidelines exist for prescribing to minors through Medplum? + +It is up to the prescriber to ensure dosages are within standard guidelines for for patient's height and weight. + +:::danger + +E-prescription providers often impose additional validation requirements when prescribing medication for minors. Please check the requirements of your eRx vendor. + +::: + +### Can prescription cost information be retrieved through the API? + +At this time, prescription cost retrieval is not available via the Medplum API. However, Some eRx vendors may display cost information at the time a prescription is written. Check with your specific eRX vendor for more details. + +### Does Medplum have connections to any pharmacy or pharmaceutical industry partners? + +No. Medplum does not have any special connections or partnerships with specific pharmacies, drug manufacturers, or other pharmaceutical industry partners. + +### How does Medplum collect and manage patient insurance details? + +It is best practice to maintain update date coverage information for each patient in Medplum, as it is important for both clinical and administrative workflows in the core EHR. See our [Guide on Patient Insurance](/docs/billing/patient-insurance) for more details. + +However, many eRx vendors do not allow users to directly input patient coverage details. Rather, they depend on clearinghouses such as Surescripts, to supply patient coverage data. Check with your eRx vendor for details on how patient coverage is managed. From 3f0da4d474995c4083a27f0ea906e731d47455b2 Mon Sep 17 00:00:00 2001 From: larry-flexpa <158080917+larry-flexpa@users.noreply.github.com> Date: Mon, 12 Feb 2024 15:00:35 -0700 Subject: [PATCH 33/81] Add search result parameters to CapabilityStatement (#3936) --- packages/server/src/fhir/metadata.ts | 33 ++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/packages/server/src/fhir/metadata.ts b/packages/server/src/fhir/metadata.ts index b284779209..b64f7ce248 100644 --- a/packages/server/src/fhir/metadata.ts +++ b/packages/server/src/fhir/metadata.ts @@ -113,6 +113,38 @@ const supportedOperations: Record Date: Mon, 12 Feb 2024 17:40:09 -0800 Subject: [PATCH 34/81] Initial medplum-provider package for #3833 (#3939) --- examples/medplum-provider/.gitignore | 25 + examples/medplum-provider/README.md | 70 ++ examples/medplum-provider/index.html | 13 + examples/medplum-provider/package.json | 42 + examples/medplum-provider/postcss.config.mjs | 19 + examples/medplum-provider/src/App.tsx | 44 ++ .../soapnote/SoapNote.questionnaire.ts | 201 +++++ .../src/components/soapnote/SoapNote.tsx | 57 ++ .../components/tasks/DiagnosticReportTask.tsx | 36 + .../components/tasks/QuestionnaireTask.tsx | 151 ++++ .../src/components/tasks/TaskList.tsx | 182 +++++ examples/medplum-provider/src/main.tsx | 47 ++ .../medplum-provider/src/pages/HomePage.tsx | 33 + .../src/pages/LandingPage.tsx | 23 + .../src/pages/PatientPage.tsx | 26 + .../src/pages/ResourcePage.tsx | 37 + .../src/pages/SearchPage.module.css | 6 + .../medplum-provider/src/pages/SearchPage.tsx | 104 +++ .../medplum-provider/src/pages/SignInPage.tsx | 17 + examples/medplum-provider/src/vite-env.d.ts | 1 + examples/medplum-provider/tsconfig.json | 19 + examples/medplum-provider/vercel.json | 5 + examples/medplum-provider/vite.config.ts | 14 + package-lock.json | 735 ++++++++++++++++++ 24 files changed, 1907 insertions(+) create mode 100644 examples/medplum-provider/.gitignore create mode 100644 examples/medplum-provider/README.md create mode 100644 examples/medplum-provider/index.html create mode 100644 examples/medplum-provider/package.json create mode 100644 examples/medplum-provider/postcss.config.mjs create mode 100644 examples/medplum-provider/src/App.tsx create mode 100644 examples/medplum-provider/src/components/soapnote/SoapNote.questionnaire.ts create mode 100644 examples/medplum-provider/src/components/soapnote/SoapNote.tsx create mode 100644 examples/medplum-provider/src/components/tasks/DiagnosticReportTask.tsx create mode 100644 examples/medplum-provider/src/components/tasks/QuestionnaireTask.tsx create mode 100644 examples/medplum-provider/src/components/tasks/TaskList.tsx create mode 100644 examples/medplum-provider/src/main.tsx create mode 100644 examples/medplum-provider/src/pages/HomePage.tsx create mode 100644 examples/medplum-provider/src/pages/LandingPage.tsx create mode 100644 examples/medplum-provider/src/pages/PatientPage.tsx create mode 100644 examples/medplum-provider/src/pages/ResourcePage.tsx create mode 100644 examples/medplum-provider/src/pages/SearchPage.module.css create mode 100644 examples/medplum-provider/src/pages/SearchPage.tsx create mode 100644 examples/medplum-provider/src/pages/SignInPage.tsx create mode 100644 examples/medplum-provider/src/vite-env.d.ts create mode 100644 examples/medplum-provider/tsconfig.json create mode 100644 examples/medplum-provider/vercel.json create mode 100644 examples/medplum-provider/vite.config.ts diff --git a/examples/medplum-provider/.gitignore b/examples/medplum-provider/.gitignore new file mode 100644 index 0000000000..b02a1ff770 --- /dev/null +++ b/examples/medplum-provider/.gitignore @@ -0,0 +1,25 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local +package-lock.json + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/examples/medplum-provider/README.md b/examples/medplum-provider/README.md new file mode 100644 index 0000000000..59a132c76f --- /dev/null +++ b/examples/medplum-provider/README.md @@ -0,0 +1,70 @@ +

Medplum Charting Demo

+

A starter application for building a charting app on Medplum.

+

+ + + +

+ +This example app demonstrates the following: + +- Using [Medplum React Components](https://storybook.medplum.com/?path=/docs/medplum-introduction--docs) to display a chart that provides visibility on a patient + - More information on a [charting experience](https://www.medplum.com/docs/charting) +- Using [Medplum GraphQL](https://graphiql.medplum.com/) queries to fetch linked resources + +### Components of the Patient Chart + +The Patient Chart has 3 distinct panels + +1. Clinical Chart + The left panel shows the patient history and their status. Notable information in the clinical chart includes the following Resources: + - Patient Information + - Upcoming Appointments + - Documented Visits + - List of Allergies + - List of Problems + - Medication Requests + - Smoking Status + - Vitals + +2. Tasks + The center panel shows list of the Task resource with a different focus resource. See our [Tasks Guide](https://www.medplum.com/docs/careplans/tasks) for more details. + - Each focus is interactive to either review or fill out + - This example project demonstrates interactions of the following resources: + - Questionnaire + - QuestionnaireResponse + - DiagnosticReport + - CarePlan + +3. SOAP Note + The right most panel documents an enounter with the patient through a questionnaire. Filling out and submitting the questionnaire automatically creates a task, with the response as the focus to be reviewed. + +### Getting Started + +If you haven't already done so, follow the instructions in [this tutorial](https://www.medplum.com/docs/tutorials/app/register) to register a Medplum project to store your data. + +[Fork](https://github.com/medplum/medplum-hello-world/fork) and clone the repo. + +Next, install the dependencies. + +```bash +npm install +``` + +Then, run the app + +```bash +npm run dev +``` + +This app should run on `http://localhost:3000/` + +### About Medplum + +[Medplum](https://www.medplum.com/) is an open-source, API-first EHR. Medplum makes it easy to build healthcare apps quickly with less code. + +Medplum supports self-hosting and provides a [hosted service](https://app.medplum.com/). Medplum Hello World uses the hosted service as a backend. + +- Read our [documentation](https://www.medplum.com/docs) +- Browse our [react component library](https://docs.medplum.com/storybook/index.html?) +- Join our [Discord](https://discord.gg/medplum) diff --git a/examples/medplum-provider/index.html b/examples/medplum-provider/index.html new file mode 100644 index 0000000000..039c0a01d5 --- /dev/null +++ b/examples/medplum-provider/index.html @@ -0,0 +1,13 @@ + + + + + + Medplum Provider + + +
+ + + + diff --git a/examples/medplum-provider/package.json b/examples/medplum-provider/package.json new file mode 100644 index 0000000000..472513055c --- /dev/null +++ b/examples/medplum-provider/package.json @@ -0,0 +1,42 @@ +{ + "name": "medplum-provider", + "version": "3.0.3", + "private": true, + "type": "module", + "scripts": { + "build": "tsc && vite build", + "dev": "vite", + "preview": "vite preview" + }, + "prettier": { + "printWidth": 120, + "singleQuote": true, + "trailingComma": "es5" + }, + "eslintConfig": { + "extends": [ + "@medplum/eslint-config" + ] + }, + "devDependencies": { + "@mantine/core": "7.5.2", + "@mantine/hooks": "7.5.2", + "@mantine/notifications": "7.5.2", + "@medplum/core": "3.0.3", + "@medplum/eslint-config": "3.0.3", + "@medplum/fhirtypes": "3.0.3", + "@medplum/react": "3.0.3", + "@tabler/icons-react": "2.47.0", + "@types/node": "20.11.17", + "@types/react": "18.2.55", + "@types/react-dom": "18.2.19", + "@vitejs/plugin-react": "4.2.1", + "postcss": "8.4.35", + "postcss-preset-mantine": "1.13.0", + "react": "18.2.0", + "react-dom": "18.2.0", + "react-router-dom": "6.22.0", + "typescript": "5.3.3", + "vite": "5.1.1" + } +} diff --git a/examples/medplum-provider/postcss.config.mjs b/examples/medplum-provider/postcss.config.mjs new file mode 100644 index 0000000000..feba649756 --- /dev/null +++ b/examples/medplum-provider/postcss.config.mjs @@ -0,0 +1,19 @@ +import mantinePreset from 'postcss-preset-mantine'; +import simpleVars from 'postcss-simple-vars'; + +const config = { + plugins: [ + mantinePreset(), + simpleVars({ + variables: { + 'mantine-breakpoint-xs': '36em', + 'mantine-breakpoint-sm': '48em', + 'mantine-breakpoint-md': '62em', + 'mantine-breakpoint-lg': '75em', + 'mantine-breakpoint-xl': '88em', + }, + }), + ], +}; + +export default config; diff --git a/examples/medplum-provider/src/App.tsx b/examples/medplum-provider/src/App.tsx new file mode 100644 index 0000000000..b6f98bd268 --- /dev/null +++ b/examples/medplum-provider/src/App.tsx @@ -0,0 +1,44 @@ +import { AppShell, ErrorBoundary, Loading, Logo, useMedplum, useMedplumProfile } from '@medplum/react'; +import { IconUser } from '@tabler/icons-react'; +import { Suspense } from 'react'; +import { Route, Routes } from 'react-router-dom'; +import { HomePage } from './pages/HomePage'; +import { LandingPage } from './pages/LandingPage'; +import { PatientPage } from './pages/PatientPage'; +import { ResourcePage } from './pages/ResourcePage'; +import { SearchPage } from './pages/SearchPage'; +import { SignInPage } from './pages/SignInPage'; + +export function App(): JSX.Element | null { + const medplum = useMedplum(); + const profile = useMedplumProfile(); + + if (medplum.isLoading()) { + return null; + } + + return ( + } + menus={[ + { + title: 'Charts', + links: [{ icon: , label: 'Patients', href: '/' }], + }, + ]} + > + + }> + + : } /> + } /> + } /> + } /> + } /> + } /> + + + + + ); +} diff --git a/examples/medplum-provider/src/components/soapnote/SoapNote.questionnaire.ts b/examples/medplum-provider/src/components/soapnote/SoapNote.questionnaire.ts new file mode 100644 index 0000000000..e801e28c80 --- /dev/null +++ b/examples/medplum-provider/src/components/soapnote/SoapNote.questionnaire.ts @@ -0,0 +1,201 @@ +import { Questionnaire } from '@medplum/fhirtypes'; + +export const defaultSoapNoteQuestionnaire: Questionnaire = { + resourceType: 'Questionnaire', + name: 'SOAP Note - Menopause', + title: 'SOAP Note - Menopause Service', + status: 'active', + id: 'b0166b1b-d4b8-4d09-b6bc-2f094cf7de9e', + item: [ + { + id: 'id-55', + linkId: 'date', + type: 'date', + text: 'Date of Visit', + initial: [ + { + valueDate: '2023-10-19', + }, + ], + }, + { + id: 'id-64', + linkId: 'g10', + type: 'group', + text: 'Subjective Evaluation', + item: [ + { + id: 'id-94', + linkId: 'q29', + type: 'boolean', + text: 'Hot flashes', + }, + { + id: 'id-96', + linkId: 'q31', + type: 'boolean', + text: 'Mood swings', + }, + { + id: 'id-95', + linkId: 'q30', + type: 'boolean', + text: 'Vaginal dryness', + }, + { + id: 'id-97', + linkId: 'q32', + type: 'boolean', + text: 'Sleep Disturbance', + }, + { + id: 'id-67', + linkId: 'q13', + type: 'open-choice', + text: 'Self-reported history', + answerOption: [ + { + id: 'id-76', + valueString: 'Blood clots', + }, + { + id: 'id-77', + valueString: 'Stroke', + }, + { + id: 'id-78', + valueString: 'Breast cancer', + }, + { + id: 'id-79', + valueString: 'Endometrial cancer', + }, + { + id: 'id-80', + valueString: 'Irregular bleeding', + }, + { + id: 'id-81', + valueString: 'BMI > 30', + }, + ], + }, + { + id: 'id-100', + linkId: 'q34', + type: 'text', + text: 'Details', + }, + ], + }, + { + id: 'id-68', + linkId: 'g14', + type: 'group', + text: 'Objective Evaluation', + item: [ + { + id: 'id-92', + linkId: 'q27', + type: 'boolean', + text: 'Confirmed patient age > 45 y as of today', + }, + ], + }, + { + id: 'id-71', + linkId: 'g17', + type: 'group', + text: 'Assessment', + item: [ + { + id: 'id-72', + linkId: 'q18', + type: 'choice', + text: 'Based on data, submit the SNOMED diagnosis codes for this patient.', + answerOption: [ + { + id: 'id-83', + valueString: 'No current problems (160245001)', + }, + { + id: 'id-84', + valueString: 'Early menopause (160397006)', + }, + { + id: 'id-85', + valueString: 'Late menopause (160398001)', + }, + ], + }, + { + id: 'id-98', + linkId: 'q33', + type: 'string', + text: 'Other Diagnosis Code', + }, + ], + }, + { + id: 'id-86', + linkId: 'g21', + type: 'group', + text: 'Plan', + item: [ + { + id: 'id-87', + linkId: 'q22', + type: 'open-choice', + text: 'Outline next steps', + answerOption: [ + { + id: 'id-88', + valueString: 'Order cholesterol test', + }, + { + id: 'id-89', + valueString: 'Order imaging', + }, + { + id: 'id-90', + valueString: 'Refer to specialist', + }, + { + id: 'id-99', + valueString: 'Systemic HRT', + }, + { + id: 'id-100', + valueString: 'SSRI/SNRI', + }, + { + id: 'id-101', + valueString: 'Local HRT', + }, + { + id: 'id-102', + valueString: 'OTC treatment', + }, + ], + }, + ], + }, + { + id: 'id-103', + linkId: 'q37', + type: 'choice', + text: 'Choose a code for this visit', + answerOption: [ + { + id: 'id-104', + valueString: '99204 (Moderate Complexity)', + }, + { + id: 'id-105', + valueString: '99205 (High Complexity)', + }, + ], + }, + ], + subjectType: ['Patient'], +}; diff --git a/examples/medplum-provider/src/components/soapnote/SoapNote.tsx b/examples/medplum-provider/src/components/soapnote/SoapNote.tsx new file mode 100644 index 0000000000..583eb78ae0 --- /dev/null +++ b/examples/medplum-provider/src/components/soapnote/SoapNote.tsx @@ -0,0 +1,57 @@ +import { Box, Flex, Text } from '@mantine/core'; +import { QuestionnaireResponse, Task } from '@medplum/fhirtypes'; +import { Document, QuestionnaireForm, useMedplum } from '@medplum/react'; +import { IconCircleCheck } from '@tabler/icons-react'; +import { useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { defaultSoapNoteQuestionnaire } from './SoapNote.questionnaire'; + +export function SoapNote(): JSX.Element { + const { id } = useParams(); + const medplum = useMedplum(); + const [submitted, setSubmitted] = useState(false); + + async function handleSubmit(questionnaireResponse: QuestionnaireResponse): Promise { + const response = await medplum.createResource(questionnaireResponse); + + const newTask: Task = { + resourceType: 'Task', + status: 'ready', + intent: 'order', + code: { + coding: [ + { + system: 'http://loinc.org', + code: 'LL1474-7', + display: 'Physician menopause management note', + }, + ], + }, + focus: { + reference: `QuestionnaireResponse/${response.id}`, + }, + for: { + reference: `Patient/${id}`, + }, + }; + await medplum.createResource(newTask); + setSubmitted(true); + } + + return ( + + + + {submitted ? ( + + Submitted + + + ) : null} + + + ); +} diff --git a/examples/medplum-provider/src/components/tasks/DiagnosticReportTask.tsx b/examples/medplum-provider/src/components/tasks/DiagnosticReportTask.tsx new file mode 100644 index 0000000000..ca59ead096 --- /dev/null +++ b/examples/medplum-provider/src/components/tasks/DiagnosticReportTask.tsx @@ -0,0 +1,36 @@ +import { Button, Flex, Modal } from '@mantine/core'; +import { DiagnosticReport, Task } from '@medplum/fhirtypes'; +import { CodeableConceptDisplay, DiagnosticReportDisplay, useMedplum } from '@medplum/react'; +import { IconCircleCheck } from '@tabler/icons-react'; +import { useState } from 'react'; +import { TaskCellProps } from './TaskList'; + +export function DiagnosticReportModal(props: TaskCellProps): JSX.Element { + const [open, setOpen] = useState(false); + const [reviewed, setReviewed] = useState(false); + const medplum = useMedplum(); + const report = props.resource as DiagnosticReport; + + async function handleClick(): Promise { + await medplum.updateResource({ ...props.task, status: 'completed' }); + setReviewed(true); + } + if (reviewed) { + return ; + } + return ( + <> + + setOpen(false)} size="xl"> + + + + + + + ); +} diff --git a/examples/medplum-provider/src/components/tasks/QuestionnaireTask.tsx b/examples/medplum-provider/src/components/tasks/QuestionnaireTask.tsx new file mode 100644 index 0000000000..e4c4866734 --- /dev/null +++ b/examples/medplum-provider/src/components/tasks/QuestionnaireTask.tsx @@ -0,0 +1,151 @@ +import { Anchor, Button, Collapse, Flex, Modal, Text } from '@mantine/core'; +import { useDisclosure } from '@mantine/hooks'; +import { TypedValue, getTypedPropertyValue } from '@medplum/core'; +import { Questionnaire, QuestionnaireResponse, QuestionnaireResponseItem, Task } from '@medplum/fhirtypes'; +import { QuestionnaireForm, ResourcePropertyDisplay, useMedplum } from '@medplum/react'; +import { IconCircleCheck } from '@tabler/icons-react'; +import { useEffect, useState } from 'react'; +import { TaskCellProps } from './TaskList'; + +export function QuestionnaireTask(props: TaskCellProps): JSX.Element { + const [submitted, setSubmitted] = useState(false); + const questionnaire = props.resource as Questionnaire; + const medplum = useMedplum(); + + if (submitted) { + return ; + } + + async function handleSubmit(questionnaireResponse: QuestionnaireResponse): Promise { + await medplum.createResource(questionnaireResponse); + await medplum.updateResource({ ...props.task, status: 'completed' }); + setSubmitted(true); + } + return (questionnaire.item ?? []).length <= 2 ? ( + + ) : ( + + ); +} + +function QuestionnaireModal(props: { + readonly task: Task; + readonly questionnaire: Questionnaire; + readonly setSubmitted: (submit: boolean) => void; + readonly handleSubmit: (questionnaireResponse: QuestionnaireResponse) => Promise; +}): JSX.Element { + const [open, setOpen] = useState(false); + + async function handleModalSubmit(questionnaireResponse: QuestionnaireResponse): Promise { + await props.handleSubmit(questionnaireResponse); + setOpen(false); + } + + return ( + <> + + setOpen(false)} size="xl"> + + + + ); +} + +function QuestionnaireQuickAction(props: { + readonly task: Task; + readonly questionnaire: Questionnaire; + readonly setSubmitted: (submit: boolean) => void; + readonly handleSubmit: (questionnaireResponse: QuestionnaireResponse) => Promise; +}): JSX.Element { + return ; +} + +export function ResponseDisplay(props: TaskCellProps): JSX.Element | null { + const resource = props.resource as QuestionnaireResponse; + const items = resource.item ?? []; + const [schemaLoaded, setSchemaLoaded] = useState(false); + const [reviewed, setReviewed] = useState(false); + const [opened, { toggle }] = useDisclosure(false); + const medplum = useMedplum(); + + useEffect(() => { + medplum + .requestSchema('QuestionnaireResponse') + .then(() => setSchemaLoaded(true)) + .catch(console.error); + }, [medplum]); + + async function handleClick(): Promise { + await medplum.updateResource({ ...props.task, status: 'completed' }); + setReviewed(true); + } + + const visibleItems = items.slice(0, 3); + const collapsedItems = items.slice(3, items.length); + + if (!schemaLoaded) { + return null; + } + + return ( + <> + {visibleItems.map((item) => ( + + ))} + + {collapsedItems.map((item) => ( + + ))} + + {showCollapsibleButton(collapsedItems) && {opened ? 'Show less' : 'Show more'}} + + {reviewed ? ( + + ) : ( + + )} + + + ); +} + +function ItemRow(props: { item: QuestionnaireResponseItem }): JSX.Element | null { + const item = props.item; + const itemValue = getTypedPropertyValue( + { type: 'QuestionnaireResponseItemAnswer', value: item?.answer?.[0] }, + 'value' + ) as TypedValue; + if (!itemValue) { + return null; + } + const propertyName = itemValue.type; + return ( + + {item.text} + + + + + ); +} + +function showCollapsibleButton(items: QuestionnaireResponseItem[]): boolean { + if (items.length === 0) { + return false; + } + return items.some((item) => item.answer?.length); +} diff --git a/examples/medplum-provider/src/components/tasks/TaskList.tsx b/examples/medplum-provider/src/components/tasks/TaskList.tsx new file mode 100644 index 0000000000..c36f7b41ac --- /dev/null +++ b/examples/medplum-provider/src/components/tasks/TaskList.tsx @@ -0,0 +1,182 @@ +import { Box, Card, Divider, Flex, Group, Text, Title } from '@mantine/core'; +import { formatDate } from '@medplum/core'; +import { CodeableConcept, Questionnaire, Reference, Resource, Task } from '@medplum/fhirtypes'; +import { + CodeableConceptDisplay, + ErrorBoundary, + ResourceName, + StatusBadge, + Timeline, + useMedplum, + useResource, +} from '@medplum/react'; +import { IconFilePencil, IconHeart, IconListCheck, IconReportMedical } from '@tabler/icons-react'; +import { Fragment, ReactNode, useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { DiagnosticReportModal } from './DiagnosticReportTask'; +import { QuestionnaireTask, ResponseDisplay } from './QuestionnaireTask'; + +const focusIcons: Record = { + Questionnaire: , + QuestionnaireResponse: , + DiagnosticReport: , + CarePlan: , +}; + +export interface TaskCellProps { + readonly task: Task; + readonly resource: Resource; +} + +interface TaskItemProps { + readonly task: Task; + readonly resource: Resource; + readonly profile?: Reference; + readonly children?: ReactNode; +} + +export function TaskList(): JSX.Element | null { + const { id } = useParams(); + const [tasks, setTasks] = useState([]); + const medplum = useMedplum(); + const patient = useResource({ reference: `Patient/${id}` }); + useEffect(() => { + medplum + .searchResources( + 'Task', + `patient=${id}&status:not=completed&status:not=failed&status:not=rejected&focus:missing=false` + ) + .then((response) => { + setTasks(response); + }) + .catch(console.error); + }); + + if (!patient) { + return null; + } + + return ( + + {`Required Action (${tasks.length})`} + + + {tasks.map((task, idx) => ( + + + {idx !== tasks.length - 1 ? : null} + + ))} + + + + ); +} + +function FocusTimeline(props: { task: Task }): JSX.Element | null { + const task = props.task; + + const focused = useResource(task.focus); + if (!focused) { + return null; + } + return ( + + + + + + ); +} + +function ResourceFocus(props: TaskCellProps): JSX.Element { + const resource = props.resource; + + function renderResourceContent(resource: Resource): JSX.Element { + switch (resource.resourceType) { + case 'Questionnaire': + return ; + case 'QuestionnaireResponse': + return ; + case 'DiagnosticReport': + return ; + case 'CarePlan': + return ; + default: + return
; + } + } + + if (!resource) { + return
; + } + return {renderResourceContent(resource)}; +} + +function TaskItem(props: TaskItemProps): JSX.Element { + const { task, resource, profile } = props; + const author = profile ?? resource.meta?.author; + const dateTime = resource.meta?.lastUpdated; + return ( + <> + + {focusIcons[resource.resourceType]} + + + + + + + + {'status' in props.resource && } + + + + + + + · + + + {formatDate(dateTime)} + + + + + + <>{props.children} + + + ); +} + +function TaskTitle(props: TaskCellProps): JSX.Element { + const [title, setTitle] = useState(); + const medplum = useMedplum(); + + useEffect(() => { + async function fetchQuestionnaireTitle(): Promise { + if (props.resource.resourceType === 'QuestionnaireResponse') { + const questionnaireId = props.resource.questionnaire?.split('/')[1]; + try { + const questionnaire = await medplum.readResource('Questionnaire', questionnaireId as string); + setTitle(<>{questionnaire?.title} Response); + } catch (err) { + setTitle(<>Response); + } + } + } + + if ('code' in props.resource && props.resource.code) { + setTitle(); + } else if (props.resource.resourceType === 'Questionnaire') { + setTitle(<>{props.resource.title}); + } else if (props.resource.resourceType === 'QuestionnaireResponse') { + fetchQuestionnaireTitle().catch(console.error); + } else { + setTitle(<>{props.task.code}); + } + }, [props.resource, props.task, medplum]); + + return title ?? <>; +} diff --git a/examples/medplum-provider/src/main.tsx b/examples/medplum-provider/src/main.tsx new file mode 100644 index 0000000000..e0f5b128cf --- /dev/null +++ b/examples/medplum-provider/src/main.tsx @@ -0,0 +1,47 @@ +import { MantineProvider, createTheme } from '@mantine/core'; +import '@mantine/core/styles.css'; +import { MedplumClient } from '@medplum/core'; +import { MedplumProvider } from '@medplum/react'; +import '@medplum/react/styles.css'; +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import { App } from './App'; + +const medplum = new MedplumClient({ + onUnauthenticated: () => (window.location.href = '/'), + // baseUrl: 'http://localhost:8103/', //Uncomment this to run against the server on your localhost +}); + +const theme = createTheme({ + headings: { + sizes: { + h1: { + fontSize: '1.125rem', + fontWeight: '500', + lineHeight: '2.0', + }, + }, + }, + fontSizes: { + xs: '0.6875rem', + sm: '0.875rem', + md: '0.875rem', + lg: '1.0rem', + xl: '1.125rem', + }, +}); + +const container = document.getElementById('root') as HTMLDivElement; +const root = createRoot(container); +root.render( + + + + + + + + + +); diff --git a/examples/medplum-provider/src/pages/HomePage.tsx b/examples/medplum-provider/src/pages/HomePage.tsx new file mode 100644 index 0000000000..6acc2372b5 --- /dev/null +++ b/examples/medplum-provider/src/pages/HomePage.tsx @@ -0,0 +1,33 @@ +import { Title } from '@mantine/core'; +import { getReferenceString } from '@medplum/core'; +import { Practitioner } from '@medplum/fhirtypes'; +import { Document, ResourceName, SearchControl, useMedplumNavigate, useMedplumProfile } from '@medplum/react'; +import { Outlet } from 'react-router-dom'; + +/** + * Home page that greets the user and displays a list of patients. + * @returns A React component that displays the home page. + */ +export function HomePage(): JSX.Element { + // useMedplumProfile() returns the "profile resource" associated with the user. + // This can be a Practitioner, Patient, or RelatedPerson depending on the user's role in the project. + // See the "Register" tutorial for more detail + // https://www.medplum.com/docs/tutorials/register + const profile = useMedplumProfile() as Practitioner; + + const navigate = useMedplumNavigate(); + + return ( + + + Welcome <ResourceName value={profile} link /> + + navigate(`/${getReferenceString(e.resource)}`)} + hideToolbar + /> + + + ); +} diff --git a/examples/medplum-provider/src/pages/LandingPage.tsx b/examples/medplum-provider/src/pages/LandingPage.tsx new file mode 100644 index 0000000000..d229737581 --- /dev/null +++ b/examples/medplum-provider/src/pages/LandingPage.tsx @@ -0,0 +1,23 @@ +import { Anchor, Button, Stack, Text, Title } from '@mantine/core'; +import { Document } from '@medplum/react'; +import { Link } from 'react-router-dom'; + +export function LandingPage(): JSX.Element { + return ( + + + + Welcome! + + + This "Hello World" example demonstrates how to build a simple React application that fetches Patient data from + Medplum. If you haven't already done so, register for + Medplum Project. After that you can sign into your project by clicking the link below. + + + + + ); +} diff --git a/examples/medplum-provider/src/pages/PatientPage.tsx b/examples/medplum-provider/src/pages/PatientPage.tsx new file mode 100644 index 0000000000..3b159a6c7e --- /dev/null +++ b/examples/medplum-provider/src/pages/PatientPage.tsx @@ -0,0 +1,26 @@ +import { Flex, Loader } from '@mantine/core'; +import { getReferenceString } from '@medplum/core'; +import { Patient } from '@medplum/fhirtypes'; +import { PatientSummary, useResource } from '@medplum/react'; +import { Fragment } from 'react'; +import { useParams } from 'react-router-dom'; +import { SoapNote } from '../components/soapnote/SoapNote'; +import { TaskList } from '../components/tasks/TaskList'; + +export function PatientPage(): JSX.Element { + const { id } = useParams(); + const patient = useResource({ reference: `Patient/${id}` }); + if (!patient) { + return ; + } + + return ( + + + + + + + + ); +} diff --git a/examples/medplum-provider/src/pages/ResourcePage.tsx b/examples/medplum-provider/src/pages/ResourcePage.tsx new file mode 100644 index 0000000000..59f9a6b759 --- /dev/null +++ b/examples/medplum-provider/src/pages/ResourcePage.tsx @@ -0,0 +1,37 @@ +import { Title } from '@mantine/core'; +import { getDisplayString, getReferenceString } from '@medplum/core'; +import { Resource, ResourceType } from '@medplum/fhirtypes'; +import { Document, ResourceTable, useMedplum } from '@medplum/react'; +import { useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; + +/** + * This is an example of a generic "Resource Display" page. + * It uses the Medplum `` component to display a resource. + * @returns A React component that displays a resource. + */ +export function ResourcePage(): JSX.Element | null { + const medplum = useMedplum(); + const { resourceType, id } = useParams(); + const [resource, setResource] = useState(undefined); + + useEffect(() => { + if (resourceType && id) { + medplum + .readResource(resourceType as ResourceType, id) + .then(setResource) + .catch(console.error); + } + }, [medplum, resourceType, id]); + + if (!resource) { + return null; + } + + return ( + + {getDisplayString(resource)} + + + ); +} diff --git a/examples/medplum-provider/src/pages/SearchPage.module.css b/examples/medplum-provider/src/pages/SearchPage.module.css new file mode 100644 index 0000000000..f80eca6060 --- /dev/null +++ b/examples/medplum-provider/src/pages/SearchPage.module.css @@ -0,0 +1,6 @@ +.paper { + @mixin smaller-than $mantine-breakpoint-sm { + margin: 2px; + padding: 4px; + } +} diff --git a/examples/medplum-provider/src/pages/SearchPage.tsx b/examples/medplum-provider/src/pages/SearchPage.tsx new file mode 100644 index 0000000000..0777d6803b --- /dev/null +++ b/examples/medplum-provider/src/pages/SearchPage.tsx @@ -0,0 +1,104 @@ +import { Paper } from '@mantine/core'; +import { + DEFAULT_SEARCH_COUNT, + Filter, + formatSearchQuery, + parseSearchDefinition, + SearchRequest, + SortRule, +} from '@medplum/core'; +import { UserConfiguration } from '@medplum/fhirtypes'; +import { Loading, MemoizedSearchControl, useMedplum } from '@medplum/react'; +import { useEffect, useState } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import classes from './SearchPage.module.css'; + +export function SearchPage(): JSX.Element { + const medplum = useMedplum(); + const navigate = useNavigate(); + const location = useLocation(); + const [search, setSearch] = useState(); + + useEffect(() => { + const parsedSearch = parseSearchDefinition(location.pathname + location.search); + + const populatedSearch = addSearchValues(parsedSearch, medplum.getUserConfiguration()); + + if ( + location.pathname === `/${populatedSearch.resourceType}` && + location.search === formatSearchQuery(populatedSearch) + ) { + saveLastSearch(populatedSearch); + setSearch(populatedSearch); + } else { + navigate(`/${populatedSearch.resourceType}${formatSearchQuery(populatedSearch)}`); + } + }, [medplum, navigate, location]); + + if (!search?.resourceType || !search.fields || search.fields.length === 0) { + return ; + } + + return ( + + navigate(`/${e.resource.resourceType}/${e.resource.id}`)} + onAuxClick={(e) => window.open(`/${e.resource.resourceType}/${e.resource.id}`, '_blank')} + onChange={(e) => { + navigate(`/${search.resourceType}${formatSearchQuery(e.definition)}`); + }} + /> + + ); +} + +function addSearchValues(search: SearchRequest, config: UserConfiguration | undefined): SearchRequest { + const resourceType = search.resourceType || getDefaultResourceType(config); + const fields = search.fields ?? ['_id', '_lastUpdated']; + const filters = search.filters ?? (!search.resourceType ? getDefaultFilters(resourceType) : undefined); + const sortRules = search.sortRules ?? getDefaultSortRules(resourceType); + const offset = search.offset ?? 0; + const count = search.count ?? DEFAULT_SEARCH_COUNT; + + return { + ...search, + resourceType, + fields, + filters, + sortRules, + offset, + count, + }; +} + +function getDefaultResourceType(config: UserConfiguration | undefined): string { + return ( + localStorage.getItem('defaultResourceType') ?? + config?.option?.find((o) => o.id === 'defaultResourceType')?.valueString ?? + 'Task' + ); +} + +function getDefaultFilters(resourceType: string): Filter[] | undefined { + return getLastSearch(resourceType)?.filters; +} + +function getDefaultSortRules(resourceType: string): SortRule[] { + const lastSearch = getLastSearch(resourceType); + if (lastSearch?.sortRules) { + return lastSearch.sortRules; + } + return [{ code: '_lastUpdated', descending: true }]; +} + +function getLastSearch(resourceType: string): SearchRequest | undefined { + const value = localStorage.getItem(resourceType + '-defaultSearch'); + return value ? (JSON.parse(value) as SearchRequest) : undefined; +} + +function saveLastSearch(search: SearchRequest): void { + localStorage.setItem('defaultResourceType', search.resourceType); + localStorage.setItem(search.resourceType + '-defaultSearch', JSON.stringify(search)); +} diff --git a/examples/medplum-provider/src/pages/SignInPage.tsx b/examples/medplum-provider/src/pages/SignInPage.tsx new file mode 100644 index 0000000000..d7770ed8a2 --- /dev/null +++ b/examples/medplum-provider/src/pages/SignInPage.tsx @@ -0,0 +1,17 @@ +import { Title } from '@mantine/core'; +import { Logo, SignInForm } from '@medplum/react'; +import { useNavigate } from 'react-router-dom'; + +export function SignInPage(): JSX.Element { + const navigate = useNavigate(); + return ( + navigate('/')} + > + + Sign in to Medplum + + ); +} diff --git a/examples/medplum-provider/src/vite-env.d.ts b/examples/medplum-provider/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/medplum-provider/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/medplum-provider/tsconfig.json b/examples/medplum-provider/tsconfig.json new file mode 100644 index 0000000000..79240f45dd --- /dev/null +++ b/examples/medplum-provider/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"] +} diff --git a/examples/medplum-provider/vercel.json b/examples/medplum-provider/vercel.json new file mode 100644 index 0000000000..33c4ed5992 --- /dev/null +++ b/examples/medplum-provider/vercel.json @@ -0,0 +1,5 @@ +{ + "installCommand": "npm install", + "buildCommand": "npm run build", + "routes": [{ "src": "/[^.]+", "dest": "/", "status": 200 }] +} diff --git a/examples/medplum-provider/vite.config.ts b/examples/medplum-provider/vite.config.ts new file mode 100644 index 0000000000..e4c46a598c --- /dev/null +++ b/examples/medplum-provider/vite.config.ts @@ -0,0 +1,14 @@ +import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vite'; +import dns from 'dns'; + +dns.setDefaultResultOrder('verbatim'); + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + server: { + host: 'localhost', + port: 3000, + }, +}); diff --git a/package-lock.json b/package-lock.json index fde88b27cd..777f4fb19b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -664,6 +664,734 @@ "typescript": "5.3.3" } }, + "examples/medplum-provider": { + "version": "3.0.3", + "devDependencies": { + "@mantine/core": "7.5.2", + "@mantine/hooks": "7.5.2", + "@mantine/notifications": "7.5.2", + "@medplum/core": "3.0.3", + "@medplum/eslint-config": "3.0.3", + "@medplum/fhirtypes": "3.0.3", + "@medplum/react": "3.0.3", + "@tabler/icons-react": "2.47.0", + "@types/node": "20.11.17", + "@types/react": "18.2.55", + "@types/react-dom": "18.2.19", + "@vitejs/plugin-react": "4.2.1", + "postcss": "8.4.35", + "postcss-preset-mantine": "1.13.0", + "react": "18.2.0", + "react-dom": "18.2.0", + "react-router-dom": "6.22.0", + "typescript": "5.3.3", + "vite": "5.1.1" + } + }, + "examples/medplum-provider/node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "examples/medplum-provider/node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "examples/medplum-provider/node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "examples/medplum-provider/node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "examples/medplum-provider/node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "examples/medplum-provider/node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "examples/medplum-provider/node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "examples/medplum-provider/node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "examples/medplum-provider/node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "examples/medplum-provider/node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "examples/medplum-provider/node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "examples/medplum-provider/node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "examples/medplum-provider/node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "examples/medplum-provider/node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "examples/medplum-provider/node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "examples/medplum-provider/node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "examples/medplum-provider/node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "examples/medplum-provider/node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "examples/medplum-provider/node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "examples/medplum-provider/node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "examples/medplum-provider/node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "examples/medplum-provider/node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "examples/medplum-provider/node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "examples/medplum-provider/node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "optional": true, + "peer": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "examples/medplum-provider/node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "examples/medplum-provider/node_modules/lightningcss": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.23.0.tgz", + "integrity": "sha512-SEArWKMHhqn/0QzOtclIwH5pXIYQOUEkF8DgICd/105O+GCgd7jxjNod/QPnBCSWvpRHQBGVz5fQ9uScby03zA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "detect-libc": "^1.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.23.0", + "lightningcss-darwin-x64": "1.23.0", + "lightningcss-freebsd-x64": "1.23.0", + "lightningcss-linux-arm-gnueabihf": "1.23.0", + "lightningcss-linux-arm64-gnu": "1.23.0", + "lightningcss-linux-arm64-musl": "1.23.0", + "lightningcss-linux-x64-gnu": "1.23.0", + "lightningcss-linux-x64-musl": "1.23.0", + "lightningcss-win32-x64-msvc": "1.23.0" + } + }, + "examples/medplum-provider/node_modules/lightningcss-darwin-arm64": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.23.0.tgz", + "integrity": "sha512-kl4Pk3Q2lnE6AJ7Qaij47KNEfY2/UXRZBT/zqGA24B8qwkgllr/j7rclKOf1axcslNXvvUdztjo4Xqh39Yq1aA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "examples/medplum-provider/node_modules/lightningcss-darwin-x64": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.23.0.tgz", + "integrity": "sha512-KeRFCNoYfDdcolcFXvokVw+PXCapd2yHS1Diko1z1BhRz/nQuD5XyZmxjWdhmhN/zj5sH8YvWsp0/lPLVzqKpg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "examples/medplum-provider/node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.23.0.tgz", + "integrity": "sha512-fBamf/bULvmWft9uuX+bZske236pUZEoUlaHNBjnueaCTJ/xd8eXgb0cEc7S5o0Nn6kxlauMBnqJpF70Bgq3zg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "examples/medplum-provider/node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.23.0.tgz", + "integrity": "sha512-RS7sY77yVLOmZD6xW2uEHByYHhQi5JYWmgVumYY85BfNoVI3DupXSlzbw+b45A9NnVKq45+oXkiN6ouMMtTwfg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "examples/medplum-provider/node_modules/lightningcss-linux-arm64-musl": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.23.0.tgz", + "integrity": "sha512-cU00LGb6GUXCwof6ACgSMKo3q7XYbsyTj0WsKHLi1nw7pV0NCq8nFTn6ZRBYLoKiV8t+jWl0Hv8KkgymmK5L5g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "examples/medplum-provider/node_modules/lightningcss-linux-x64-gnu": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.23.0.tgz", + "integrity": "sha512-q4jdx5+5NfB0/qMbXbOmuC6oo7caPnFghJbIAV90cXZqgV8Am3miZhC4p+sQVdacqxfd+3nrle4C8icR3p1AYw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "examples/medplum-provider/node_modules/lightningcss-linux-x64-musl": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.23.0.tgz", + "integrity": "sha512-G9Ri3qpmF4qef2CV/80dADHKXRAQeQXpQTLx7AiQrBYQHqBjB75oxqj06FCIe5g4hNCqLPnM9fsO4CyiT1sFSQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "examples/medplum-provider/node_modules/lightningcss-win32-x64-msvc": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.23.0.tgz", + "integrity": "sha512-1rcBDJLU+obPPJM6qR5fgBUiCdZwZLafZM5f9kwjFLkb/UBNIzmae39uCSmh71nzPCTXZqHbvwu23OWnWEz+eg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "examples/medplum-provider/node_modules/rollup": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", + "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.10.0", + "@rollup/rollup-android-arm64": "4.10.0", + "@rollup/rollup-darwin-arm64": "4.10.0", + "@rollup/rollup-darwin-x64": "4.10.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", + "@rollup/rollup-linux-arm64-gnu": "4.10.0", + "@rollup/rollup-linux-arm64-musl": "4.10.0", + "@rollup/rollup-linux-riscv64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-musl": "4.10.0", + "@rollup/rollup-win32-arm64-msvc": "4.10.0", + "@rollup/rollup-win32-ia32-msvc": "4.10.0", + "@rollup/rollup-win32-x64-msvc": "4.10.0", + "fsevents": "~2.3.2" + } + }, + "examples/medplum-provider/node_modules/vite": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", + "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", + "dev": true, + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.35", + "rollup": "^4.2.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, "examples/medplum-react-native-example": { "version": "3.0.3", "dependencies": { @@ -32353,6 +33081,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -32366,6 +33095,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -39684,6 +40414,10 @@ "resolved": "examples/medplum-nextjs-demo", "link": true }, + "node_modules/medplum-provider": { + "resolved": "examples/medplum-provider", + "link": true + }, "node_modules/medplum-react-native-example": { "resolved": "examples/medplum-react-native-example", "link": true @@ -42723,6 +43457,7 @@ "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, From 5e7543473ae19e077d21be403adc5fe1f732daa6 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Mon, 12 Feb 2024 21:45:01 -0800 Subject: [PATCH 35/81] Fixed socket.dev dependency warnings (#3930) --- package-lock.json | 30 +++++++++++++----------------- package.json | 2 ++ 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index 777f4fb19b..a37e327e54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29926,17 +29926,13 @@ } }, "node_modules/es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", - "hasInstallScript": true, + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" - }, - "engines": { - "node": ">=0.10" + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" } }, "node_modules/es6-error": { @@ -44078,9 +44074,9 @@ } }, "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha512-mc/caHeUcdjnC/boPWJefDr4KUIWQNv+tlnFnJd38QMou86QtxQzBJfxgGRzvx8jazYRqrVlaHarfO72uNxPOg==" }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", @@ -52775,10 +52771,10 @@ } }, "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "deprecated": "Please use @jridgewell/sourcemap-codec instead" + "name": "@jridgewell/sourcemap-codec", + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/space-separated-tokens": { "version": "2.0.2", diff --git a/package.json b/package.json index a7027603bf..b4e08daeef 100644 --- a/package.json +++ b/package.json @@ -65,10 +65,12 @@ "node": ">=18.0.0" }, "overrides": { + "es5-ext": "0.10.53", "esbuild": "0.20.0", "got": "11.8.6", "react": "18.2.0", "react-dom": "18.2.0", + "sourcemap-codec": "npm:@jridgewell/sourcemap-codec@1.4.15", "trim": "0.0.3" } } From c6216ab38db68a54a001eb629762acdef58c8934 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Tue, 13 Feb 2024 09:06:15 -0800 Subject: [PATCH 36/81] Fixes #3786 - startAsyncRequest and token refresh (#3937) * Fixes #3786 - startAsyncRequest and token refresh * Fixed tests * Cleanup * Check for double reading stream * Cleanup * Fixed mock headers --- packages/cli/src/bulk.test.ts | 32 ++- packages/cli/src/profiles.test.ts | 24 ++- packages/core/src/client-test-utils.ts | 35 ++- packages/core/src/client.test.ts | 281 +++++++++++-------------- packages/core/src/client.ts | 118 ++++++----- packages/mock/src/client.ts | 6 +- 6 files changed, 270 insertions(+), 226 deletions(-) diff --git a/packages/cli/src/bulk.test.ts b/packages/cli/src/bulk.test.ts index a4542fc298..e038231cc7 100644 --- a/packages/cli/src/bulk.test.ts +++ b/packages/cli/src/bulk.test.ts @@ -51,7 +51,13 @@ describe('CLI Bulk Commands', () => { if (url.includes('/$export?_since=200')) { return { status: 200, - headers: { get: () => ContentType.FHIR_JSON }, + headers: { + get(name: string): string | undefined { + return { + 'content-type': ContentType.FHIR_JSON, + }[name]; + }, + }, json: jest.fn(async () => { return { resourceType: 'OperationOutcome', @@ -104,7 +110,13 @@ describe('CLI Bulk Commands', () => { count++; return { status: 202, - headers: { get: () => ContentType.FHIR_JSON }, + headers: { + get(name: string): string | undefined { + return { + 'content-type': ContentType.FHIR_JSON, + }[name]; + }, + }, json: jest.fn(async () => { return {}; }), @@ -114,7 +126,13 @@ describe('CLI Bulk Commands', () => { return { status: 200, - headers: { get: () => ContentType.FHIR_JSON }, + headers: { + get(name: string): string | undefined { + return { + 'content-type': ContentType.FHIR_JSON, + }[name]; + }, + }, json: jest.fn(async () => ({ transactionTime: '2023-05-18T22:55:31.280Z', request: 'https://api.medplum.com/fhir/R4/$export?_type=Observation', @@ -186,7 +204,13 @@ describe('CLI Bulk Commands', () => { fetch = jest.fn(async () => { return { status: 200, - headers: { get: () => ContentType.FHIR_JSON }, + headers: { + get(name: string): string | undefined { + return { + 'content-type': ContentType.FHIR_JSON, + }[name]; + }, + }, json: jest.fn(async () => ({ resourceType: 'Bundle', type: 'transaction-response', diff --git a/packages/cli/src/profiles.test.ts b/packages/cli/src/profiles.test.ts index 97e64c448c..47129e4237 100644 --- a/packages/cli/src/profiles.test.ts +++ b/packages/cli/src/profiles.test.ts @@ -32,7 +32,13 @@ describe('Profiles', () => { if (url.includes('/$export?_since=200')) { return { status: 200, - headers: { get: () => ContentType.FHIR_JSON }, + headers: { + get(name: string): string | undefined { + return { + 'content-type': ContentType.FHIR_JSON, + }[name]; + }, + }, json: jest.fn(async () => { return { resourceType: 'OperationOutcome', @@ -85,7 +91,13 @@ describe('Profiles', () => { count++; return { status: 202, - headers: { get: () => ContentType.FHIR_JSON }, + headers: { + get(name: string): string | undefined { + return { + 'content-type': ContentType.FHIR_JSON, + }[name]; + }, + }, json: jest.fn(async () => { return {}; }), @@ -95,7 +107,13 @@ describe('Profiles', () => { return { status: 200, - headers: { get: () => ContentType.FHIR_JSON }, + headers: { + get(name: string): string | undefined { + return { + 'content-type': ContentType.FHIR_JSON, + }[name]; + }, + }, json: jest.fn(async () => ({ transactionTime: '2023-05-18T22:55:31.280Z', request: 'https://api.medplum.com/fhir/R4/$export?_type=Observation', diff --git a/packages/core/src/client-test-utils.ts b/packages/core/src/client-test-utils.ts index 765fd6294a..2d61c31fa2 100644 --- a/packages/core/src/client-test-utils.ts +++ b/packages/core/src/client-test-utils.ts @@ -12,12 +12,33 @@ export function mockFetch( return jest.fn((url: string, options?: any) => { const response = bodyFn(url, options); const responseStatus = isOperationOutcome(response) ? getStatus(response) : status; - return Promise.resolve({ - ok: responseStatus < 400, - status: responseStatus, - headers: { get: () => contentType }, - blob: () => Promise.resolve(response), - json: () => Promise.resolve(response), - }); + return Promise.resolve(mockFetchResponse(responseStatus, response, { 'content-type': contentType })); }); } + +export function mockFetchResponse(status: number, body: any, headers?: Record): Response { + const headersMap = new Map(); + if (headers) { + for (const [key, value] of Object.entries(headers)) { + headersMap.set(key, value); + } + } + if (!headersMap.has('content-type')) { + headersMap.set('content-type', ContentType.FHIR_JSON); + } + let streamRead = false; + const streamReader = async (): Promise => { + if (streamRead) { + throw new Error('Stream already read'); + } + streamRead = true; + return body; + }; + return { + ok: status < 400, + status, + headers: headersMap, + blob: streamReader, + json: streamReader, + } as unknown as Response; +} diff --git a/packages/core/src/client.test.ts b/packages/core/src/client.test.ts index f9c08d4a21..984c6ca8b3 100644 --- a/packages/core/src/client.test.ts +++ b/packages/core/src/client.test.ts @@ -13,9 +13,9 @@ import { NewProjectRequest, NewUserRequest, } from './client'; -import { mockFetch } from './client-test-utils'; +import { mockFetch, mockFetchResponse } from './client-test-utils'; import { ContentType } from './contenttype'; -import { OperationOutcomeError, notFound, unauthorized } from './outcomes'; +import { OperationOutcomeError, accepted, allOk, forbidden, notFound, unauthorized } from './outcomes'; import { MockAsyncClientStorage } from './storage'; import { getDataType, isDataTypeLoaded, isProfileLoaded } from './typeschema/types'; import { ProfileResource, createReference } from './utils'; @@ -2287,18 +2287,7 @@ describe('Client', () => { test('Auto batch single request error', async () => { const fetch = mockFetch(404, notFound); - (fetch as unknown as jest.Mock).mockImplementation(() => ({ - status: 404, - json: () => notFound, - headers: { - get(name: string): string | undefined { - return { - 'content-type': ContentType.FHIR_JSON, - }[name]; - }, - }, - })); - const medplum = new MedplumClient({ fetch: fetch, autoBatchTime: 100 }); + const medplum = new MedplumClient({ fetch, autoBatchTime: 100 }); try { await medplum.readResource('Patient', 'xyz-not-found'); @@ -2440,84 +2429,32 @@ describe('Client', () => { let count = 0; fetch = jest.fn(async (url) => { if (url.includes('/$export?_since=200')) { - return { - status: 200, - headers: { get: () => ContentType.FHIR_JSON }, - json: jest.fn(async () => { - return { - resourceType: 'OperationOutcome', - id: 'accepted', - issue: [ - { - severity: 'information', - code: 'informational', - details: { - text: 'Accepted', - }, - }, - ], - }; - }), - }; + return mockFetchResponse(200, accepted('bulkdata/id/status'), { 'content-location': 'bulkdata/id/status' }); } if (url.includes('/$export')) { - return { - status: 202, - json: jest.fn(async () => { - return { - resourceType: 'OperationOutcome', - id: 'accepted', - issue: [ - { - severity: 'information', - code: 'informational', - details: { - text: 'Accepted', - }, - }, - ], - }; - }), - headers: { - get(name: string): string | undefined { - return { - 'content-type': ContentType.FHIR_JSON, - 'content-location': 'bulkdata/id/status', - }[name]; - }, - }, - }; + return mockFetchResponse(202, accepted('bulkdata/id/status'), { 'content-location': 'bulkdata/id/status' }); } if (url.includes('bulkdata/id/status')) { if (count < 1) { count++; - return { - status: 202, - json: jest.fn(async () => { - return {}; - }), - }; + return mockFetchResponse(202, {}); } } - return { - status: 200, - headers: { get: () => ContentType.FHIR_JSON }, - json: jest.fn(async () => ({ - transactionTime: '2023-05-18T22:55:31.280Z', - request: 'https://api.medplum.com/fhir/R4/$export?_type=Observation', - requiresAccessToken: false, - output: [ - { - type: 'ProjectMembership', - url: 'https://api.medplum.com/storage/TEST', - }, - ], - error: [], - })), - }; + return mockFetchResponse(200, { + transactionTime: '2023-05-18T22:55:31.280Z', + request: 'https://api.medplum.com/fhir/R4/$export?_type=Observation', + requiresAccessToken: false, + output: [ + { + type: 'ProjectMembership', + url: 'https://api.medplum.com/storage/TEST', + }, + ], + error: [], + }); }); }); @@ -2571,29 +2508,7 @@ describe('Client', () => { }); test('Kick off missing content-location', async () => { - const fetch = jest.fn(async () => { - return { - status: 202, - json: jest.fn(async () => { - return { - resourceType: 'OperationOutcome', - id: 'accepted', - issue: [ - { - severity: 'information', - code: 'informational', - details: { - text: 'Accepted', - }, - }, - ], - }; - }), - headers: { - get: (key: string) => (key === 'content-type' ? ContentType.FHIR_JSON : null), - }, - }; - }); + const fetch = mockFetch(202, allOk); const medplum = new MedplumClient({ fetch }); const response = await medplum.bulkExport(); @@ -2620,11 +2535,88 @@ describe('Client', () => { expect((err as Error).message).toBe('Not found'); } }); + + test('Poll after token refresh', async () => { + const clientId = randomUUID(); + const clientSecret = randomUUID(); + const statusUrl = 'status-' + randomUUID(); + const locationUrl = 'location-' + randomUUID(); + + const mockTokens = { + access_token: createFakeJwt({ client_id: clientId, login_id: '123' }), + refresh_token: createFakeJwt({ client_id: clientId }), + profile: { reference: 'Patient/123' }, + }; + + const mockMe = { + project: { resourceType: 'Project', id: '123' }, + membership: { resourceType: 'ProjectMembership', id: '123' }, + profile: { resouceType: 'Practitioner', id: '123' }, + config: { resourceType: 'UserConfiguration', id: '123' }, + accessPolicy: { resourceType: 'AccessPolicy', id: '123' }, + }; + + let count = 0; + + const mockFetch = async (url: string, options: any): Promise => { + count++; + switch (count) { + case 1: + // First, handle the initial startClientLogin client credentials flow + expect(options.method).toBe('POST'); + expect(url).toBe('https://api.medplum.com/oauth2/token'); + return mockFetchResponse(200, mockTokens); + case 2: + // MedplumClient will automatically fetch the user profile after token refresh + expect(options.method).toBe('GET'); + expect(url).toBe('https://api.medplum.com/auth/me'); + return mockFetchResponse(200, mockMe); + case 3: + // Next, handle the initial bulk export - mock an expired token response + expect(options.method).toBe('POST'); + expect(url).toBe('https://api.medplum.com/fhir/R4/$export'); + return mockFetchResponse(401, forbidden); + case 4: + // Now MedplumClient will try to automatically refresh the token + expect(options.method).toBe('POST'); + expect(url).toBe('https://api.medplum.com/oauth2/token'); + return mockFetchResponse(200, mockTokens); + case 5: + // And then MedplumClient will automatically fetch the user profile again + expect(options.method).toBe('GET'); + expect(url).toBe('https://api.medplum.com/auth/me'); + return mockFetchResponse(200, mockMe); + case 6: + // Ok, whew, we are refreshed, so we can finally get the bulk export + // However, the bulk export isn't "done", so return "Accepted" + expect(options.method).toBe('POST'); + expect(url).toBe('https://api.medplum.com/fhir/R4/$export'); + return mockFetchResponse(202, accepted(statusUrl)); + case 7: + // Report status complete, and send the location of the bulk export + expect(options.method).toBe('GET'); + expect(url).toBe('https://api.medplum.com/' + statusUrl); + return mockFetchResponse(201, {}, { location: locationUrl }); + case 8: + // What a journey! Finally, we can get the contents of the bulk export + expect(options.method).toBe('GET'); + expect(url).toBe('https://api.medplum.com/' + locationUrl); + return mockFetchResponse(200, { resourceType: 'Bundle' }); + } + throw new Error('Unexpected fetch call: ' + url); + }; + + const medplum = new MedplumClient({ fetch: mockFetch }); + await medplum.startClientLogin(clientId, clientSecret); + const result = await medplum.bulkExport(); + expect(result).toMatchObject({ resourceType: 'Bundle' }); + }); }); describe('Downloading resources', () => { const baseUrl = 'https://api.medplum.com/'; const fhirUrlPath = 'fhir/R4/'; + const accessToken = 'fake'; let fetch: FetchLike; let client: MedplumClient; @@ -2633,6 +2625,7 @@ describe('Client', () => { text: () => Promise.resolve(url), })); client = new MedplumClient({ fetch, baseUrl, fhirUrlPath }); + client.setAccessToken(accessToken); }); test('Downloading resources via URL', async () => { @@ -2642,6 +2635,7 @@ describe('Client', () => { expect.objectContaining({ headers: { Accept: DEFAULT_ACCEPT, + Authorization: `Bearer ${accessToken}`, 'X-Medplum': 'extended', }, }) @@ -2656,6 +2650,7 @@ describe('Client', () => { expect.objectContaining({ headers: { Accept: DEFAULT_ACCEPT, + Authorization: `Bearer ${accessToken}`, 'X-Medplum': 'extended', }, }) @@ -2693,66 +2688,34 @@ describe('Client', () => { const fetch = jest.fn(); // First time, return 202 Accepted with Content-Location - fetch.mockImplementationOnce(async () => ({ - ok: true, - status: 202, - headers: { - get: (key: string) => { - if (key.toLowerCase() === 'content-location') { - return 'https://example.com/content-location/1'; - } - if (key.toLowerCase() === 'content-type') { - return ContentType.FHIR_JSON; - } - return null; - }, - }, - json: async () => ({}), - })); + fetch.mockImplementationOnce(async () => + mockFetchResponse( + 202, + {}, + { + 'content-location': 'https://example.com/content-location/1', + } + ) + ); // Second time, return 202 Accepted with Content-Location - fetch.mockImplementationOnce(async () => ({ - ok: true, - status: 202, - headers: { - get: (key: string) => { - if (key.toLowerCase() === 'content-location') { - return 'https://example.com/content-location/1'; - } - if (key.toLowerCase() === 'content-type') { - return ContentType.FHIR_JSON; - } - return null; - }, - }, - json: async () => ({}), - })); + fetch.mockImplementationOnce(async () => + mockFetchResponse( + 202, + {}, + { + 'content-location': 'https://example.com/content-location/1', + } + ) + ); // Third time, return 201 Created with Location - fetch.mockImplementationOnce(async () => ({ - ok: true, - status: 201, - headers: { - get: (key: string) => { - if (key.toLowerCase() === 'location') { - return 'https://example.com/location/1'; - } - if (key.toLowerCase() === 'content-type') { - return ContentType.FHIR_JSON; - } - return null; - }, - }, - json: async () => ({}), - })); + fetch.mockImplementationOnce(async () => + mockFetchResponse(201, {}, { location: 'https://example.com/location/1' }) + ); - // Fourth time, return 200 with JSON - fetch.mockImplementationOnce(async () => ({ - ok: true, - status: 201, - headers: { get: () => ContentType.FHIR_JSON }, - json: async () => ({ resourceType: 'Patient' }), - })); + // Fourth time, return 201 with JSON + fetch.mockImplementationOnce(async () => mockFetchResponse(201, { resourceType: 'Patient' })); const client = new MedplumClient({ fetch }); const response = await client.startAsyncRequest('/test', { method: 'POST', body: '{}' }); diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index e7370c6fc0..444b328e6f 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -518,6 +518,11 @@ interface AutoBatchEntry { readonly reject: (reason: any) => void; } +interface RequestState { + statusUrl?: string; + pollCount?: number; +} + /** * OAuth 2.0 Grant Type Identifiers * Standard identifiers: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07#name-grant-types @@ -2699,16 +2704,7 @@ export class MedplumClient extends EventTarget { const headers = options.headers as Record; headers['Prefer'] = 'respond-async'; - const response = await this.fetchWithRetry(url, options); - - if (response.status === 202) { - const contentLocation = await tryGetContentLocation(response); - if (contentLocation) { - return this.pollStatus(contentLocation); - } - } - - return this.parseResponse(response, 'POST', url); + return this.request('POST', url, options); } // @@ -2773,9 +2769,15 @@ export class MedplumClient extends EventTarget { * @param method - The HTTP method (GET, POST, etc). * @param url - The target URL. * @param options - Optional fetch request init options. + * @param state - Optional request state. * @returns The JSON content body if available. */ - private async request(method: string, url: string, options: RequestInit = {}): Promise { + private async request( + method: string, + url: string, + options: RequestInit = {}, + state: RequestState = {} + ): Promise { await this.refreshIfExpired(); options.method = method; @@ -2783,15 +2785,6 @@ export class MedplumClient extends EventTarget { const response = await this.fetchWithRetry(url, options); - return this.parseResponse(response, method, url, options); - } - - private async parseResponse( - response: Response, - method: string, - url: string, - options: RequestInit = {} - ): Promise { if (response.status === 401) { // Refresh and try again return this.handleUnauthenticated(method, url, options); @@ -2806,16 +2799,40 @@ export class MedplumClient extends EventTarget { const isJson = contentType?.includes('json'); if (response.status === 404 && !isJson) { + // Special case for non-JSON 404 responses + // In the common case, the 404 response will include an OperationOutcome in JSON with additional details. + // In the non-JSON case, we can't parse the response, so we'll just throw a generic "Not Found" error. throw new OperationOutcomeError(notFound); } - const contentLocation = response.headers.get('content-location'); + const obj = await this.parseBody(response, isJson); + const redirectMode = options.redirect ?? this.options.redirect; - if (response.status === 201 && contentLocation && redirectMode === 'follow') { - // Follow redirect - return this.request('GET', contentLocation, { ...options, body: undefined }); + if ((response.status === 200 || response.status === 201) && redirectMode === 'follow') { + const contentLocation = await tryGetContentLocation(response, obj); + if (contentLocation) { + // Follow redirect + return this.request('GET', contentLocation, { ...options, body: undefined }); + } + } + + const preferMode = (options.headers as Record | undefined)?.['Prefer']; + if (response.status === 202 && preferMode === 'respond-async') { + const contentLocation = await tryGetContentLocation(response, obj); + const statusUrl = contentLocation ?? state.statusUrl; + if (statusUrl) { + return this.pollStatus(statusUrl, options, state); + } } + if (response.status >= 400) { + throw new OperationOutcomeError(normalizeOperationOutcome(obj)); + } + + return obj; + } + + private async parseBody(response: Response, isJson: boolean | undefined): Promise { let obj: any = undefined; if (isJson) { try { @@ -2827,11 +2844,6 @@ export class MedplumClient extends EventTarget { } else { obj = await response.text(); } - - if (response.status >= 400) { - throw new OperationOutcomeError(normalizeOperationOutcome(obj)); - } - return obj; } @@ -2881,29 +2893,19 @@ export class MedplumClient extends EventTarget { } } - private async pollStatus(statusUrl: string): Promise { - let checkStatus = true; - let resultResponse; - const retryDelay = 2000; - - while (checkStatus) { - const fetchOptions = {}; - this.addFetchOptionsDefaults(fetchOptions); - const statusResponse = await this.fetchWithRetry(statusUrl, fetchOptions); - if (statusResponse.status !== 202) { - checkStatus = false; - resultResponse = statusResponse; - - if (statusResponse.status === 201) { - const contentLocation = await tryGetContentLocation(statusResponse); - if (contentLocation) { - resultResponse = await this.fetchWithRetry(contentLocation, fetchOptions); - } - } - } + private async pollStatus(statusUrl: string, options: RequestInit, state: RequestState): Promise { + if (state.pollCount === undefined) { + // First request - try request immediately + options.redirect = 'follow'; + state.statusUrl = statusUrl; + state.pollCount = 1; + } else { + // Subsequent requests - wait and retry + const retryDelay = 1000; await sleep(retryDelay); + state.pollCount++; } - return this.parseResponse(resultResponse as Response, 'POST', statusUrl); + return this.request('GET', statusUrl, options, state); } /** @@ -3559,15 +3561,28 @@ function concatUrls(baseUrl: string, url: string): string { * most authoritative source for the content location. If this header is * not present, it falls back to the "Location" HTTP header. * + * Note that the FHIR spec does not follow the traditional HTTP semantics of "Content-Location" and "Location". + * "Content-Location" is not typically used with HTTP 202 responses because the content itself isn't available at the time of the response. + * However, the FHIR spec explicitly recommends it: + * + * 3.2.6.1.2 Kick-off Request + * 3.2.6.1.2.0.3 Response - Success + * HTTP Status Code of 202 Accepted + * Content-Location header with the absolute URL of an endpoint for subsequent status requests (polling location) + * + * Source: https://hl7.org/fhir/async-bulk.html + * * In cases where neither of these headers are available (for instance, * due to CORS restrictions), it attempts to retrieve the content location * from the 'diagnostics' field of the first issue in an OperationOutcome object * present in the response body. If all attempts fail, the function returns 'undefined'. + * * @async * @param response - The HTTP response object from which to extract the content location. + * @param body - The response body. * @returns A Promise that resolves to the content location string if it is found, or 'undefined' if the content location cannot be determined from the response. */ -async function tryGetContentLocation(response: Response): Promise { +async function tryGetContentLocation(response: Response, body: any): Promise { // Accepted content location can come from multiple sources // The authoritative source is the "Content-Location" HTTP header. const contentLocation = response.headers.get('content-location'); @@ -3583,7 +3598,6 @@ async function tryGetContentLocation(response: Response): Promise ContentType.FHIR_JSON, + get(name: string): string | undefined { + return { + 'content-type': ContentType.FHIR_JSON, + }[name]; + }, } as unknown as Headers, blob: () => Promise.resolve(response), json: () => Promise.resolve(response), From 614846e671aef49a8795df2cd23887768b44bca6 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Tue, 13 Feb 2024 10:41:57 -0800 Subject: [PATCH 37/81] Remove vercel commands from medplum-provider/vercel.json (#3941) --- examples/medplum-provider/vercel.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/medplum-provider/vercel.json b/examples/medplum-provider/vercel.json index 33c4ed5992..00e7eccdce 100644 --- a/examples/medplum-provider/vercel.json +++ b/examples/medplum-provider/vercel.json @@ -1,5 +1,3 @@ { - "installCommand": "npm install", - "buildCommand": "npm run build", "routes": [{ "src": "/[^.]+", "dest": "/", "status": 200 }] } From 8feed563c51cde32167b5be83c1cc3b2e013c06a Mon Sep 17 00:00:00 2001 From: Matt Long Date: Tue, 13 Feb 2024 16:52:11 -0800 Subject: [PATCH 38/81] Don't return early on cache miss (#3948) --- packages/server/src/fhir/repo.test.ts | 16 +++++++++++++--- packages/server/src/fhir/repo.ts | 4 +++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/server/src/fhir/repo.test.ts b/packages/server/src/fhir/repo.test.ts index cca31d7648..884980373d 100644 --- a/packages/server/src/fhir/repo.test.ts +++ b/packages/server/src/fhir/repo.test.ts @@ -885,6 +885,16 @@ describe('FHIR Repo', () => { test('Profile validation', async () => withTestContext(async () => { + const clientApp = 'ClientApplication/' + randomUUID(); + const projectId = randomUUID(); + const repo = new Repository({ + strictMode: true, + projects: [projectId], + author: { + reference: clientApp, + }, + }); + const profile = JSON.parse( readFileSync(resolve(__dirname, '__test__/us-core-patient.json'), 'utf8') ) as StructureDefinition; @@ -909,9 +919,9 @@ describe('FHIR Repo', () => { // Missing gender property is required by profile }; - await expect(systemRepo.createResource(patient)).resolves.toBeTruthy(); - await systemRepo.createResource(profile); - await expect(systemRepo.createResource(patient)).rejects.toEqual( + await expect(repo.createResource(patient)).resolves.toBeTruthy(); + await repo.createResource(profile); + await expect(repo.createResource(patient)).rejects.toEqual( new Error('Missing required property (Patient.gender)') ); })); diff --git a/packages/server/src/fhir/repo.ts b/packages/server/src/fhir/repo.ts index feddb355a4..f1605c5105 100644 --- a/packages/server/src/fhir/repo.ts +++ b/packages/server/src/fhir/repo.ts @@ -613,7 +613,9 @@ export class Repository extends BaseRepository implements FhirRepository getProfileCacheKey(id, url)); const results = await getRedis().mget(...cacheKeys); const cachedProfile = results.find(Boolean) as string | undefined; - return cachedProfile ? (JSON.parse(cachedProfile) as CacheEntry).resource : undefined; + if (cachedProfile) { + return (JSON.parse(cachedProfile) as CacheEntry).resource; + } } // Fall back to loading from the DB; descending version sort approximates version resolution for some cases From 3418445b5c04f7f48ebde70d562abf3b46606e4e Mon Sep 17 00:00:00 2001 From: Derrick Farris Date: Wed, 14 Feb 2024 07:45:26 -0800 Subject: [PATCH 39/81] feat(subscriptions): add client utils for WebSocket subscriptions (#3890) * feat(subscription): client utils for sub * feat(subscriptions): handle `heartbeat` in `SubscriptionEmitter` * cleanup(chat-demo): rm unnecessary check for heartbeat * cleanup: fix imports * cleanup: 3 effects -> 2 effects * refactor(subscriptions): `ensureSubscriptionManager() -> getSubscriptionManager()` * test(subscription): test `SubscriptionManager` * fix: `removeCriteria()` -> `derefCriteria()` * test(subscription): more `SubscriptionManager` tests * fix(chat-demo): `showSeen` -> `showDelivered` * test(subscriptions): more tests * feat(subscriptions): add `hydrateLookupTables()` * cleanup: remove `hydrateLookupTables()` for now * cleanup: `derefCriteria()` -> `removeCriteria()` * test(subscriptions): add tests for `medplum.subscribeToCriteria()` * test(subscriptions): test `useSubscription()` * fix(subscriptions-demo): add back 'marked as delivered' logic * test(useSubscription): test unmounting * fix: resolve some `sonar` issues * cleanup: pull out `setupWebSocketListeners()` from constructor * cleanup(chat-demo): rm dev monorepo aliases * docs: add `JSDoc` comments for `subscribeToCriteria()` * docs: add `JSDoc` comments for `SubscriptionEmitter` * cleanup(test): clean up new tests a little * cleanup(chat-demo): uncomment stylesheet * cleanup: remove useless type assert * test(useSubscription): more testing * test(useSubscription): actually test for `StrictMode` weirdness * test(useSubscription): test criteria change * cleanup(client): prefer `URL.searchParams` over str concat * cleanup(subscriptions): `[kAddCriteria]` -> `._kAddCriteria` * refactor(subscriptions): only accept URL / string for `wsUrl` * fix(client): fix usage of `SubscriptionManager` ctor * fix(subscriptions): handle invalid WebSocket messages * fix(mock): use string instead of WebSocket in `MockSubscriptionManager` * refactor(SubscriptionManager): add `CriteriaEntry` class * fix(SubscriptionManager): del lookup entry if applicable * fix(SubscriptionManager): emit msg errors for other emitters too * fix(SubscriptionManager): make WebSocket lifecycle more robust * fix(RobustWebSocket): fix emitted events * fix(client): check for escaped query string * cleanup: remove stray commented code --- .../src/components/Chat.tsx | 152 +--- examples/medplum-live-chat-demo/tsconfig.json | 4 +- package-lock.json | 10 +- .../core/src/client-subscriptions.test.ts | 129 +++ packages/core/src/client-test-utils.ts | 81 +- packages/core/src/client.test.ts | 8 +- packages/core/src/client.ts | 93 ++- packages/core/src/index.ts | 1 + packages/core/src/subscriptions/index.test.ts | 751 ++++++++++++++++++ packages/core/src/subscriptions/index.ts | 342 ++++++++ packages/core/src/utils.ts | 4 + packages/mock/package.json | 1 + packages/mock/src/client.test.ts | 32 + packages/mock/src/client.ts | 24 +- packages/mock/src/index.ts | 1 + .../mock/src/subscription-manager.test.ts | 80 ++ packages/mock/src/subscription-manager.ts | 61 ++ packages/react-hooks/package.json | 1 + packages/react-hooks/src/index.ts | 3 +- .../react-hooks/src/useSearch/useSearch.ts | 2 +- .../useSubscription/useSubscription.test.tsx | 243 ++++++ .../src/useSubscription/useSubscription.ts | 56 ++ 22 files changed, 1934 insertions(+), 145 deletions(-) create mode 100644 packages/core/src/client-subscriptions.test.ts create mode 100644 packages/core/src/subscriptions/index.test.ts create mode 100644 packages/core/src/subscriptions/index.ts create mode 100644 packages/mock/src/subscription-manager.test.ts create mode 100644 packages/mock/src/subscription-manager.ts create mode 100644 packages/react-hooks/src/useSubscription/useSubscription.test.tsx create mode 100644 packages/react-hooks/src/useSubscription/useSubscription.ts diff --git a/examples/medplum-live-chat-demo/src/components/Chat.tsx b/examples/medplum-live-chat-demo/src/components/Chat.tsx index 0b3ec32dd6..1e4fe5108a 100644 --- a/examples/medplum-live-chat-demo/src/components/Chat.tsx +++ b/examples/medplum-live-chat-demo/src/components/Chat.tsx @@ -1,15 +1,9 @@ import { ActionIcon, Avatar, Group, Paper, ScrollArea, Stack, TextInput } from '@mantine/core'; import { showNotification } from '@mantine/notifications'; -import { - MedplumClient, - ProfileResource, - createReference, - getReferenceString, - normalizeErrorString, -} from '@medplum/core'; -import { Bundle, Communication, Parameters, Practitioner, Reference, Subscription } from '@medplum/fhirtypes'; +import { ProfileResource, createReference, getReferenceString, normalizeErrorString } from '@medplum/core'; +import { Bundle, Communication, Practitioner, Reference } from '@medplum/fhirtypes'; import { DrAliceSmith } from '@medplum/mock'; -import { Form, useMedplum } from '@medplum/react'; +import { Form, useMedplum, useSubscription } from '@medplum/react'; import { IconArrowRight, IconChevronDown, IconMessage } from '@tabler/icons-react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import classes from './Chat.module.css'; @@ -49,44 +43,6 @@ function upsertCommunications( setCommunications(newCommunications); } -async function listenForSub( - medplum: MedplumClient, - subscription: Subscription, - setWebSocket: (ws: WebSocket) => void, - onNewMessage: (c: Communication) => void -): Promise { - const { parameter } = (await medplum.get( - `/fhir/R4/Subscription/${subscription.id}/$get-ws-binding-token` - )) as Parameters; - const token = parameter?.find((param) => param.name === 'token')?.valueString; - const url = parameter?.find((param) => param.name === 'websocket-url')?.valueUrl; - if (!token) { - throw new Error('Failed to get token!'); - } - - if (!url) { - throw new Error('Failed to get URL from $get-ws-binding-token!'); - } - - const ws = new WebSocket(url); - - ws.addEventListener('open', () => { - ws.send(JSON.stringify({ type: 'bind-with-token', payload: { token } })); - }); - - ws.addEventListener('message', (event: MessageEvent) => { - const bundle = JSON.parse(event.data) as Bundle; - const communication = bundle.entry?.[1]?.resource; - if (!communication || communication.resourceType !== 'Communication') { - console.error('Invalid chat bundle!'); - return; - } - onNewMessage(communication); - }); - - setWebSocket(ws); -} - export function Chat(): JSX.Element | null { const medplum = useMedplum(); const [open, setOpen] = useState(false); @@ -94,22 +50,36 @@ export function Chat(): JSX.Element | null { const inputRef = useRef(null); const scrollAreaRef = useRef(null); const [profile, setProfile] = useState(medplum.getProfile()); - const [subscription, setSubscription] = useState(undefined); - const [webSocket, setWebSocket] = useState(); - - const creatingSubRef = useRef(false); - const deleteSubTimerRef = useRef(); const profileRefStr = useMemo( () => (profile ? getReferenceString(medplum.getProfile() as ProfileResource) : ''), [profile, medplum] ); + useSubscription( + `Communication?sender=${profileRefStr},${DR_ALICE_SMITH.reference}&recipient=${DR_ALICE_SMITH.reference},${profileRefStr}`, + (bundle: Bundle) => { + const communication = bundle.entry?.[1]?.resource as Communication; + upsertCommunications(communicationsRef.current, [communication], setCommunications); + if (!(communication.received && communication.status === 'completed')) { + medplum + .updateResource({ + ...communication, + received: communication.received ?? new Date().toISOString(), // Mark as received if needed + status: 'completed', // Mark as read + // See: https://www.medplum.com/docs/communications/organizing-communications#:~:text=THE%20Communication%20LIFECYCLE + // for more info about recommended `Communication` lifecycle + }) + .catch(console.error); + } + } + ); + // Disabled because we can make sure this will trigger an update when local profile !== medplum.getProfile() // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => { const latestProfile = medplum.getProfile(); - if (profile !== latestProfile) { + if (profile?.id !== latestProfile?.id) { setProfile(latestProfile); } }); @@ -118,9 +88,6 @@ export function Chat(): JSX.Element | null { communicationsRef.current = communications; const prevCommunicationsRef = useRef(communications); - const openRef = useRef(); - openRef.current = open; - const scrollToBottomRef = useRef(false); const searchMessages = useCallback(async (): Promise => { @@ -136,66 +103,6 @@ export function Chat(): JSX.Element | null { upsertCommunications(communicationsRef.current, searchResult, setCommunications); }, [medplum, profileRefStr]); - useEffect(() => { - // Create subscription... - // Check for creatingSubRef - if (!(profile && profileRefStr) || creatingSubRef.current) { - return () => undefined; - } - if (!subscription) { - creatingSubRef.current = true; - medplum - .createResource({ - resourceType: 'Subscription', - criteria: `Communication?sender=${profileRefStr},${DR_ALICE_SMITH.reference}&recipient=${DR_ALICE_SMITH.reference},${profileRefStr}`, - status: 'active', - reason: `Watch for Communications between ${profileRefStr} and ${DR_ALICE_SMITH.reference}.`, - channel: { - type: 'websocket', - }, - }) - .then((subscription) => { - setSubscription(subscription); - listenForSub(medplum, subscription, setWebSocket, (communication) => { - upsertCommunications(communicationsRef.current, [communication], setCommunications); - // NOTE: We may normally want to do a guard like this to prevent our client from updating messages that we have sent ourselves, but in this case - // We allow them to be updated anyways so that we have received timestamps on our sent messages - // const senderId = resolveId(communication.sender); - // if (senderId === profile.id) { - // return; - // } - // You may want to update received time and "completed" status independently, but for the purposes of this demo we are updating them together - if (!(communication.received && communication.status === 'completed')) { - medplum - .updateResource({ - ...communication, - received: new Date().toISOString(), // Mark as received - status: 'completed', // Mark as read - // See: https://www.medplum.com/docs/communications/organizing-communications#:~:text=THE%20Communication%20LIFECYCLE - // for more info about recommended `Communication` lifecycle - }) - .catch(console.error); - } - }) - .then(() => { - creatingSubRef.current = false; - }) - .catch(console.error); - }) - .catch(console.error); - } - if (deleteSubTimerRef.current) { - clearTimeout(deleteSubTimerRef.current); - deleteSubTimerRef.current = undefined; - } - return () => { - if (deleteSubTimerRef.current) { - return; - } - deleteSubTimerRef.current = setTimeout(() => {}, 1000); - }; - }, [medplum, profile, profileRefStr, subscription]); - const sendMessage = useCallback( async (formData: Record) => { if (inputRef.current) { @@ -254,7 +161,7 @@ export function Chat(): JSX.Element | null { return null; } - if (open && webSocket) { + if (open) { return ( <>
@@ -272,7 +179,10 @@ export function Chat(): JSX.Element | null { (currCommTime !== prevCommTime &&
{currCommTime}
)} {c.sender?.reference === profileRefStr ? ( - + ) : ( @@ -341,7 +251,7 @@ export function Chat(): JSX.Element | null { interface ChatBubbleProps { communication: Communication; - showSeen?: boolean; + showDelivered?: boolean; } function ChatBubble(props: ChatBubbleProps): JSX.Element { @@ -350,9 +260,9 @@ function ChatBubble(props: ChatBubbleProps): JSX.Element { return (
{content}
- {props.showSeen && ( + {props.showDelivered && (
- Seen {seenTime.getHours()}:{seenTime.getMinutes().toString().length === 1 ? '0' : ''} + Delivered {seenTime.getHours()}:{seenTime.getMinutes().toString().length === 1 ? '0' : ''} {seenTime.getMinutes()}
)} diff --git a/examples/medplum-live-chat-demo/tsconfig.json b/examples/medplum-live-chat-demo/tsconfig.json index 79240f45dd..e963f801eb 100644 --- a/examples/medplum-live-chat-demo/tsconfig.json +++ b/examples/medplum-live-chat-demo/tsconfig.json @@ -13,7 +13,7 @@ "moduleResolution": "Node", "resolveJsonModule": true, "noEmit": true, - "jsx": "react-jsx" + "jsx": "react-jsx", }, - "include": ["src"] + "include": ["src"], } diff --git a/package-lock.json b/package-lock.json index a37e327e54..e97e4d99a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29311,7 +29311,6 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -36800,7 +36799,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", @@ -36815,7 +36813,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -36830,7 +36827,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -36846,7 +36842,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -36855,7 +36850,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -38061,7 +38055,6 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/jest-websocket-mock/-/jest-websocket-mock-2.5.0.tgz", "integrity": "sha512-a+UJGfowNIWvtIKIQBHoEWIUqRxxQHFx4CXT+R5KxxKBtEQ5rS3pPOV/5299sHzqbmeCzxxY5qE4+yfXePePig==", - "dev": true, "dependencies": { "jest-diff": "^29.2.0", "mock-socket": "^9.3.0" @@ -43723,7 +43716,6 @@ "version": "9.3.1", "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz", "integrity": "sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==", - "dev": true, "engines": { "node": ">= 8" } @@ -59336,6 +59328,7 @@ "@medplum/fhir-router": "*", "@medplum/fhirtypes": "*", "dataloader": "2.2.2", + "jest-websocket-mock": "2.5.0", "rfc6902": "5.1.1" }, "devDependencies": { @@ -59434,6 +59427,7 @@ "@types/react-dom": "18.2.19", "jest": "29.7.0", "jest-each": "29.7.0", + "jest-websocket-mock": "2.5.0", "react": "18.2.0", "react-dom": "18.2.0", "rimraf": "5.0.5", diff --git a/packages/core/src/client-subscriptions.test.ts b/packages/core/src/client-subscriptions.test.ts new file mode 100644 index 0000000000..22010ee331 --- /dev/null +++ b/packages/core/src/client-subscriptions.test.ts @@ -0,0 +1,129 @@ +import { Parameters, Patient } from '@medplum/fhirtypes'; +import WS from 'jest-websocket-mock'; +import { MedplumClient } from './client'; +import { createFakeJwt, mockFetchWithStatus } from './client-test-utils'; +import { SubscriptionEmitter, SubscriptionEventMap, SubscriptionManager } from './subscriptions'; + +const ONE_HOUR = 60 * 60 * 1000; +const MOCK_SUBSCRIPTION_ID = '7b081dd8-a2d2-40dd-9596-58a7305a73b0'; + +const fetch = mockFetchWithStatus((url: string, options?: { body: string }) => { + switch (url) { + // createResource + case 'https://api.medplum.com/fhir/R4/Subscription': + return [ + 201, + { + ...(options?.body ? JSON.parse(options?.body) : {}), + id: MOCK_SUBSCRIPTION_ID, + }, + ] as [number, string]; + // $get-ws-binding-token + case `https://api.medplum.com/fhir/R4/Subscription/${MOCK_SUBSCRIPTION_ID}/$get-ws-binding-token`: + return [ + 200, + { + resourceType: 'Parameters', + parameter: [ + { + name: 'token', + valueString: 'token-123', + }, + { + name: 'expiration', + valueDateTime: new Date(Date.now() + ONE_HOUR).toISOString(), + }, + { + name: 'websocket-url', + valueUrl: 'wss://example.com/ws/subscriptions-r4', + }, + ], + } as Parameters, + ]; + // Get profile + case 'https://api.medplum.com/auth/me': + return [200, { profile: { resourceType: 'Patient', id: '123' } as Patient }]; + default: + throw new Error('Invalid URL'); + } +}); + +describe('MedplumClient -- Subscriptions', () => { + let medplum: MedplumClient; + let warnMockFn: jest.Mock; + let originalWarn: typeof console.warn; + + beforeAll(async () => { + originalWarn = console.warn; + console.warn = warnMockFn = jest.fn(); + jest.useFakeTimers(); + + // @ts-expect-error Need this to be here even if we are not using it so that WS reqs don't fail + const _wsServer = new WS('wss://api.medplum.com/ws/subscriptions-r4', { jsonProtocol: true }); + }); + + afterAll(async () => { + console.warn = originalWarn; + jest.useRealTimers(); + }); + + beforeEach(async () => { + warnMockFn.mockClear(); + medplum = new MedplumClient({ fetch, accessToken: createFakeJwt({ client_id: '123', login_id: '123' }) }); + await medplum.getProfileAsync(); + }); + + // This should be a no-op + test('unsubscribeFromCriteria() -- no SubscriptionManager', async () => { + expect(() => medplum.unsubscribeFromCriteria('Communication')).not.toThrow(); + }); + + test('getSubscriptionManager()', () => { + expect(medplum.getSubscriptionManager()).toBeInstanceOf(SubscriptionManager); + }); + + test('getMasterSubscriptionEmitter()', () => { + expect(medplum.getMasterSubscriptionEmitter()).toBeInstanceOf(SubscriptionEmitter); + }); + + test('subscribeToCriteria()', async () => { + const emitter1 = medplum.subscribeToCriteria('Communication'); + expect(emitter1).toBeInstanceOf(SubscriptionEmitter); + + const emitter2 = medplum.subscribeToCriteria('Communication'); + expect(emitter2).toBeInstanceOf(SubscriptionEmitter); + expect(emitter1).toBe(emitter2); + + const connectEvent = await new Promise((resolve) => { + emitter1.addEventListener('connect', (event) => { + resolve(event); + }); + }); + expect(connectEvent?.type).toEqual('connect'); + expect(connectEvent?.payload?.subscriptionId).toEqual(MOCK_SUBSCRIPTION_ID); + }); + + test('unsubscribeFromCriteria() -- SubscriptionManager exists', async () => { + const emitter = medplum.subscribeToCriteria('Communication'); + + const connectEvent = await new Promise((resolve) => { + emitter.addEventListener('connect', (event) => { + resolve(event); + }); + }); + expect(connectEvent?.type).toEqual('connect'); + expect(connectEvent?.payload?.subscriptionId).toEqual(MOCK_SUBSCRIPTION_ID); + + const disconnectEvent = await new Promise((resolve) => { + emitter.addEventListener('disconnect', (event) => { + resolve(event); + }); + expect(() => medplum.unsubscribeFromCriteria('Communication')).not.toThrow(); + }); + expect(disconnectEvent?.type).toEqual('disconnect'); + expect(disconnectEvent?.payload?.subscriptionId).toEqual(MOCK_SUBSCRIPTION_ID); + + expect(() => medplum.unsubscribeFromCriteria('Communication')).not.toThrow(); + expect(console.warn).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/core/src/client-test-utils.ts b/packages/core/src/client-test-utils.ts index 2d61c31fa2..1e2c1eede6 100644 --- a/packages/core/src/client-test-utils.ts +++ b/packages/core/src/client-test-utils.ts @@ -1,7 +1,10 @@ -import { OperationOutcome } from '@medplum/fhirtypes'; -import { FetchLike } from './client'; +import { OperationOutcome, Practitioner, Resource } from '@medplum/fhirtypes'; +import { FetchLike, MedplumClient } from './client'; import { ContentType } from './contenttype'; -import { getStatus, isOperationOutcome } from './outcomes'; +import { generateId } from './crypto'; +import { OperationOutcomeError, badRequest, getStatus, isOperationOutcome } from './outcomes'; +import { ReadablePromise } from './readablepromise'; +import { ProfileResource } from './utils'; export function mockFetch( status: number, @@ -16,6 +19,17 @@ export function mockFetch( }); } +export function mockFetchWithStatus( + onFetch: (url: string, options?: any) => [number, any], + contentType = ContentType.FHIR_JSON +): FetchLike & jest.Mock { + return jest.fn((url: string, options?: any) => { + const [status, response] = onFetch(url, options); + const responseStatus = isOperationOutcome(response) ? getStatus(response) : status; + return Promise.resolve(mockFetchResponse(responseStatus, response, { 'content-type': contentType })); + }); +} + export function mockFetchResponse(status: number, body: any, headers?: Record): Response { const headersMap = new Map(); if (headers) { @@ -42,3 +56,64 @@ export function mockFetchResponse(status: number, body: any, headers?: Record Record>; + constructor() { + this.routes = new Map(); + } + + makeKey(method: 'GET' | 'POST', path: string): string { + return `${method} ${path}`; + } + + addRoute(method: 'GET' | 'POST', path: string, callback: () => Record): void { + this.routes.set(this.makeKey(method, path), callback); + } + + fetchRoute>(method: 'GET' | 'POST', path: string): T { + const key = this.makeKey(method, path); + if (!this.routes.has(key)) { + throw new OperationOutcomeError(badRequest('Invalid route')); + } + return (this.routes.get(key) as () => T)(); + } +} + +export interface MockClientOptions { + fetch?: FetchLike; +} + +export class MockMedplumClient extends MedplumClient { + router: MockFhirRouter; + profile: Practitioner; + nextResourceId: string; + + constructor(options?: MockClientOptions) { + // @ts-expect-error need to pass something for fetch otherwise MedplumClient ctor will complain + super({ fetch: options?.fetch ?? (() => undefined) }); + this.router = new MockFhirRouter(); + this.profile = { resourceType: 'Practitioner', id: generateId() } as Practitioner; + this.nextResourceId = 'DEFAULT_MOCK_ID'; + } + + get(url: string | URL, _options?: RequestInit): ReadablePromise { + return new ReadablePromise(Promise.resolve(this.router.fetchRoute('GET', url.toString()))); + } + + addNextResourceId(id: string): void { + this.nextResourceId = id; + } + + createResource(resource: T, _options?: RequestInit | undefined): Promise { + return Promise.resolve({ ...resource, id: this.nextResourceId }); + } + + getProfile(): ProfileResource | undefined { + return this.profile; + } +} + +export function createFakeJwt(claims: Record): string { + return 'header.' + window.btoa(JSON.stringify(claims)) + '.signature'; +} diff --git a/packages/core/src/client.test.ts b/packages/core/src/client.test.ts index 984c6ca8b3..02216385ef 100644 --- a/packages/core/src/client.test.ts +++ b/packages/core/src/client.test.ts @@ -13,7 +13,7 @@ import { NewProjectRequest, NewUserRequest, } from './client'; -import { mockFetch, mockFetchResponse } from './client-test-utils'; +import { createFakeJwt, mockFetch, mockFetchResponse } from './client-test-utils'; import { ContentType } from './contenttype'; import { OperationOutcomeError, accepted, allOk, forbidden, notFound, unauthorized } from './outcomes'; import { MockAsyncClientStorage } from './storage'; @@ -1713,7 +1713,7 @@ describe('Client', () => { const result2 = await client.executeBot(bot.identifier?.[0] as Identifier, {}); expect(result2).toBeDefined(); expect(fetch).toHaveBeenCalledWith( - 'https://api.medplum.com/fhir/R4/Bot/$execute?identifier=https://example.com|123', + 'https://api.medplum.com/fhir/R4/Bot/$execute?identifier=https%3A%2F%2Fexample.com%7C123', expect.objectContaining({}) ); }); @@ -2792,10 +2792,6 @@ function createPdf( }); } -function createFakeJwt(claims: Record): string { - return 'header.' + window.btoa(JSON.stringify(claims)) + '.signature'; -} - function fail(message: string): never { throw new Error(message); } diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 444b328e6f..0c9572a261 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -66,6 +66,7 @@ import { } from './outcomes'; import { ReadablePromise } from './readablepromise'; import { ClientStorage, IClientStorage } from './storage'; +import { SubscriptionEmitter, SubscriptionManager } from './subscriptions'; import { indexSearchParameter } from './types'; import { indexStructureDefinitionBundle, isDataTypeLoaded, isProfileLoaded, loadDataType } from './typeschema/types'; import { @@ -74,6 +75,7 @@ import { arrayBufferToBase64, createReference, getReferenceString, + getWebSocketUrl, resolveId, sleep, } from './utils'; @@ -672,6 +674,7 @@ export class MedplumClient extends EventTarget { private readonly onUnauthenticated?: () => void; private readonly autoBatchTime: number; private readonly autoBatchQueue: AutoBatchEntry[] | undefined; + private subscriptionManager?: SubscriptionManager; private medplumServer?: boolean; private clientId?: string; private clientSecret?: string; @@ -2227,13 +2230,14 @@ export class MedplumClient extends EventTarget { contentType?: string, options?: RequestInit ): Promise { - let url; + let url: URL; if (typeof idOrIdentifier === 'string') { const id = idOrIdentifier; url = this.fhirUrl('Bot', id, '$execute'); } else { const identifier = idOrIdentifier; - url = this.fhirUrl('Bot', '$execute') + `?identifier=${identifier.system}|${identifier.value}`; + url = this.fhirUrl('Bot', '$execute'); + url.searchParams.set('identifier', identifier.system + '|' + identifier.value); } return this.post(url, body, contentType, options); } @@ -3505,6 +3509,91 @@ export class MedplumClient extends EventTarget { throw err; } } + + /** + * Gets the `SubscriptionManager` for WebSocket subscriptions. + * + * @category Subscriptions + * @returns the `SubscriptionManager` for this client. + */ + getSubscriptionManager(): SubscriptionManager { + if (!this.subscriptionManager) { + this.subscriptionManager = new SubscriptionManager(this, getWebSocketUrl('/ws/subscriptions-r4', this.baseUrl)); + } + return this.subscriptionManager; + } + + /** + * Subscribes to a given criteria, listening to notifications over WebSockets. + * + * This uses Medplum's `WebSocket Subscriptions` under the hood. + * + * A `SubscriptionEmitter` is returned from this function, which can be used to listen for updates to resources described by the given criteria. + * + * When subscribing to the same criteria multiple times, the same `SubscriptionEmitter` will be returned, and a reference count will be incremented. + * + * ----- + * @example + * ```ts + * const emitter = medplum.subscribeToCriteria('Communication'); + * + * emitter.addEventListener('message', (bundle: Bundle) => { + * // Called when a `Communication` resource is created or modified + * console.log(bundle?.entry?.[1]?.resource); // Logs the `Communication` resource that was updated + * }); + * ``` + * + * @category Subscriptions + * @param criteria - The criteria to subscribe to. + * @returns a `SubscriptionEmitter` that emits `Bundle` resources containing changes to resources based on the given criteria. + */ + subscribeToCriteria(criteria: string): SubscriptionEmitter { + return this.getSubscriptionManager().addCriteria(criteria); + } + + /** + * Unsubscribes from the given criteria. + * + * When called the same amount of times as proceeding calls to `subscribeToCriteria` on a given `criteria`, + * the criteria is fully removed from the `SubscriptionManager`. + * + * @category Subscriptions + * @param criteria - The criteria to unsubscribe from. + */ + unsubscribeFromCriteria(criteria: string): void { + if (!this.subscriptionManager) { + return; + } + this.subscriptionManager.removeCriteria(criteria); + if (this.subscriptionManager.getCriteriaCount() === 0) { + this.subscriptionManager.closeWebSocket(); + } + } + + /** + * Get the master `SubscriptionEmitter` for the `SubscriptionManager`. + * + * The master `SubscriptionEmitter` gets messages for all subscribed `criteria` as well as WebSocket errors, `connect` and `disconnect` events, and the `close` event. + * + * It can also be used to listen for `heartbeat` messages. + * + *------ + * @example + * ### Listening for `heartbeat`: + * ```ts + * const masterEmitter = medplum.getMasterSubscriptionEmitter(); + * + * masterEmitter.addEventListener('heartbeat', (bundle: Bundle) => { + * console.log(bundle?.entry?.[0]?.resource); // A `SubscriptionStatus` of type `heartbeat` + * }); + * + * ``` + * @category Subscriptions + * @returns the master `SubscriptionEmitter` from the `SubscriptionManager`. + */ + getMasterSubscriptionEmitter(): SubscriptionEmitter { + return this.getSubscriptionManager().getMasterEmitter(); + } } /** diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 2eba61bfac..5e2af92de6 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -33,6 +33,7 @@ export * from './search/match'; export * from './search/search'; export * from './sftp'; export * from './storage'; +export * from './subscriptions'; export * from './types'; export * from './typeschema/crawler'; export * from './typeschema/types'; diff --git a/packages/core/src/subscriptions/index.test.ts b/packages/core/src/subscriptions/index.test.ts new file mode 100644 index 0000000000..41552dbc80 --- /dev/null +++ b/packages/core/src/subscriptions/index.test.ts @@ -0,0 +1,751 @@ +import { Bundle, Communication, Parameters, SubscriptionStatus } from '@medplum/fhirtypes'; +import WS from 'jest-websocket-mock'; +import { RobustWebSocket, SubscriptionEmitter, SubscriptionEventMap, SubscriptionManager } from '.'; +import { MockMedplumClient } from '../client-test-utils'; +import { generateId } from '../crypto'; +import { OperationOutcomeError } from '../outcomes'; +import { createReference, sleep } from '../utils'; + +const ONE_HOUR = 60 * 60 * 1000; +const MOCK_SUBSCRIPTION_ID = '7b081dd8-a2d2-40dd-9596-58a7305a73b0'; + +const medplum = new MockMedplumClient(); +medplum.addNextResourceId(MOCK_SUBSCRIPTION_ID); + +describe('RobustWebSocket', () => { + let wsServer: WS; + + beforeEach(() => { + wsServer = new WS('wss://example.com/ws/subscriptions-r4', { jsonProtocol: true }); + }); + + afterEach(() => { + WS.clean(); + }); + + test('.close()', async () => { + const robustWebSocket = new RobustWebSocket('wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + expect(robustWebSocket.readyState).toEqual(WebSocket.OPEN); + + robustWebSocket.close(); + expect(robustWebSocket.readyState).not.toEqual(WebSocket.OPEN); + }); + + test('Getting readyState of underlying WebSocket', async () => { + const robustWebSocket = new RobustWebSocket('wss://example.com/ws/subscriptions-r4'); + expect(robustWebSocket.readyState).toEqual(WebSocket.CONNECTING); + + await wsServer.connected; + expect(robustWebSocket.readyState).toEqual(WebSocket.OPEN); + + robustWebSocket.close(); + expect(robustWebSocket.readyState).toEqual(WebSocket.CLOSING); + + await wsServer.closed; + expect(robustWebSocket.readyState).toEqual(WebSocket.CLOSED); + }); + + test('Sending before WebSocket is connected', async () => { + const robustWebSocket = new RobustWebSocket('wss://example.com/ws/subscriptions-r4'); + expect(() => robustWebSocket.send(JSON.stringify({ hello: 'medplum' }))).not.toThrow(); + expect(() => robustWebSocket.send(JSON.stringify({ med: 'plum' }))).not.toThrow(); + + // Test that open is fired + await new Promise((resolve) => { + robustWebSocket.addEventListener('open', () => { + resolve(); + }); + }); + + await expect(wsServer).toReceiveMessage({ hello: 'medplum' }); + await expect(wsServer).toReceiveMessage({ med: 'plum' }); + }); + + test('Wait for `open` before sending', async () => { + const robustWebSocket = new RobustWebSocket('wss://example.com/ws/subscriptions-r4'); + // Test that open is fired + await new Promise((resolve) => { + robustWebSocket.addEventListener('open', () => { + resolve(); + }); + }); + + expect(() => robustWebSocket.send(JSON.stringify({ hello: 'medplum' }))).not.toThrow(); + await expect(wsServer).toReceiveMessage({ hello: 'medplum' }); + expect(() => robustWebSocket.send(JSON.stringify({ med: 'plum' }))).not.toThrow(); + await expect(wsServer).toReceiveMessage({ med: 'plum' }); + }); + + test('Should emit `message` when message received from WebSocket', async () => { + const robustWebSocket = new RobustWebSocket('wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + const receivedEvent = await new Promise((resolve) => { + robustWebSocket.addEventListener('message', (event) => { + resolve(event as MessageEvent); + }); + wsServer.send({ med: 'plum' }); + }); + expect(receivedEvent?.type).toEqual('message'); + expect(receivedEvent?.data).toBeDefined(); + expect(JSON.parse(receivedEvent.data)).toEqual({ med: 'plum' }); + }); + + test('Should emit `error` when error received from WebSocket', async () => { + const robustWebSocket = new RobustWebSocket('wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + const receivedEvent = await new Promise((resolve) => { + robustWebSocket.addEventListener('error', (event) => { + resolve(event as MessageEvent); + }); + wsServer.error(); + }); + expect(receivedEvent?.type).toEqual('error'); + }); + + test('Should emit `close` when server closes connection', async () => { + const robustWebSocket = new RobustWebSocket('wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + const receivedEvent = await new Promise((resolve) => { + robustWebSocket.addEventListener('close', (event) => { + resolve(event as CloseEvent); + }); + wsServer.close(); + }); + expect(receivedEvent?.type).toEqual('close'); + }); +}); + +describe('SubscriptionEmitter', () => { + test('getCriteria()', () => { + const emitter = new SubscriptionEmitter(); + expect(emitter.getCriteria().size).toEqual(0); + emitter._addCriteria('Communication'); + expect(emitter.getCriteria().size).toEqual(1); + + // Should be able to add again without changing count + emitter._addCriteria('Communication'); + expect(emitter.getCriteria().size).toEqual(1); + + emitter._addCriteria('DiagnosticReport'); + expect(emitter.getCriteria().size).toEqual(2); + + emitter._removeCriteria('DiagnosticReport'); + expect(emitter.getCriteria().size).toEqual(1); + }); +}); + +describe('SubscriptionManager', () => { + describe('Constructor', () => { + let wsServer: WS; + + beforeAll(async () => { + wsServer = new WS('wss://example.com/ws/subscriptions-r4', { jsonProtocol: true }); + }); + + afterAll(() => { + WS.clean(); + }); + + test('should throw if not passed a `MedplumClient`', () => { + // @ts-expect-error Invalid value for `medplum` + expect(() => new SubscriptionManager(undefined, 'wss://example.com/ws/subscriptions-r4')).toThrow( + OperationOutcomeError + ); + }); + + test('should throw if `wsUrl` is not a URL or URL string', async () => { + // @ts-expect-error Invalid value for `wsUrl` + expect(() => new SubscriptionManager(medplum, undefined)).toThrow(OperationOutcomeError); + // @ts-expect-error Invalid value for `wsUrl` + expect(() => new SubscriptionManager(medplum, new WebSocket('wss://example.com/ws/subscriptions-r4'))).toThrow( + OperationOutcomeError + ); + }); + + test('should NOT throw if `wsUrl` is a VALID URL or URL string', async () => { + const manager1 = new SubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + expect(manager1).toBeDefined(); + await wsServer.connected; + + const manager2 = new SubscriptionManager(medplum, new URL('wss://example.com/ws/subscriptions-r4')); + expect(manager2).toBeDefined(); + await wsServer.connected; + }); + + test('should throw if `wsUrl` is an INVALID URL string', () => { + expect(() => new SubscriptionManager(medplum, 'abc123')).toThrow(OperationOutcomeError); + }); + }); + + describe('addCriteria()', () => { + let wsServer: WS; + + beforeAll(() => { + medplum.router.addRoute('GET', `/fhir/R4/Subscription/${MOCK_SUBSCRIPTION_ID}/$get-ws-binding-token`, () => { + return { + resourceType: 'Parameters', + parameter: [ + { + name: 'token', + valueString: 'token-123', + }, + { + name: 'expiration', + valueDateTime: new Date(Date.now() + ONE_HOUR).toISOString(), + }, + { + name: 'websocket-url', + valueUrl: 'wss://example.com/ws/subscriptions-r4', + }, + ], + } as Parameters; + }); + wsServer = new WS('wss://example.com/ws/subscriptions-r4', { jsonProtocol: true }); + }); + + afterAll(() => { + WS.clean(); + }); + + test('should add a criteria and receive messages for that criteria', async () => { + const manager = new SubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + + const emitter = manager.addCriteria('Communication'); + expect(emitter).toBeInstanceOf(SubscriptionEmitter); + + const subscriptionId = await new Promise((resolve) => { + const handler = (event: SubscriptionEventMap['connect']): void => { + emitter.removeEventListener('connect', handler); + resolve(event.payload.subscriptionId); + }; + emitter.addEventListener('connect', handler); + }); + + expect(typeof subscriptionId).toEqual('string'); + await expect(wsServer).toReceiveMessage({ type: 'bind-with-token', payload: { token: 'token-123' } }); + + const timestamp = new Date().toISOString(); + const resource = { resourceType: 'Communication', id: generateId() } as Communication; + const sentBundle = { + resourceType: 'Bundle', + timestamp, + type: 'history', + entry: [ + { + resource: { + resourceType: 'SubscriptionStatus', + type: 'event-notification', + subscription: { reference: `Subscription/${subscriptionId}` }, + notificationEvent: [{ eventNumber: '0', timestamp, focus: createReference(resource) }], + } as SubscriptionStatus, + }, + { + resource, + fullUrl: `https://example.com/fhir/R4/Communication/${resource.id}`, + }, + ], + } as Bundle; + + const receivedBundle = await new Promise((resolve) => { + const handler = (event: SubscriptionEventMap['message']): void => { + resolve(event.payload); + emitter.removeEventListener('message', handler); + }; + emitter.addEventListener('message', handler); + + wsServer.send(sentBundle); + }); + expect(receivedBundle).toEqual(sentBundle); + }); + + test('should emit `error` when token or url missing from `Subscription/$get-ws-binding-token` operation', async () => { + const originalError = console.error; + console.error = jest.fn(); + + const manager1 = new SubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + + medplum.router.addRoute('GET', `/fhir/R4/Subscription/${MOCK_SUBSCRIPTION_ID}/$get-ws-binding-token`, () => { + return { + resourceType: 'Parameters', + parameter: [ + { + name: 'expiration', + valueDateTime: new Date(Date.now() + ONE_HOUR).toISOString(), + }, + { + name: 'websocket-url', + valueUrl: 'wss://example.com/ws/subscriptions-r4', + }, + ], + } as Parameters; + }); + + const criteriaEmitter1 = manager1.addCriteria('Communication'); + + const [masterEvent1, criteriaEvent1] = await new Promise((resolve, reject) => { + const promises = []; + promises.push( + new Promise((resolve) => { + manager1.getMasterEmitter().addEventListener('error', (event) => { + resolve(event); + }); + }) + ); + promises.push( + new Promise((resolve) => { + criteriaEmitter1.addEventListener('error', (event) => { + resolve(event); + }); + }) + ); + + Promise.all(promises).then(resolve).catch(reject); + }); + + expect(masterEvent1?.type).toEqual('error'); + expect(masterEvent1?.payload).toBeInstanceOf(OperationOutcomeError); + expect(criteriaEvent1?.type).toEqual('error'); + expect(criteriaEvent1?.payload).toBeInstanceOf(OperationOutcomeError); + + medplum.router.addRoute('GET', `/fhir/R4/Subscription/${MOCK_SUBSCRIPTION_ID}/$get-ws-binding-token`, () => { + return { + resourceType: 'Parameters', + parameter: [ + { + name: 'token', + valueString: 'token-123', + }, + { + name: 'expiration', + valueDateTime: new Date(Date.now() + ONE_HOUR).toISOString(), + }, + ], + } as Parameters; + }); + + const manager2 = new SubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + + const criteriaEmitter2 = manager2.addCriteria('Communication'); + + const [masterEvent2, criteriaEvent2] = await new Promise((resolve, reject) => { + const promises = []; + promises.push( + new Promise((resolve) => { + manager2.getMasterEmitter().addEventListener('error', (event) => { + resolve(event); + }); + }) + ); + promises.push( + new Promise((resolve) => { + criteriaEmitter2.addEventListener('error', (event) => { + resolve(event); + }); + }) + ); + + Promise.all(promises).then(resolve).catch(reject); + }); + + expect(masterEvent2?.type).toEqual('error'); + expect(masterEvent2?.payload).toBeInstanceOf(OperationOutcomeError); + expect(criteriaEvent2?.type).toEqual('error'); + expect(criteriaEvent2?.payload).toBeInstanceOf(OperationOutcomeError); + + expect(console.error).toHaveBeenCalledTimes(2); + console.error = originalError; + }); + }); + + describe('removeCriteria()', () => { + let wsServer: WS; + let manager: SubscriptionManager; + let emitter: SubscriptionEmitter; + + beforeAll(async () => { + medplum.router.addRoute('GET', `/fhir/R4/Subscription/${MOCK_SUBSCRIPTION_ID}/$get-ws-binding-token`, () => { + return { + resourceType: 'Parameters', + parameter: [ + { + name: 'token', + valueString: 'token-123', + }, + { + name: 'expiration', + valueDateTime: new Date(Date.now() + ONE_HOUR).toISOString(), + }, + { + name: 'websocket-url', + valueUrl: 'wss://example.com/ws/subscriptions-r4', + }, + ], + } as Parameters; + }); + wsServer = new WS('wss://example.com/ws/subscriptions-r4', { jsonProtocol: true }); + + manager = new SubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + + emitter = manager.addCriteria('Communication'); + expect(emitter).toBeInstanceOf(SubscriptionEmitter); + + const subscriptionId = await new Promise((resolve) => { + const handler = (event: SubscriptionEventMap['connect']): void => { + emitter.removeEventListener('connect', handler); + resolve(event.payload.subscriptionId); + }; + emitter.addEventListener('connect', handler); + }); + + expect(typeof subscriptionId).toEqual('string'); + await expect(wsServer).toReceiveMessage({ type: 'bind-with-token', payload: { token: 'token-123' } }); + }); + + afterAll(() => { + WS.clean(); + }); + + test('should not throw when remove has been called on a criteria that is not known', () => { + const originalWarn = console.warn; + console.warn = jest.fn(); + expect(() => manager.removeCriteria('DiagnosticReport')).not.toThrow(); + expect(console.warn).toHaveBeenCalledTimes(1); + console.warn = originalWarn; + }); + + test('should not clean up a criteria if there are outstanding listeners', (done) => { + let success = false; + const emitter = manager.addCriteria('Communication'); + expect(emitter).toBeInstanceOf(SubscriptionEmitter); + + const handler = (): void => { + emitter.removeEventListener('disconnect', handler); + if (!success) { + done(new Error('Received `disconnect` when not expected')); + } + }; + emitter.addEventListener('disconnect', handler); + + manager.removeCriteria('Communication'); + + sleep(200) + .then(() => { + emitter.removeEventListener('disconnect', handler); + success = true; + done(); + }) + .catch(console.error); + }); + + test('should clean up for a criteria if we are the last subscriber', (done) => { + let success = false; + const handler = (): void => { + emitter.removeEventListener('disconnect', handler); + expect(true).toBeTruthy(); + success = true; + done(); + }; + emitter.addEventListener('disconnect', handler); + + manager.removeCriteria('Communication'); + + sleep(200) + .then(() => { + emitter.removeEventListener('disconnect', handler); + if (!success) { + done(new Error('Expected to receive `disconnect` message')); + } + }) + .catch(console.error); + }); + }); + + describe('getCriteriaCount()', () => { + let wsServer: WS; + + beforeAll(() => { + medplum.router.addRoute('GET', `/fhir/R4/Subscription/${MOCK_SUBSCRIPTION_ID}/$get-ws-binding-token`, () => { + return { + resourceType: 'Parameters', + parameter: [ + { + name: 'token', + valueString: 'token-123', + }, + { + name: 'expiration', + valueDateTime: new Date(Date.now() + ONE_HOUR).toISOString(), + }, + { + name: 'websocket-url', + valueUrl: 'wss://example.com/ws/subscriptions-r4', + }, + ], + } as Parameters; + }); + wsServer = new WS('wss://example.com/ws/subscriptions-r4', { jsonProtocol: true }); + }); + + afterAll(() => { + WS.clean(); + }); + + test('should return the correct amount of criteria', async () => { + const manager = new SubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + + expect(manager.getCriteriaCount()).toEqual(0); + manager.addCriteria('Communication'); + expect(manager.getCriteriaCount()).toEqual(1); + manager.removeCriteria('Communication'); + expect(manager.getCriteriaCount()).toEqual(0); + }); + }); + + describe('closeWebSocket()', () => { + let wsServer: WS; + + beforeAll(() => { + wsServer = new WS('wss://example.com/ws/subscriptions-r4', { jsonProtocol: true }); + }); + + afterAll(() => { + WS.clean(); + }); + + test('should close websocket and emit `close` when called', async () => { + const manager = new SubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + + const criteriaEmitter = manager.addCriteria('Communication'); + + const [masterEvent, criteriaEvent] = await new Promise((resolve, reject) => { + const promises = []; + promises.push( + new Promise((resolve) => { + manager.getMasterEmitter().addEventListener('close', (event) => { + resolve(event); + }); + }) + ); + promises.push( + new Promise((resolve) => { + criteriaEmitter.addEventListener('close', (event) => { + resolve(event); + }); + }) + ); + + expect(() => manager.closeWebSocket()).not.toThrow(); + Promise.all(promises).then(resolve).catch(reject); + }); + + await wsServer.closed; + expect(masterEvent?.type).toEqual('close'); + expect(criteriaEvent?.type).toEqual('close'); + }); + + test('should not emit close twice', async () => { + const manager = new SubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + + const event = await new Promise((resolve) => { + manager.getMasterEmitter().addEventListener('close', (event) => { + resolve(event); + }); + expect(() => manager.closeWebSocket()).not.toThrow(); + }); + expect(event?.type).toEqual('close'); + + await wsServer.closed; + + await new Promise((resolve, reject) => { + manager.getMasterEmitter().addEventListener('close', () => { + reject(new Error('Expected not to call')); + }); + expect(() => manager.closeWebSocket()).not.toThrow(); + setTimeout(() => resolve(), 250); + }); + + await wsServer.closed; + }); + }); + + describe('getMasterEmitter()', () => { + let wsServer: WS; + + beforeAll(async () => { + wsServer = new WS('wss://example.com/ws/subscriptions-r4', { jsonProtocol: true }); + }); + + afterAll(() => { + WS.clean(); + }); + + test('should always get the same emitter', async () => { + const manager = new SubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + expect(manager.getMasterEmitter()).toEqual(manager.getMasterEmitter()); + }); + }); + + describe('Scenarios', () => { + let wsServer: WS; + + beforeAll(() => { + medplum.router.addRoute('GET', `/fhir/R4/Subscription/${MOCK_SUBSCRIPTION_ID}/$get-ws-binding-token`, () => { + return { + resourceType: 'Parameters', + parameter: [ + { + name: 'token', + valueString: 'token-123', + }, + { + name: 'expiration', + valueDateTime: new Date(Date.now() + ONE_HOUR).toISOString(), + }, + { + name: 'websocket-url', + valueUrl: 'wss://example.com/ws/subscriptions-r4', + }, + ], + } as Parameters; + }); + wsServer = new WS('wss://example.com/ws/subscriptions-r4', { jsonProtocol: true }); + }); + + afterAll(() => { + WS.clean(); + }); + + test("should warn when receiving notification for subscription we aren't expecting", async () => { + const originalWarn = console.warn; + console.warn = jest.fn(); + + // @ts-expect-error We don't use manager + const _manager = new SubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + + const timestamp = new Date().toISOString(); + const resource = { resourceType: 'Communication', id: generateId() } as Communication; + const sentBundle = { + resourceType: 'Bundle', + timestamp, + type: 'history', + entry: [ + { + resource: { + resourceType: 'SubscriptionStatus', + type: 'event-notification', + subscription: { reference: `Subscription/${MOCK_SUBSCRIPTION_ID}` }, + notificationEvent: [{ eventNumber: '0', timestamp, focus: createReference(resource) }], + } as SubscriptionStatus, + }, + { + resource, + fullUrl: `https://example.com/fhir/R4/Communication/${resource.id}`, + }, + ], + } as Bundle; + + wsServer.send(sentBundle); + + expect(console.warn).toHaveBeenCalled(); + console.warn = originalWarn; + }); + + test('should emit `heartbeat` event when heartbeat received', async () => { + const manager = new SubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + + const timestamp = new Date().toISOString(); + const sentBundle = { + resourceType: 'Bundle', + timestamp, + type: 'history', + entry: [ + { + resource: { + resourceType: 'SubscriptionStatus', + status: 'active', + type: 'heartbeat', + subscription: { reference: `Subscription/${MOCK_SUBSCRIPTION_ID}` }, + } as SubscriptionStatus, + }, + ], + } as Bundle; + + const receivedBundle = await new Promise((resolve) => { + const emitter = manager.getMasterEmitter(); + emitter.addEventListener('heartbeat', (event) => { + resolve(event.payload); + }); + wsServer.send(sentBundle); + }); + + expect(receivedBundle).toEqual(sentBundle); + }); + + test('should emit `error` event when error received from WS', async () => { + const manager = new SubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + + const receivedEvent = await new Promise((resolve) => { + manager.getMasterEmitter().addEventListener('error', (event) => { + resolve(event); + }); + wsServer.error(); + }); + + expect(receivedEvent).toMatchObject({ type: 'error', payload: expect.any(Error) }); + }); + + test('should emit `error` event when invalid message comes in over WebSocket', async () => { + const originalError = console.error; + console.error = jest.fn(); + + const wsServer = new WS('wss://example.com/ws/subscriptions-r4'); + const manager = new SubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + + const emitter = manager.addCriteria('Communication'); + const [receivedEvent1, receivedEvent2] = await new Promise((resolve, reject) => { + const promises = []; + promises.push( + new Promise((resolve) => { + manager.getMasterEmitter().addEventListener('error', (event) => { + resolve(event); + }); + }) + ); + promises.push( + new Promise((resolve) => { + emitter.addEventListener('error', (event) => { + resolve(event); + }); + }) + ); + + wsServer.send('invalid_json'); + Promise.all(promises).then(resolve).catch(reject); + }); + + expect(receivedEvent1?.type).toEqual('error'); + expect(receivedEvent1?.payload).toBeInstanceOf(SyntaxError); + expect(receivedEvent1?.payload?.message).toMatch(/^Unexpected token/); + + expect(receivedEvent2?.type).toEqual('error'); + expect(receivedEvent2?.payload).toBeInstanceOf(SyntaxError); + expect(receivedEvent2?.payload?.message).toMatch(/^Unexpected token/); + + expect(console.error).toHaveBeenCalledTimes(1); + console.error = originalError; + }); + }); +}); diff --git a/packages/core/src/subscriptions/index.ts b/packages/core/src/subscriptions/index.ts new file mode 100644 index 0000000000..51fc6dc4f7 --- /dev/null +++ b/packages/core/src/subscriptions/index.ts @@ -0,0 +1,342 @@ +import { Bundle, Parameters, Subscription, SubscriptionStatus } from '@medplum/fhirtypes'; +import { MedplumClient } from '../client'; +import { TypedEventTarget } from '../eventtarget'; +import { OperationOutcomeError, serverError, validationError } from '../outcomes'; +import { ProfileResource, getReferenceString, resolveId } from '../utils'; + +export type SubscriptionEventMap = { + connect: { type: 'connect'; payload: { subscriptionId: string } }; + disconnect: { type: 'disconnect'; payload: { subscriptionId: string } }; + error: { type: 'error'; payload: Error }; + message: { type: 'message'; payload: Bundle }; + close: { type: 'close' }; + heartbeat: { type: 'heartbeat'; payload: Bundle }; +}; + +export type RobustWebSocketEventMap = { + open: { type: 'open' }; + message: MessageEvent; + error: Event; + close: CloseEvent; +}; + +export class RobustWebSocket extends TypedEventTarget { + private ws: WebSocket; + private messageBuffer: string[]; + bufferedAmount = -Infinity; + extensions = 'NOT_IMPLEMENTED'; + + constructor(url: string) { + super(); + this.messageBuffer = []; + + const ws = new WebSocket(url); + + ws.addEventListener('open', () => { + if (this.messageBuffer.length) { + const buffer = this.messageBuffer; + for (const msg of buffer) { + ws.send(msg); + } + } + this.dispatchEvent(new Event('open')); + }); + + ws.addEventListener('error', (event) => { + this.dispatchEvent(event); + }); + + ws.addEventListener('message', (event) => { + this.dispatchEvent(event); + }); + + ws.addEventListener('close', (event) => { + this.dispatchEvent(event); + }); + + this.ws = ws; + } + + get readyState(): number { + return this.ws.readyState; + } + + close(): void { + this.ws.close(); + } + + send(message: string): void { + if (this.ws.readyState !== WebSocket.OPEN) { + this.messageBuffer.push(message); + return; + } + + try { + this.ws.send(message); + } catch (err: unknown) { + this.dispatchEvent(new ErrorEvent('error', { error: err as Error, message: (err as Error).message })); + this.messageBuffer.push(message); + } + } +} + +/** + * An `EventTarget` that emits events when new subscription notifications come in over WebSockets. + * + * ----- + * + * ### Events emitted: + * + * - `connect` - A new subscription is connected to the `SubscriptionManager` and `message` events for this subscription can be expected. + * - `disconnect` - The specified subscription is no longer being monitored by the `SubscriptionManager`. + * - `error` - An error has occurred. + * - `message` - A message containing a notification `Bundle` has been received. + * - `close` - The WebSocket has been closed. + * - `heartbeat` - A `heartbeat` message has been received. + */ +export class SubscriptionEmitter extends TypedEventTarget { + private criteria: Set; + constructor(...criteria: string[]) { + super(); + this.criteria = new Set(criteria); + } + getCriteria(): Set { + return this.criteria; + } + /** + * @internal + * @param criteria - The criteria to add to this `SubscriptionEmitter`. + */ + _addCriteria(criteria: string): void { + this.criteria.add(criteria); + } + /** + * @internal + * @param criteria - The criteria to remove from this `SubscriptionEmitter`. + */ + _removeCriteria(criteria: string): void { + this.criteria.delete(criteria); + } +} + +class CriteriaEntry { + readonly criteria: string; + readonly emitter: SubscriptionEmitter; + refCount: number; + subscriptionId?: string; + + constructor(criteria: string) { + this.criteria = criteria; + this.emitter = new SubscriptionEmitter(criteria); + this.refCount = 1; + } +} + +export class SubscriptionManager { + private readonly medplum: MedplumClient; + private ws: RobustWebSocket; + private masterSubEmitter?: SubscriptionEmitter; + private criteriaEntries: Map; // Map + private criteriaEntriesBySubscriptionId: Map; // Map + private wsClosed: boolean; + + constructor(medplum: MedplumClient, wsUrl: URL | string) { + if (!(medplum instanceof MedplumClient)) { + throw new OperationOutcomeError(validationError('First arg of constructor should be a `MedplumClient`')); + } + let url: string; + try { + url = new URL(wsUrl).toString(); + } catch (_err) { + throw new OperationOutcomeError(validationError('Not a valid URL')); + } + const ws = new RobustWebSocket(url); + + this.medplum = medplum; + this.ws = ws; + this.masterSubEmitter = new SubscriptionEmitter(); + this.criteriaEntries = new Map(); + this.criteriaEntriesBySubscriptionId = new Map(); + this.wsClosed = false; + + this.setupWebSocketListeners(); + } + + private setupWebSocketListeners(): void { + const ws = this.ws; + + ws.addEventListener('message', (event) => { + try { + const bundle = JSON.parse(event.data) as Bundle; + // Get criteria for event + const status = bundle?.entry?.[0]?.resource as SubscriptionStatus; + // Handle heartbeat + if (status.type === 'heartbeat') { + this.masterSubEmitter?.dispatchEvent({ type: 'heartbeat', payload: bundle }); + return; + } + this.masterSubEmitter?.dispatchEvent({ type: 'message', payload: bundle }); + const criteriaEntry = this.criteriaEntriesBySubscriptionId.get(resolveId(status.subscription) as string); + if (!criteriaEntry) { + console.warn('Received notification for criteria the SubscriptionManager is not listening for'); + return; + } + // Emit event for criteria + criteriaEntry.emitter.dispatchEvent({ type: 'message', payload: bundle }); + } catch (err: unknown) { + console.error(err); + const errorEvent = { type: 'error', payload: err as Error } as SubscriptionEventMap['error']; + this.masterSubEmitter?.dispatchEvent(errorEvent); + for (const { emitter } of this.criteriaEntries.values()) { + emitter.dispatchEvent(errorEvent); + } + } + }); + + ws.addEventListener('error', () => { + const errorEvent = { + type: 'error', + payload: new OperationOutcomeError(serverError(new Error('WebSocket error'))), + } as SubscriptionEventMap['error']; + this.masterSubEmitter?.dispatchEvent(errorEvent); + for (const { emitter } of this.criteriaEntries.values()) { + emitter.dispatchEvent(errorEvent); + } + }); + + ws.addEventListener('close', () => { + const closeEvent = { type: 'close' } as SubscriptionEventMap['close']; + if (this.wsClosed) { + this.masterSubEmitter?.dispatchEvent(closeEvent); + } + for (const { emitter } of this.criteriaEntries.values()) { + emitter.dispatchEvent(closeEvent); + } + }); + } + + private emitConnect(subscriptionId: string): void { + const connectEvent = { type: 'connect', payload: { subscriptionId } } as SubscriptionEventMap['connect']; + this.masterSubEmitter?.dispatchEvent(connectEvent); + for (const { emitter } of this.criteriaEntries.values()) { + emitter.dispatchEvent(connectEvent); + } + } + + private emitError(criteria: string, error: Error): void { + const errorEvent = { type: 'error', payload: error } as SubscriptionEventMap['error']; + this.masterSubEmitter?.dispatchEvent(errorEvent); + this.criteriaEntries.get(criteria)?.emitter?.dispatchEvent(errorEvent); + } + + private async getTokenForCriteria(criteriaEntry: CriteriaEntry): Promise<[string, string]> { + let subscriptionId = criteriaEntry?.subscriptionId; + if (!subscriptionId) { + // Make a new subscription + const subscription = await this.medplum.createResource({ + resourceType: 'Subscription', + status: 'active', + reason: `WebSocket subscription for ${getReferenceString(this.medplum.getProfile() as ProfileResource)}`, + criteria: criteriaEntry.criteria, + channel: { type: 'websocket' }, + }); + subscriptionId = subscription.id as string; + } + + // Get binding token + const { parameter } = (await this.medplum.get( + `/fhir/R4/Subscription/${subscriptionId}/$get-ws-binding-token` + )) as Parameters; + const token = parameter?.find((param) => param.name === 'token')?.valueString; + const url = parameter?.find((param) => param.name === 'websocket-url')?.valueUrl; + + if (!token) { + throw new OperationOutcomeError(validationError('Failed to get token')); + } + if (!url) { + throw new OperationOutcomeError(validationError('Failed to get URL from $get-ws-binding-token')); + } + + return [subscriptionId, token]; + } + + addCriteria(criteria: string): SubscriptionEmitter { + if (this.masterSubEmitter) { + this.masterSubEmitter._addCriteria(criteria); + } + const criteriaEntry = this.criteriaEntries.get(criteria); + if (criteriaEntry) { + criteriaEntry.refCount += 1; + return criteriaEntry.emitter; + } + const newCriteriaEntry = new CriteriaEntry(criteria); + this.criteriaEntries.set(criteria, newCriteriaEntry); + + this.getTokenForCriteria(newCriteriaEntry) + .then(([subscriptionId, token]) => { + newCriteriaEntry.subscriptionId = subscriptionId; + this.criteriaEntriesBySubscriptionId.set(subscriptionId, newCriteriaEntry); + + // Emit connect event + this.emitConnect(subscriptionId); + // Send binding message + this.ws.send(JSON.stringify({ type: 'bind-with-token', payload: { token } })); + }) + .catch((err) => { + console.error(err.message); + this.emitError(criteria, err); + this.criteriaEntries.delete(criteria); + }); + + return newCriteriaEntry.emitter; + } + + removeCriteria(criteria: string): void { + const criteriaEntry = this.criteriaEntries.get(criteria); + if (!criteriaEntry) { + console.warn('Criteria not known to `SubscriptionManager`. Possibly called remove too many times.'); + return; + } + + criteriaEntry.refCount -= 1; + if (criteriaEntry.refCount > 0) { + return; + } + + // If actually removing + const subscriptionId = this.criteriaEntries.get(criteria)?.subscriptionId; + const disconnectEvent = { type: 'disconnect', payload: { subscriptionId } } as SubscriptionEventMap['disconnect']; + // Remove from master + if (this.masterSubEmitter) { + this.masterSubEmitter._removeCriteria(criteria); + + // Emit disconnect on master + this.masterSubEmitter.dispatchEvent(disconnectEvent); + } + // Emit disconnect on criteria emitter + this.criteriaEntries.get(criteria)?.emitter?.dispatchEvent(disconnectEvent); + this.criteriaEntries.delete(criteria); + if (subscriptionId) { + this.criteriaEntriesBySubscriptionId.delete(subscriptionId); + } + } + + closeWebSocket(): void { + if (this.wsClosed) { + return; + } + this.wsClosed = true; + this.ws.close(); + } + + getCriteriaCount(): number { + return this.criteriaEntries.size; + } + + getMasterEmitter(): SubscriptionEmitter { + if (!this.masterSubEmitter) { + this.masterSubEmitter = new SubscriptionEmitter(...Array.from(this.criteriaEntries.keys())); + } + return this.masterSubEmitter; + } +} diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 67f76709a6..c55afdb9a7 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -979,3 +979,7 @@ export function append(array: T[] | undefined, value: T): T[] { array.push(value); return array; } + +export function getWebSocketUrl(path: string, baseUrl: URL | string): string { + return new URL(path, baseUrl).toString().replace('http://', 'ws://').replace('https://', 'wss://'); +} diff --git a/packages/mock/package.json b/packages/mock/package.json index a19e9127fb..e5fa2939dd 100644 --- a/packages/mock/package.json +++ b/packages/mock/package.json @@ -58,6 +58,7 @@ "@medplum/fhir-router": "*", "@medplum/fhirtypes": "*", "dataloader": "2.2.2", + "jest-websocket-mock": "2.5.0", "rfc6902": "5.1.1" }, "devDependencies": { diff --git a/packages/mock/src/client.test.ts b/packages/mock/src/client.test.ts index 41b9282ba9..63a620a0bc 100644 --- a/packages/mock/src/client.test.ts +++ b/packages/mock/src/client.test.ts @@ -7,6 +7,7 @@ import { NewProjectRequest, NewUserRequest, OperationOutcomeError, + SubscriptionEmitter, allOk, getReferenceString, indexSearchParameterBundle, @@ -18,6 +19,7 @@ import { randomUUID, webcrypto } from 'crypto'; import { TextEncoder } from 'util'; import { MockClient } from './client'; import { DrAliceSmith, DrAliceSmithSchedule, HomerSimpson } from './mocks'; +import { MockSubscriptionManager } from './subscription-manager'; describe('MockClient', () => { beforeAll(() => { @@ -709,6 +711,36 @@ describe('MockClient', () => { medplum.setAgentAvailable(true); await expect(medplum.pushToAgent(agent, '8.8.8.8', 'PING', ContentType.PING, true)).resolves.toBeDefined(); }); + + test('getSubscriptionManager()', () => { + const medplum = new MockClient(); + expect(medplum.getSubscriptionManager()).toBeInstanceOf(MockSubscriptionManager); + }); + + test('getMasterSubscriptionEmitter()', () => { + const medplum = new MockClient(); + expect(medplum.getMasterSubscriptionEmitter()).toBeInstanceOf(SubscriptionEmitter); + }); + + test('subscribeToCriteria()', () => { + const medplum = new MockClient(); + const emitter1 = medplum.subscribeToCriteria('Communication'); + expect(emitter1).toBeInstanceOf(SubscriptionEmitter); + const emitter2 = medplum.subscribeToCriteria('Communication'); + expect(emitter1).toEqual(emitter2); + }); + + test('unsubscribeFromCriteria()', () => { + const medplum = new MockClient(); + + medplum.subscribeToCriteria('Communication'); + medplum.subscribeToCriteria('Communication'); + expect(medplum.getSubscriptionManager().getCriteriaCount()).toEqual(1); + + medplum.unsubscribeFromCriteria('Communication'); + medplum.unsubscribeFromCriteria('Communication'); + expect(medplum.getSubscriptionManager().getCriteriaCount()).toEqual(0); + }); }); describe('MockAsyncClientStorage', () => { diff --git a/packages/mock/src/client.ts b/packages/mock/src/client.ts index fa943805b7..a7d9088b9d 100644 --- a/packages/mock/src/client.ts +++ b/packages/mock/src/client.ts @@ -10,6 +10,7 @@ import { MedplumClientOptions, OperationOutcomeError, ProfileResource, + SubscriptionEmitter, } from '@medplum/core'; import { FhirRequest, FhirRouter, HttpMethod, MemoryRepository } from '@medplum/fhir-router'; import { @@ -72,6 +73,7 @@ import { ExampleWorkflowTask2, ExampleWorkflowTask3, } from './mocks/workflow'; +import { MockSubscriptionManager } from './subscription-manager'; export interface MockClientOptions extends MedplumClientOptions { readonly debug?: boolean; @@ -88,8 +90,9 @@ export class MockClient extends MedplumClient { readonly client: MockFetchClient; readonly debug: boolean; activeLoginOverride?: LoginState; - private agentAvailable: boolean = true; + private agentAvailable = true; private readonly profile: ReturnType; + subManager: MockSubscriptionManager | undefined; constructor(clientOptions?: MockClientOptions) { const router = new FhirRouter(); @@ -227,6 +230,25 @@ round-trip min/avg/max/stddev = 10.977/14.975/23.159/4.790 ms setAgentAvailable(value: boolean): void { this.agentAvailable = value; } + + getSubscriptionManager(): MockSubscriptionManager { + if (!this.subManager) { + this.subManager = new MockSubscriptionManager(this, 'wss://example.com/ws/subscriptions-r4'); + } + return this.subManager; + } + + subscribeToCriteria(criteria: string): SubscriptionEmitter { + return this.getSubscriptionManager().addCriteria(criteria); + } + + unsubscribeFromCriteria(criteria: string): void { + this.getSubscriptionManager().removeCriteria(criteria); + } + + getMasterSubscriptionEmitter(): SubscriptionEmitter { + return this.getSubscriptionManager().getMasterEmitter(); + } } export class MockFetchClient { diff --git a/packages/mock/src/index.ts b/packages/mock/src/index.ts index 89a92906c0..c920c7dce7 100644 --- a/packages/mock/src/index.ts +++ b/packages/mock/src/index.ts @@ -1,3 +1,4 @@ export * from './client'; export * from './mocks'; +export * from './subscription-manager'; export * from './test-resources/fish-patient'; diff --git a/packages/mock/src/subscription-manager.test.ts b/packages/mock/src/subscription-manager.test.ts new file mode 100644 index 0000000000..247288a797 --- /dev/null +++ b/packages/mock/src/subscription-manager.test.ts @@ -0,0 +1,80 @@ +import { SubscriptionEmitter, SubscriptionEventMap, generateId } from '@medplum/core'; +import { Bundle } from '@medplum/fhirtypes'; +import 'jest-websocket-mock'; +import { MockClient } from './client'; +import { MockSubscriptionManager } from './subscription-manager'; + +describe('MockSubscriptionManager', () => { + let medplum: MockClient; + let manager: MockSubscriptionManager; + + beforeAll(() => { + medplum = new MockClient(); + manager = new MockSubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + }); + + test('addCriteria()', () => { + const emitter1 = manager.addCriteria('Communication'); + expect(emitter1).toBeInstanceOf(SubscriptionEmitter); + expect(manager.getCriteriaCount()).toEqual(1); + + const emitter2 = manager.addCriteria('Communication'); + expect(emitter2).toBeInstanceOf(SubscriptionEmitter); + expect(manager.getCriteriaCount()).toEqual(1); + + expect(emitter1).toBe(emitter2); + }); + + test('removeCriteria()', () => { + manager.removeCriteria('Communication'); + expect(manager.getCriteriaCount()).toEqual(1); + manager.removeCriteria('Communication'); + expect(manager.getCriteriaCount()).toEqual(0); + expect(() => manager.removeCriteria('Communucation')).not.toThrow(); + expect(manager.getCriteriaCount()).toEqual(0); + }); + + test('getMasterEmitter()', () => { + expect(manager.getMasterEmitter()).toBeInstanceOf(SubscriptionEmitter); + }); + + test('emitEventForCriteria()', async () => { + const emitter = manager.addCriteria('Communication'); + expect(emitter).toBeInstanceOf(SubscriptionEmitter); + expect(emitter.getCriteria().has('Communication')).toEqual(true); + + const bundleId = generateId(); + + const receivedEvent = await new Promise((resolve) => { + emitter.addEventListener('message', (event) => { + resolve(event); + }); + manager.emitEventForCriteria<'message'>('Communication', { + type: 'message', + payload: { resourceType: 'Bundle', id: bundleId } as Bundle, + }); + }); + + expect(receivedEvent).toMatchObject({ + type: 'message', + payload: { resourceType: 'Bundle', id: bundleId }, + } as SubscriptionEventMap['message']); + }); + + test('closeWebSockets()', async () => { + const receivedEvent = await new Promise((resolve) => { + manager.getMasterEmitter().addEventListener('close', (event) => { + resolve(event); + }); + manager.closeWebSocket(); + }); + expect(receivedEvent?.type).toEqual('close'); + }); + + test('getEmitter()', async () => { + expect(manager.getEmitter('Subscription')).toBeUndefined(); + const emitter = manager.addCriteria('Subscription'); + expect(emitter).toBeInstanceOf(SubscriptionEmitter); + expect(manager.getEmitter('Subscription')).toBe(emitter); + }); +}); diff --git a/packages/mock/src/subscription-manager.ts b/packages/mock/src/subscription-manager.ts new file mode 100644 index 0000000000..e849d4bb98 --- /dev/null +++ b/packages/mock/src/subscription-manager.ts @@ -0,0 +1,61 @@ +import { MedplumClient, SubscriptionEmitter, SubscriptionEventMap, SubscriptionManager } from '@medplum/core'; + +export class MockSubscriptionManager extends SubscriptionManager { + emitters: Map; + counts: Map; + masterEmitter: SubscriptionEmitter; + + constructor(medplum: MedplumClient, _wsOrUrl: WebSocket | string) { + super(medplum, 'wss://example.com/ws/subscriptions-r4'); + this.emitters = new Map(); + this.counts = new Map(); + this.masterEmitter = new SubscriptionEmitter(); + } + + addCriteria(criteria: string): SubscriptionEmitter { + if (!this.emitters.has(criteria)) { + this.emitters.set(criteria, new SubscriptionEmitter(criteria)); + } + this.counts.set(criteria, (this.counts.get(criteria) ?? 0) + 1); + return this.emitters.get(criteria) as SubscriptionEmitter; + } + + removeCriteria(criteria: string): void { + if (!this.emitters.has(criteria)) { + return; + } + this.counts.set(criteria, (this.counts.get(criteria) as number) - 1); + if (this.counts.get(criteria) === 0) { + this.emitters.delete(criteria); + this.counts.delete(criteria); + } + } + + closeWebSocket(): void { + this.masterEmitter.dispatchEvent({ type: 'close' }); + for (const emitter of this.emitters.values()) { + emitter.dispatchEvent({ type: 'close' }); + } + } + + getCriteriaCount(): number { + return this.emitters.size; + } + + getMasterEmitter(): SubscriptionEmitter { + return this.masterEmitter; + } + + emitEventForCriteria( + criteria: string, + event: SubscriptionEventMap[K] + ): void { + this.emitters.get(criteria)?.dispatchEvent(event); + } + + // Guess this has to do with the unique symbols on the type definition... + // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents + getEmitter(criteria: string): SubscriptionEmitter | undefined { + return this.emitters.get(criteria); + } +} diff --git a/packages/react-hooks/package.json b/packages/react-hooks/package.json index 0115890bd7..ed837e0595 100644 --- a/packages/react-hooks/package.json +++ b/packages/react-hooks/package.json @@ -70,6 +70,7 @@ "@types/react-dom": "18.2.19", "jest": "29.7.0", "jest-each": "29.7.0", + "jest-websocket-mock": "2.5.0", "react": "18.2.0", "react-dom": "18.2.0", "rimraf": "5.0.5", diff --git a/packages/react-hooks/src/index.ts b/packages/react-hooks/src/index.ts index 74af4dae34..461a2d4985 100644 --- a/packages/react-hooks/src/index.ts +++ b/packages/react-hooks/src/index.ts @@ -1,5 +1,6 @@ export * from './MedplumProvider/MedplumProvider'; export * from './MedplumProvider/MedplumProvider.context'; +export * from './useCachedBinaryUrl/useCachedBinaryUrl'; export * from './useResource/useResource'; export * from './useSearch/useSearch'; -export * from './useCachedBinaryUrl/useCachedBinaryUrl'; +export * from './useSubscription/useSubscription'; diff --git a/packages/react-hooks/src/useSearch/useSearch.ts b/packages/react-hooks/src/useSearch/useSearch.ts index d4afdb31fb..7ff43ecd06 100644 --- a/packages/react-hooks/src/useSearch/useSearch.ts +++ b/packages/react-hooks/src/useSearch/useSearch.ts @@ -80,7 +80,7 @@ function useSearchImpl( setOutcome(normalizeOperationOutcome(err)); }); } - }, [medplum, searchFn, resourceType, query, searchKey, setResult]); + }, [medplum, searchFn, resourceType, query, searchKey]); return [result, loading, outcome]; } diff --git a/packages/react-hooks/src/useSubscription/useSubscription.test.tsx b/packages/react-hooks/src/useSubscription/useSubscription.test.tsx new file mode 100644 index 0000000000..332b69963b --- /dev/null +++ b/packages/react-hooks/src/useSubscription/useSubscription.test.tsx @@ -0,0 +1,243 @@ +import { SubscriptionEmitter, generateId } from '@medplum/core'; +import { Bundle } from '@medplum/fhirtypes'; +import { MockClient } from '@medplum/mock'; +import { act, render, screen } from '@testing-library/react'; +import 'jest-websocket-mock'; +import { ReactNode, StrictMode, useState } from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import { MedplumProvider } from '../MedplumProvider/MedplumProvider'; +import { useSubscription } from './useSubscription'; + +function TestComponent({ + criteria, + callback, +}: { + criteria?: string; + callback?: (bundle: Bundle) => void; +}): JSX.Element { + const [lastReceived, setLastReceived] = useState(); + useSubscription( + criteria ?? 'Communication', + callback ?? + ((bundle: Bundle) => { + setLastReceived(bundle); + }) + ); + return ( +
+
{JSON.stringify(lastReceived)}
+
+ ); +} + +function RenderToggleComponent({ render }: { render: boolean }): JSX.Element { + return <>{render ? : null}; +} + +describe('useSubscription()', () => { + let medplum: MockClient; + + beforeAll(() => { + medplum = new MockClient(); + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + function setup( + children: ReactNode, + strict = false + ): { + unmount: ReturnType['unmount']; + rerender: (element: JSX.Element) => void; + } { + const defaultWrapper = (children: ReactNode): JSX.Element => ( + + {children} + + ); + + const strictWrapper = (children: ReactNode): JSX.Element => { + return {defaultWrapper(children)}; + }; + + const wrapper = strict ? strictWrapper : defaultWrapper; + const { unmount, rerender } = render(wrapper(children)); + return { unmount, rerender: (element: JSX.Element) => rerender(wrapper(element)) }; + } + + test('Mount and unmount completely', async () => { + const { unmount } = setup(); + + act(() => { + medplum.getSubscriptionManager().emitEventForCriteria<'message'>('Communication', { + type: 'message', + payload: { resourceType: 'Bundle', id: generateId(), type: 'history' }, + }); + }); + + const el = await screen.findByTestId('bundle'); + expect(el).toBeInTheDocument(); + + const bundle = JSON.parse(el.innerHTML); + expect(bundle.resourceType).toBe('Bundle'); + expect(bundle.type).toBe('history'); + + // Make sure subscription is cleaned up + unmount(); + jest.advanceTimersByTime(5000); + expect(medplum.getSubscriptionManager().getCriteriaCount()).toEqual(0); + }); + + test('Mount and remount before debounce timeout', async () => { + expect(medplum.getSubscriptionManager().getCriteriaCount()).toEqual(0); + const { rerender } = setup(); + expect(medplum.getSubscriptionManager().getCriteriaCount()).toEqual(1); + + const emitter = medplum.getSubscriptionManager().getEmitter('Communication') as SubscriptionEmitter; + expect(emitter).toBeInstanceOf(SubscriptionEmitter); + expect(medplum.getSubscriptionManager().getCriteriaCount()).toEqual(1); + + rerender(); + jest.advanceTimersByTime(1000); + expect(medplum.getSubscriptionManager().getCriteriaCount()).toEqual(1); + + rerender(); + jest.advanceTimersByTime(5000); + expect(medplum.getSubscriptionManager().getCriteriaCount()).toEqual(1); + expect(medplum.getSubscriptionManager().getEmitter('Communication')).toBe(emitter); + + // Make sure we fully unmount later when actually unmounting + rerender(); + jest.advanceTimersByTime(5000); + expect(medplum.getSubscriptionManager().getCriteriaCount()).toEqual(0); + }); + + test('Debounces properly in StrictMode', async () => { + expect(medplum.getSubscriptionManager().getCriteriaCount()).toEqual(0); + const emitter = medplum.getSubscriptionManager().addCriteria('Communication'); + expect(medplum.getSubscriptionManager().getCriteriaCount()).toEqual(1); + + setup(, true); + jest.advanceTimersByTime(5000); + expect(medplum.getSubscriptionManager().getCriteriaCount()).toEqual(1); + expect(medplum.getSubscriptionManager().getEmitter('Communication')).toBe(emitter); + }); + + test('Callback changed', async () => { + let lastFromCb1: Bundle | undefined; + let lastFromCb2: Bundle | undefined; + const id1 = generateId(); + const id2 = generateId(); + + const { rerender } = setup( + { + lastFromCb1 = bundle; + }} + /> + ); + + act(() => { + medplum.getSubscriptionManager().emitEventForCriteria<'message'>('Communication', { + type: 'message', + payload: { resourceType: 'Bundle', id: id1, type: 'history' }, + }); + }); + + expect(lastFromCb1?.resourceType).toEqual('Bundle'); + expect(lastFromCb1?.type).toEqual('history'); + expect(lastFromCb1?.id).toEqual(id1); + expect(lastFromCb2).not.toBeDefined(); + + rerender( + { + lastFromCb2 = bundle; + }} + /> + ); + + act(() => { + medplum.getSubscriptionManager().emitEventForCriteria<'message'>('Communication', { + type: 'message', + payload: { resourceType: 'Bundle', id: id2, type: 'history' }, + }); + }); + + expect(lastFromCb1?.resourceType).toEqual('Bundle'); + expect(lastFromCb1?.type).toEqual('history'); + expect(lastFromCb1?.id).toEqual(id1); + + expect(lastFromCb2?.resourceType).toEqual('Bundle'); + expect(lastFromCb2?.type).toEqual('history'); + expect(lastFromCb2?.id).toEqual(id2); + }); + + test('Criteria changed', () => { + let lastFromCb1: Bundle | undefined; + let lastFromCb2: Bundle | undefined; + const id1 = generateId(); + const id2 = generateId(); + const id3 = generateId(); + + const { rerender } = setup( + { + lastFromCb1 = bundle; + }} + /> + ); + + act(() => { + medplum.getSubscriptionManager().emitEventForCriteria<'message'>('Communication', { + type: 'message', + payload: { resourceType: 'Bundle', id: id1, type: 'history' }, + }); + }); + + expect(lastFromCb1?.resourceType).toEqual('Bundle'); + expect(lastFromCb1?.type).toEqual('history'); + expect(lastFromCb1?.id).toEqual(id1); + expect(lastFromCb2).not.toBeDefined(); + + rerender( + { + lastFromCb2 = bundle; + }} + /> + ); + + act(() => { + medplum.getSubscriptionManager().emitEventForCriteria<'message'>('Communication', { + type: 'message', + payload: { resourceType: 'Bundle', id: id2, type: 'history' }, + }); + }); + + expect(lastFromCb1?.resourceType).toEqual('Bundle'); + expect(lastFromCb1?.type).toEqual('history'); + expect(lastFromCb1?.id).toEqual(id1); + expect(lastFromCb2).not.toBeDefined(); + + act(() => { + medplum.getSubscriptionManager().emitEventForCriteria<'message'>('DiagnosticReport', { + type: 'message', + payload: { resourceType: 'Bundle', id: id3, type: 'history' }, + }); + }); + + expect(lastFromCb1?.resourceType).toEqual('Bundle'); + expect(lastFromCb1?.type).toEqual('history'); + expect(lastFromCb1?.id).toEqual(id1); + + expect(lastFromCb2?.resourceType).toEqual('Bundle'); + expect(lastFromCb2?.type).toEqual('history'); + expect(lastFromCb2?.id).toEqual(id3); + }); +}); diff --git a/packages/react-hooks/src/useSubscription/useSubscription.ts b/packages/react-hooks/src/useSubscription/useSubscription.ts new file mode 100644 index 0000000000..5baeb74672 --- /dev/null +++ b/packages/react-hooks/src/useSubscription/useSubscription.ts @@ -0,0 +1,56 @@ +import { SubscriptionEmitter, SubscriptionEventMap } from '@medplum/core'; +import { Bundle } from '@medplum/fhirtypes'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useMedplum } from '../MedplumProvider/MedplumProvider.context'; + +const SUBSCRIPTION_DEBOUNCE_MS = 3000; + +export function useSubscription(criteria: string, callback: (bundle: Bundle) => void): void { + const medplum = useMedplum(); + const [emitter, setEmitter] = useState(); + + const listeningRef = useRef(false); + const unsubTimerRef = useRef>(); + const prevCriteriaRef = useRef(); + + const callbackRef = useRef(); + callbackRef.current = callback; + + useEffect(() => { + if (unsubTimerRef.current) { + clearTimeout(unsubTimerRef.current); + unsubTimerRef.current = undefined; + } + if (prevCriteriaRef.current !== criteria) { + setEmitter(medplum.subscribeToCriteria(criteria)); + } + + // Set prev criteria to latest + prevCriteriaRef.current = criteria; + + return () => { + unsubTimerRef.current = setTimeout(() => { + setEmitter(undefined); + medplum.unsubscribeFromCriteria(criteria); + }, SUBSCRIPTION_DEBOUNCE_MS); + }; + }, [medplum, criteria]); + + const emitterCallback = useCallback((event: SubscriptionEventMap['message']) => { + callbackRef.current?.(event.payload); + }, []); + + useEffect(() => { + if (!emitter) { + return () => undefined; + } + if (!listeningRef.current) { + emitter.addEventListener('message', emitterCallback); + listeningRef.current = true; + } + return () => { + listeningRef.current = false; + emitter.removeEventListener('message', emitterCallback); + }; + }, [emitter, emitterCallback]); +} From 347c2becf0ef93b23b51da55920aa703d2854aa1 Mon Sep 17 00:00:00 2001 From: Rahul Agarwal Date: Wed, 14 Feb 2024 11:20:43 -0800 Subject: [PATCH 40/81] Dosespot Website Enhancements: part 2 (#3949) * WIP removing references to SureScripts * Remove all references to dosespot * Remove Pharmacy Partnership Language --- .../blog/2023-03-08-patient-deduplication.md | 2 +- packages/docs/docs/integration/dosespot.md | 24 +++---------------- packages/docs/docs/medications/e-prescibe.md | 6 +---- .../docs/src/pages/products/integration.md | 2 +- 4 files changed, 6 insertions(+), 28 deletions(-) diff --git a/packages/docs/blog/2023-03-08-patient-deduplication.md b/packages/docs/blog/2023-03-08-patient-deduplication.md index d8766afec9..97c99c6cb8 100644 --- a/packages/docs/blog/2023-03-08-patient-deduplication.md +++ b/packages/docs/blog/2023-03-08-patient-deduplication.md @@ -31,7 +31,7 @@ A sample (skeleton) deduplication bot can be found in the [Medplum Demo Bot](htt ## Maintaining Identifiers -Many systems issue patient identifiers, like payors (e.g. United Healthcare), pharmacy and medication systems (e.g. Surescripts or DoseSpot) and even payment providers like Stripe. FHIR support maintaining multiple identifiers from different systems. If you maintain records with patient identifiers from different systems, this can be the basis for detecting duplicates with high accuracy. +Many systems issue patient identifiers, like payors (e.g. United Healthcare), pharmacy and medication systems (e.g. DoseSpot) and even payment providers like Stripe. FHIR support maintaining multiple identifiers from different systems. If you maintain records with patient identifiers from different systems, this can be the basis for detecting duplicates with high accuracy. The sample deduplication bot test shows a patient with multiple identifiers for reference. diff --git a/packages/docs/docs/integration/dosespot.md b/packages/docs/docs/integration/dosespot.md index 3d034854a8..3f2cc2921d 100644 --- a/packages/docs/docs/integration/dosespot.md +++ b/packages/docs/docs/integration/dosespot.md @@ -32,12 +32,6 @@ A "Prescriber" is a user authorized to directly issue prescriptions, holding the A "Proxy" user, however, acts as an assistant or delegate, performing tasks on behalf of a Prescriber but does not have the authority to finalize prescriptions without review and approval by a Prescriber. -### What guidelines exist for prescribing to minors through Medplum? {#prescribing-to-minors} - -Surescripts requires e-Prescriptions sent for pediatric patients (defined as ages 18 and younger – up until their 19th birthday) to include a **height and weight**. This is a firm requirement across all prescribers; it was implemented in September of 2021 as part of the Surescripts update to SCRIPT Standard v2017071 which followed NCPDP regulations to maintain compliance. - -Client's 18 years old and under are required to have a documented height & weight on file otherwise eRX cannot be accessed. This requirement cannot be bypassed. Please ensure that any pediatric patients have their date of birth documented on their `Patient` resource, as well as update height and weight documented according to the [US Core Guidelines](/docs/fhir-datastore/understanding-uscdi-dataclasses) - ### How do "Refill" and "Reorder" differ within Medplum? A "Refill" refers to authorizing additional quantities of a medication under an existing prescription, is initiated by the pharmacy. @@ -50,25 +44,13 @@ When formulary data indicates a need for ePA based on insurance, the clinician c ### Does Medplum support prescription submissions to pharmacies across all 50 states? -Yes. Medplum utilizes the Surescripts Pharmacy network through our partnership with our eRx provider, DoseSpot, ensuring nationwide coverage. - -### Are there limits on a patient's choice of pharmacy? - -No. Patients are free to choose any pharmacy within the SureScripts network. - -### Why might a pharmacy not appear in Medplum's search? - -A pharmacy might not be found due to reasons such as it not being registered within the network Medplum uses, data entry errors, or it being outside the service area of our eRx integration partner. - -To verify that a pharmacy exists in the SureScripts database, you can navigate to to https://surescripts.com/network-connections and scroll to the section "Locate E-Prescribing Pharmacies." +Yes. Medplum Medplum's integration with DoseSpot allows ordering prescriptions to any pharmacy across 50 states. ### How does Medplum collect and manage patient insurance details? -Patient insurance details within Medplum's eRx system are pulled by DoseSpot from from Surescripts, which matches insurance information based on patient demographics: name, gender, and date of birth. - -The SureScripts database covers approximately 95% of U.S. pharmacies. When patients enter information through their pharmacy, their data is uploaded to SureScripts. +Patient insurance details within Medplum's eRx system are pulled by DoseSpot, which matches insurance information based on patient demographics: name, gender, and date of birth. -It's important to note that insurance information stored directly in Medplum **does not integrate with Surescripts**, nor can it be manually entered into the eRx system. +It's important to note that insurance information stored directly in Medplum **does not integrate with DoseSpot**, nor can it be manually entered into the eRx system. ### What constitutes a transmission error in Medplum's eRx service? diff --git a/packages/docs/docs/medications/e-prescibe.md b/packages/docs/docs/medications/e-prescibe.md index fdca5a91e5..7d68013597 100644 --- a/packages/docs/docs/medications/e-prescibe.md +++ b/packages/docs/docs/medications/e-prescibe.md @@ -34,12 +34,8 @@ E-prescription providers often impose additional validation requirements when pr At this time, prescription cost retrieval is not available via the Medplum API. However, Some eRx vendors may display cost information at the time a prescription is written. Check with your specific eRX vendor for more details. -### Does Medplum have connections to any pharmacy or pharmaceutical industry partners? - -No. Medplum does not have any special connections or partnerships with specific pharmacies, drug manufacturers, or other pharmaceutical industry partners. - ### How does Medplum collect and manage patient insurance details? It is best practice to maintain update date coverage information for each patient in Medplum, as it is important for both clinical and administrative workflows in the core EHR. See our [Guide on Patient Insurance](/docs/billing/patient-insurance) for more details. -However, many eRx vendors do not allow users to directly input patient coverage details. Rather, they depend on clearinghouses such as Surescripts, to supply patient coverage data. Check with your eRx vendor for details on how patient coverage is managed. +However, many eRx vendors do not allow users to directly input patient coverage details. Rather, they depend on clearinghouses to supply patient coverage data. Check with your eRx vendor for details on how patient coverage is managed. diff --git a/packages/docs/src/pages/products/integration.md b/packages/docs/src/pages/products/integration.md index 411f2f20c8..9c6b3d7fa8 100644 --- a/packages/docs/src/pages/products/integration.md +++ b/packages/docs/src/pages/products/integration.md @@ -70,7 +70,7 @@ Below are some of the classes of applications indexed by common integration meth | PACS | HL7 | HL7 ORU, OBX message types common | | Vaccine Registry | SFTP | Often operated by public health departments, government | | Payor | FHIR | Regulatory changes have increased payor FHIR adoption | -| Pharmacy | REST | Pharmacy API such as DoseSpot, Surescripts | +| Pharmacy | REST | Pharmacy API such as DoseSpot | | Logging and Analytics | REST | Splunk, Freshpaint, Amplitude, Segment and related | | CRM | REST, CSV | CRM often have API or file based import | | Forms | REST | Qualtrics, Jotforms, Formstack have REST API with webhooks | From 0b1a46b4c76f0b55fc1e854b831d39ffb3099cb9 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Wed, 14 Feb 2024 16:11:58 -0800 Subject: [PATCH 41/81] Patient tabs in example provider app (#3947) * Checkpoint on provider app * Timeline tab, edit tab, style fixes --- examples/medplum-provider/src/App.tsx | 39 +++++++++--- .../src/components/soapnote/SoapNote.tsx | 9 ++- .../src/components/tasks/TaskList.tsx | 10 ++-- .../medplum-provider/src/hooks/usePatient.ts | 11 ++++ examples/medplum-provider/src/main.tsx | 4 +- .../src/pages/LandingPage.tsx | 23 ------- .../src/pages/PatientPage.tsx | 26 -------- .../src/pages/patient/EditTab.tsx | 51 ++++++++++++++++ .../src/pages/patient/EncounterTab.tsx | 12 ++++ .../src/pages/patient/LabsTab.tsx | 5 ++ .../src/pages/patient/MedsTab.tsx | 5 ++ .../src/pages/patient/PatientPage.module.css | 15 +++++ .../src/pages/patient/PatientPage.tsx | 60 +++++++++++++++++++ .../src/pages/patient/TasksTab.tsx | 5 ++ .../src/pages/patient/TimelineTab.tsx | 11 ++++ examples/medplum-provider/vite.config.ts | 7 +++ .../QuestionnaireForm/QuestionnaireForm.tsx | 2 +- .../ResourceDiffTable.test.tsx | 6 ++ .../ResourceDiffTable/ResourceDiffTable.tsx | 7 ++- 19 files changed, 236 insertions(+), 72 deletions(-) create mode 100644 examples/medplum-provider/src/hooks/usePatient.ts delete mode 100644 examples/medplum-provider/src/pages/LandingPage.tsx delete mode 100644 examples/medplum-provider/src/pages/PatientPage.tsx create mode 100644 examples/medplum-provider/src/pages/patient/EditTab.tsx create mode 100644 examples/medplum-provider/src/pages/patient/EncounterTab.tsx create mode 100644 examples/medplum-provider/src/pages/patient/LabsTab.tsx create mode 100644 examples/medplum-provider/src/pages/patient/MedsTab.tsx create mode 100644 examples/medplum-provider/src/pages/patient/PatientPage.module.css create mode 100644 examples/medplum-provider/src/pages/patient/PatientPage.tsx create mode 100644 examples/medplum-provider/src/pages/patient/TasksTab.tsx create mode 100644 examples/medplum-provider/src/pages/patient/TimelineTab.tsx diff --git a/examples/medplum-provider/src/App.tsx b/examples/medplum-provider/src/App.tsx index b6f98bd268..9e6792cdca 100644 --- a/examples/medplum-provider/src/App.tsx +++ b/examples/medplum-provider/src/App.tsx @@ -1,13 +1,18 @@ import { AppShell, ErrorBoundary, Loading, Logo, useMedplum, useMedplumProfile } from '@medplum/react'; import { IconUser } from '@tabler/icons-react'; import { Suspense } from 'react'; -import { Route, Routes } from 'react-router-dom'; +import { Navigate, Route, Routes } from 'react-router-dom'; import { HomePage } from './pages/HomePage'; -import { LandingPage } from './pages/LandingPage'; -import { PatientPage } from './pages/PatientPage'; import { ResourcePage } from './pages/ResourcePage'; import { SearchPage } from './pages/SearchPage'; import { SignInPage } from './pages/SignInPage'; +import { EditTab } from './pages/patient/EditTab'; +import { EncounterTab } from './pages/patient/EncounterTab'; +import { LabsTab } from './pages/patient/LabsTab'; +import { MedsTab } from './pages/patient/MedsTab'; +import { PatientPage } from './pages/patient/PatientPage'; +import { TasksTab } from './pages/patient/TasksTab'; +import { TimelineTab } from './pages/patient/TimelineTab'; export function App(): JSX.Element | null { const medplum = useMedplum(); @@ -30,12 +35,28 @@ export function App(): JSX.Element | null { }> - : } /> - } /> - } /> - } /> - } /> - } /> + {profile ? ( + <> + } /> + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + } /> + } /> + } /> + + ) : ( + <> + } /> + } /> + + )} diff --git a/examples/medplum-provider/src/components/soapnote/SoapNote.tsx b/examples/medplum-provider/src/components/soapnote/SoapNote.tsx index 583eb78ae0..f656c05ac8 100644 --- a/examples/medplum-provider/src/components/soapnote/SoapNote.tsx +++ b/examples/medplum-provider/src/components/soapnote/SoapNote.tsx @@ -1,14 +1,15 @@ import { Box, Flex, Text } from '@mantine/core'; +import { createReference } from '@medplum/core'; import { QuestionnaireResponse, Task } from '@medplum/fhirtypes'; import { Document, QuestionnaireForm, useMedplum } from '@medplum/react'; import { IconCircleCheck } from '@tabler/icons-react'; import { useState } from 'react'; -import { useParams } from 'react-router-dom'; +import { usePatient } from '../../hooks/usePatient'; import { defaultSoapNoteQuestionnaire } from './SoapNote.questionnaire'; export function SoapNote(): JSX.Element { - const { id } = useParams(); const medplum = useMedplum(); + const patient = usePatient(); const [submitted, setSubmitted] = useState(false); async function handleSubmit(questionnaireResponse: QuestionnaireResponse): Promise { @@ -30,9 +31,7 @@ export function SoapNote(): JSX.Element { focus: { reference: `QuestionnaireResponse/${response.id}`, }, - for: { - reference: `Patient/${id}`, - }, + for: patient ? createReference(patient) : undefined, }; await medplum.createResource(newTask); setSubmitted(true); diff --git a/examples/medplum-provider/src/components/tasks/TaskList.tsx b/examples/medplum-provider/src/components/tasks/TaskList.tsx index c36f7b41ac..de7956bc75 100644 --- a/examples/medplum-provider/src/components/tasks/TaskList.tsx +++ b/examples/medplum-provider/src/components/tasks/TaskList.tsx @@ -12,7 +12,7 @@ import { } from '@medplum/react'; import { IconFilePencil, IconHeart, IconListCheck, IconReportMedical } from '@tabler/icons-react'; import { Fragment, ReactNode, useEffect, useState } from 'react'; -import { useParams } from 'react-router-dom'; +import { usePatient } from '../../hooks/usePatient'; import { DiagnosticReportModal } from './DiagnosticReportTask'; import { QuestionnaireTask, ResponseDisplay } from './QuestionnaireTask'; @@ -36,15 +36,15 @@ interface TaskItemProps { } export function TaskList(): JSX.Element | null { - const { id } = useParams(); - const [tasks, setTasks] = useState([]); const medplum = useMedplum(); - const patient = useResource({ reference: `Patient/${id}` }); + const patient = usePatient(); + const [tasks, setTasks] = useState([]); + useEffect(() => { medplum .searchResources( 'Task', - `patient=${id}&status:not=completed&status:not=failed&status:not=rejected&focus:missing=false` + `patient=${patient?.id}&status:not=completed&status:not=failed&status:not=rejected&focus:missing=false` ) .then((response) => { setTasks(response); diff --git a/examples/medplum-provider/src/hooks/usePatient.ts b/examples/medplum-provider/src/hooks/usePatient.ts new file mode 100644 index 0000000000..9d9cfd4b0a --- /dev/null +++ b/examples/medplum-provider/src/hooks/usePatient.ts @@ -0,0 +1,11 @@ +import { Patient } from '@medplum/fhirtypes'; +import { useResource } from '@medplum/react'; +import { useParams } from 'react-router-dom'; + +export function usePatient(): Patient | undefined { + const { patientId } = useParams(); + if (!patientId) { + throw new Error('Patient ID not found'); + } + return useResource({ reference: `Patient/${patientId}` }); +} diff --git a/examples/medplum-provider/src/main.tsx b/examples/medplum-provider/src/main.tsx index e0f5b128cf..544c09acd8 100644 --- a/examples/medplum-provider/src/main.tsx +++ b/examples/medplum-provider/src/main.tsx @@ -2,7 +2,7 @@ import { MantineProvider, createTheme } from '@mantine/core'; import '@mantine/core/styles.css'; import { MedplumClient } from '@medplum/core'; import { MedplumProvider } from '@medplum/react'; -import '@medplum/react/styles.css'; +// import '@medplum/react/styles.css'; import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; @@ -10,7 +10,7 @@ import { App } from './App'; const medplum = new MedplumClient({ onUnauthenticated: () => (window.location.href = '/'), - // baseUrl: 'http://localhost:8103/', //Uncomment this to run against the server on your localhost + // baseUrl: 'http://localhost:8103/', // Uncomment this to run against the server on your localhost }); const theme = createTheme({ diff --git a/examples/medplum-provider/src/pages/LandingPage.tsx b/examples/medplum-provider/src/pages/LandingPage.tsx deleted file mode 100644 index d229737581..0000000000 --- a/examples/medplum-provider/src/pages/LandingPage.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Anchor, Button, Stack, Text, Title } from '@mantine/core'; -import { Document } from '@medplum/react'; -import { Link } from 'react-router-dom'; - -export function LandingPage(): JSX.Element { - return ( - - - - Welcome! - - - This "Hello World" example demonstrates how to build a simple React application that fetches Patient data from - Medplum. If you haven't already done so, register for - Medplum Project. After that you can sign into your project by clicking the link below. - - - - - ); -} diff --git a/examples/medplum-provider/src/pages/PatientPage.tsx b/examples/medplum-provider/src/pages/PatientPage.tsx deleted file mode 100644 index 3b159a6c7e..0000000000 --- a/examples/medplum-provider/src/pages/PatientPage.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Flex, Loader } from '@mantine/core'; -import { getReferenceString } from '@medplum/core'; -import { Patient } from '@medplum/fhirtypes'; -import { PatientSummary, useResource } from '@medplum/react'; -import { Fragment } from 'react'; -import { useParams } from 'react-router-dom'; -import { SoapNote } from '../components/soapnote/SoapNote'; -import { TaskList } from '../components/tasks/TaskList'; - -export function PatientPage(): JSX.Element { - const { id } = useParams(); - const patient = useResource({ reference: `Patient/${id}` }); - if (!patient) { - return ; - } - - return ( - - - - - - - - ); -} diff --git a/examples/medplum-provider/src/pages/patient/EditTab.tsx b/examples/medplum-provider/src/pages/patient/EditTab.tsx new file mode 100644 index 0000000000..961cb0dd70 --- /dev/null +++ b/examples/medplum-provider/src/pages/patient/EditTab.tsx @@ -0,0 +1,51 @@ +import { showNotification } from '@mantine/notifications'; +import { deepClone, normalizeErrorString, normalizeOperationOutcome } from '@medplum/core'; +import { OperationOutcome, Resource } from '@medplum/fhirtypes'; +import { Document, ResourceForm, useMedplum } from '@medplum/react'; +import { useCallback, useEffect, useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; + +export function EditTab(): JSX.Element | null { + const medplum = useMedplum(); + const { patientId } = useParams() as { patientId: string }; + const [value, setValue] = useState(); + const navigate = useNavigate(); + const [outcome, setOutcome] = useState(); + + useEffect(() => { + medplum + .readResource('Patient', patientId) + .then((resource) => setValue(deepClone(resource))) + .catch((err) => { + setOutcome(normalizeOperationOutcome(err)); + showNotification({ color: 'red', message: normalizeErrorString(err), autoClose: false }); + }); + }, [medplum, patientId]); + + const handleSubmit = useCallback( + (newResource: Resource): void => { + setOutcome(undefined); + medplum + .updateResource(newResource) + .then(() => { + navigate(`/Patient/${patientId}/timeline`); + showNotification({ color: 'green', message: 'Success' }); + }) + .catch((err) => { + setOutcome(normalizeOperationOutcome(err)); + showNotification({ color: 'red', message: normalizeErrorString(err), autoClose: false }); + }); + }, + [medplum, patientId, navigate] + ); + + if (!value) { + return null; + } + + return ( + + + + ); +} diff --git a/examples/medplum-provider/src/pages/patient/EncounterTab.tsx b/examples/medplum-provider/src/pages/patient/EncounterTab.tsx new file mode 100644 index 0000000000..b808269a7f --- /dev/null +++ b/examples/medplum-provider/src/pages/patient/EncounterTab.tsx @@ -0,0 +1,12 @@ +import { Group } from '@mantine/core'; +import { SoapNote } from '../../components/soapnote/SoapNote'; +import { TaskList } from '../../components/tasks/TaskList'; + +export function EncounterTab(): JSX.Element { + return ( + + + + + ); +} diff --git a/examples/medplum-provider/src/pages/patient/LabsTab.tsx b/examples/medplum-provider/src/pages/patient/LabsTab.tsx new file mode 100644 index 0000000000..1f65971444 --- /dev/null +++ b/examples/medplum-provider/src/pages/patient/LabsTab.tsx @@ -0,0 +1,5 @@ +import { Document } from '@medplum/react'; + +export function LabsTab(): JSX.Element { + return Labs; +} diff --git a/examples/medplum-provider/src/pages/patient/MedsTab.tsx b/examples/medplum-provider/src/pages/patient/MedsTab.tsx new file mode 100644 index 0000000000..e8aacf1759 --- /dev/null +++ b/examples/medplum-provider/src/pages/patient/MedsTab.tsx @@ -0,0 +1,5 @@ +import { Document } from '@medplum/react'; + +export function MedsTab(): JSX.Element { + return Meds; +} diff --git a/examples/medplum-provider/src/pages/patient/PatientPage.module.css b/examples/medplum-provider/src/pages/patient/PatientPage.module.css new file mode 100644 index 0000000000..45466f220a --- /dev/null +++ b/examples/medplum-provider/src/pages/patient/PatientPage.module.css @@ -0,0 +1,15 @@ +.container { + display: flex; + flex-direction: row; + align-items: stretch; +} + +.sidebar { + flex: 1; + width: 350px; + height: 100%; +} + +.content { + width: 100%; +} diff --git a/examples/medplum-provider/src/pages/patient/PatientPage.tsx b/examples/medplum-provider/src/pages/patient/PatientPage.tsx new file mode 100644 index 0000000000..dbfb7e072c --- /dev/null +++ b/examples/medplum-provider/src/pages/patient/PatientPage.tsx @@ -0,0 +1,60 @@ +import { Loader, Paper, ScrollArea, Tabs } from '@mantine/core'; +import { getReferenceString } from '@medplum/core'; +import { PatientSummary } from '@medplum/react'; +import { Fragment, useState } from 'react'; +import { Outlet, useNavigate } from 'react-router-dom'; +import { usePatient } from '../../hooks/usePatient'; +import classes from './PatientPage.module.css'; + +const tabs = ['Timeline', 'Edit', 'Encounter', 'Tasks', 'Meds', 'Labs']; + +export function PatientPage(): JSX.Element { + const navigate = useNavigate(); + const patient = usePatient(); + const [currentTab, setCurrentTab] = useState(() => { + const tab = window.location.pathname.split('/').pop(); + return tab && tabs.map((t) => t.toLowerCase()).includes(tab) ? tab : tabs[0].toLowerCase(); + }); + + if (!patient) { + return ; + } + + /** + * Handles a tab change event. + * @param newTabName - The new tab name. + */ + function onTabChange(newTabName: string | null): void { + if (!newTabName) { + newTabName = tabs[0].toLowerCase(); + } + setCurrentTab(newTabName); + navigate(`/Patient/${patient?.id}/${newTabName}`); + } + + return ( + +
+
+ +
+
+ + + + + {tabs.map((t) => ( + + {t} + + ))} + + + + + +
+
+
+ ); +} diff --git a/examples/medplum-provider/src/pages/patient/TasksTab.tsx b/examples/medplum-provider/src/pages/patient/TasksTab.tsx new file mode 100644 index 0000000000..66237dacd0 --- /dev/null +++ b/examples/medplum-provider/src/pages/patient/TasksTab.tsx @@ -0,0 +1,5 @@ +import { Document } from '@medplum/react'; + +export function TasksTab(): JSX.Element { + return Tasks; +} diff --git a/examples/medplum-provider/src/pages/patient/TimelineTab.tsx b/examples/medplum-provider/src/pages/patient/TimelineTab.tsx new file mode 100644 index 0000000000..a236133af3 --- /dev/null +++ b/examples/medplum-provider/src/pages/patient/TimelineTab.tsx @@ -0,0 +1,11 @@ +import { Loader } from '@mantine/core'; +import { PatientTimeline } from '@medplum/react'; +import { usePatient } from '../../hooks/usePatient'; + +export function TimelineTab(): JSX.Element { + const patient = usePatient(); + if (!patient) { + return ; + } + return ; +} diff --git a/examples/medplum-provider/vite.config.ts b/examples/medplum-provider/vite.config.ts index e4c46a598c..f21161a976 100644 --- a/examples/medplum-provider/vite.config.ts +++ b/examples/medplum-provider/vite.config.ts @@ -1,6 +1,7 @@ import react from '@vitejs/plugin-react'; import { defineConfig } from 'vite'; import dns from 'dns'; +import path from 'path'; dns.setDefaultResultOrder('verbatim'); @@ -11,4 +12,10 @@ export default defineConfig({ host: 'localhost', port: 3000, }, + resolve: { + alias: { + '@medplum/core': path.resolve(__dirname, '../../packages/core/src'), + '@medplum/react': path.resolve(__dirname, '../../packages/react/src'), + }, + }, }); diff --git a/packages/react/src/QuestionnaireForm/QuestionnaireForm.tsx b/packages/react/src/QuestionnaireForm/QuestionnaireForm.tsx index 719a31f786..4be1c20d44 100644 --- a/packages/react/src/QuestionnaireForm/QuestionnaireForm.tsx +++ b/packages/react/src/QuestionnaireForm/QuestionnaireForm.tsx @@ -63,7 +63,7 @@ export function QuestionnaireForm(props: QuestionnaireFormProps): JSX.Element | return isQuestionEnabled(item, response?.item ?? []); } - if (!schemaLoaded || !questionnaire) { + if (!schemaLoaded || !questionnaire || !response) { return null; } diff --git a/packages/react/src/ResourceDiffTable/ResourceDiffTable.test.tsx b/packages/react/src/ResourceDiffTable/ResourceDiffTable.test.tsx index b241270a1b..d6349b89d0 100644 --- a/packages/react/src/ResourceDiffTable/ResourceDiffTable.test.tsx +++ b/packages/react/src/ResourceDiffTable/ResourceDiffTable.test.tsx @@ -20,6 +20,7 @@ describe('ResourceDiffTable', () => { resourceType: 'Patient', id: '123', meta: { + author: { reference: 'Practitioner/456' }, versionId: '456', }, birthDate: '1990-01-01', @@ -30,6 +31,7 @@ describe('ResourceDiffTable', () => { resourceType: 'Patient', id: '123', meta: { + author: { reference: 'Practitioner/457' }, versionId: '457', }, birthDate: '1990-01-01', @@ -56,6 +58,10 @@ describe('ResourceDiffTable', () => { // Birth date did not change, and therefore should not be shown expect(screen.queryByText('Birth Date')).toBeNull(); + + // Certain meta fields should not be shown + expect(screen.queryByText('Author')).toBeNull(); + expect(screen.queryByText('Version ID')).toBeNull(); }); test('Array index operations', async () => { diff --git a/packages/react/src/ResourceDiffTable/ResourceDiffTable.tsx b/packages/react/src/ResourceDiffTable/ResourceDiffTable.tsx index 7b1f48a967..fe3f6fb783 100644 --- a/packages/react/src/ResourceDiffTable/ResourceDiffTable.tsx +++ b/packages/react/src/ResourceDiffTable/ResourceDiffTable.tsx @@ -114,7 +114,12 @@ function mergePatchOperations(patch: Operation[]): Operation[] { const result: Operation[] = []; for (const patchOperation of patch) { const { op, path } = patchOperation; - if (path.startsWith('/meta/lastUpdated') || path.startsWith('/meta/versionId')) { + if ( + path.startsWith('/meta/author') || + path.startsWith('/meta/compartment') || + path.startsWith('/meta/lastUpdated') || + path.startsWith('/meta/versionId') + ) { continue; } const count = patch.filter((el) => el.op === op && el.path === path).length; From e49192f39dfc581ed2ae8a175eed9346f7885716 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Wed, 14 Feb 2024 16:28:18 -0800 Subject: [PATCH 42/81] Show system and code in ValueSetAutocomplete (#3963) --- .../react/src/ResourceInput/ResourceInput.tsx | 32 ++++++++++--------- .../ValueSetAutocomplete.tsx | 21 +++++++++++- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/packages/react/src/ResourceInput/ResourceInput.tsx b/packages/react/src/ResourceInput/ResourceInput.tsx index 048ce3f586..0cc8613186 100644 --- a/packages/react/src/ResourceInput/ResourceInput.tsx +++ b/packages/react/src/ResourceInput/ResourceInput.tsx @@ -144,21 +144,23 @@ export function ResourceInput(props: ResourceInpu ); } -const ItemComponent = forwardRef(({ label, resource, ...others }: any, ref) => { - return ( -
- - -
- {label} - - {(resource as Patient).birthDate} - -
-
-
- ); -}); +const ItemComponent = forwardRef>( + ({ label, resource, ...others }: AsyncAutocompleteOption, ref) => { + return ( +
+ + +
+ {label} + + {(resource as Patient).birthDate} + +
+
+
+ ); + } +); /** * Returns the search parameter to use for the given resource type. diff --git a/packages/react/src/ValueSetAutocomplete/ValueSetAutocomplete.tsx b/packages/react/src/ValueSetAutocomplete/ValueSetAutocomplete.tsx index 61850fcff0..3a3e609cd2 100644 --- a/packages/react/src/ValueSetAutocomplete/ValueSetAutocomplete.tsx +++ b/packages/react/src/ValueSetAutocomplete/ValueSetAutocomplete.tsx @@ -1,7 +1,8 @@ +import { Group, Text } from '@mantine/core'; import { ValueSetExpandParams } from '@medplum/core'; import { ValueSetExpansionContains } from '@medplum/fhirtypes'; import { useMedplum } from '@medplum/react-hooks'; -import { useCallback } from 'react'; +import { forwardRef, useCallback } from 'react'; import { AsyncAutocomplete, AsyncAutocompleteOption, @@ -89,6 +90,24 @@ export function ValueSetAutocomplete(props: ValueSetAutocompleteProps): JSX.Elem toOption={toOption} loadOptions={loadValues} onCreate={createValue} + itemComponent={ItemComponent} /> ); } + +const ItemComponent = forwardRef>( + ({ label, resource, ...others }: AsyncAutocompleteOption, ref) => { + return ( +
+ +
+ {label} + + {`${resource.system}#${resource.code}`} + +
+
+
+ ); + } +); From 867efc623d35665474c9ab4abe25437d9663c28e Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Wed, 14 Feb 2024 19:10:20 -0800 Subject: [PATCH 43/81] Add maxValues prop to ResourceTypeInput (#3964) * Add maxValues prop to ResourceTypeInput * Fixed tests --- packages/react/src/AppShell/Navbar.tsx | 1 + .../AsyncAutocomplete/AsyncAutocomplete.tsx | 28 +++++++++++-------- .../src/CodingInput/CodingInput.test.tsx | 2 +- .../ResourcePropertyInput.test.tsx | 5 ++-- .../ResourceTypeInput/ResourceTypeInput.tsx | 3 +- .../SearchFilterEditor.test.tsx | 8 +++++- .../SearchFilterValueInput.test.tsx | 8 +++++- 7 files changed, 36 insertions(+), 19 deletions(-) diff --git a/packages/react/src/AppShell/Navbar.tsx b/packages/react/src/AppShell/Navbar.tsx index f35dae3db9..234016efc6 100644 --- a/packages/react/src/AppShell/Navbar.tsx +++ b/packages/react/src/AppShell/Navbar.tsx @@ -58,6 +58,7 @@ export function Navbar(props: NavbarProps): JSX.Element { key={window.location.pathname} name="resourceType" placeholder="Resource Type" + maxValues={0} onChange={(newValue) => navigateResourceType(newValue)} /> diff --git a/packages/react/src/AsyncAutocomplete/AsyncAutocomplete.tsx b/packages/react/src/AsyncAutocomplete/AsyncAutocomplete.tsx index 7cae006ccc..13ba5fc36c 100644 --- a/packages/react/src/AsyncAutocomplete/AsyncAutocomplete.tsx +++ b/packages/react/src/AsyncAutocomplete/AsyncAutocomplete.tsx @@ -176,6 +176,7 @@ export function AsyncAutocomplete(props: AsyncAutocompleteProps): JSX.Elem const handleValueSelect = (val: string): void => { setSearch(''); setOptions([]); + combobox.closeDropdown(); lastValueRef.current = undefined; if (val === '$create') { addSelected(search); @@ -233,6 +234,7 @@ export function AsyncAutocomplete(props: AsyncAutocompleteProps): JSX.Elem setSearch(''); setSelected([]); onChange([]); + combobox.closeDropdown(); }} /> ); @@ -256,18 +258,20 @@ export function AsyncAutocomplete(props: AsyncAutocompleteProps): JSX.Elem ))} - - combobox.closeDropdown()} - onKeyDown={handleKeyDown} - onChange={handleSearchChange} - /> - + {(maxValues === undefined || maxValues === 0 || selected.length < maxValues) && ( + + combobox.closeDropdown()} + onKeyDown={handleKeyDown} + onChange={handleSearchChange} + /> + + )} diff --git a/packages/react/src/CodingInput/CodingInput.test.tsx b/packages/react/src/CodingInput/CodingInput.test.tsx index b3f79332da..18a486aab7 100644 --- a/packages/react/src/CodingInput/CodingInput.test.tsx +++ b/packages/react/src/CodingInput/CodingInput.test.tsx @@ -34,7 +34,7 @@ describe('CodingInput', () => { test('Renders Coding default value', async () => { await setup(); - expect(screen.getByRole('searchbox')).toBeInTheDocument(); + expect(screen.queryByRole('searchbox')).not.toBeInTheDocument(); expect(screen.getByText('abc')).toBeDefined(); }); diff --git a/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.test.tsx b/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.test.tsx index 37243786fb..7e41c816e5 100644 --- a/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.test.tsx +++ b/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.test.tsx @@ -514,9 +514,8 @@ describe('ResourcePropertyInput', () => { expect(comboboxes).toHaveLength(1); expect(comboboxes[0]).toBeInstanceOf(HTMLSelectElement); - const searchBoxes = screen.getAllByRole('searchbox'); - expect(searchBoxes).toHaveLength(1); - expect(searchBoxes[0]).toBeInstanceOf(HTMLInputElement); + const searchBoxes = screen.queryAllByRole('searchbox'); + expect(searchBoxes).toHaveLength(0); }); test('Type selector', async () => { diff --git a/packages/react/src/ResourceTypeInput/ResourceTypeInput.tsx b/packages/react/src/ResourceTypeInput/ResourceTypeInput.tsx index 3e5e762a02..195588473c 100644 --- a/packages/react/src/ResourceTypeInput/ResourceTypeInput.tsx +++ b/packages/react/src/ResourceTypeInput/ResourceTypeInput.tsx @@ -8,6 +8,7 @@ export interface ResourceTypeInputProps { readonly defaultValue?: ResourceType; readonly autoFocus?: boolean; readonly testId?: string; + readonly maxValues?: number; readonly onChange?: (value: ResourceType | undefined) => void; } @@ -35,7 +36,7 @@ export function ResourceTypeInput(props: ResourceTypeInputProps): JSX.Element { placeholder={props.placeholder} binding="https://medplum.com/fhir/ValueSet/resource-types" creatable={false} - maxValues={0} + maxValues={props.maxValues ?? 1} clearable={false} /> ); diff --git a/packages/react/src/SearchFilterEditor/SearchFilterEditor.test.tsx b/packages/react/src/SearchFilterEditor/SearchFilterEditor.test.tsx index d234de6615..75a96633f1 100644 --- a/packages/react/src/SearchFilterEditor/SearchFilterEditor.test.tsx +++ b/packages/react/src/SearchFilterEditor/SearchFilterEditor.test.tsx @@ -122,7 +122,13 @@ describe('SearchFilterEditor', () => { fireEvent.click(screen.getByText('Edit')); }); - const input = screen.getAllByRole('searchbox')[1] as HTMLInputElement; + // Clear the existing value + const clearButton = screen.getByTitle('Clear all'); + await act(async () => { + fireEvent.click(clearButton); + }); + + const input = screen.getAllByRole('searchbox')[0] as HTMLInputElement; await act(async () => { fireEvent.change(input, { target: { value: 'Different' } }); }); diff --git a/packages/react/src/SearchFilterValueInput/SearchFilterValueInput.test.tsx b/packages/react/src/SearchFilterValueInput/SearchFilterValueInput.test.tsx index 1125ebd63b..020273af97 100644 --- a/packages/react/src/SearchFilterValueInput/SearchFilterValueInput.test.tsx +++ b/packages/react/src/SearchFilterValueInput/SearchFilterValueInput.test.tsx @@ -145,7 +145,13 @@ describe('SearchFilterValueInput', () => { await waitFor(() => screen.getByText('Test Organization')); }); - const input = screen.getAllByRole('searchbox')[1] as HTMLInputElement; + // Clear the existing value + const clearButton = screen.getByTitle('Clear all'); + await act(async () => { + fireEvent.click(clearButton); + }); + + const input = screen.getAllByRole('searchbox')[0] as HTMLInputElement; await act(async () => { fireEvent.change(input, { target: { value: 'Different' } }); }); From 101a617188752d7089b4f6047ea6bd6e8f9a625b Mon Sep 17 00:00:00 2001 From: ksmith94 <102421938+ksmith94@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:16:30 -0500 Subject: [PATCH 44/81] Link sort section to search parameters (#3967) --- packages/docs/docs/search/basic-search.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/docs/docs/search/basic-search.mdx b/packages/docs/docs/search/basic-search.mdx index a4f260c199..7269d1db84 100644 --- a/packages/docs/docs/search/basic-search.mdx +++ b/packages/docs/docs/search/basic-search.mdx @@ -324,7 +324,7 @@ You can search an exclusive range using an OR search You can sort your search results by using the special search parameter `_sort`. -The `_sort` parameter allows you specify a list of search parameters to sort by, in order. +The `_sort` parameter allows you specify a list of [search parameters](#search-parameters) to sort by, in order. The example below searches for all [`RiskAssessments`](/docs/api/fhir/resources/riskassessment), sorted by their `probability`, then by `date`. From 867185e3c570c15c7eabd9d68a80494e1fbc2954 Mon Sep 17 00:00:00 2001 From: ksmith94 <102421938+ksmith94@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:16:57 -0500 Subject: [PATCH 45/81] Add note about admin access policies (#3966) --- packages/docs/docs/access/admin.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/docs/docs/access/admin.md b/packages/docs/docs/access/admin.md index 33fbe10109..d178f3dd21 100644 --- a/packages/docs/docs/access/admin.md +++ b/packages/docs/docs/access/admin.md @@ -21,6 +21,12 @@ Project Admins have the following privileges: - [`PasswordChangeRequest`](/docs/api/fhir/medplum/passwordchangerequest) - used to [send custom emails](/docs/auth/custom-emails#password-change-request-bot) - [`User`](/docs/api/fhir/medplum/user) - only for [project scoped users](/docs/auth/user-management-guide#project-scoped-users) +:::note Applying Access Policies to Admins + +If you want to limit these privileges, you can apply Access Policies to your Admin users. See the [Access Policies docs](/docs/access/access-policies) for more details. + +::: + ## Super Admin A super admin user has an increased level privileges for performing server-level operations. **This level of privilege can cause irreparable data changes, and should be limited to system administrators.** From 2f47bdd7f601c0283e42a5a4ccf5ee39915c540e Mon Sep 17 00:00:00 2001 From: Matt Long Date: Thu, 15 Feb 2024 10:55:01 -0800 Subject: [PATCH 46/81] Apply pattern and fixed values to profile resources (#3918) * First steps * Something working * Observation.code pattern working * very close * temp * Tests passing * Start cleaning up code * fixed/pattern values nested below non-required elements excluded * Can get default value of single slice value * little things * Remove most TypedValue usage * crawlElement temp * temp * All tests passing! * Code cleanup * add getDefaultValuesForNewSliceEntry * Start applying default values in ResourceForm * Extract profile tabs component * Profiles tab for create new form * Simplify DefaultValueCrawler interface * Can save a new blood pressure observation * Empty object it not really populated * Can actually save new us core blood pressure * US Core Patient slices working as well * Clean up default-value tests * Add documentation for SchemaCrawler * Cleanup and document DefaultValueVisitor * Dedupe slicing logic * Start deduping elementscontext * Fix loading bug in ExtensionInput * Preven creating unnecessary contexts * Simplify ElementsContext * The great elementscontext merge! * Move US Core SDs to packages/definitions * Change to relative import within package * Unbreak form create page for non-profile experience * Import JSON directly from definitions * Duplicate uscore SDs for now to unbreak build * Remove debugging code * More tests, reduce complexity * Remove unused,newly introduce option * Remove extraneous function * Sonar fixes * Don't use toSorted * Add documentation to elements-context * Simplify ResourcePropertyInput * Specify path in ExtensionInput story * isEmpty is not the exact opposite of isPopulated * prefer isEmpty to an undefined check * Add ResourceForm test working with extensions --- packages/app/src/AppRoutes.tsx | 1 + packages/app/src/CreateResourcePage.tsx | 17 +- packages/app/src/resource/FormCreatePage.tsx | 51 +- packages/app/src/resource/ProfileTabs.tsx | 66 +++ packages/app/src/resource/ProfilesPage.tsx | 58 +-- .../app/src/resource/useCreateResource.ts | 9 +- packages/core/src/default-values.test.ts | 406 ++++++++++++++++ packages/core/src/default-values.ts | 439 ++++++++++++++++++ packages/core/src/elements-context.test.ts | 112 +++++ packages/core/src/elements-context.ts | 109 +++++ packages/core/src/index.ts | 3 + packages/core/src/schema-crawler.ts | 343 ++++++++++++++ packages/core/src/typeschema/slices.ts | 56 +++ packages/core/src/utils.test.ts | 24 + packages/core/src/utils.ts | 20 +- packages/core/test.setup.cjs | 2 +- .../uscore-v5.0.1-structuredefinitions.json | 1 + packages/generator/src/storybook.ts | 14 +- .../react/src/AddressInput/AddressInput.tsx | 4 - .../BackboneElementInput.tsx | 41 +- .../ContactPointInput/ContactPointInput.tsx | 6 +- .../react/src/ElementsInput/ElementsInput.tsx | 6 - .../ElementsInput/ElementsInput.utils.test.ts | 25 - .../src/ElementsInput/ElementsInput.utils.ts | 150 +----- .../ExtensionInput/ExtensionInput.stories.tsx | 2 +- .../src/ExtensionInput/ExtensionInput.tsx | 6 +- .../src/IdentifierInput/IdentifierInput.tsx | 6 +- .../ResourceArrayInput.test.tsx | 25 +- .../ResourceArrayInput/ResourceArrayInput.tsx | 5 +- .../ResourceArrayInput.utils.test.ts | 46 +- .../ResourceArrayInput.utils.ts | 67 +-- .../src/ResourceForm/ResourceForm.stories.tsx | 4 +- .../src/ResourceForm/ResourceForm.test.tsx | 139 +++++- .../react/src/ResourceForm/ResourceForm.tsx | 10 +- .../react/src/ResourceInput/ResourceInput.tsx | 4 +- .../ResourcePropertyInput.tsx | 52 ++- packages/react/src/SliceInput/SliceInput.tsx | 38 +- .../react/src/SliceInput/SliceInput.utils.ts | 10 - .../react/src/utils/maybeWrapWithContext.tsx | 11 + .../structuredefinitionexpandprofile.test.ts | 34 +- 40 files changed, 1984 insertions(+), 438 deletions(-) create mode 100644 packages/app/src/resource/ProfileTabs.tsx create mode 100644 packages/core/src/default-values.test.ts create mode 100644 packages/core/src/default-values.ts create mode 100644 packages/core/src/elements-context.test.ts create mode 100644 packages/core/src/elements-context.ts create mode 100644 packages/core/src/schema-crawler.ts create mode 100644 packages/core/src/typeschema/slices.ts create mode 100644 packages/definitions/dist/fhir/r4/testing/uscore-v5.0.1-structuredefinitions.json delete mode 100644 packages/react/src/ElementsInput/ElementsInput.utils.test.ts delete mode 100644 packages/react/src/SliceInput/SliceInput.utils.ts create mode 100644 packages/react/src/utils/maybeWrapWithContext.tsx diff --git a/packages/app/src/AppRoutes.tsx b/packages/app/src/AppRoutes.tsx index 7f571d2184..fb53ba7e69 100644 --- a/packages/app/src/AppRoutes.tsx +++ b/packages/app/src/AppRoutes.tsx @@ -96,6 +96,7 @@ export function AppRoutes(): JSX.Element { } /> } /> } /> + } /> } /> }> diff --git a/packages/app/src/CreateResourcePage.tsx b/packages/app/src/CreateResourcePage.tsx index 7c75b74411..c5b20f5785 100644 --- a/packages/app/src/CreateResourcePage.tsx +++ b/packages/app/src/CreateResourcePage.tsx @@ -1,12 +1,14 @@ -import { Paper, ScrollArea, Tabs, Text } from '@mantine/core'; +import { Badge, Group, Paper, ScrollArea, Tabs, Text, useMantineTheme } from '@mantine/core'; import { useState } from 'react'; import { Outlet, useNavigate, useParams } from 'react-router-dom'; -const tabs = ['Form', 'JSON']; +const tabs = ['Form', 'JSON', 'Profiles'] as const; +const BETA_TABS: (typeof tabs)[number][] = ['Profiles']; const defaultTab = tabs[0].toLowerCase(); export function CreateResourcePage(): JSX.Element { const navigate = useNavigate(); + const theme = useMantineTheme(); const { resourceType } = useParams(); const tab = window.location.pathname.split('/').pop(); const [currentTab, setCurrentTab] = useState(tab ?? defaultTab); @@ -34,7 +36,16 @@ export function CreateResourcePage(): JSX.Element { {tabs.map((t) => ( - {t} + {BETA_TABS.includes(t) ? ( + + {t} + + Beta + + + ) : ( + t + )} ))} diff --git a/packages/app/src/resource/FormCreatePage.tsx b/packages/app/src/resource/FormCreatePage.tsx index fd24a72b9c..da0e27b1ce 100644 --- a/packages/app/src/resource/FormCreatePage.tsx +++ b/packages/app/src/resource/FormCreatePage.tsx @@ -1,17 +1,58 @@ -import { OperationOutcome } from '@medplum/fhirtypes'; -import { Document, ResourceForm } from '@medplum/react'; -import { useState } from 'react'; -import { useParams } from 'react-router-dom'; +import { Stack, Text } from '@mantine/core'; +import { OperationOutcome, Resource } from '@medplum/fhirtypes'; +import { Document, ResourceForm, SupportedProfileStructureDefinition } from '@medplum/react'; +import { useCallback, useState } from 'react'; +import { useLocation, useParams } from 'react-router-dom'; import { useCreateResource } from './useCreateResource'; +import { ProfileTabs } from './ProfileTabs'; +import { addProfileToResource, cleanResource } from './utils'; export function FormCreatePage(): JSX.Element { const { resourceType } = useParams(); + const location = useLocation(); const [outcome, setOutcome] = useState(); const { defaultValue, handleSubmit } = useCreateResource(resourceType, setOutcome); + const [currentProfile, setCurrentProfile] = useState(); + + const isProfilesPage = location.pathname.toLowerCase().endsWith('profiles'); + + const onProfileSubmit = useCallback( + (resource: Resource): void => { + const cleanedResource = cleanResource(resource); + if (currentProfile) { + addProfileToResource(cleanedResource, currentProfile.url); + } + handleSubmit(cleanedResource); + }, + [currentProfile, handleSubmit] + ); + + if (!isProfilesPage) { + return ( + + + + ); + } return ( - + + + {currentProfile ? ( + + ) : ( + + Select a profile above + + )} + ); } diff --git a/packages/app/src/resource/ProfileTabs.tsx b/packages/app/src/resource/ProfileTabs.tsx new file mode 100644 index 0000000000..30d1532b66 --- /dev/null +++ b/packages/app/src/resource/ProfileTabs.tsx @@ -0,0 +1,66 @@ +import { Tabs, ThemeIcon } from '@mantine/core'; +import { Resource } from '@medplum/fhirtypes'; +import { SupportedProfileStructureDefinition, isSupportedProfileStructureDefinition, useMedplum } from '@medplum/react'; +import { IconCircleFilled } from '@tabler/icons-react'; +import { useEffect, useState } from 'react'; +export interface ProfileTabsProps { + readonly resource: Resource; + readonly currentProfile: SupportedProfileStructureDefinition | undefined; + readonly onChange: (newProfile: SupportedProfileStructureDefinition) => void; +} + +export function ProfileTabs({ resource, currentProfile, onChange }: ProfileTabsProps): JSX.Element { + const resourceType = resource.resourceType; + + const medplum = useMedplum(); + const [availableProfiles, setAvailableProfiles] = useState(); + + // This is a bit inefficient since the entire structure definition + // for each available profile is being fetched. All that is really needed is the title & url + // The SD is useful for the time being to populate the Snapshot and JSON debugging tabs; + // but those will likely be removed before deploying + useEffect(() => { + medplum + .searchResources('StructureDefinition', { type: resourceType, derivation: 'constraint', _count: 50 }) + .then((results) => { + setAvailableProfiles(results.filter(isSupportedProfileStructureDefinition)); + }) + .catch(console.error); + }, [medplum, onChange, resourceType]); + return ( + { + const newProfile = availableProfiles?.find((p) => p.url === newProfileUrl); + if (newProfile) { + onChange(newProfile); + } + }} + > + + {availableProfiles?.map((profile) => { + const isActive = resource.meta?.profile?.includes(profile.url); + const title = isActive + ? `This profile is present in this resource's meta.profile property.` + : `This profile is not included in this resource's meta.profile property.`; + return ( + + + + ) + } + > + {profile.title || profile.name} + + ); + })} + + + ); +} diff --git a/packages/app/src/resource/ProfilesPage.tsx b/packages/app/src/resource/ProfilesPage.tsx index 4660b616fb..9ee6bc4f8b 100644 --- a/packages/app/src/resource/ProfilesPage.tsx +++ b/packages/app/src/resource/ProfilesPage.tsx @@ -1,25 +1,18 @@ -import { Button, Group, Stack, Switch, Tabs, Text, ThemeIcon, Title } from '@mantine/core'; +import { Button, Group, Stack, Switch, Text, Title } from '@mantine/core'; import { showNotification } from '@mantine/notifications'; import { deepClone, normalizeErrorString, normalizeOperationOutcome } from '@medplum/core'; import { OperationOutcome, Resource, ResourceType } from '@medplum/fhirtypes'; -import { - Document, - ResourceForm, - SupportedProfileStructureDefinition, - isSupportedProfileStructureDefinition, - useMedplum, -} from '@medplum/react'; -import { IconCircleFilled } from '@tabler/icons-react'; +import { Document, ResourceForm, SupportedProfileStructureDefinition, useMedplum } from '@medplum/react'; import React, { useCallback, useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; import { addProfileToResource, cleanResource, removeProfileFromResource } from './utils'; +import { ProfileTabs } from './ProfileTabs'; export function ProfilesPage(): JSX.Element | null { const medplum = useMedplum(); const { resourceType, id } = useParams() as { resourceType: ResourceType; id: string }; const [resource, setResource] = useState(); const [currentProfile, setCurrentProfile] = useState(); - const [availableProfiles, setAvailableProfiles] = useState(); useEffect(() => { medplum @@ -30,19 +23,6 @@ export function ProfilesPage(): JSX.Element | null { }); }, [medplum, resourceType, id]); - // This is a bit inefficient since the entire structure definition - // for each available profile is being fetched. All that is really needed is the title & url - // The SD is useful for the time being to populate the Snapshot and JSON debugging tabs; - // but those will likely be removed before deploying - useEffect(() => { - medplum - .searchResources('StructureDefinition', { type: resourceType, derivation: 'constraint', _count: 50 }) - .then((results) => { - setAvailableProfiles(results.filter(isSupportedProfileStructureDefinition)); - }) - .catch(console.error); - }, [medplum, resourceType]); - if (!resource) { return null; } @@ -52,35 +32,7 @@ export function ProfilesPage(): JSX.Element | null { Available {resourceType} profiles <> - setCurrentProfile(availableProfiles?.find((p) => p.url === newProfileUrl))} - > - - {availableProfiles?.map((profile) => { - const isActive = resource.meta?.profile?.includes(profile.url); - const title = isActive - ? `This profile is present in this resource's meta.profile property.` - : `This profile is not included in this resource's meta.profile property.`; - return ( - - - - ) - } - > - {profile.title || profile.name} - - ); - })} - - + {currentProfile ? ( = ({ profile, resource, onReso }) .catch((err) => { setOutcome(normalizeOperationOutcome(err)); - showNotification({ color: 'red', message: normalizeErrorString(err) }); + showNotification({ color: 'red', message: normalizeErrorString(err), autoClose: false }); }); }, [medplum, profile.url, onResourceUpdated, active] diff --git a/packages/app/src/resource/useCreateResource.ts b/packages/app/src/resource/useCreateResource.ts index 4aebdabf43..6a700b5aca 100644 --- a/packages/app/src/resource/useCreateResource.ts +++ b/packages/app/src/resource/useCreateResource.ts @@ -1,4 +1,5 @@ -import { normalizeOperationOutcome } from '@medplum/core'; +import { showNotification } from '@mantine/notifications'; +import { normalizeErrorString, normalizeOperationOutcome } from '@medplum/core'; import { OperationOutcome, Resource } from '@medplum/fhirtypes'; import { useMedplum } from '@medplum/react'; import { useNavigate } from 'react-router-dom'; @@ -32,6 +33,12 @@ export function useCreateResource( if (setOutcome) { setOutcome(normalizeOperationOutcome(err)); } + showNotification({ + color: 'red', + message: normalizeErrorString(err), + autoClose: false, + styles: { description: { whiteSpace: 'pre-line' } }, + }); }); }; diff --git a/packages/core/src/default-values.test.ts b/packages/core/src/default-values.test.ts new file mode 100644 index 0000000000..d7e905058f --- /dev/null +++ b/packages/core/src/default-values.test.ts @@ -0,0 +1,406 @@ +import { + InternalSchemaElement, + InternalTypeSchema, + SliceDefinition, + SlicingRules, + loadDataType, + tryGetProfile, +} from './typeschema/types'; +import { isPopulated } from './utils'; +import { Observation, Patient, StructureDefinition } from '@medplum/fhirtypes'; +import { + applyDefaultValuesToElement, + applyDefaultValuesToResource, + applyDefaultValuesToElementWithVisitor, + getDefaultValuesForNewSliceEntry, + applyFixedOrPatternValue, +} from './default-values'; +import { HTTP_HL7_ORG } from './constants'; +import { readJson } from '@medplum/definitions'; + +function isStructureDefinition(sd: any): sd is StructureDefinition { + if (!isPopulated(sd)) { + return false; + } + return sd.resourceType === 'StructureDefinition'; +} + +function getSlicedElement( + schema: InternalTypeSchema, + slicedElementKey: string +): InternalSchemaElement & { slicing: SlicingRules } { + const slicedElement = schema.elements[slicedElementKey]; + if (!isPopulated(slicedElement)) { + fail(`Expected ${slicedElementKey} element to be defined`); + } + if (!isPopulated(slicedElement.slicing)) { + fail(`Expected slicing to exist on ${slicedElementKey} element`); + } + + return slicedElement as InternalSchemaElement & { slicing: SlicingRules }; +} +function getSlice(schema: InternalTypeSchema, slicedElementKey: string, sliceName: string): SliceDefinition { + const slicedElement = getSlicedElement(schema, slicedElementKey); + const slice = slicedElement.slicing?.slices.find((s) => s.name === sliceName); + if (!isPopulated(slice)) { + fail(`Expected ${sliceName} slice to be defined`); + } + + return slice; +} + +describe('apply default values', () => { + let USCoreStructureDefinitions: StructureDefinition[]; + beforeAll(() => { + USCoreStructureDefinitions = readJson('fhir/r4/testing/uscore-v5.0.1-structuredefinitions.json'); + }); + + function loadProfiles(profileUrls: string[]): void { + const sds: StructureDefinition[] = profileUrls + .map((profileUrl) => { + return USCoreStructureDefinitions.find((sd) => sd.url === profileUrl); + }) + .filter(isStructureDefinition); + + expect(sds.length).toEqual(profileUrls.length); + + for (const sd of sds) { + loadDataType(sd, sd?.url); + } + } + + describe('US Blood Pressure', () => { + const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-blood-pressure`; + const profileUrls = [profileUrl]; + + let schema: InternalTypeSchema; + + beforeAll(() => { + loadProfiles(profileUrls); + schema = tryGetProfile(profileUrl) as InternalTypeSchema; + if (!schema) { + fail(`Failed to load schema for ${profileUrl}`); + } + }); + + test('new Blood Pressure observation', async () => { + // casting to avoid specifying any required (according to typescript) fields + // since populating them is the point of the code being tested + const resource: Observation = { resourceType: 'Observation' } as Observation; + const withDefaults = applyDefaultValuesToResource(resource, schema); + + // fixed values in Observation.component.value[x] excluded since value[x] itself is optional (min === 0) + // In other words, { valueQuantity: {code: "mm[Hg]", system: "http://unitsofmeasure.org"} } should NOT be included + // in either component + expect(withDefaults).toEqual({ + resourceType: 'Observation', + category: [ + { + coding: [ + { + code: 'vital-signs', + system: 'http://terminology.hl7.org/CodeSystem/observation-category', + }, + ], + }, + ], + code: { + coding: [ + { + system: 'http://loinc.org', + code: '85354-9', + }, + ], + }, + component: [ + { + code: { + coding: [ + { + system: 'http://loinc.org', + code: '8480-6', + }, + ], + }, + }, + { + code: { + coding: [ + { + system: 'http://loinc.org', + code: '8462-4', + }, + ], + }, + }, + ], + }); + }); + + describe('required values within optional element', () => { + test('value for Observation.component.value[x] in systolic slice', () => { + const slice = getSlice(schema, 'component', 'systolic'); + expect(slice.elements['value[x]'].min).toEqual(0); + + const result = applyDefaultValuesToElement(Object.create(null), slice.elements, 'value[x]'); + expect(result).toEqual({ code: 'mm[Hg]', system: 'http://unitsofmeasure.org' }); + }); + + test('value for Observation.component.value[x] in systolic slice with visitor', () => { + const slice = getSlice(schema, 'component', 'systolic'); + const result = applyDefaultValuesToElementWithVisitor( + undefined, + 'Observation.component.value[x]', + slice.elements['value[x]'], + slice.elements, + schema + ); + expect(result).toEqual({ code: 'mm[Hg]', system: 'http://unitsofmeasure.org' }); + }); + }); + }); + + describe('US Core Patient', () => { + const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-patient`; + const raceExtensionUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-race`; + const ethnicityExtensionUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-ethnicity`; + const profileUrls = [ + profileUrl, + raceExtensionUrl, + ethnicityExtensionUrl, + `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-birthsex`, + `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-genderIdentity`, + ]; + + let schema: InternalTypeSchema; + + beforeAll(() => { + loadProfiles(profileUrls); + schema = tryGetProfile(profileUrl) as InternalTypeSchema; + if (!schema) { + fail(`Failed to load schema for ${profileUrl}`); + } + }); + + test('new Patient has no fixed/pattern values', async () => { + const resource: Patient = { resourceType: 'Patient' }; + const withDefaults = applyDefaultValuesToResource(resource, schema); + + expect(withDefaults).toEqual({ resourceType: 'Patient' }); + // For now, a different object is returned by design + expect(withDefaults).not.toBe(resource); + }); + + test('HomerSimpsonUSCorePatient', async () => { + const resource = getComplexUSCorePatient(); + const withDefaults = applyDefaultValuesToResource(resource, schema); + + const expected = getComplexUSCorePatient(); + + // Prepare expected value + // Expect stub values for a slice named 'text' to have been added for race and ethnicity extensions + [ethnicityExtensionUrl, raceExtensionUrl].forEach((extUrl) => { + const ext = expected.extension?.find((e) => e.url === extUrl); + if (ext?.extension === undefined) { + fail(`expected ${extUrl} extensions to exist`); + } + + const textExt = ext.extension?.find((e) => e.url === 'text'); + expect(textExt).toBeUndefined(); + ext.extension.push({ url: 'text' }); + }); + + expect(withDefaults).toEqual(expected); + }); + + describe('fixed/pattern values within non-required extension slice entry', () => { + test('new race extension entry', () => { + const sliceSchema = tryGetProfile(raceExtensionUrl) as InternalTypeSchema; + if (!sliceSchema) { + fail(`Failed to load schema for ${raceExtensionUrl}`); + } + const result = applyDefaultValuesToElement(Object.create(null), sliceSchema.elements); + expect(result).toEqual({ url: raceExtensionUrl }); + }); + + test('new race extension entry with visitor', () => { + const slicedElement = getSlicedElement(schema, 'extension'); + const slice = getSlice(schema, 'extension', 'race'); + const result = getDefaultValuesForNewSliceEntry('extension', slice, slicedElement.slicing, schema); + expect(result).toEqual({ url: raceExtensionUrl }); + }); + }); + + test('applyFixedOrPatternValue with intermediate elements undefined', () => { + const elem = schema.elements['identifier.value']; + expect(elem).toBeDefined(); + expect(elem.fixed).toBeUndefined(); + // define a fake fixed value + elem.fixed = { type: 'string', value: '42' }; + const result = applyFixedOrPatternValue({}, 'identifier.value', elem, schema.elements); + expect(result).toEqual({ identifier: [{ value: '42' }] }); + + delete elem.fixed; + expect(elem.fixed).toBeUndefined(); + }); + + test('applyFixedOrPatternValue with intermediate elements undefined', () => { + const elem = schema.elements['identifier.value']; + expect(elem).toBeDefined(); + expect(elem.fixed).toBeUndefined(); + // define a fake fixed value + elem.fixed = { type: 'string', value: '42' }; + const result = applyFixedOrPatternValue({}, 'identifier.value', elem, schema.elements); + expect(result).toEqual({ identifier: [{ value: '42' }] }); + + delete elem.fixed; + expect(elem.fixed).toBeUndefined(); + }); + + test('applyFixedOrPatternValue on choice of types', () => { + const key = 'multipleBirth[x]'; + const originalElem = schema.elements[key]; + expect(originalElem).toBeDefined(); + + schema.elements[key] = { + ...originalElem, + fixed: { + type: 'integer', + value: 2, + }, + type: [ + { + code: 'integer', + targetProfile: undefined, + profile: undefined, + }, + ], + }; + + const elem = schema.elements[key]; + const result = applyFixedOrPatternValue({}, key, elem, schema.elements); + expect(result).toEqual({ multipleBirthInteger: 2 }); + + schema.elements[key] = originalElem; + }); + + test('applyFixedOrPatternValue with non-empty array', () => { + const key = 'maritalStatus'; + const elem = schema.elements[key]; + expect(elem).toBeDefined(); + expect(elem.pattern).toBeUndefined(); + + // define a fake pattern value + elem.pattern = { + type: 'CodeableConcept', + value: { + coding: [ + { + system: 'http://terminology.hl7.org/CodeSystem/v3-MaritalStatus', + code: 'UNK', + }, + ], + }, + }; + + expect(applyFixedOrPatternValue({}, key, elem, schema.elements)).toEqual({ + maritalStatus: { coding: [{ system: 'http://terminology.hl7.org/CodeSystem/v3-MaritalStatus', code: 'UNK' }] }, + }); + + expect(applyFixedOrPatternValue({ maritalStatus: { coding: [] } }, key, elem, schema.elements)).toEqual({ + maritalStatus: { coding: [{ system: 'http://terminology.hl7.org/CodeSystem/v3-MaritalStatus', code: 'UNK' }] }, + }); + + expect(applyFixedOrPatternValue({ maritalStatus: { coding: [{}] } }, key, elem, schema.elements)).toEqual({ + maritalStatus: { coding: [{}] }, + }); + + // degenerate case; maritalStatus should NOT be an array + expect(applyFixedOrPatternValue({ maritalStatus: [] }, key, elem, schema.elements)).toEqual({ + maritalStatus: [], + }); + + // unexpected pattern value type + elem.pattern = { ...elem.pattern, value: 42 }; + expect(applyFixedOrPatternValue({}, key, elem, schema.elements)).toEqual({}); + + delete elem.pattern; + expect(elem.pattern).toBeUndefined(); + }); + }); +}); + +function getComplexUSCorePatient(): Patient { + return { + resourceType: 'Patient', + id: '123', + gender: 'male', + meta: { + versionId: '2', + lastUpdated: '2020-01-02T00:00:00.000Z', + author: { + reference: 'Practitioner/123', + }, + }, + identifier: [ + { system: 'abc', value: '123' }, + { system: 'def', value: '456' }, + ], + active: true, + birthDate: '1956-05-12', + name: [ + { + given: ['Homer'], + family: 'Simpson', + }, + ], + extension: [ + { + extension: [ + { + valueCoding: { + system: 'urn:oid:2.16.840.1.113883.6.238', + code: '2106-3', + display: 'White', + }, + url: 'ombCategory', + }, + ], + url: `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-race`, + }, + { + extension: [ + { + valueCoding: { + system: 'urn:oid:2.16.840.1.113883.6.238', + code: '2186-5', + display: 'Not Hispanic or Latino', + }, + url: 'ombCategory', + }, + ], + url: `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-ethnicity`, + }, + { + valueCode: 'M', + url: `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-birthsex`, + }, + { + valueCode: 'M', + url: `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-sex`, + }, + { + valueCodeableConcept: { + coding: [ + { + system: 'urn:oid:2.16.840.1.113762.1.4.1021.32', + code: 'M', + display: 'Male', + }, + ], + }, + url: `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-genderIdentity`, + }, + ], + }; +} diff --git a/packages/core/src/default-values.ts b/packages/core/src/default-values.ts new file mode 100644 index 0000000000..4e1a60157e --- /dev/null +++ b/packages/core/src/default-values.ts @@ -0,0 +1,439 @@ +import { Resource } from '@medplum/fhirtypes'; +import { SchemaCrawler, SchemaVisitor, VisitorSlicingRules } from './schema-crawler'; +import { SliceDefinitionWithTypes, getValueSliceName } from './typeschema/slices'; +import { InternalSchemaElement, InternalTypeSchema, SliceDefinition, SlicingRules } from './typeschema/types'; +import { capitalize, deepClone, getPathDifference, isComplexTypeCode, isEmpty, isObject, isPopulated } from './utils'; +import { ElementsContextType } from './elements-context'; + +/** + * Used when an array entry, typically an empty one, needs to be assigned + * to a given slice even though it doesn't match the slice's discriminator. + */ +const SLICE_NAME_KEY = '__sliceName'; + +/** + * Adds default values to `resource` based on the supplied `schema`. Default values includes all required fixed and pattern + * values specified on elements in the schema. If an element has a fixed/pattern value but is optional, i.e. + * `element.min === 0`, the default value is not added. + * + * @param resource - The resource to which default values should be added. + * @param schema - The schema to use for adding default values. + * @returns A clone of `resource` with default values added. + */ +export function applyDefaultValuesToResource(resource: Resource, schema: InternalTypeSchema): Resource { + const visitor = new DefaultValueVisitor(resource, resource.resourceType, 'resource'); + const crawler = new SchemaCrawler(schema, visitor); + crawler.crawlResource(); + return visitor.getDefaultValue(); +} + +/** + * Adds default values to `existingValue` for the given `key` and its children. If `key` is undefined, + * default values are added to all elements in `elements`. Default values consist of all fixed and pattern + * values defined in the relevant elements. + * @param existingValue - The + * @param elements - The elements to which default values should be added. + * @param key - (optional) The key of the element(s) for which default values should be added. Elements with nested + * keys are also included. If undefined, default values for all elements are added. + * @returns `existingValue` with default values added + */ +export function applyDefaultValuesToElement( + existingValue: object, + elements: Record, + key?: string +): object { + for (const [elementKey, element] of Object.entries(elements)) { + if (key === undefined || key === elementKey) { + applyFixedOrPatternValue(existingValue, elementKey, element, elements); + continue; + } + + const keyDifference = getPathDifference(key, elementKey); + if (keyDifference !== undefined) { + applyFixedOrPatternValue(existingValue, keyDifference, element, elements); + } + } + + return existingValue; +} + +export function applyDefaultValuesToElementWithVisitor( + existingValue: any, + path: string, + element: InternalSchemaElement, + elements: Record, + schema: InternalTypeSchema +): any { + const inputValue: object = existingValue ?? Object.create(null); + + const [parentPath, key] = splitOnceRight(path, '.'); + const parent = Object.create(null); + setValueAtKey(parent, inputValue, key, element); + + const visitor = new DefaultValueVisitor(parent, parentPath, 'element'); + const crawler = new SchemaCrawler(schema, visitor, elements); + crawler.crawlElement(element, key, parentPath); + const modifiedContainer = visitor.getDefaultValue(); + + return getValueAtKey(modifiedContainer, key, element, elements); +} + +export function getDefaultValuesForNewSliceEntry( + key: string, + slice: SliceDefinition, + slicing: SlicingRules, + schema: InternalTypeSchema +): Resource { + const visitor = new DefaultValueVisitor([{ [SLICE_NAME_KEY]: slice.name }], slice.path, 'element'); + const crawler = new SchemaCrawler(schema, visitor); + crawler.crawlSlice(key, slice, slicing); + return visitor.getDefaultValue()[0]; +} + +type ValueContext = { + type: 'resource' | 'element' | 'slice'; + path: string; + values: any[]; +}; + +class DefaultValueVisitor implements SchemaVisitor { + private rootValue: any; + + private readonly schemaStack: InternalTypeSchema[]; + private readonly valueStack: ValueContext[]; + + constructor(rootValue: any, path: string, type: ValueContext['type']) { + this.schemaStack = []; + this.valueStack = []; + + this.rootValue = deepClone(rootValue); + this.valueStack.splice(0, this.valueStack.length, { + type, + path, + values: [this.rootValue], + }); + } + + private get schema(): InternalTypeSchema { + return this.schemaStack[this.schemaStack.length - 1]; + } + + private get value(): ValueContext { + return this.valueStack[this.valueStack.length - 1]; + } + + onEnterSchema(schema: InternalTypeSchema): void { + this.schemaStack.push(schema); + } + + onExitSchema(): void { + this.schemaStack.pop(); + } + + onEnterElement(path: string, element: InternalSchemaElement, elementsContext: ElementsContextType): void { + // eld-6: Fixed value may only be specified if there is one type + // eld-7: Pattern may only be specified if there is one type + // It may be possible to optimize this by checking element.type.length > 1 and short-circuiting + + const parentValues = this.value.values; + const parentPath = this.value.path; + const key = getPathDifference(parentPath, path); + if (key === undefined) { + throw new Error(`Expected ${path} to be prefixed by ${parentPath}`); + } + const elementValues: any[] = []; + + for (const parentValue of parentValues) { + if (parentValue === undefined) { + continue; + } + + const parentArray: any[] = Array.isArray(parentValue) ? parentValue : [parentValue]; + for (const parent of parentArray) { + applyMinimums(parent, key, element, elementsContext.elements); + applyFixedOrPatternValue(parent, key, element, elementsContext.elements); + const elementValue = getValueAtKey(parent, key, element, elementsContext.elements); + if (elementValue !== undefined) { + elementValues.push(elementValue); + } + } + } + + this.valueStack.push({ + type: 'element', + path: path, + values: elementValues, + }); + } + + onExitElement(path: string, element: InternalSchemaElement, elementsContext: ElementsContextType): void { + const elementValueContext = this.valueStack.pop(); + if (!elementValueContext) { + throw new Error('Expected value context to exist when exiting element'); + } + + const key = getPathDifference(this.value.path, path); + if (key === undefined) { + throw new Error(`Expected ${path} to be prefixed by ${this.value.path}`); + } + + for (const parentValue of this.value.values) { + const elementValue = getValueAtKey(parentValue, key, element, elementsContext.elements); + + // remove empty items from arrays + if (Array.isArray(elementValue)) { + for (let i = elementValue.length - 1; i >= 0; i--) { + const value = elementValue[i]; + if (!isPopulated(value)) { + elementValue.splice(i, 1); + } + } + } + + if (isEmpty(elementValue)) { + // setting undefined to delete the key + setValueAtKey(parentValue, undefined, key, element); + } + } + } + + onEnterSlice(path: string, slice: SliceDefinitionWithTypes, slicing: VisitorSlicingRules): void { + const elementValues = this.value.values; + const sliceValues: any[] = []; + + for (const elementValue of elementValues) { + if (elementValue === undefined) { + continue; + } + + if (!Array.isArray(elementValue)) { + throw new Error('Expected array value for sliced element'); + } + + const matchingItems: any[] = this.getMatchingSliceValues(elementValue, slice, slicing); + sliceValues.push(matchingItems); + } + + this.valueStack.push({ + type: 'slice', + path, + values: sliceValues, + }); + } + + getMatchingSliceValues(elementValue: any[], slice: SliceDefinitionWithTypes, slicing: VisitorSlicingRules): any[] { + const matchingItems: any[] = []; + for (const arrayItem of elementValue) { + const sliceName: string | undefined = + arrayItem[SLICE_NAME_KEY] ?? getValueSliceName(arrayItem, [slice], slicing.discriminator, this.schema.url); + + if (sliceName === slice.name) { + matchingItems.push(arrayItem); + } + } + + // Make sure at least slice.min values exist + for (let i = matchingItems.length; i < slice.min; i++) { + if (isComplexTypeCode(slice.type[0].code)) { + const emptySliceValue = Object.create(null); + matchingItems.push(emptySliceValue); + + // push onto input array so that it propagates upwards as well + elementValue.push(emptySliceValue); + } + } + + return matchingItems; + } + + onExitSlice(): void { + const sliceValuesContext = this.valueStack.pop(); + if (!sliceValuesContext) { + throw new Error('Expected value context to exist in onExitSlice'); + } + + for (const sliceValueArray of sliceValuesContext.values) { + for (let i = sliceValueArray.length - 1; i >= 0; i--) { + const sliceValue = sliceValueArray[i]; + if (SLICE_NAME_KEY in sliceValue) { + delete sliceValue[SLICE_NAME_KEY]; + } + } + } + } + + getDefaultValue(): any { + return this.rootValue; + } +} + +function applyMinimums( + parent: any, + key: string, + element: InternalSchemaElement, + elements: Record +): void { + const existingValue = getValueAtKey(parent, key, element, elements); + + if (element.min > 0 && existingValue === undefined) { + if (isComplexTypeCode(element.type[0].code)) { + if (element.isArray) { + setValueAtKey(parent, [Object.create(null)], key, element); + } else { + setValueAtKey(parent, Object.create(null), key, element); + } + } + } +} + +function setValueAtKey(parent: any, value: any, key: string, element: InternalSchemaElement): void { + if (key.includes('.')) { + throw new Error('key cannot be nested'); + } + + let resolvedKey = key; + + if (key.includes('[x]')) { + const code = element.type[0].code; + resolvedKey = key.replace('[x]', capitalize(code)); + } + + if (value === undefined) { + delete parent[resolvedKey]; + } else { + parent[resolvedKey] = value; + } +} + +function getValueAtKey( + value: object, + key: string, + element: InternalSchemaElement, + elements: Record +): any { + const keyParts = key.split('.'); + let last: any = value; + let answer: any; + for (let i = 0; i < keyParts.length; i++) { + let keyPart = keyParts[i]; + if (keyPart.includes('[x]')) { + const keyPartElem = elements[keyParts.slice(0, i + 1).join('.')]; + // should this loop through all possible types instead of using type[0]? + const code = keyPartElem.type[0].code; + keyPart = keyPart.replace('[x]', capitalize(code)); + } + + // final key part + if (i === keyParts.length - 1) { + if (Array.isArray(last)) { + answer = last.map((item) => item[keyPart]); + } else { + answer = last[keyPart]; + } + continue; + } + + // intermediate key part + if (Array.isArray(last)) { + last = last.map((lastItem) => lastItem[keyPart]); + } else if (isObject(last)) { + if (last[keyPart] === undefined) { + return undefined; + } + last = last[keyPart]; + } else { + return undefined; + } + } + + return answer; +} + +export function applyFixedOrPatternValue( + inputValue: any, + key: string, + element: InternalSchemaElement, + elements: Record +): any { + if (!(element.fixed || element.pattern)) { + return inputValue; + } + + if (Array.isArray(inputValue)) { + return inputValue.map((iv) => applyFixedOrPatternValue(iv, key, element, elements)); + } + + if (inputValue === undefined || inputValue === null) { + inputValue = Object.create(null); + } + + const outputValue = inputValue; + + const keyParts = key.split('.'); + let last: any = outputValue; + for (let i = 0; i < keyParts.length; i++) { + let keyPart = keyParts[i]; + if (keyPart.includes('[x]')) { + const keyPartElem = elements[keyParts.slice(0, i + 1).join('.')]; + const code = keyPartElem.type[0].code; + keyPart = keyPart.replace('[x]', capitalize(code)); + } + + if (i === keyParts.length - 1) { + const lastArray = Array.isArray(last) ? last : [last]; + for (const item of lastArray) { + if (element.fixed) { + item[keyPart] ??= element.fixed.value; + } else if (element.pattern) { + item[keyPart] = applyPattern(item[keyPart], element.pattern.value); + } + } + } else { + if (!(keyPart in last)) { + const elementKey = keyParts.slice(0, i + 1).join('.'); + last[keyPart] = elements[elementKey].isArray ? [Object.create(null)] : Object.create(null); + } + last = last[keyPart]; + } + } + return outputValue; +} + +function applyPattern(existingValue: any, pattern: any): any { + if (Array.isArray(pattern) && (Array.isArray(existingValue) || existingValue === undefined)) { + if ((existingValue?.length ?? 0) > 0) { + // Cannot yet apply a pattern to a non-empty array since that would require considering cardinality and slicing + return existingValue; + } + return deepClone(pattern); + } else if (isObject(pattern)) { + if ((isObject(existingValue) && !Array.isArray(existingValue)) || existingValue === undefined) { + const resultObj = (deepClone(existingValue) ?? Object.create(null)) as { [key: string]: any }; + for (const key of Object.keys(pattern)) { + resultObj[key] = applyPattern(resultObj[key], pattern[key]); + } + return resultObj; + } + } + + return existingValue; +} + +/** + * Splits a string on the last occurrence of the delimiter + * @param str - The string to split + * @param delim - The delimiter string + * @returns An array of two strings; the first consisting of the beginning of the + * string up to the last occurrence of the delimiter. the second is the remainder of the + * string after the last occurrence of the delimiter. If the delimiter is not present + * in the string, the first element is empty and the second is the input string. + */ +function splitOnceRight(str: string, delim: string): [string, string] { + const delimIndex = str.lastIndexOf(delim); + if (delimIndex === -1) { + return ['', str]; + } + const beginning = str.substring(0, delimIndex); + const last = str.substring(delimIndex + delim.length); + return [beginning, last]; +} diff --git a/packages/core/src/elements-context.test.ts b/packages/core/src/elements-context.test.ts new file mode 100644 index 0000000000..b2e346b6e0 --- /dev/null +++ b/packages/core/src/elements-context.test.ts @@ -0,0 +1,112 @@ +import { buildElementsContext } from './elements-context'; +import { HTTP_HL7_ORG } from './constants'; +import { isPopulated } from './utils'; +import { InternalTypeSchema, parseStructureDefinition } from './typeschema/types'; +import { StructureDefinition } from '@medplum/fhirtypes'; +import { readJson } from '@medplum/definitions'; + +describe('buildElementsContext', () => { + let USCoreStructureDefinitions: StructureDefinition[]; + + function getSchemaFromProfileUrl(url: string): InternalTypeSchema { + const sd = USCoreStructureDefinitions.find((sd) => sd.url === url); + if (!isPopulated(sd)) { + fail(`Expected structure definition for ${url} to be found`); + } + return parseStructureDefinition(sd); + } + beforeAll(() => { + USCoreStructureDefinitions = readJson('fhir/r4/testing/uscore-v5.0.1-structuredefinitions.json'); + }); + + test('deeply nested schema', () => { + const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-medicationrequest`; + const schema = getSchemaFromProfileUrl(profileUrl); + + const context = buildElementsContext({ + elements: schema.elements, + path: 'MedicationRequest', + parentContext: undefined, + profileUrl, + }); + + if (context === undefined) { + fail('Expected context to be defined'); + } + + expect(context.profileUrl).toEqual(profileUrl); + expect(context.elements['dosageInstruction.method']).toBeDefined(); + expect(context.elementsByPath['MedicationRequest.dosageInstruction.method']).toBeDefined(); + expect(context.elements['dosageInstruction.method']).toBe( + context.elementsByPath['MedicationRequest.dosageInstruction.method'] + ); + }); + + test('building context at same path returns undefined', () => { + const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-patient`; + const schema = getSchemaFromProfileUrl(profileUrl); + const context = buildElementsContext({ + elements: schema.elements, + path: 'Patient', + parentContext: undefined, + profileUrl, + }); + + if (context === undefined) { + fail('Expected context to be defined'); + } + + const samePath = buildElementsContext({ + elements: schema.elements, + path: 'Patient', + parentContext: context, + profileUrl, + }); + + expect(samePath).toBeUndefined(); + }); + + test('nested context', () => { + const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-patient`; + const profileSchema = getSchemaFromProfileUrl(profileUrl); + + const context = buildElementsContext({ + elements: profileSchema.elements, + path: 'Patient', + parentContext: undefined, + profileUrl, + }); + + if (context === undefined) { + fail('Expected context to be defined'); + } + + const extensionUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-race`; + const extensionSchema = getSchemaFromProfileUrl(extensionUrl); + + const extensionContext = buildElementsContext({ + elements: extensionSchema.elements, + path: 'Patient.extension', + parentContext: context, + profileUrl: extensionUrl, + debugMode: true, + }); + + if (extensionContext === undefined) { + fail('Expected extension context to be defined'); + } + + expect(extensionContext.profileUrl).toEqual(extensionUrl); + expect(Object.keys(extensionContext.elements)).toEqual( + expect.arrayContaining(['extension', 'id', 'url', 'value[x]']) + ); + + expect(extensionContext.elements['extension'].slicing?.slices.length).toBe(3); + expect(extensionContext.elements['extension']).toBe(extensionContext.elementsByPath['Patient.extension.extension']); + + expect(extensionContext.elements['url'].fixed).toEqual({ + type: 'uri', + value: 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-race', + }); + }); +}); diff --git a/packages/core/src/elements-context.ts b/packages/core/src/elements-context.ts new file mode 100644 index 0000000000..e94bd0f601 --- /dev/null +++ b/packages/core/src/elements-context.ts @@ -0,0 +1,109 @@ +import { InternalSchemaElement } from './typeschema/types'; +import { getPathDifference } from './utils'; + +/** + * Information for the set of elements at a given path within in a resource. This mostly exists to + * normalize access to elements regardless of whether they are from a profile, extension, or slice. + */ +export type ElementsContextType = { + /** The FHIR path from the root resource to which the keys of `elements` are relative. */ + path: string; + /** + * The mapping of keys to `InternalSchemaElement` at the current `path` relative to the + * root resource. `elements` originate from either `InternalTypeSchema.elements` or + * `SliceDefinition.elements` when the elements context is created within a slice. + */ + elements: Record; + /** + * Similar mapping as `elements`, but with keys being the full path from the root resource rather + * than relative to `path`, in other words, the keys of the Record are `${path}.${key}`. + */ + elementsByPath: Record; + /** The URL, if any, of the resource profile or extension from which the `elements` collection originated. */ + profileUrl: string | undefined; + /** Whether debug logging is enabled */ + debugMode: boolean; +}; + +export function buildElementsContext({ + parentContext, + path, + elements, + profileUrl, + debugMode, +}: { + /** The most recent `ElementsContextType` in which this context is being built. */ + parentContext: ElementsContextType | undefined; + /** The FHIR path from the root resource to which the keys of `elements` are relative. */ + path: string; + /** + * The mapping of keys to `InternalSchemaElement` at the current `path` relative to the + * root resource. This should be either `InternalTypeSchema.elements` or `SliceDefinition.elements`. + */ + elements: Record; + /** The URL, if any, of the resource profile or extension from which the `elements` collection originated. */ + profileUrl?: string; + /** Whether debug logging is enabled */ + debugMode?: boolean; +}): ElementsContextType | undefined { + if (path === parentContext?.path) { + return undefined; + } + + const mergedElements: ElementsContextType['elements'] = mergeElementsForContext( + path, + elements, + parentContext, + Boolean(debugMode) + ); + const elementsByPath: Record = Object.create(null); + + for (const [key, property] of Object.entries(mergedElements)) { + elementsByPath[path + '.' + key] = property; + } + + return { + path: path, + elements: mergedElements, + elementsByPath, + profileUrl: profileUrl ?? parentContext?.profileUrl, + debugMode: debugMode ?? parentContext?.debugMode ?? false, + }; +} + +function mergeElementsForContext( + path: string, + elements: Record, + parentContext: ElementsContextType | undefined, + debugMode: boolean +): Record { + const result: Record = Object.create(null); + + if (parentContext) { + for (const [elementPath, element] of Object.entries(parentContext.elementsByPath)) { + const key = getPathDifference(path, elementPath); + if (key !== undefined) { + result[key] = element; + } + } + } + + let usedNewElements = false; + if (elements) { + for (const [key, element] of Object.entries(elements)) { + if (!(key in result)) { + result[key] = element; + usedNewElements = true; + } + } + } + + // if no new elements are used, the ElementsContext is unnecessary. + // We could add another guard against unnecessary contexts if usedNewElements is false, + // but unnecessary contexts **should** already be taken care before + // this is ever hit. Leaving the debug logging in for now. + if (debugMode) { + console.assert(usedNewElements, 'Unnecessary ElementsContext; not using any newly provided elements'); + } + return result; +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 5e2af92de6..cdd5970587 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -9,6 +9,8 @@ export * from './config'; export * from './constants'; export * from './contenttype'; export * from './crypto'; +export * from './default-values'; +export * from './elements-context'; export * from './eventtarget'; export * from './fhircast'; export * from './fhirlexer/parse'; @@ -38,4 +40,5 @@ export * from './types'; export * from './typeschema/crawler'; export * from './typeschema/types'; export * from './typeschema/validation'; +export * from './typeschema/slices'; export * from './utils'; diff --git a/packages/core/src/schema-crawler.ts b/packages/core/src/schema-crawler.ts new file mode 100644 index 0000000000..d41c77fdc5 --- /dev/null +++ b/packages/core/src/schema-crawler.ts @@ -0,0 +1,343 @@ +import { ElementsContextType, buildElementsContext } from './elements-context'; +import { SliceDefinitionWithTypes, isSliceDefinitionWithTypes } from './typeschema/slices'; +import { + InternalSchemaElement, + InternalTypeSchema, + SliceDefinition, + SlicingRules, + tryGetProfile, +} from './typeschema/types'; +import { isPopulated } from './utils'; + +export type VisitorSlicingRules = Omit & { + slices: SliceDefinitionWithTypes[]; +}; + +export interface SchemaVisitor { + /** + * Called when entering a schema. This is called once for the root profile and once for each + * extension with a profile associated with it. + * @param schema - The schema being entered. + */ + onEnterSchema?: (schema: InternalTypeSchema) => void; + /** + * Called when exiting a schema. See `onEnterSchema` for more information. + * @param schema - The schema being exited. + */ + onExitSchema?: (schema: InternalTypeSchema) => void; + + /** + * Called when entering an element. This is called for every element in the schema in a + * tree-like fashion. If the element has slices, the slices are crawled after `onEnterElement` + * but before `onExitElement`. + * + * @example + * Example of tree-like method invocation ordering: + * '''typescript + * onEnterElement('Patient.name') + * onEnterElement('Patient.name.given') + * onExitElement('Patient.name.given') + * onEnterElement('Patient.name.family') + * onExitElement('Patient.name.family') + * onExitElement('Patient.name') + * ''' + * + * + * @param path - The full path of the element being entered, even if within an extension. e.g The + * path of the ombCategory extension within the US Core Race extension will be + * 'Patient.extension.extension.value[x]' rather than 'Extension.extension.value[x]'. The latter is + * accessible on the element parameter. + * @param element - The element being entered. + * @param elementsContext - The context of the elements currently being crawled. + */ + onEnterElement?: (path: string, element: InternalSchemaElement, elementsContext: ElementsContextType) => void; + + /** + * Called when exiting an element. See `onEnterElement` for more information. + * @param path - The full path of the element being exited. + * @param element - The element being exited. + * @param elementsContext - The context of the elements currently being crawled. + */ + onExitElement?: (path: string, element: InternalSchemaElement, elementsContext: ElementsContextType) => void; + + /** + * Called when entering a slice. Called for every slice in a given sliced element. `onEnterElement` and `onExitElement` + * will be called in a tree-like fashion for elements within the slice followed by `onExitSlice`. + * + * @example + * Example of a sliced element being crawled with some elements excluded for brevity: + * '''typescript + * onEnterElement ('Observation.component') + * + * // systolic + * onEnterSlice ('Observation.component', systolicSlice, slicingRules) + * onEnterElement ('Observation.component.code') + * onExitElement ('Observation.component.code') + * onEnterElement ('Observation.component.value[x]') + * onEnterElement ('Observation.component.value[x].code') + * onExitElement ('Observation.component.value[x].code') + * onEnterElement ('Observation.component.value[x].system') + * onExitElement ('Observation.component.value[x].system') + * onExitElement ('Observation.component.value[x]') + * onExitSlice ('Observation.component', systolicSlice, slicingRules) + * + * // similar set of invocations for diastolic slice + * + * onExitElement ('Observation.component') + * ''' + * + * @param path - The full path of the sliced element being entered. See `onEnterElement` for more information. + * @param slice - The slice being entered. + * @param slicing - The slicing rules related to the slice being entered. + */ + onEnterSlice?: (path: string, slice: SliceDefinitionWithTypes, slicing: VisitorSlicingRules) => void; + + /** + * Called when exiting a slice. See `onEnterSlice` for more information. + * @param path - The full path of the sliced element being exited. See `onEnterElement` for more information. + * @param slice - The slice being exited. + * @param slicing - The slicing rules related to the slice. + */ + onExitSlice?: (path: string, slice: SliceDefinitionWithTypes, slicing: VisitorSlicingRules) => void; +} + +export class SchemaCrawler { + private readonly rootSchema: InternalTypeSchema & { type: string }; + private readonly visitor: SchemaVisitor; + private readonly elementsContextStack: ElementsContextType[]; + private sliceAllowList: SliceDefinition[] | undefined; + + constructor(schema: InternalTypeSchema, visitor: SchemaVisitor, elements?: InternalTypeSchema['elements']) { + if (schema.type === undefined) { + throw new Error('schema must include a type'); + } + this.rootSchema = schema as InternalTypeSchema & { type: string }; + + const rootContext = buildElementsContext({ + parentContext: undefined, + path: this.rootSchema.type, + elements: elements ?? this.rootSchema.elements, + profileUrl: this.rootSchema.name === this.rootSchema.type ? undefined : this.rootSchema.url, + }); + if (rootContext === undefined) { + throw new Error('Could not create root elements context'); + } + + this.elementsContextStack = [rootContext]; + this.visitor = visitor; + } + + private get elementsContext(): ElementsContextType { + return this.elementsContextStack[this.elementsContextStack.length - 1]; + } + + crawlElement(element: InternalSchemaElement, key: string, path: string): void { + if (this.visitor.onEnterSchema) { + this.visitor.onEnterSchema(this.rootSchema); + } + + const allowedElements = Object.fromEntries( + Object.entries(this.elementsContext.elements).filter(([elementKey]) => { + return elementKey.startsWith(key); + }) + ); + + this.crawlElementsImpl(allowedElements, path); + + if (this.visitor.onExitSchema) { + this.visitor.onExitSchema(this.rootSchema); + } + } + + crawlSlice(key: string, slice: SliceDefinition, slicing: SlicingRules): void { + const visitorSlicing = this.prepareSlices(slicing.slices, slicing); + + if (!isPopulated(visitorSlicing.slices)) { + throw new Error(`cannot crawl slice ${slice.name} since it has no type information`); + } + + if (this.visitor.onEnterSchema) { + this.visitor.onEnterSchema(this.rootSchema); + } + + this.sliceAllowList = [slice]; + + this.crawlSliceImpl(visitorSlicing.slices[0], slice.path, visitorSlicing); + this.sliceAllowList = undefined; + + if (this.visitor.onExitSchema) { + this.visitor.onExitSchema(this.rootSchema); + } + } + + crawlResource(): void { + if (this.visitor.onEnterSchema) { + this.visitor.onEnterSchema(this.rootSchema); + } + + this.crawlElementsImpl(this.rootSchema.elements, this.rootSchema.type); + + if (this.visitor.onExitSchema) { + this.visitor.onExitSchema(this.rootSchema); + } + } + + private crawlElementsImpl(elements: InternalTypeSchema['elements'], path: string): void { + const elementTree = createElementTree(elements); + for (const node of elementTree) { + this.crawlElementNode(node, path); + } + } + + private crawlElementNode(node: ElementNode, path: string): void { + const nodePath = path + '.' + node.key; + if (this.visitor.onEnterElement) { + this.visitor.onEnterElement(nodePath, node.element, this.elementsContext); + } + + for (const child of node.children) { + this.crawlElementNode(child, path); + } + + if (isPopulated(node.element?.slicing?.slices)) { + this.crawlSlicingImpl(node.element.slicing, nodePath); + } + + if (this.visitor.onExitElement) { + this.visitor.onExitElement(nodePath, node.element, this.elementsContext); + } + } + + private prepareSlices(slices: SliceDefinition[], slicing: SlicingRules): VisitorSlicingRules { + const slicesToVisit: SliceDefinitionWithTypes[] = []; + for (const slice of slices) { + if (!isSliceDefinitionWithTypes(slice)) { + continue; + } + const profileUrl = slice.type.find((t) => isPopulated(t.profile))?.profile?.[0]; + if (isPopulated(profileUrl)) { + const schema = tryGetProfile(profileUrl); + if (schema) { + slice.typeSchema = schema; + } + } + slicesToVisit.push(slice); + } + + const visitorSlicing = { ...slicing, slices: slicesToVisit } as VisitorSlicingRules; + return visitorSlicing; + } + + private crawlSlicingImpl(slicing: SlicingRules, path: string): void { + const visitorSlicing = this.prepareSlices(slicing.slices, slicing); + + for (const slice of visitorSlicing.slices) { + if (this.sliceAllowList === undefined || this.sliceAllowList.includes(slice)) { + this.crawlSliceImpl(slice, path, visitorSlicing); + } + } + } + + private crawlSliceImpl(slice: SliceDefinitionWithTypes, path: string, slicing: VisitorSlicingRules): void { + const sliceSchema = slice.typeSchema; + if (sliceSchema) { + if (this.visitor.onEnterSchema) { + this.visitor.onEnterSchema(sliceSchema); + } + } + + if (this.visitor.onEnterSlice) { + this.visitor.onEnterSlice(path, slice, slicing); + } + + let elementsContext: ElementsContextType | undefined; + + const sliceElements = sliceSchema?.elements ?? slice.elements; + if (isPopulated(sliceElements)) { + elementsContext = buildElementsContext({ + path, + parentContext: this.elementsContext, + elements: sliceElements, + }); + } + if (elementsContext) { + this.elementsContextStack.push(elementsContext); + } + + this.crawlElementsImpl(sliceElements, path); + + if (elementsContext) { + this.elementsContextStack.pop(); + } + + if (this.visitor.onExitSlice) { + this.visitor.onExitSlice(path, slice, slicing); + } + + if (sliceSchema) { + if (this.visitor.onExitSchema) { + this.visitor.onExitSchema(sliceSchema); + } + } + } +} + +type ElementNode = { + key: string; + element: InternalSchemaElement; + children: ElementNode[]; +}; + +/** + * Creates a tree of InternalSchemaElements nested by their key hierarchy: + * + * @param elements - + * @returns The list of root nodes of the tree + */ +function createElementTree(elements: Record): ElementNode[] { + const rootNodes: ElementNode[] = []; + + function isChildKey(parentKey: string, childKey: string): boolean { + return childKey.startsWith(parentKey + '.'); + } + + function addNode(currentNode: ElementNode, newNode: ElementNode): void { + for (const child of currentNode.children) { + // If the new node is a child of an existing child, recurse deeper + if (isChildKey(child.key, newNode.key)) { + addNode(child, newNode); + return; + } + } + // Otherwise, add it here + currentNode.children.push(newNode); + } + + const elementEntries = Object.entries(elements); + /* + By sorting beforehand, we guarantee that no false root nodes are created. + e.g. if 'a.b' were to be added to the tree before 'a', 'a.b' would be made a + root node when it should be a child of 'a'. + */ + elementEntries.sort((a, b) => a[0].localeCompare(b[0])); + + for (const [key, element] of elementEntries) { + const newNode: ElementNode = { key, element, children: [] }; + + let added = false; + for (const rootNode of rootNodes) { + if (isChildKey(rootNode.key, key)) { + addNode(rootNode, newNode); + added = true; + break; + } + } + + // If the string is not a child of any existing node, add it as a new root + if (!added) { + rootNodes.push(newNode); + } + } + + return rootNodes; +} diff --git a/packages/core/src/typeschema/slices.ts b/packages/core/src/typeschema/slices.ts new file mode 100644 index 0000000000..f5647258e2 --- /dev/null +++ b/packages/core/src/typeschema/slices.ts @@ -0,0 +1,56 @@ +import { TypedValue } from '../types'; +import { getNestedProperty } from './crawler'; +import { InternalTypeSchema, SliceDefinition, SliceDiscriminator } from './types'; +import { matchDiscriminant } from './validation'; + +export type SliceDefinitionWithTypes = SliceDefinition & { + type: NonNullable; + typeSchema?: InternalTypeSchema; +}; + +export function isSliceDefinitionWithTypes(slice: SliceDefinition): slice is SliceDefinitionWithTypes { + return slice.type !== undefined && slice.type.length > 0; +} + +function isDiscriminatorComponentMatch( + typedValue: TypedValue, + discriminator: SliceDiscriminator, + slice: SliceDefinitionWithTypes, + profileUrl: string | undefined +): boolean { + const nestedProp = getNestedProperty(typedValue, discriminator.path, { profileUrl }); + + if (nestedProp) { + const elements = slice.typeSchema?.elements ?? slice.elements; + return nestedProp.some((v: any) => matchDiscriminant(v, discriminator, slice, elements)) ?? false; + } + + console.assert(false, 'getNestedProperty[%s] in isDiscriminatorComponentMatch missed', discriminator.path); + return false; +} + +export function getValueSliceName( + value: any, + slices: SliceDefinitionWithTypes[], + discriminators: SliceDiscriminator[], + profileUrl: string | undefined +): string | undefined { + if (!value) { + return undefined; + } + + for (const slice of slices) { + const typedValue: TypedValue = { + value, + type: slice.typeSchema?.name ?? slice.type?.[0].code, + }; + if ( + discriminators.every((d) => + isDiscriminatorComponentMatch(typedValue, d, slice, slice.typeSchema?.url ?? profileUrl) + ) + ) { + return slice.name; + } + } + return undefined; +} diff --git a/packages/core/src/utils.test.ts b/packages/core/src/utils.test.ts index b92973e430..bf8cea045c 100644 --- a/packages/core/src/utils.test.ts +++ b/packages/core/src/utils.test.ts @@ -32,8 +32,10 @@ import { getExtensionValue, getIdentifier, getImageSrc, + getPathDifference, getQuestionnaireAnswers, getReferenceString, + isComplexTypeCode, isEmpty, isLowerCase, isPopulated, @@ -53,6 +55,7 @@ import { splitN, stringify, } from './utils'; +import { PropertyType } from './types'; if (typeof btoa === 'undefined') { global.btoa = function (str) { @@ -635,6 +638,8 @@ describe('Core Utils', () => { const output = deepClone(input); expect(output).toEqual(input); expect(output).not.toBe(input); + + expect(deepClone(undefined)).toBeUndefined(); }); test('Capitalize', () => { @@ -652,6 +657,25 @@ describe('Core Utils', () => { expect(isLowerCase('3')).toEqual(false); }); + test('isComplexTypeCode', () => { + expect(isComplexTypeCode('url')).toEqual(false); + expect(isComplexTypeCode(PropertyType.SystemString)).toEqual(false); + expect(isComplexTypeCode('')).toEqual(false); + }); + + test('getPathDifference', () => { + expect(getPathDifference('a', 'b')).toEqual(undefined); + expect(getPathDifference('a', 'a')).toEqual(undefined); + expect(getPathDifference('A', 'A')).toEqual(undefined); + expect(getPathDifference('a.b', 'a')).toEqual(undefined); + + expect(getPathDifference('a', 'a.b')).toEqual('b'); + expect(getPathDifference('A.b', 'A.b.c.d')).toEqual('c.d'); + expect(getPathDifference('Patient.extension', 'Patient.extension.extension.value[x]')).toEqual( + 'extension.value[x]' + ); + }); + test('isUUID', () => { expect(isUUID('')).toBe(false); expect(isUUID('asdf')).toBe(false); diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index c55afdb9a7..c4fb00e7d2 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -606,7 +606,7 @@ function deepIncludesObject(value: { [key: string]: unknown }, pattern: { [key: * @returns A deep clone of the input. */ export function deepClone(input: T): T { - return JSON.parse(JSON.stringify(input)) as T; + return input === undefined ? input : (JSON.parse(JSON.stringify(input)) as T); } /** @@ -683,6 +683,24 @@ export function isLowerCase(c: string): boolean { return c === c.toLowerCase() && c !== c.toUpperCase(); } +export function isComplexTypeCode(code: string): boolean { + return code.length > 0 && code.startsWith(code[0].toUpperCase()); +} + +/** + * Returns the difference between two paths which is often suitable to use as a key in a `Record` + * @param parentPath - The parent path that will be removed from `path`. + * @param path - The element path that should be a child of `parentPath`. + * @returns - The difference between `path` and `parentPath` or `undefined` if `path` is not a child of `parentPath`. + */ +export function getPathDifference(parentPath: string, path: string): string | undefined { + const parentPathPrefix = parentPath + '.'; + if (path.startsWith(parentPathPrefix)) { + return path.slice(parentPathPrefix.length); + } + return undefined; +} + /** * Tries to find a code string for a given system within a given codeable concept. * @param concept - The codeable concept. diff --git a/packages/core/test.setup.cjs b/packages/core/test.setup.cjs index 7d6793ba41..3b0e32dce9 100644 --- a/packages/core/test.setup.cjs +++ b/packages/core/test.setup.cjs @@ -1,6 +1,6 @@ /* globals module require globalThis */ // eslint-disable-next-line @typescript-eslint/no-var-requires -const { MemoryStorage } = require('@medplum/core'); +const { MemoryStorage } = require('./src/storage'); module.exports = () => { Object.defineProperty(globalThis.window, 'sessionStorage', { value: new MemoryStorage() }); diff --git a/packages/definitions/dist/fhir/r4/testing/uscore-v5.0.1-structuredefinitions.json b/packages/definitions/dist/fhir/r4/testing/uscore-v5.0.1-structuredefinitions.json new file mode 100644 index 0000000000..41e4cba781 --- /dev/null +++ b/packages/definitions/dist/fhir/r4/testing/uscore-v5.0.1-structuredefinitions.json @@ -0,0 +1 @@ +[{"resourceType":"StructureDefinition","id":"us-core-patient","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient","version":"5.0.1","name":"USCorePatientProfile","title":"US Core Patient Profile","status":"active","experimental":false,"date":"2022-04-20T15:02:49-07:00","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"The US Core Patient Profile meets the U.S. Core Data for Interoperability (USCDI) v2 'Patient Demographics' requirements. This profile sets minimum expectations for the Patient resource to record, search, and fetch basic demographics and other administrative information about an individual patient. It identifies which core elements, extensions, vocabularies and value sets **SHALL** be present in the resource when using this profile to promote interoperability and adoption through common implementation. It identifies which core elements, extensions, vocabularies and value sets **SHALL** be present in the resource when using this profile. It provides the floor for standards development for specific uses cases.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"resource","abstract":false,"type":"Patient","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Patient","derivation":"constraint","snapshot":{"element":[{"id":"Patient","path":"Patient","short":"Information about an individual or animal receiving health care services","definition":"\\-","comment":"\\-","alias":["SubjectOfCare Client Resident"],"min":0,"max":"*","base":{"path":"Patient","min":0,"max":"*"},"constraint":[{"key":"dom-2","severity":"error","human":"If the resource is contained in another resource, it SHALL NOT contain nested Resources","expression":"contained.contained.empty()","xpath":"not(parent::f:contained and f:contained)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-3","severity":"error","human":"If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource","expression":"contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()","xpath":"not(exists(for $id in f:contained/*/f:id/@value return $contained[not(parent::*/descendant::f:reference/@value=concat('#', $contained/*/id/@value) or descendant::f:reference[@value='#'])]))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-4","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated","expression":"contained.meta.versionId.empty() and contained.meta.lastUpdated.empty()","xpath":"not(exists(f:contained/*/f:meta/f:versionId)) and not(exists(f:contained/*/f:meta/f:lastUpdated))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-5","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a security label","expression":"contained.meta.security.empty()","xpath":"not(exists(f:contained/*/f:meta/f:security))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice","valueBoolean":true},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice-explanation","valueMarkdown":"When a resource has no narrative, only systems that fully understand the data can display the resource to a human safely. Including a human readable representation in the resource makes for a much more robust eco-system and cheaper handling of resources by intermediary systems. Some ecosystems restrict distribution of resources to only those systems that do fully understand the resources, and as a consequence implementers may believe that the narrative is superfluous. However experience shows that such eco-systems often open up to new participants over time."}],"key":"dom-6","severity":"warning","human":"A resource should have narrative for robust management","expression":"text.`div`.exists()","xpath":"exists(f:text/h:div)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"us-core-6","severity":"error","human":"Either Patient.name.given and/or Patient.name.family SHALL be present or a Data Absent Reason Extension SHALL be present.","expression":"(name.family.exists() or name.given.exists()) xor extension.where(url='http://hl7.org/fhir/StructureDefinition/data-absent-reason').exists()","xpath":"(/f:name/f:extension/@url='http://hl7.org/fhir/StructureDefinition/data-absent-reason' and not(/f:name/f:family or /f:name/f:given)) or (not(/f:name/f:extension/@url='http://hl7.org/fhir/StructureDefinition/data-absent-reason') and (/f:name/f:family or /f:name/f:given))"}],"mustSupport":false,"isModifier":false,"isSummary":false},{"id":"Patient.id","path":"Patient.id","short":"Logical id of this artifact","definition":"The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.","comment":"The only time that a resource does not have an id is when it is being submitted to the server using a create operation.","min":0,"max":"1","base":{"path":"Resource.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":true},{"id":"Patient.meta","path":"Patient.meta","short":"Metadata about the resource","definition":"The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource.","min":0,"max":"1","base":{"path":"Resource.meta","min":0,"max":"1"},"type":[{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.implicitRules","path":"Patient.implicitRules","short":"A set of rules under which this content was created","definition":"A reference to a set of rules that were followed when the resource was constructed, and which must be understood when processing the content. Often, this is a reference to an implementation guide that defines the special rules along with other profiles etc.","comment":"Asserting this rule set restricts the content to be only understood by a limited set of trading partners. This inherently limits the usefulness of the data in the long term. However, the existing health eco-system is highly fractured, and not yet ready to define, collect, and exchange data in a generally computable sense. Wherever possible, implementers and/or specification writers should avoid using this element. Often, when used, the URL is a reference to an implementation guide that defines these special rules as part of it's narrative along with other profiles, value sets, etc.","min":0,"max":"1","base":{"path":"Resource.implicitRules","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because the implicit rules may provide additional knowledge about the resource that modifies it's meaning or interpretation","isSummary":true},{"id":"Patient.language","path":"Patient.language","short":"Language of the resource content","definition":"The base language in which the resource is written.","comment":"Language is provided to support indexing and accessibility (typically, services such as text to speech use the language tag). The html language tag in the narrative applies to the narrative. The language tag on the resource may be used to specify the language of other presentations generated from the data in the resource. Not all the content has to be in the base language. The Resource.language should not be assumed to apply to the narrative automatically. If a language is specified, it should it also be specified on the div element in the html (see rules in HTML5 for information about the relationship between xml:lang and the html lang attribute).","min":0,"max":"1","base":{"path":"Resource.language","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet","valueCanonical":"http://hl7.org/fhir/ValueSet/all-languages"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"Language"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"preferred","description":"A human language.","valueSet":"http://hl7.org/fhir/ValueSet/languages"}},{"id":"Patient.text","path":"Patient.text","short":"Text summary of the resource, for human interpretation","definition":"A human-readable narrative that contains a summary of the resource and can be used to represent the content of the resource to a human. The narrative need not encode all the structured data, but is required to contain sufficient detail to make it \"clinically safe\" for a human to just read the narrative. Resource definitions may define what content should be represented in the narrative to ensure clinical safety.","comment":"Contained resources do not have narrative. Resources that are not contained SHOULD have a narrative. In some cases, a resource may only have text with little or no additional discrete data (as long as all minOccurs=1 elements are satisfied). This may be necessary for data from legacy systems where information is captured as a \"text blob\" or where text is additionally entered raw or narrated and encoded information is added later.","alias":["narrative","html","xhtml","display"],"min":0,"max":"1","base":{"path":"DomainResource.text","min":0,"max":"1"},"type":[{"code":"Narrative"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contained","path":"Patient.contained","short":"Contained, inline Resources","definition":"These resources do not have an independent existence apart from the resource that contains them - they cannot be identified independently, and nor can they have their own independent transaction scope.","comment":"This should never be done when the content can be identified properly, as once identification is lost, it is extremely difficult (and context dependent) to restore it again. Contained resources may have profiles and tags In their meta elements, but SHALL NOT have security labels.","alias":["inline resources","anonymous resources","contained resources"],"min":0,"max":"*","base":{"path":"DomainResource.contained","min":0,"max":"*"},"type":[{"code":"Resource"}],"isModifier":false,"isSummary":false},{"id":"Patient.extension","path":"Patient.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"ordered":false,"rules":"open"},"short":"Extension","definition":"An Extension","min":0,"max":"*","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.extension:race","path":"Patient.extension","sliceName":"race","short":"US Core Race Extension","definition":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The race codes used to represent these concepts are based upon the [CDC Race and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 921 reference race. The race concepts are grouped by and pre-mapped to the 5 OMB race categories:\n\n - American Indian or Alaska Native\n - Asian\n - Black or African American\n - Native Hawaiian or Other Pacific Islander\n - White.","min":0,"max":"1","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension","profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-race"]}],"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":false,"isModifier":false},{"id":"Patient.extension:ethnicity","path":"Patient.extension","sliceName":"ethnicity","short":"US Core ethnicity Extension","definition":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The ethnicity codes used to represent these concepts are based upon the [CDC ethnicity and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 43 reference ethnicity. The ethnicity concepts are grouped by and pre-mapped to the 2 OMB ethnicity categories: - Hispanic or Latino - Not Hispanic or Latino.","min":0,"max":"1","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension","profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity"]}],"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":false,"isModifier":false},{"id":"Patient.extension:birthsex","path":"Patient.extension","sliceName":"birthsex","short":"Extension","definition":"A code classifying the person's sex assigned at birth as specified by the [Office of the National Coordinator for Health IT (ONC)](https://www.healthit.gov/newsroom/about-onc).","comment":"The codes required are intended to present birth sex (i.e., the sex recorded on the patient’s birth certificate) and not gender identity or reassigned sex.","min":0,"max":"1","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension","profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex"]}],"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":false,"isModifier":false},{"id":"Patient.extension:genderIdentity","path":"Patient.extension","sliceName":"genderIdentity","short":"Extension","definition":"An Extension","min":0,"max":"1","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension","profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity"]}],"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":false,"isModifier":false},{"id":"Patient.modifierExtension","path":"Patient.modifierExtension","short":"Extensions that cannot be ignored","definition":"May be used to represent additional information that is not part of the basic definition of the resource and that modifies the understanding of the element that contains it and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the resource that contains them","isSummary":false},{"id":"Patient.identifier","path":"Patient.identifier","short":"An identifier for this patient","definition":"An identifier for this patient.","requirements":"Patients are almost always assigned specific numerical identifiers.","min":1,"max":"*","base":{"path":"Patient.identifier","min":0,"max":"*"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.identifier.id","path":"Patient.identifier.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.identifier.extension","path":"Patient.identifier.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.identifier.use","path":"Patient.identifier.use","short":"usual | official | temp | secondary | old (If known)","definition":"The purpose of this identifier.","comment":"Applications can assume that an identifier is permanent unless it explicitly says that it is temporary.","requirements":"Allows the appropriate identifier for a particular context of use to be selected from among a set of identifiers.","min":0,"max":"1","base":{"path":"Identifier.use","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because applications should not mistake a temporary id for a permanent one.","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"IdentifierUse"}],"strength":"required","description":"Identifies the purpose for this identifier, if known .","valueSet":"http://hl7.org/fhir/ValueSet/identifier-use|4.0.1"}},{"id":"Patient.identifier.type","path":"Patient.identifier.type","short":"Description of identifier","definition":"A coded type for the identifier that can be used to determine which identifier to use for a specific purpose.","comment":"This element deals only with general categories of identifiers. It SHOULD not be used for codes that correspond 1..1 with the Identifier.system. Some identifiers may fall into multiple categories due to common usage. Where the system is known, a type is unnecessary because the type is always part of the system definition. However systems often need to handle identifiers where the system is not known. There is not a 1:1 relationship between type and system, since many different systems have the same type.","requirements":"Allows users to make use of identifiers when the identifier system is not known.","min":0,"max":"1","base":{"path":"Identifier.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"IdentifierType"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"extensible","description":"A coded type for an identifier that can be used to determine which identifier to use for a specific purpose.","valueSet":"http://hl7.org/fhir/ValueSet/identifier-type"}},{"id":"Patient.identifier.system","path":"Patient.identifier.system","short":"The namespace for the identifier value","definition":"Establishes the namespace for the value - that is, a URL that describes a set values that are unique.","comment":"Identifier.system is always case sensitive.","requirements":"There are many sets of identifiers. To perform matching of two identifiers, we need to know what set we're dealing with. The system identifies a particular set of unique identifiers.","min":1,"max":"1","base":{"path":"Identifier.system","min":0,"max":"1"},"type":[{"code":"uri"}],"example":[{"label":"General","valueUri":"http://www.acme.com/identifiers/patient"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.identifier.value","path":"Patient.identifier.value","short":"The value that is unique within the system.","definition":"The portion of the identifier typically relevant to the user and which is unique within the context of the system.","comment":"If the value is a full URI, then the system SHALL be urn:ietf:rfc:3986. The value's primary purpose is computational mapping. As a result, it may be normalized for comparison purposes (e.g. removing non-significant whitespace, dashes, etc.) A value formatted for human display can be conveyed using the [Rendered Value extension](http://hl7.org/fhir/R4/extension-rendered-value.html). Identifier.value is to be treated as case sensitive unless knowledge of the Identifier.system allows the processer to be confident that non-case-sensitive processing is safe.","min":1,"max":"1","base":{"path":"Identifier.value","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"123456"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.identifier.period","path":"Patient.identifier.period","short":"Time period when id is/was valid for use","definition":"Time period during which identifier is/was valid for use.","min":0,"max":"1","base":{"path":"Identifier.period","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.identifier.assigner","path":"Patient.identifier.assigner","short":"Organization that issued id (may be just text)","definition":"Organization that issued/manages the identifier.","comment":"The Identifier.assigner may omit the .reference element and only contain a .display element reflecting the name or other textual information about the assigning organization.","min":0,"max":"1","base":{"path":"Identifier.assigner","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.active","path":"Patient.active","short":"Whether this patient's record is in active use","definition":"Whether this patient record is in active use. \nMany systems use this property to mark as non-current patients, such as those that have not been seen for a period of time based on an organization's business rules.\n\nIt is often used to filter patient lists to exclude inactive patients\n\nDeceased patients may also be marked as inactive for the same reasons, but may be active for some time after death.","comment":"If a record is inactive, and linked to an active record, then future patient/record updates should occur on the other patient.","requirements":"Need to be able to mark a patient record as not to be used because it was created in error.","min":0,"max":"1","base":{"path":"Patient.active","min":0,"max":"1"},"type":[{"code":"boolean"}],"meaningWhenMissing":"This resource is generally assumed to be active if no value is provided for the active element","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labelled as a modifier because it is a status element that can indicate that a record should not be treated as valid","isSummary":true},{"id":"Patient.name","path":"Patient.name","short":"A name associated with the patient","definition":"A name associated with the individual.","comment":"A patient may have multiple names with different uses or applicable periods. For animals, the name is a \"HumanName\" in the sense that is assigned and used by humans and has the same patterns.","requirements":"Need to be able to track the patient by multiple names. Examples are your official name and a partner name.","min":1,"max":"*","base":{"path":"Patient.name","min":0,"max":"*"},"type":[{"code":"HumanName"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.name.id","path":"Patient.name.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.name.extension","path":"Patient.name.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.name.use","path":"Patient.name.use","short":"usual | official | temp | nickname | anonymous | old | maiden","definition":"Identifies the purpose for this name.","comment":"Applications can assume that a name is current unless it explicitly says that it is temporary or old.","requirements":"Allows the appropriate name for a particular context of use to be selected from among a set of names.","min":0,"max":"1","base":{"path":"HumanName.use","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because applications should not mistake a temporary or old name etc.for a current/permanent one","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"NameUse"}],"strength":"required","description":"The use of a human name.","valueSet":"http://hl7.org/fhir/ValueSet/name-use|4.0.1"}},{"id":"Patient.name.text","path":"Patient.name.text","short":"Text representation of the full name","definition":"Specifies the entire name as it should be displayed e.g. on an application UI. This may be provided instead of or as well as the specific parts.","comment":"Can provide both a text representation and parts. Applications updating a name SHALL ensure that when both text and parts are present, no content is included in the text that isn't found in a part.","requirements":"A renderable, unencoded form.","min":0,"max":"1","base":{"path":"HumanName.text","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.name.family","path":"Patient.name.family","short":"Family name (often called 'Surname')","definition":"The part of a name that links to the genealogy. In some cultures (e.g. Eritrea) the family name of a son is the first name of his father.","comment":"Family Name may be decomposed into specific parts using extensions (de, nl, es related cultures).","alias":["surname"],"min":0,"max":"1","base":{"path":"HumanName.family","min":0,"max":"1"},"type":[{"code":"string"}],"condition":["us-core-6"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.name.given","path":"Patient.name.given","short":"Given names (not always 'first'). Includes middle names","definition":"Given name.","comment":"If only initials are recorded, they may be used in place of the full name parts. Initials may be separated into multiple given names but often aren't due to paractical limitations. This element is not called \"first name\" since given names do not always come first.","alias":["first name","middle name"],"min":0,"max":"*","base":{"path":"HumanName.given","min":0,"max":"*"},"type":[{"code":"string"}],"orderMeaning":"Given Names appear in the correct order for presenting the name","condition":["us-core-6"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.name.prefix","path":"Patient.name.prefix","short":"Parts that come before the name","definition":"Part of the name that is acquired as a title due to academic, legal, employment or nobility status, etc. and that appears at the start of the name.","min":0,"max":"*","base":{"path":"HumanName.prefix","min":0,"max":"*"},"type":[{"code":"string"}],"orderMeaning":"Prefixes appear in the correct order for presenting the name","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.name.suffix","path":"Patient.name.suffix","short":"Parts that come after the name","definition":"Part of the name that is acquired as a title due to academic, legal, employment or nobility status, etc. and that appears at the end of the name.","min":0,"max":"*","base":{"path":"HumanName.suffix","min":0,"max":"*"},"type":[{"code":"string"}],"orderMeaning":"Suffixes appear in the correct order for presenting the name","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":false,"isModifier":false,"isSummary":true},{"id":"Patient.name.period","path":"Patient.name.period","short":"Time period when name was/is in use","definition":"Indicates the period of time when this name was valid for the named person.","requirements":"Allows names to be placed in historical context.","min":0,"max":"1","base":{"path":"HumanName.period","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":false,"isModifier":false,"isSummary":true},{"id":"Patient.telecom","path":"Patient.telecom","short":"A contact detail for the individual","definition":"A contact detail (e.g. a telephone number or an email address) by which the individual may be contacted.","comment":"A Patient may have multiple ways to be contacted with different uses or applicable periods. May need to have options for contacting the person urgently and also to help with identification. The address might not go directly to the individual, but may reach another party that is able to proxy for the patient (i.e. home phone, or pet owner's phone).","requirements":"People have (primary) ways to contact them in some way such as phone, email.","min":0,"max":"*","base":{"path":"Patient.telecom","min":0,"max":"*"},"type":[{"code":"ContactPoint"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":false,"isModifier":false,"isSummary":true},{"id":"Patient.telecom.id","path":"Patient.telecom.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.telecom.extension","path":"Patient.telecom.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.telecom.system","path":"Patient.telecom.system","short":"phone | fax | email | pager | url | sms | other","definition":"Telecommunications form for contact point - what communications system is required to make use of the contact.","min":1,"max":"1","base":{"path":"ContactPoint.system","min":0,"max":"1"},"type":[{"code":"code"}],"condition":["cpt-2"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"required","description":"Telecommunications form for contact point.","valueSet":"http://hl7.org/fhir/ValueSet/contact-point-system"}},{"id":"Patient.telecom.value","path":"Patient.telecom.value","short":"The actual contact point details","definition":"The actual contact point details, in a form that is meaningful to the designated communication system (i.e. phone number or email address).","comment":"Additional text data such as phone extension numbers, or notes about use of the contact are sometimes included in the value.","requirements":"Need to support legacy numbers that are not in a tightly controlled format.","min":1,"max":"1","base":{"path":"ContactPoint.value","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.telecom.use","path":"Patient.telecom.use","short":"home | work | temp | old | mobile - purpose of this contact point","definition":"Identifies the purpose for the contact point.","comment":"Applications can assume that a contact is current unless it explicitly says that it is temporary or old.","requirements":"Need to track the way a person uses this contact, so a user can choose which is appropriate for their purpose.","min":0,"max":"1","base":{"path":"ContactPoint.use","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because applications should not mistake a temporary or old contact etc.for a current/permanent one","isSummary":true,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/ValueSet/contact-point-use"}},{"id":"Patient.telecom.rank","path":"Patient.telecom.rank","short":"Specify preferred order of use (1 = highest)","definition":"Specifies a preferred order in which to use a set of contacts. ContactPoints with lower rank values are more preferred than those with higher rank values.","comment":"Note that rank does not necessarily follow the order in which the contacts are represented in the instance.","min":0,"max":"1","base":{"path":"ContactPoint.rank","min":0,"max":"1"},"type":[{"code":"positiveInt"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.telecom.period","path":"Patient.telecom.period","short":"Time period when the contact point was/is in use","definition":"Time period when the contact point was/is in use.","min":0,"max":"1","base":{"path":"ContactPoint.period","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.gender","path":"Patient.gender","short":"male | female | other | unknown","definition":"Administrative Gender - the gender that the patient is considered to have for administration and record keeping purposes.","comment":"The gender might not match the biological sex as determined by genetics or the individual's preferred identification. Note that for both humans and particularly animals, there are other legitimate possibilities than male and female, though the vast majority of systems and contexts only support male and female. Systems providing decision support or enforcing business rules should ideally do this on the basis of Observations dealing with the specific sex or gender aspect of interest (anatomical, chromosomal, social, etc.) However, because these observations are infrequently recorded, defaulting to the administrative gender is common practice. Where such defaulting occurs, rule enforcement should allow for the variation between administrative and biological, chromosomal and other gender aspects. For example, an alert about a hysterectomy on a male should be handled as a warning or overridable error, not a \"hard\" error. See the Patient Gender and Sex section for additional information about communicating patient gender and sex.","requirements":"Needed for identification of the individual, in combination with (at least) name and birth date.","min":1,"max":"1","base":{"path":"Patient.gender","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/ValueSet/administrative-gender"}},{"id":"Patient.birthDate","path":"Patient.birthDate","short":"The date of birth for the individual","definition":"The date of birth for the individual.","comment":"At least an estimated year should be provided as a guess if the real DOB is unknown There is a standard extension \"patient-birthTime\" available that should be used where Time is required (such as in maternity/infant care systems).","requirements":"Age of the individual drives many clinical processes.","min":0,"max":"1","base":{"path":"Patient.birthDate","min":0,"max":"1"},"type":[{"code":"date"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.deceased[x]","path":"Patient.deceased[x]","short":"Indicates if the individual is deceased or not","definition":"Indicates if the individual is deceased or not.","comment":"If there's no value in the instance, it means there is no statement on whether or not the individual is deceased. Most systems will interpret the absence of a value as a sign of the person being alive.","requirements":"The fact that a patient is deceased influences the clinical process. Also, in human communication and relation management it is necessary to know whether the person is alive.","min":0,"max":"1","base":{"path":"Patient.deceased[x]","min":0,"max":"1"},"type":[{"code":"boolean"},{"code":"dateTime"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because once a patient is marked as deceased, the actions that are appropriate to perform on the patient may be significantly different.","isSummary":true},{"id":"Patient.address","path":"Patient.address","short":"An address for the individual","definition":"An address for the individual.","comment":"Patient may have multiple addresses with different uses or applicable periods.","requirements":"May need to keep track of patient addresses for contacting, billing or reporting requirements and also to help with identification.","min":0,"max":"*","base":{"path":"Patient.address","min":0,"max":"*"},"type":[{"code":"Address"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.address.id","path":"Patient.address.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.address.extension","path":"Patient.address.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.address.use","path":"Patient.address.use","short":"home | work | temp | old | billing - purpose of this address","definition":"The purpose of this address.","comment":"Applications can assume that an address is current unless it explicitly says that it is temporary or old.","requirements":"Allows an appropriate address to be chosen from a list of many.","min":0,"max":"1","base":{"path":"Address.use","min":0,"max":"1"},"type":[{"code":"code"}],"example":[{"label":"General","valueCode":"home"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because applications should not mistake a temporary or old address etc.for a current/permanent one","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"AddressUse"}],"strength":"required","description":"The use of an address.","valueSet":"http://hl7.org/fhir/ValueSet/address-use|4.0.1"}},{"id":"Patient.address.type","path":"Patient.address.type","short":"postal | physical | both","definition":"Distinguishes between physical addresses (those you can visit) and mailing addresses (e.g. PO Boxes and care-of addresses). Most addresses are both.","comment":"The definition of Address states that \"address is intended to describe postal addresses, not physical locations\". However, many applications track whether an address has a dual purpose of being a location that can be visited as well as being a valid delivery destination, and Postal addresses are often used as proxies for physical locations (also see the [Location](http://hl7.org/fhir/R4/location.html#) resource).","min":0,"max":"1","base":{"path":"Address.type","min":0,"max":"1"},"type":[{"code":"code"}],"example":[{"label":"General","valueCode":"both"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"AddressType"}],"strength":"required","description":"The type of an address (physical / postal).","valueSet":"http://hl7.org/fhir/ValueSet/address-type|4.0.1"}},{"id":"Patient.address.text","path":"Patient.address.text","short":"Text representation of the address","definition":"Specifies the entire address as it should be displayed e.g. on a postal label. This may be provided instead of or as well as the specific parts.","comment":"Can provide both a text representation and parts. Applications updating an address SHALL ensure that when both text and parts are present, no content is included in the text that isn't found in a part.","requirements":"A renderable, unencoded form.","min":0,"max":"1","base":{"path":"Address.text","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"137 Nowhere Street, Erewhon 9132"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.address.line","path":"Patient.address.line","short":"Street name, number, direction & P.O. Box etc.","definition":"This component contains the house number, apartment number, street name, street direction, P.O. Box number, delivery hints, and similar address information.","min":0,"max":"*","base":{"path":"Address.line","min":0,"max":"*"},"type":[{"code":"string"}],"orderMeaning":"The order in which lines should appear in an address label","example":[{"label":"General","valueString":"137 Nowhere Street"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.address.city","path":"Patient.address.city","short":"Name of city, town etc.","definition":"The name of the city, town, suburb, village or other community or delivery center.","alias":["Municpality"],"min":0,"max":"1","base":{"path":"Address.city","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"Erewhon"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.address.district","path":"Patient.address.district","short":"District name (aka county)","definition":"The name of the administrative area (county).","comment":"District is sometimes known as county, but in some regions 'county' is used in place of city (municipality), so county name should be conveyed in city instead.","alias":["County"],"min":0,"max":"1","base":{"path":"Address.district","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"Madison"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.address.state","path":"Patient.address.state","short":"Sub-unit of country (abbreviations ok)","definition":"Sub-unit of a country with limited sovereignty in a federally organized country. A code may be used if codes are in common use (e.g. US 2 letter state codes).","alias":["Province","Territory"],"min":0,"max":"1","base":{"path":"Address.state","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"Two Letter USPS alphabetic codes.","valueSet":"http://hl7.org/fhir/us/core/ValueSet/us-core-usps-state"}},{"id":"Patient.address.postalCode","path":"Patient.address.postalCode","short":"US Zip Codes","definition":"A postal code designating a region defined by the postal service.","alias":["Zip","Zip Code"],"min":0,"max":"1","base":{"path":"Address.postalCode","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"9132"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.address.country","path":"Patient.address.country","short":"Country (e.g. can be ISO 3166 2 or 3 letter code)","definition":"Country - a nation as commonly understood or generally accepted.","comment":"ISO 3166 3 letter codes can be used in place of a human readable country name.","min":0,"max":"1","base":{"path":"Address.country","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.address.period","path":"Patient.address.period","short":"Time period when address was/is in use","definition":"Time period when address was/is in use.","requirements":"Allows addresses to be placed in historical context.","min":0,"max":"1","base":{"path":"Address.period","min":0,"max":"1"},"type":[{"code":"Period"}],"example":[{"label":"General","valuePeriod":{"start":"2010-03-23","end":"2010-07-01"}}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.maritalStatus","path":"Patient.maritalStatus","short":"Marital (civil) status of a patient","definition":"This field contains a patient's most recent marital (civil) status.","requirements":"Most, if not all systems capture it.","min":0,"max":"1","base":{"path":"Patient.maritalStatus","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MaritalStatus"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"extensible","description":"The domestic partnership status of a person.","valueSet":"http://hl7.org/fhir/ValueSet/marital-status"}},{"id":"Patient.multipleBirth[x]","path":"Patient.multipleBirth[x]","short":"Whether patient is part of a multiple birth","definition":"Indicates whether the patient is part of a multiple (boolean) or indicates the actual birth order (integer).","comment":"Where the valueInteger is provided, the number is the birth number in the sequence. E.g. The middle birth in triplets would be valueInteger=2 and the third born would have valueInteger=3 If a boolean value was provided for this triplets example, then all 3 patient records would have valueBoolean=true (the ordering is not indicated).","requirements":"For disambiguation of multiple-birth children, especially relevant where the care provider doesn't meet the patient, such as labs.","min":0,"max":"1","base":{"path":"Patient.multipleBirth[x]","min":0,"max":"1"},"type":[{"code":"boolean"},{"code":"integer"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.photo","path":"Patient.photo","short":"Image of the patient","definition":"Image of the patient.","comment":"Guidelines:\n* Use id photos, not clinical photos.\n* Limit dimensions to thumbnail.\n* Keep byte count low to ease resource updates.","requirements":"Many EHR systems have the capability to capture an image of the patient. Fits with newer social media usage too.","min":0,"max":"*","base":{"path":"Patient.photo","min":0,"max":"*"},"type":[{"code":"Attachment"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name","valueString":"Contact"}],"path":"Patient.contact","short":"A contact party (e.g. guardian, partner, friend) for the patient","definition":"A contact party (e.g. guardian, partner, friend) for the patient.","comment":"Contact covers all kinds of contact parties: family members, business contacts, guardians, caregivers. Not applicable to register pedigree and family ties beyond use of having contact.","requirements":"Need to track people you can contact about the patient.","min":0,"max":"*","base":{"path":"Patient.contact","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"pat-1","severity":"error","human":"SHALL at least contain a contact's details or a reference to an organization","expression":"name.exists() or telecom.exists() or address.exists() or organization.exists()","xpath":"exists(f:name) or exists(f:telecom) or exists(f:address) or exists(f:organization)","source":"http://hl7.org/fhir/StructureDefinition/Patient"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.id","path":"Patient.contact.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.extension","path":"Patient.contact.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.modifierExtension","path":"Patient.contact.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Patient.contact.relationship","path":"Patient.contact.relationship","short":"The kind of relationship","definition":"The nature of the relationship between the patient and the contact person.","requirements":"Used to determine which contact person is the most relevant to approach, depending on circumstances.","min":0,"max":"*","base":{"path":"Patient.contact.relationship","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ContactRelationship"}],"strength":"extensible","description":"The nature of the relationship between a patient and a contact person for that patient.","valueSet":"http://hl7.org/fhir/ValueSet/patient-contactrelationship"}},{"id":"Patient.contact.name","path":"Patient.contact.name","short":"A name associated with the contact person","definition":"A name associated with the contact person.","requirements":"Contact persons need to be identified by name, but it is uncommon to need details about multiple other names for that contact person.","min":0,"max":"1","base":{"path":"Patient.contact.name","min":0,"max":"1"},"type":[{"code":"HumanName"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.telecom","path":"Patient.contact.telecom","short":"A contact detail for the person","definition":"A contact detail for the person, e.g. a telephone number or an email address.","comment":"Contact may have multiple ways to be contacted with different uses or applicable periods. May need to have options for contacting the person urgently, and also to help with identification.","requirements":"People have (primary) ways to contact them in some way such as phone, email.","min":0,"max":"*","base":{"path":"Patient.contact.telecom","min":0,"max":"*"},"type":[{"code":"ContactPoint"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.address","path":"Patient.contact.address","short":"Address for the contact person","definition":"Address for the contact person.","requirements":"Need to keep track where the contact person can be contacted per postal mail or visited.","min":0,"max":"1","base":{"path":"Patient.contact.address","min":0,"max":"1"},"type":[{"code":"Address"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.gender","path":"Patient.contact.gender","short":"male | female | other | unknown","definition":"Administrative Gender - the gender that the contact person is considered to have for administration and record keeping purposes.","requirements":"Needed to address the person correctly.","min":0,"max":"1","base":{"path":"Patient.contact.gender","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"AdministrativeGender"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"required","description":"The gender of a person used for administrative purposes.","valueSet":"http://hl7.org/fhir/ValueSet/administrative-gender|4.0.1"}},{"id":"Patient.contact.organization","path":"Patient.contact.organization","short":"Organization that is associated with the contact","definition":"Organization on behalf of which the contact is acting or for which the contact is working.","requirements":"For guardians or business related contacts, the organization is relevant.","min":0,"max":"1","base":{"path":"Patient.contact.organization","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"condition":["pat-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.period","path":"Patient.contact.period","short":"The period during which this contact person or organization is valid to be contacted relating to this patient","definition":"The period during which this contact person or organization is valid to be contacted relating to this patient.","min":0,"max":"1","base":{"path":"Patient.contact.period","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.communication","path":"Patient.communication","short":"A language which may be used to communicate with the patient about his or her health","definition":"A language which may be used to communicate with the patient about his or her health.","comment":"If no language is specified, this *implies* that the default local language is spoken. If you need to convey proficiency for multiple modes, then you need multiple Patient.Communication associations. For animals, language is not a relevant field, and should be absent from the instance. If the Patient does not speak the default local language, then the Interpreter Required Standard can be used to explicitly declare that an interpreter is required.","requirements":"If a patient does not speak the local language, interpreters may be required, so languages spoken and proficiency are important things to keep track of both for patient and other persons of interest.","min":0,"max":"*","base":{"path":"Patient.communication","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":false,"isModifier":false,"isSummary":false},{"id":"Patient.communication.id","path":"Patient.communication.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.communication.extension","path":"Patient.communication.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.communication.modifierExtension","path":"Patient.communication.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Patient.communication.language","path":"Patient.communication.language","short":"The language which can be used to communicate with the patient about his or her health","definition":"The ISO-639-1 alpha 2 code in lower case for the language, optionally followed by a hyphen and the ISO-3166-1 alpha 2 code for the region in upper case; e.g. \"en\" for English, or \"en-US\" for American English versus \"en-EN\" for England English.","comment":"The structure aa-BB with this exact casing is one the most widely used notations for locale. However not all systems actually code this but instead have it as free text. Hence CodeableConcept instead of code as the data type.","requirements":"Most systems in multilingual countries will want to convey language. Not all systems actually need the regional dialect.","min":1,"max":"1","base":{"path":"Patient.communication.language","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"strength":"extensible","valueSet":"http://hl7.org/fhir/us/core/ValueSet/simple-language"}},{"id":"Patient.communication.preferred","path":"Patient.communication.preferred","short":"Language preference indicator","definition":"Indicates whether or not the patient prefers this language (over other languages he masters up a certain level).","comment":"This language is specifically identified for communicating healthcare information.","requirements":"People that master multiple languages up to certain level may prefer one or more, i.e. feel more confident in communicating in a particular language making other languages sort of a fall back method.","min":0,"max":"1","base":{"path":"Patient.communication.preferred","min":0,"max":"1"},"type":[{"code":"boolean"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.generalPractitioner","path":"Patient.generalPractitioner","short":"Patient's nominated primary care provider","definition":"Patient's nominated care provider.","comment":"This may be the primary care provider (in a GP context), or it may be a patient nominated care manager in a community/disability setting, or even organization that will provide people to perform the care provider roles. It is not to be used to record Care Teams, these should be in a CareTeam resource that may be linked to the CarePlan or EpisodeOfCare resources.\nMultiple GPs may be recorded against the patient for various reasons, such as a student that has his home GP listed along with the GP at university during the school semesters, or a \"fly-in/fly-out\" worker that has the onsite GP also included with his home GP to remain aware of medical issues.\n\nJurisdictions may decide that they can profile this down to 1 if desired, or 1 per type.","alias":["careProvider"],"min":0,"max":"*","base":{"path":"Patient.generalPractitioner","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization","http://hl7.org/fhir/StructureDefinition/Practitioner","http://hl7.org/fhir/StructureDefinition/PractitionerRole"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.managingOrganization","path":"Patient.managingOrganization","short":"Organization that is the custodian of the patient record","definition":"Organization that is the custodian of the patient record.","comment":"There is only one managing organization for a specific patient record. Other organizations will have their own Patient record, and may use the Link property to join the records together (or a Person resource which can include confidence ratings for the association).","requirements":"Need to know who recognizes this patient record, manages and updates it.","min":0,"max":"1","base":{"path":"Patient.managingOrganization","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.link","path":"Patient.link","short":"Link to another patient resource that concerns the same actual person","definition":"Link to another patient resource that concerns the same actual patient.","comment":"There is no assumption that linked patient records have mutual links.","requirements":"There are multiple use cases: \n\n* Duplicate patient records due to the clerical errors associated with the difficulties of identifying humans consistently, and \n* Distribution of patient information across multiple servers.","min":0,"max":"*","base":{"path":"Patient.link","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because it might not be the main Patient resource, and the referenced patient should be used instead of this Patient record. This is when the link.type value is 'replaced-by'","isSummary":true},{"id":"Patient.link.id","path":"Patient.link.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.link.extension","path":"Patient.link.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.link.modifierExtension","path":"Patient.link.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Patient.link.other","path":"Patient.link.other","short":"The other patient or related person resource that the link refers to","definition":"The other patient resource that the link refers to.","comment":"Referencing a RelatedPerson here removes the need to use a Person record to associate a Patient and RelatedPerson as the same individual.","min":1,"max":"1","base":{"path":"Patient.link.other","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-hierarchy","valueBoolean":false}],"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Patient","http://hl7.org/fhir/StructureDefinition/RelatedPerson"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.link.type","path":"Patient.link.type","short":"replaced-by | replaces | refer | seealso","definition":"The type of link between this patient resource and another patient resource.","min":1,"max":"1","base":{"path":"Patient.link.type","min":1,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"LinkType"}],"strength":"required","description":"The type of link between this patient resource and another patient resource.","valueSet":"http://hl7.org/fhir/ValueSet/link-type|4.0.1"}}]}},{"resourceType":"StructureDefinition","id":"us-core-race","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","version":"5.0.1","name":"USCoreRaceExtension","title":"US Core Race Extension","status":"active","date":"2019-05-21","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The race codes used to represent these concepts are based upon the [CDC Race and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 921 reference race. The race concepts are grouped by and pre-mapped to the 5 OMB race categories:\n\n - American Indian or Alaska Native\n - Asian\n - Black or African American\n - Native Hawaiian or Other Pacific Islander\n - White.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"purpose":"Complies with 2015 Edition Common Clinical Data Set for patient race.","copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"complex-type","abstract":false,"context":[{"type":"element","expression":"Patient"},{"type":"element","expression":"RelatedPerson"},{"type":"element","expression":"Person"},{"type":"element","expression":"Practitioner"},{"type":"element","expression":"FamilyMemberHistory"}],"type":"Extension","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Extension","derivation":"constraint","snapshot":{"element":[{"id":"Extension","path":"Extension","short":"US Core Race Extension","definition":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The race codes used to represent these concepts are based upon the [CDC Race and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 921 reference race. The race concepts are grouped by and pre-mapped to the 5 OMB race categories:\n\n - American Indian or Alaska Native\n - Asian\n - Black or African American\n - Native Hawaiian or Other Pacific Islander\n - White.","min":0,"max":"1","base":{"path":"Extension","min":0,"max":"*"},"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false},{"id":"Extension.id","path":"Extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension","path":"Extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory","path":"Extension.extension","sliceName":"ombCategory","short":"American Indian or Alaska Native|Asian|Black or African American|Native Hawaiian or Other Pacific Islander|White","definition":"The 5 race category codes according to the [OMB Standards for Maintaining, Collecting, and Presenting Federal Data on Race and Ethnicity, Statistical Policy Directive No. 15, as revised, October 30, 1997](https://www.govinfo.gov/content/pkg/FR-1997-10-30/pdf/97-28653.pdf).","min":0,"max":"5","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"ombCategory","isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","description":"The 5 race category codes according to the [OMB Standards for Maintaining, Collecting, and Presenting Federal Data on Race and Ethnicity, Statistical Policy Directive No. 15, as revised, October 30, 1997](https://www.govinfo.gov/content/pkg/FR-1997-10-30/pdf/97-28653.pdf).","valueSet":"http://hl7.org/fhir/us/core/ValueSet/omb-race-category"}},{"id":"Extension.extension:detailed","path":"Extension.extension","sliceName":"detailed","short":"Extended race codes","definition":"The 900+ CDC race codes that are grouped under one of the 5 OMB race category codes:.","min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"detailed","isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/us/core/ValueSet/detailed-race"}},{"id":"Extension.extension:text","path":"Extension.extension","sliceName":"text","short":"Race Text","definition":"Plain text representation of the race concept(s).","min":1,"max":"1","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"text","isModifier":false,"isSummary":false},{"id":"Extension.extension:text.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Extension.url","path":"Extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"uri"}],"code":"http://hl7.org/fhirpath/System.String"}],"fixedUri":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","isModifier":false,"isSummary":false},{"id":"Extension.value[x]","path":"Extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":0,"max":"0","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"base64Binary"},{"code":"boolean"},{"code":"canonical"},{"code":"code"},{"code":"date"},{"code":"dateTime"},{"code":"decimal"},{"code":"id"},{"code":"instant"},{"code":"integer"},{"code":"markdown"},{"code":"oid"},{"code":"positiveInt"},{"code":"string"},{"code":"time"},{"code":"unsignedInt"},{"code":"uri"},{"code":"url"},{"code":"uuid"},{"code":"Address"},{"code":"Age"},{"code":"Annotation"},{"code":"Attachment"},{"code":"CodeableConcept"},{"code":"Coding"},{"code":"ContactPoint"},{"code":"Count"},{"code":"Distance"},{"code":"Duration"},{"code":"HumanName"},{"code":"Identifier"},{"code":"Money"},{"code":"Period"},{"code":"Quantity"},{"code":"Range"},{"code":"Ratio"},{"code":"Reference"},{"code":"SampledData"},{"code":"Signature"},{"code":"Timing"},{"code":"ContactDetail"},{"code":"Contributor"},{"code":"DataRequirement"},{"code":"Expression"},{"code":"ParameterDefinition"},{"code":"RelatedArtifact"},{"code":"TriggerDefinition"},{"code":"UsageContext"},{"code":"Dosage"},{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false}]}},{"resourceType":"StructureDefinition","id":"us-core-ethnicity","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","version":"5.0.1","name":"USCoreEthnicityExtension","title":"US Core Ethnicity Extension","status":"active","date":"2019-05-21T00:00:00-04:00","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The ethnicity codes used to represent these concepts are based upon the [CDC ethnicity and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 43 reference ethnicity. The ethnicity concepts are grouped by and pre-mapped to the 2 OMB ethnicity categories: - Hispanic or Latino - Not Hispanic or Latino.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"purpose":"Complies with 2015 Edition Common Clinical Data Set for patient race.","copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"complex-type","abstract":false,"context":[{"type":"element","expression":"Patient"},{"type":"element","expression":"RelatedPerson"},{"type":"element","expression":"Person"},{"type":"element","expression":"Practitioner"},{"type":"element","expression":"FamilyMemberHistory"}],"type":"Extension","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Extension","derivation":"constraint","snapshot":{"element":[{"id":"Extension","path":"Extension","short":"US Core ethnicity Extension","definition":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The ethnicity codes used to represent these concepts are based upon the [CDC ethnicity and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 43 reference ethnicity. The ethnicity concepts are grouped by and pre-mapped to the 2 OMB ethnicity categories: - Hispanic or Latino - Not Hispanic or Latino.","min":0,"max":"1","base":{"path":"Extension","min":0,"max":"*"},"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false},{"id":"Extension.id","path":"Extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension","path":"Extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory","path":"Extension.extension","sliceName":"ombCategory","short":"Hispanic or Latino|Not Hispanic or Latino","definition":"The 2 ethnicity category codes according to the [OMB Standards for Maintaining, Collecting, and Presenting Federal Data on Race and Ethnicity, Statistical Policy Directive No. 15, as revised, October 30, 1997](https://www.govinfo.gov/content/pkg/FR-1997-10-30/pdf/97-28653.pdf).","min":0,"max":"1","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"ombCategory","isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/us/core/ValueSet/omb-ethnicity-category"}},{"id":"Extension.extension:detailed","path":"Extension.extension","sliceName":"detailed","short":"Extended ethnicity codes","definition":"The 41 CDC ethnicity codes that are grouped under one of the 2 OMB ethnicity category codes.","min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"detailed","isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/us/core/ValueSet/detailed-ethnicity"}},{"id":"Extension.extension:text","path":"Extension.extension","sliceName":"text","short":"ethnicity Text","definition":"Plain text representation of the ethnicity concept(s).","min":1,"max":"1","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"text","isModifier":false,"isSummary":false},{"id":"Extension.extension:text.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Extension.url","path":"Extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"uri"}],"code":"http://hl7.org/fhirpath/System.String"}],"fixedUri":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","isModifier":false,"isSummary":false},{"id":"Extension.value[x]","path":"Extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":0,"max":"0","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"base64Binary"},{"code":"boolean"},{"code":"canonical"},{"code":"code"},{"code":"date"},{"code":"dateTime"},{"code":"decimal"},{"code":"id"},{"code":"instant"},{"code":"integer"},{"code":"markdown"},{"code":"oid"},{"code":"positiveInt"},{"code":"string"},{"code":"time"},{"code":"unsignedInt"},{"code":"uri"},{"code":"url"},{"code":"uuid"},{"code":"Address"},{"code":"Age"},{"code":"Annotation"},{"code":"Attachment"},{"code":"CodeableConcept"},{"code":"Coding"},{"code":"ContactPoint"},{"code":"Count"},{"code":"Distance"},{"code":"Duration"},{"code":"HumanName"},{"code":"Identifier"},{"code":"Money"},{"code":"Period"},{"code":"Quantity"},{"code":"Range"},{"code":"Ratio"},{"code":"Reference"},{"code":"SampledData"},{"code":"Signature"},{"code":"Timing"},{"code":"ContactDetail"},{"code":"Contributor"},{"code":"DataRequirement"},{"code":"Expression"},{"code":"ParameterDefinition"},{"code":"RelatedArtifact"},{"code":"TriggerDefinition"},{"code":"UsageContext"},{"code":"Dosage"},{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false}]}},{"resourceType":"StructureDefinition","id":"us-core-birthsex","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","version":"5.0.1","name":"USCoreBirthSexExtension","title":"US Core Birth Sex Extension","status":"active","date":"2019-05-21","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"A code classifying the person's sex assigned at birth as specified by the [Office of the National Coordinator for Health IT (ONC)](https://www.healthit.gov/newsroom/about-onc). This extension aligns with the C-CDA Birth Sex Observation (LOINC 76689-9).","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"complex-type","abstract":false,"context":[{"type":"element","expression":"Patient"}],"type":"Extension","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Extension","derivation":"constraint","snapshot":{"element":[{"id":"Extension","path":"Extension","short":"Extension","definition":"A code classifying the person's sex assigned at birth as specified by the [Office of the National Coordinator for Health IT (ONC)](https://www.healthit.gov/newsroom/about-onc).","comment":"The codes required are intended to present birth sex (i.e., the sex recorded on the patient’s birth certificate) and not gender identity or reassigned sex.","min":0,"max":"1","base":{"path":"Extension","min":0,"max":"*"},"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false},{"id":"Extension.id","path":"Extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension","path":"Extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.url","path":"Extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"uri"}],"code":"http://hl7.org/fhirpath/System.String"}],"fixedUri":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","isModifier":false,"isSummary":false},{"id":"Extension.value[x]","path":"Extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","description":"Code for sex assigned at birth","valueSet":"http://hl7.org/fhir/us/core/ValueSet/birthsex"}}]}},{"resourceType":"StructureDefinition","id":"us-core-genderIdentity","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity","version":"5.0.1","name":"USCoreGenderIdentityExtension","title":"US Core Gender Identity Extension","status":"active","date":"2022-01-22","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"This extension provides concepts to describe the gender a person identifies as.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"purpose":"Complies with USCDI v2","copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"complex-type","abstract":false,"context":[{"type":"element","expression":"Patient"},{"type":"element","expression":"RelatedPerson"},{"type":"element","expression":"Person"},{"type":"element","expression":"Practitioner"}],"type":"Extension","baseDefinition":"http://hl7.org/fhir/StructureDefinition/patient-genderIdentity","derivation":"constraint","snapshot":{"element":[{"id":"Extension","path":"Extension","short":"Extension","definition":"An Extension","min":0,"max":"1","base":{"path":"Extension","min":0,"max":"*"},"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false},{"id":"Extension.id","path":"Extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension","path":"Extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Extension","definition":"An Extension","min":0,"max":"0","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.url","path":"Extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"uri"}],"code":"http://hl7.org/fhirpath/System.String"}],"fixedUri":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity","isModifier":false,"isSummary":false},{"id":"Extension.value[x]","path":"Extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"extensible","valueSet":"http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1021.32"}}]}},{"resourceType":"StructureDefinition","id":"us-core-implantable-device","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-implantable-device","version":"5.0.1","name":"USCoreImplantableDeviceProfile","title":"US Core Implantable Device Profile","status":"active","experimental":false,"date":"2022-04-20","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"The US Core Implantable Device Profile is based upon the core FHIR Device Resource and meets the U.S. Core Data for Interoperability (USCDI) v2 ‘Unique Device Identifier(s) for a Patient’s Implantable Device(s)’ requirements. To promote interoperability and adoption through common implementation, this profile sets minimum expectations for the Device resource to record, search, and fetch UDI information associated with a patient's implantable device(s). It identifies which core elements, extensions, vocabularies and value sets **SHALL** be present in the resource when using this profile. It provides the floor for standards development for specific uses cases.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"resource","abstract":false,"type":"Device","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Device","derivation":"constraint","snapshot":{"element":[{"id":"Device","path":"Device","short":"Item used in healthcare","definition":"\\-","comment":"\\-","min":0,"max":"*","base":{"path":"Device","min":0,"max":"*"},"constraint":[{"key":"dom-2","severity":"error","human":"If the resource is contained in another resource, it SHALL NOT contain nested Resources","expression":"contained.contained.empty()","xpath":"not(parent::f:contained and f:contained)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-3","severity":"error","human":"If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource","expression":"contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()","xpath":"not(exists(for $id in f:contained/*/f:id/@value return $contained[not(parent::*/descendant::f:reference/@value=concat('#', $contained/*/id/@value) or descendant::f:reference[@value='#'])]))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-4","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated","expression":"contained.meta.versionId.empty() and contained.meta.lastUpdated.empty()","xpath":"not(exists(f:contained/*/f:meta/f:versionId)) and not(exists(f:contained/*/f:meta/f:lastUpdated))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-5","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a security label","expression":"contained.meta.security.empty()","xpath":"not(exists(f:contained/*/f:meta/f:security))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice","valueBoolean":true},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice-explanation","valueMarkdown":"When a resource has no narrative, only systems that fully understand the data can display the resource to a human safely. Including a human readable representation in the resource makes for a much more robust eco-system and cheaper handling of resources by intermediary systems. Some ecosystems restrict distribution of resources to only those systems that do fully understand the resources, and as a consequence implementers may believe that the narrative is superfluous. However experience shows that such eco-systems often open up to new participants over time."}],"key":"dom-6","severity":"warning","human":"A resource should have narrative for robust management","expression":"text.`div`.exists()","xpath":"exists(f:text/h:div)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"}],"mustSupport":false,"isModifier":false,"isSummary":false},{"id":"Device.id","path":"Device.id","short":"Logical id of this artifact","definition":"The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.","comment":"The only time that a resource does not have an id is when it is being submitted to the server using a create operation.","min":0,"max":"1","base":{"path":"Resource.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":true},{"id":"Device.meta","path":"Device.meta","short":"Metadata about the resource","definition":"The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource.","min":0,"max":"1","base":{"path":"Resource.meta","min":0,"max":"1"},"type":[{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Device.implicitRules","path":"Device.implicitRules","short":"A set of rules under which this content was created","definition":"A reference to a set of rules that were followed when the resource was constructed, and which must be understood when processing the content. Often, this is a reference to an implementation guide that defines the special rules along with other profiles etc.","comment":"Asserting this rule set restricts the content to be only understood by a limited set of trading partners. This inherently limits the usefulness of the data in the long term. However, the existing health eco-system is highly fractured, and not yet ready to define, collect, and exchange data in a generally computable sense. Wherever possible, implementers and/or specification writers should avoid using this element. Often, when used, the URL is a reference to an implementation guide that defines these special rules as part of it's narrative along with other profiles, value sets, etc.","min":0,"max":"1","base":{"path":"Resource.implicitRules","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because the implicit rules may provide additional knowledge about the resource that modifies it's meaning or interpretation","isSummary":true},{"id":"Device.language","path":"Device.language","short":"Language of the resource content","definition":"The base language in which the resource is written.","comment":"Language is provided to support indexing and accessibility (typically, services such as text to speech use the language tag). The html language tag in the narrative applies to the narrative. The language tag on the resource may be used to specify the language of other presentations generated from the data in the resource. Not all the content has to be in the base language. The Resource.language should not be assumed to apply to the narrative automatically. If a language is specified, it should it also be specified on the div element in the html (see rules in HTML5 for information about the relationship between xml:lang and the html lang attribute).","min":0,"max":"1","base":{"path":"Resource.language","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet","valueCanonical":"http://hl7.org/fhir/ValueSet/all-languages"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"Language"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"preferred","description":"A human language.","valueSet":"http://hl7.org/fhir/ValueSet/languages"}},{"id":"Device.text","path":"Device.text","short":"Text summary of the resource, for human interpretation","definition":"A human-readable narrative that contains a summary of the resource and can be used to represent the content of the resource to a human. The narrative need not encode all the structured data, but is required to contain sufficient detail to make it \"clinically safe\" for a human to just read the narrative. Resource definitions may define what content should be represented in the narrative to ensure clinical safety.","comment":"Contained resources do not have narrative. Resources that are not contained SHOULD have a narrative. In some cases, a resource may only have text with little or no additional discrete data (as long as all minOccurs=1 elements are satisfied). This may be necessary for data from legacy systems where information is captured as a \"text blob\" or where text is additionally entered raw or narrated and encoded information is added later.","alias":["narrative","html","xhtml","display"],"min":0,"max":"1","base":{"path":"DomainResource.text","min":0,"max":"1"},"type":[{"code":"Narrative"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.contained","path":"Device.contained","short":"Contained, inline Resources","definition":"These resources do not have an independent existence apart from the resource that contains them - they cannot be identified independently, and nor can they have their own independent transaction scope.","comment":"This should never be done when the content can be identified properly, as once identification is lost, it is extremely difficult (and context dependent) to restore it again. Contained resources may have profiles and tags In their meta elements, but SHALL NOT have security labels.","alias":["inline resources","anonymous resources","contained resources"],"min":0,"max":"*","base":{"path":"DomainResource.contained","min":0,"max":"*"},"type":[{"code":"Resource"}],"isModifier":false,"isSummary":false},{"id":"Device.extension","path":"Device.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the resource. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.modifierExtension","path":"Device.modifierExtension","short":"Extensions that cannot be ignored","definition":"May be used to represent additional information that is not part of the basic definition of the resource and that modifies the understanding of the element that contains it and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the resource that contains them","isSummary":false},{"id":"Device.identifier","path":"Device.identifier","short":"Instance identifier","definition":"Unique instance identifiers assigned to a device by manufacturers other organizations or owners.","comment":"The barcode string from a barcode present on a device label or package may identify the instance, include names given to the device in local usage, or may identify the type of device. If the identifier identifies the type of device, Device.type element should be used.","min":0,"max":"*","base":{"path":"Device.identifier","min":0,"max":"*"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.definition","path":"Device.definition","short":"The reference to the definition for the device","definition":"The reference to the definition for the device.","min":0,"max":"1","base":{"path":"Device.definition","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/DeviceDefinition"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier","path":"Device.udiCarrier","short":"Unique Device Identifier (UDI) Barcode string","definition":"Unique device identifier (UDI) assigned to device label or package. Note that the Device may include multiple udiCarriers as it either may include just the udiCarrier for the jurisdiction it is sold, or for multiple jurisdictions it could have been sold.","comment":"Some devices may not have UDI information (for example. historical data or patient reported data).","min":0,"max":"1","base":{"path":"Device.udiCarrier","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Device.udiCarrier.id","path":"Device.udiCarrier.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier.extension","path":"Device.udiCarrier.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier.modifierExtension","path":"Device.udiCarrier.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.udiCarrier.deviceIdentifier","path":"Device.udiCarrier.deviceIdentifier","short":"Mandatory fixed portion of UDI","definition":"The device identifier (DI) is a mandatory, fixed portion of a UDI that identifies the labeler and the specific version or model of a device.","alias":["DI"],"min":1,"max":"1","base":{"path":"Device.udiCarrier.deviceIdentifier","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Device.udiCarrier.issuer","path":"Device.udiCarrier.issuer","short":"UDI Issuing Organization","definition":"Organization that is charged with issuing UDIs for devices. For example, the US FDA issuers include :\n1) GS1: \nhttp://hl7.org/fhir/NamingSystem/gs1-di, \n2) HIBCC:\nhttp://hl7.org/fhir/NamingSystem/hibcc-dI, \n3) ICCBBA for blood containers:\nhttp://hl7.org/fhir/NamingSystem/iccbba-blood-di, \n4) ICCBA for other devices:\nhttp://hl7.org/fhir/NamingSystem/iccbba-other-di.","alias":["Barcode System"],"min":0,"max":"1","base":{"path":"Device.udiCarrier.issuer","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier.jurisdiction","path":"Device.udiCarrier.jurisdiction","short":"Regional UDI authority","definition":"The identity of the authoritative source for UDI generation within a jurisdiction. All UDIs are globally unique within a single namespace with the appropriate repository uri as the system. For example, UDIs of devices managed in the U.S. by the FDA, the value is http://hl7.org/fhir/NamingSystem/fda-udi.","requirements":"Allows a recipient of a UDI to know which database will contain the UDI-associated metadata.","min":0,"max":"1","base":{"path":"Device.udiCarrier.jurisdiction","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier.carrierAIDC","path":"Device.udiCarrier.carrierAIDC","short":"UDI Machine Readable Barcode String","definition":"The full UDI carrier of the Automatic Identification and Data Capture (AIDC) technology representation of the barcode string as printed on the packaging of the device - e.g., a barcode or RFID. Because of limitations on character sets in XML and the need to round-trip JSON data through XML, AIDC Formats *SHALL* be base64 encoded.","comment":"The AIDC form of UDIs should be scanned or otherwise used for the identification of the device whenever possible to minimize errors in records resulting from manual transcriptions. If separate barcodes for DI and PI are present, concatenate the string with DI first and in order of human readable expression on label.","alias":["Automatic Identification and Data Capture"],"min":0,"max":"1","base":{"path":"Device.udiCarrier.carrierAIDC","min":0,"max":"1"},"type":[{"code":"base64Binary"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Device.udiCarrier.carrierHRF","path":"Device.udiCarrier.carrierHRF","short":"UDI Human Readable Barcode String","definition":"The full UDI carrier as the human readable form (HRF) representation of the barcode string as printed on the packaging of the device.","comment":"If separate barcodes for DI and PI are present, concatenate the string with DI first and in order of human readable expression on label.","alias":["Human Readable Form","UDI","Barcode String"],"min":0,"max":"1","base":{"path":"Device.udiCarrier.carrierHRF","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Device.udiCarrier.entryType","path":"Device.udiCarrier.entryType","short":"barcode | rfid | manual +","definition":"A coded entry to indicate how the data was entered.","requirements":"Supports a way to distinguish hand entered from machine read data.","min":0,"max":"1","base":{"path":"Device.udiCarrier.entryType","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"UDIEntryType"}],"strength":"required","description":"Codes to identify how UDI data was entered.","valueSet":"http://hl7.org/fhir/ValueSet/udi-entry-type|4.0.1"}},{"id":"Device.status","path":"Device.status","short":"active | inactive | entered-in-error | unknown","definition":"Status of the Device availability.","comment":"This element is labeled as a modifier because the status contains the codes inactive and entered-in-error that mark the device (record)as not currently valid.","min":0,"max":"1","base":{"path":"Device.status","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labelled as a modifier because it is a status element that contains status entered-in-error which means that the resource should not be treated as valid","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"FHIRDeviceStatus"}],"strength":"required","description":"The availability status of the device.","valueSet":"http://hl7.org/fhir/ValueSet/device-status|4.0.1"}},{"id":"Device.statusReason","path":"Device.statusReason","short":"online | paused | standby | offline | not-ready | transduc-discon | hw-discon | off","definition":"Reason for the dtatus of the Device availability.","min":0,"max":"*","base":{"path":"Device.statusReason","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"FHIRDeviceStatusReason"}],"strength":"extensible","description":"The availability status reason of the device.","valueSet":"http://hl7.org/fhir/ValueSet/device-status-reason"}},{"id":"Device.distinctIdentifier","path":"Device.distinctIdentifier","short":"The distinct identification string","definition":"The distinct identification string as required by regulation for a human cell, tissue, or cellular and tissue-based product.","comment":"For example, this applies to devices in the United States regulated under *Code of Federal Regulation 21CFR§1271.290(c)*.","alias":["Distinct Identification Code (DIC)"],"min":0,"max":"1","base":{"path":"Device.distinctIdentifier","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.manufacturer","path":"Device.manufacturer","short":"Name of device manufacturer","definition":"A name of the manufacturer.","min":0,"max":"1","base":{"path":"Device.manufacturer","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.manufactureDate","path":"Device.manufactureDate","short":"Date when the device was made","definition":"The date and time when the device was manufactured.","min":0,"max":"1","base":{"path":"Device.manufactureDate","min":0,"max":"1"},"type":[{"code":"dateTime"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.expirationDate","path":"Device.expirationDate","short":"Date and time of expiry of this device (if applicable)","definition":"The date and time beyond which this device is no longer valid or should not be used (if applicable).","min":0,"max":"1","base":{"path":"Device.expirationDate","min":0,"max":"1"},"type":[{"code":"dateTime"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.lotNumber","path":"Device.lotNumber","short":"Lot number of manufacture","definition":"Lot number assigned by the manufacturer.","min":0,"max":"1","base":{"path":"Device.lotNumber","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.serialNumber","path":"Device.serialNumber","short":"Serial number assigned by the manufacturer","definition":"The serial number assigned by the organization when the device was manufactured.","comment":"Alphanumeric Maximum 20.","min":0,"max":"1","base":{"path":"Device.serialNumber","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.deviceName","path":"Device.deviceName","short":"The name of the device as given by the manufacturer","definition":"This represents the manufacturer's name of the device as provided by the device, from a UDI label, or by a person describing the Device. This typically would be used when a person provides the name(s) or when the device represents one of the names available from DeviceDefinition.","min":0,"max":"*","base":{"path":"Device.deviceName","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.deviceName.id","path":"Device.deviceName.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.deviceName.extension","path":"Device.deviceName.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.deviceName.modifierExtension","path":"Device.deviceName.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.deviceName.name","path":"Device.deviceName.name","short":"The name of the device","definition":"The name of the device.","alias":["Σ"],"min":1,"max":"1","base":{"path":"Device.deviceName.name","min":1,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.deviceName.type","path":"Device.deviceName.type","short":"udi-label-name | user-friendly-name | patient-reported-name | manufacturer-name | model-name | other","definition":"The type of deviceName.\nUDILabelName | UserFriendlyName | PatientReportedName | ManufactureDeviceName | ModelName.","min":1,"max":"1","base":{"path":"Device.deviceName.type","min":1,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"DeviceNameType"}],"strength":"required","description":"The type of name the device is referred by.","valueSet":"http://hl7.org/fhir/ValueSet/device-nametype|4.0.1"}},{"id":"Device.modelNumber","path":"Device.modelNumber","short":"The model number for the device","definition":"The model number for the device.","min":0,"max":"1","base":{"path":"Device.modelNumber","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.partNumber","path":"Device.partNumber","short":"The part number of the device","definition":"The part number of the device.","comment":"Alphanumeric Maximum 20.","min":0,"max":"1","base":{"path":"Device.partNumber","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.type","path":"Device.type","short":"The kind or type of device","definition":"The kind or type of device.","min":1,"max":"1","base":{"path":"Device.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"strength":"extensible","description":"Codes to identify medical devices","valueSet":"http://hl7.org/fhir/ValueSet/device-kind"}},{"id":"Device.specialization","path":"Device.specialization","short":"The capabilities supported on a device, the standards to which the device conforms for a particular purpose, and used for the communication","definition":"The capabilities supported on a device, the standards to which the device conforms for a particular purpose, and used for the communication.","min":0,"max":"*","base":{"path":"Device.specialization","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.specialization.id","path":"Device.specialization.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.specialization.extension","path":"Device.specialization.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.specialization.modifierExtension","path":"Device.specialization.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.specialization.systemType","path":"Device.specialization.systemType","short":"The standard that is used to operate and communicate","definition":"The standard that is used to operate and communicate.","alias":["Σ"],"min":1,"max":"1","base":{"path":"Device.specialization.systemType","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.specialization.version","path":"Device.specialization.version","short":"The version of the standard that is used to operate and communicate","definition":"The version of the standard that is used to operate and communicate.","min":0,"max":"1","base":{"path":"Device.specialization.version","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.version","path":"Device.version","short":"The actual design of the device or software version running on the device","definition":"The actual design of the device or software version running on the device.","min":0,"max":"*","base":{"path":"Device.version","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.version.id","path":"Device.version.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.version.extension","path":"Device.version.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.version.modifierExtension","path":"Device.version.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.version.type","path":"Device.version.type","short":"The type of the device version","definition":"The type of the device version.","alias":["Σ"],"min":0,"max":"1","base":{"path":"Device.version.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.version.component","path":"Device.version.component","short":"A single component of the device version","definition":"A single component of the device version.","min":0,"max":"1","base":{"path":"Device.version.component","min":0,"max":"1"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.version.value","path":"Device.version.value","short":"The version text","definition":"The version text.","min":1,"max":"1","base":{"path":"Device.version.value","min":1,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.property","path":"Device.property","short":"The actual configuration settings of a device as it actually operates, e.g., regulation status, time properties","definition":"The actual configuration settings of a device as it actually operates, e.g., regulation status, time properties.","min":0,"max":"*","base":{"path":"Device.property","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.property.id","path":"Device.property.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.property.extension","path":"Device.property.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.property.modifierExtension","path":"Device.property.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.property.type","path":"Device.property.type","short":"Code that specifies the property DeviceDefinitionPropetyCode (Extensible)","definition":"Code that specifies the property DeviceDefinitionPropetyCode (Extensible).","min":1,"max":"1","base":{"path":"Device.property.type","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.property.valueQuantity","path":"Device.property.valueQuantity","short":"Property value as a quantity","definition":"Property value as a quantity.","min":0,"max":"*","base":{"path":"Device.property.valueQuantity","min":0,"max":"*"},"type":[{"code":"Quantity"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.property.valueCode","path":"Device.property.valueCode","short":"Property value as a code, e.g., NTP4 (synced to NTP)","definition":"Property value as a code, e.g., NTP4 (synced to NTP).","min":0,"max":"*","base":{"path":"Device.property.valueCode","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.patient","path":"Device.patient","short":"Patient to whom Device is affixed","definition":"Patient information, If the device is affixed to a person.","requirements":"If the device is implanted in a patient, then need to associate the device to the patient.","min":1,"max":"1","base":{"path":"Device.patient","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.owner","path":"Device.owner","short":"Organization responsible for device","definition":"An organization that is responsible for the provision and ongoing maintenance of the device.","min":0,"max":"1","base":{"path":"Device.owner","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.contact","path":"Device.contact","short":"Details for human/organization for support","definition":"Contact details for an organization or a particular human that is responsible for the device.","comment":"used for troubleshooting etc.","min":0,"max":"*","base":{"path":"Device.contact","min":0,"max":"*"},"type":[{"code":"ContactPoint"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.location","path":"Device.location","short":"Where the device is found","definition":"The place where the device can be found.","requirements":"Device.location can be used to track device location.","min":0,"max":"1","base":{"path":"Device.location","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Location"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.url","path":"Device.url","short":"Network address to contact device","definition":"A network address on which the device may be contacted directly.","comment":"If the device is running a FHIR server, the network address should be the Base URL from which a conformance statement may be retrieved.","min":0,"max":"1","base":{"path":"Device.url","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.note","path":"Device.note","short":"Device notes and comments","definition":"Descriptive information, usage information or implantation information that is not captured in an existing element.","min":0,"max":"*","base":{"path":"Device.note","min":0,"max":"*"},"type":[{"code":"Annotation"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.safety","path":"Device.safety","short":"Safety Characteristics of Device","definition":"Provides additional safety characteristics about a medical device. For example devices containing latex.","min":0,"max":"*","base":{"path":"Device.safety","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Device.parent","path":"Device.parent","short":"The parent device","definition":"The parent device.","min":0,"max":"1","base":{"path":"Device.parent","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Device"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false}]}},{"resourceType":"StructureDefinition","id":"us-core-blood-pressure","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-blood-pressure","version":"5.0.1","name":"USCoreBloodPressureProfile","title":"US Core Blood Pressure Profile","status":"active","experimental":false,"date":"2022-04-20","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"To promote interoperability and adoption through common implementation, this profile sets minimum expectations for the Observation resource to record, search, and fetch diastolic and systolic blood pressure observations with standard LOINC codes and UCUM units of measure. It is based on the US Core Vital Signs Profile and identifies the *additional* mandatory core elements, extensions, vocabularies and value sets which **SHALL** be present in the Observation resource when using this profile. It provides the floor for standards development for specific uses cases.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"resource","abstract":false,"type":"Observation","baseDefinition":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-vital-signs","derivation":"constraint","snapshot":{"element":[{"id":"Observation","path":"Observation","short":"US Core Blood Pressure Profile","definition":"\\-","comment":"\\-","alias":["Vital Signs","Measurement","Results","Tests"],"min":0,"max":"*","base":{"path":"Observation","min":0,"max":"*"},"constraint":[{"key":"dom-2","severity":"error","human":"If the resource is contained in another resource, it SHALL NOT contain nested Resources","expression":"contained.contained.empty()","xpath":"not(parent::f:contained and f:contained)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-3","severity":"error","human":"If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource","expression":"contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()","xpath":"not(exists(for $id in f:contained/*/f:id/@value return $contained[not(parent::*/descendant::f:reference/@value=concat('#', $contained/*/id/@value) or descendant::f:reference[@value='#'])]))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-4","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated","expression":"contained.meta.versionId.empty() and contained.meta.lastUpdated.empty()","xpath":"not(exists(f:contained/*/f:meta/f:versionId)) and not(exists(f:contained/*/f:meta/f:lastUpdated))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-5","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a security label","expression":"contained.meta.security.empty()","xpath":"not(exists(f:contained/*/f:meta/f:security))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice","valueBoolean":true},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice-explanation","valueMarkdown":"When a resource has no narrative, only systems that fully understand the data can display the resource to a human safely. Including a human readable representation in the resource makes for a much more robust eco-system and cheaper handling of resources by intermediary systems. Some ecosystems restrict distribution of resources to only those systems that do fully understand the resources, and as a consequence implementers may believe that the narrative is superfluous. However experience shows that such eco-systems often open up to new participants over time."}],"key":"dom-6","severity":"warning","human":"A resource should have narrative for robust management","expression":"text.`div`.exists()","xpath":"exists(f:text/h:div)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"obs-6","severity":"error","human":"dataAbsentReason SHALL only be present if Observation.value[x] is not present","expression":"dataAbsentReason.empty() or value.empty()","xpath":"not(exists(f:dataAbsentReason)) or (not(exists(*[starts-with(local-name(.), 'value')])))","source":"http://hl7.org/fhir/StructureDefinition/Observation"},{"key":"obs-7","severity":"error","human":"If Observation.code is the same as an Observation.component.code then the value element associated with the code SHALL NOT be present","expression":"value.empty() or component.code.where(coding.intersect(%resource.code.coding).exists()).empty()","xpath":"not(f:*[starts-with(local-name(.), 'value')] and (for $coding in f:code/f:coding return f:component/f:code/f:coding[f:code/@value=$coding/f:code/@value] [f:system/@value=$coding/f:system/@value]))","source":"http://hl7.org/fhir/StructureDefinition/Observation"},{"key":"vs-2","severity":"error","human":"If there is no component or hasMember element then either a value[x] or a data absent reason must be present.","expression":"(component.empty() and hasMember.empty()) implies (dataAbsentReason.exists() or value.exists())","xpath":"f:component or f:memberOF or f:*[starts-with(local-name(.), 'value')] or f:dataAbsentReason","source":"http://hl7.org/fhir/StructureDefinition/vitalsigns"}],"isModifier":false,"isSummary":false},{"id":"Observation.id","path":"Observation.id","short":"Logical id of this artifact","definition":"The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.","comment":"The only time that a resource does not have an id is when it is being submitted to the server using a create operation.","min":0,"max":"1","base":{"path":"Resource.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":true},{"id":"Observation.meta","path":"Observation.meta","short":"Metadata about the resource","definition":"The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource.","min":0,"max":"1","base":{"path":"Resource.meta","min":0,"max":"1"},"type":[{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.implicitRules","path":"Observation.implicitRules","short":"A set of rules under which this content was created","definition":"A reference to a set of rules that were followed when the resource was constructed, and which must be understood when processing the content. Often, this is a reference to an implementation guide that defines the special rules along with other profiles etc.","comment":"Asserting this rule set restricts the content to be only understood by a limited set of trading partners. This inherently limits the usefulness of the data in the long term. However, the existing health eco-system is highly fractured, and not yet ready to define, collect, and exchange data in a generally computable sense. Wherever possible, implementers and/or specification writers should avoid using this element. Often, when used, the URL is a reference to an implementation guide that defines these special rules as part of it's narrative along with other profiles, value sets, etc.","min":0,"max":"1","base":{"path":"Resource.implicitRules","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because the implicit rules may provide additional knowledge about the resource that modifies it's meaning or interpretation","isSummary":true},{"id":"Observation.language","path":"Observation.language","short":"Language of the resource content","definition":"The base language in which the resource is written.","comment":"Language is provided to support indexing and accessibility (typically, services such as text to speech use the language tag). The html language tag in the narrative applies to the narrative. The language tag on the resource may be used to specify the language of other presentations generated from the data in the resource. Not all the content has to be in the base language. The Resource.language should not be assumed to apply to the narrative automatically. If a language is specified, it should it also be specified on the div element in the html (see rules in HTML5 for information about the relationship between xml:lang and the html lang attribute).","min":0,"max":"1","base":{"path":"Resource.language","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet","valueCanonical":"http://hl7.org/fhir/ValueSet/all-languages"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"Language"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"preferred","description":"A human language.","valueSet":"http://hl7.org/fhir/ValueSet/languages"}},{"id":"Observation.text","path":"Observation.text","short":"Text summary of the resource, for human interpretation","definition":"A human-readable narrative that contains a summary of the resource and can be used to represent the content of the resource to a human. The narrative need not encode all the structured data, but is required to contain sufficient detail to make it \"clinically safe\" for a human to just read the narrative. Resource definitions may define what content should be represented in the narrative to ensure clinical safety.","comment":"Contained resources do not have narrative. Resources that are not contained SHOULD have a narrative. In some cases, a resource may only have text with little or no additional discrete data (as long as all minOccurs=1 elements are satisfied). This may be necessary for data from legacy systems where information is captured as a \"text blob\" or where text is additionally entered raw or narrated and encoded information is added later.","alias":["narrative","html","xhtml","display"],"min":0,"max":"1","base":{"path":"DomainResource.text","min":0,"max":"1"},"type":[{"code":"Narrative"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.contained","path":"Observation.contained","short":"Contained, inline Resources","definition":"These resources do not have an independent existence apart from the resource that contains them - they cannot be identified independently, and nor can they have their own independent transaction scope.","comment":"This should never be done when the content can be identified properly, as once identification is lost, it is extremely difficult (and context dependent) to restore it again. Contained resources may have profiles and tags In their meta elements, but SHALL NOT have security labels.","alias":["inline resources","anonymous resources","contained resources"],"min":0,"max":"*","base":{"path":"DomainResource.contained","min":0,"max":"*"},"type":[{"code":"Resource"}],"isModifier":false,"isSummary":false},{"id":"Observation.extension","path":"Observation.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the resource. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.modifierExtension","path":"Observation.modifierExtension","short":"Extensions that cannot be ignored","definition":"May be used to represent additional information that is not part of the basic definition of the resource and that modifies the understanding of the element that contains it and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/extensibility.html#modifierExtension).","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the resource that contains them","isSummary":false},{"id":"Observation.identifier","path":"Observation.identifier","short":"Business Identifier for observation","definition":"A unique identifier assigned to this observation.","requirements":"Allows observations to be distinguished and referenced.","min":0,"max":"*","base":{"path":"Observation.identifier","min":0,"max":"*"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.basedOn","path":"Observation.basedOn","short":"Fulfills plan, proposal or order","definition":"A plan, proposal or order that is fulfilled in whole or in part by this event. For example, a MedicationRequest may require a patient to have laboratory test performed before it is dispensed.","requirements":"Allows tracing of authorization for the event and tracking whether proposals/recommendations were acted upon.","alias":["Fulfills"],"min":0,"max":"*","base":{"path":"Observation.basedOn","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/CarePlan","http://hl7.org/fhir/StructureDefinition/DeviceRequest","http://hl7.org/fhir/StructureDefinition/ImmunizationRecommendation","http://hl7.org/fhir/StructureDefinition/MedicationRequest","http://hl7.org/fhir/StructureDefinition/NutritionOrder","http://hl7.org/fhir/StructureDefinition/ServiceRequest"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.partOf","path":"Observation.partOf","short":"Part of referenced event","definition":"A larger event of which this particular Observation is a component or step. For example, an observation as part of a procedure.","comment":"To link an Observation to an Encounter use `encounter`. See the [Notes](http://hl7.org/fhir/observation.html#obsgrouping) below for guidance on referencing another Observation.","alias":["Container"],"min":0,"max":"*","base":{"path":"Observation.partOf","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/MedicationAdministration","http://hl7.org/fhir/StructureDefinition/MedicationDispense","http://hl7.org/fhir/StructureDefinition/MedicationStatement","http://hl7.org/fhir/StructureDefinition/Procedure","http://hl7.org/fhir/StructureDefinition/Immunization","http://hl7.org/fhir/StructureDefinition/ImagingStudy"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.status","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-display-hint","valueString":"default: final"}],"path":"Observation.status","short":"registered | preliminary | final | amended +","definition":"The status of the result value.","comment":"This element is labeled as a modifier because the status contains codes that mark the resource as not currently valid.","requirements":"Need to track the status of individual results. Some results are finalized before the whole report is finalized.","min":1,"max":"1","base":{"path":"Observation.status","min":1,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":true,"isModifierReason":"This element is labeled as a modifier because it is a status element that contains status entered-in-error which means that the resource should not be treated as valid","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"Status"}],"strength":"required","valueSet":"http://hl7.org/fhir/ValueSet/observation-status|4.0.1"}},{"id":"Observation.category","path":"Observation.category","slicing":{"discriminator":[{"type":"value","path":"coding.code"},{"type":"value","path":"coding.system"}],"ordered":false,"rules":"open"},"short":"Classification of type of observation","definition":"A code that classifies the general type of observation being made.","comment":"In addition to the required category valueset, this element allows various categorization schemes based on the owner’s definition of the category and effectively multiple categories can be used at once. The level of granularity is defined by the category concepts in the value set.","requirements":"Used for filtering what observations are retrieved and displayed.","min":1,"max":"*","base":{"path":"Observation.category","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationCategory"}],"strength":"preferred","description":"Codes for high level observation categories.","valueSet":"http://hl7.org/fhir/ValueSet/observation-category"}},{"id":"Observation.category:VSCat","path":"Observation.category","sliceName":"VSCat","short":"Classification of type of observation","definition":"A code that classifies the general type of observation being made.","comment":"In addition to the required category valueset, this element allows various categorization schemes based on the owner’s definition of the category and effectively multiple categories can be used at once. The level of granularity is defined by the category concepts in the value set.","requirements":"Used for filtering what observations are retrieved and displayed.","min":1,"max":"1","base":{"path":"Observation.category","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationCategory"}],"strength":"preferred","description":"Codes for high level observation categories.","valueSet":"http://hl7.org/fhir/ValueSet/observation-category"}},{"id":"Observation.category:VSCat.id","path":"Observation.category.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.category:VSCat.extension","path":"Observation.category.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.category:VSCat.coding","path":"Observation.category.coding","short":"Code defined by a terminology system","definition":"A reference to a code defined by a terminology system.","comment":"Codes may be defined very casually in enumerations, or code lists, up to very formal definitions such as SNOMED CT - see the HL7 v3 Core Principles for more information. Ordering of codings is undefined and SHALL NOT be used to infer meaning. Generally, at most only one of the coding values will be labeled as UserSelected = true.","requirements":"Allows for alternative encodings within a code system, and translations to other code systems.","min":1,"max":"*","base":{"path":"CodeableConcept.coding","min":0,"max":"*"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.category:VSCat.coding.id","path":"Observation.category.coding.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.category:VSCat.coding.extension","path":"Observation.category.coding.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.category:VSCat.coding.system","path":"Observation.category.coding.system","short":"Identity of the terminology system","definition":"The identification of the code system that defines the meaning of the symbol in the code.","comment":"The URI may be an OID (urn:oid:...) or a UUID (urn:uuid:...). OIDs and UUIDs SHALL be references to the HL7 OID registry. Otherwise, the URI should come from HL7's list of FHIR defined special URIs or it should reference to some definition that establishes the system clearly and unambiguously.","requirements":"Need to be unambiguous about the source of the definition of the symbol.","min":1,"max":"1","base":{"path":"Coding.system","min":0,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"http://terminology.hl7.org/CodeSystem/observation-category","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.category:VSCat.coding.version","path":"Observation.category.coding.version","short":"Version of the system - if relevant","definition":"The version of the code system which was used when choosing this code. Note that a well-maintained code system does not need the version reported, because the meaning of codes is consistent across versions. However this cannot consistently be assured, and when the meaning is not guaranteed to be consistent, the version SHOULD be exchanged.","comment":"Where the terminology does not clearly define what string should be used to identify code system versions, the recommendation is to use the date (expressed in FHIR date format) on which that version was officially published as the version date.","min":0,"max":"1","base":{"path":"Coding.version","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.category:VSCat.coding.code","path":"Observation.category.coding.code","short":"Symbol in syntax defined by the system","definition":"A symbol in syntax defined by the system. The symbol may be a predefined code or an expression in a syntax defined by the coding system (e.g. post-coordination).","requirements":"Need to refer to a particular code in the system.","min":1,"max":"1","base":{"path":"Coding.code","min":0,"max":"1"},"type":[{"code":"code"}],"fixedCode":"vital-signs","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.category:VSCat.coding.display","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-translatable","valueBoolean":true}],"path":"Observation.category.coding.display","short":"Representation defined by the system","definition":"A representation of the meaning of the code in the system, following the rules of the system.","requirements":"Need to be able to carry a human-readable meaning of the code for readers that do not know the system.","min":0,"max":"1","base":{"path":"Coding.display","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.category:VSCat.coding.userSelected","path":"Observation.category.coding.userSelected","short":"If this coding was chosen directly by the user","definition":"Indicates that this coding was chosen by a user directly - e.g. off a pick list of available items (codes or displays).","comment":"Amongst a set of alternatives, a directly chosen code is the most appropriate starting point for new translations. There is some ambiguity about what exactly 'directly chosen' implies, and trading partner agreement may be needed to clarify the use of this element and its consequences more completely.","requirements":"This has been identified as a clinical safety criterium - that this exact system/code pair was chosen explicitly, rather than inferred by the system based on some rules or language processing.","min":0,"max":"1","base":{"path":"Coding.userSelected","min":0,"max":"1"},"type":[{"code":"boolean"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.category:VSCat.text","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-translatable","valueBoolean":true}],"path":"Observation.category.text","short":"Plain text representation of the concept","definition":"A human language representation of the concept as seen/selected/uttered by the user who entered the data and/or which represents the intended meaning of the user.","comment":"Very often the text is the same as a displayName of one of the codings.","requirements":"The codes from the terminologies do not always capture the correct meaning with all the nuances of the human using them, or sometimes there is no appropriate code at all. In these cases, the text is used to capture the full meaning of the source.","min":0,"max":"1","base":{"path":"CodeableConcept.text","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.code","path":"Observation.code","short":"Blood Pressure","definition":"Coded Responses from C-CDA Vital Sign Results.","comment":"*All* code-value and, if present, component.code-component.value pairs need to be taken into account to correctly understand the meaning of the observation.","requirements":"5. SHALL contain exactly one [1..1] code, where the @code SHOULD be selected from ValueSet HITSP Vital Sign Result Type 2.16.840.1.113883.3.88.12.80.62 DYNAMIC (CONF:7301).","alias":["Name"],"min":1,"max":"1","base":{"path":"Observation.code","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"patternCodeableConcept":{"coding":[{"system":"http://loinc.org","code":"85354-9"}]},"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"The vital sign codes from the base FHIR and US Core Vital Signs.","valueSet":"http://hl7.org/fhir/us/core/ValueSet/us-core-vital-signs"}},{"id":"Observation.subject","path":"Observation.subject","short":"Who and/or what the observation is about","definition":"The patient, or group of patients, location, or device this observation is about and into whose record the observation is placed. If the actual focus of the observation is different from the subject (or a sample of, part, or region of the subject), the `focus` element or the `code` itself specifies the actual focus of the observation.","comment":"One would expect this element to be a cardinality of 1..1. The only circumstance in which the subject can be missing is when the observation is made by a device that does not know the patient. In this case, the observation SHALL be matched to a patient through some context/channel matching technique, and at this point, the observation should be updated.","requirements":"Observations have no value if you don't know who or what they're about.","min":1,"max":"1","base":{"path":"Observation.subject","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.focus","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status","valueCode":"trial-use"}],"path":"Observation.focus","short":"What the observation is about, when it is not about the subject of record","definition":"The actual focus of an observation when it is not the patient of record representing something or someone associated with the patient such as a spouse, parent, fetus, or donor. For example, fetus observations in a mother's record. The focus of an observation could also be an existing condition, an intervention, the subject's diet, another observation of the subject, or a body structure such as tumor or implanted device. An example use case would be using the Observation resource to capture whether the mother is trained to change her child's tracheostomy tube. In this example, the child is the patient of record and the mother is the focus.","comment":"Typically, an observation is made about the subject - a patient, or group of patients, location, or device - and the distinction between the subject and what is directly measured for an observation is specified in the observation code itself ( e.g., \"Blood Glucose\") and does not need to be represented separately using this element. Use `specimen` if a reference to a specimen is required. If a code is required instead of a resource use either `bodysite` for bodysites or the standard extension [focusCode](http://hl7.org/fhir/extension-observation-focuscode.html).","min":0,"max":"*","base":{"path":"Observation.focus","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Resource"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.encounter","path":"Observation.encounter","short":"Healthcare event during which this observation is made","definition":"The healthcare event (e.g. a patient and healthcare provider interaction) during which this observation is made.","comment":"This will typically be the encounter the event occurred within, but some events may be initiated prior to or after the official completion of an encounter but still be tied to the context of the encounter (e.g. pre-admission laboratory tests).","requirements":"For some observations it may be important to know the link between an observation and a particular encounter.","alias":["Context"],"min":0,"max":"1","base":{"path":"Observation.encounter","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Encounter"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.effective[x]","path":"Observation.effective[x]","short":"Often just a dateTime for Vital Signs","definition":"Often just a dateTime for Vital Signs.","comment":"At least a date should be present unless this observation is a historical report. For recording imprecise or \"fuzzy\" times (For example, a blood glucose measurement taken \"after breakfast\") use the [Timing](http://hl7.org/fhir/datatypes.html#timing) datatype which allow the measurement to be tied to regular life events.","requirements":"Knowing when an observation was deemed true is important to its relevance as well as determining trends.","alias":["Occurrence"],"min":1,"max":"1","base":{"path":"Observation.effective[x]","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"dateTime"},{"code":"Period"}],"condition":["vs-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"vs-1","severity":"error","human":"if Observation.effective[x] is dateTime and has a value then that value shall be precise to the day","expression":"($this as dateTime).toString().length() >= 8","xpath":"f:effectiveDateTime[matches(@value, '^\\d{4}-\\d{2}-\\d{2}')]","source":"http://hl7.org/fhir/StructureDefinition/vitalsigns"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.issued","path":"Observation.issued","short":"Date/Time this version was made available","definition":"The date and time this version of the observation was made available to providers, typically after the results have been reviewed and verified.","comment":"For Observations that don’t require review and verification, it may be the same as the [`lastUpdated` ](http://hl7.org/fhir/resource-definitions.html#Meta.lastUpdated) time of the resource itself. For Observations that do require review and verification for certain updates, it might not be the same as the `lastUpdated` time of the resource itself due to a non-clinically significant update that doesn’t require the new version to be reviewed and verified again.","min":0,"max":"1","base":{"path":"Observation.issued","min":0,"max":"1"},"type":[{"code":"instant"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.performer","path":"Observation.performer","short":"Who is responsible for the observation","definition":"Who was responsible for asserting the observed value as \"true\".","requirements":"May give a degree of confidence in the observation and also indicates where follow-up questions should be directed.","min":0,"max":"*","base":{"path":"Observation.performer","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Practitioner","http://hl7.org/fhir/StructureDefinition/PractitionerRole","http://hl7.org/fhir/StructureDefinition/Organization","http://hl7.org/fhir/StructureDefinition/CareTeam","http://hl7.org/fhir/StructureDefinition/Patient","http://hl7.org/fhir/StructureDefinition/RelatedPerson"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.value[x]","path":"Observation.value[x]","short":"Vital Signs Value","definition":"Vital Signs value are typically recorded using the Quantity data type.","comment":"An observation may have; 1) a single value here, 2) both a value and a set of related or component values, or 3) only a set of related or component values. If a value is present, the datatype for this element should be determined by Observation.code. A CodeableConcept with just a text would be used instead of a string if the field was usually coded, or if the type associated with the Observation.code defines a coded value. For additional guidance, see the [Notes section](http://hl7.org/fhir/observation.html#notes) below.","requirements":"9. SHALL contain exactly one [1..1] value with @xsi:type=\"PQ\" (CONF:7305).","min":0,"max":"1","base":{"path":"Observation.value[x]","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"Quantity"},{"code":"CodeableConcept"},{"code":"string"},{"code":"boolean"},{"code":"integer"},{"code":"Range"},{"code":"Ratio"},{"code":"SampledData"},{"code":"time"},{"code":"dateTime"},{"code":"Period"}],"condition":["obs-7","vs-2"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"Common UCUM units for recording Vital Signs.","valueSet":"http://hl7.org/fhir/ValueSet/ucum-vitals-common|4.0.1"}},{"id":"Observation.dataAbsentReason","path":"Observation.dataAbsentReason","short":"Why the result is missing","definition":"Provides a reason why the expected value in the element Observation.value[x] is missing.","comment":"Null or exceptional values can be represented two ways in FHIR Observations. One way is to simply include them in the value set and represent the exceptions in the value. For example, measurement values for a serology test could be \"detected\", \"not detected\", \"inconclusive\", or \"specimen unsatisfactory\". \n\nThe alternate way is to use the value element for actual observations and use the explicit dataAbsentReason element to record exceptional values. For example, the dataAbsentReason code \"error\" could be used when the measurement was not completed. Note that an observation may only be reported if there are values to report. For example differential cell counts values may be reported only when > 0. Because of these options, use-case agreements are required to interpret general observations for null or exceptional values.","requirements":"For many results it is necessary to handle exceptional values in measurements.","min":0,"max":"1","base":{"path":"Observation.dataAbsentReason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"condition":["obs-6","vs-2"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationValueAbsentReason"}],"strength":"extensible","description":"Codes specifying why the result (`Observation.value[x]`) is missing.","valueSet":"http://hl7.org/fhir/ValueSet/data-absent-reason"}},{"id":"Observation.interpretation","path":"Observation.interpretation","short":"High, low, normal, etc.","definition":"A categorical assessment of an observation value. For example, high, low, normal.","comment":"Historically used for laboratory results (known as 'abnormal flag' ), its use extends to other use cases where coded interpretations are relevant. Often reported as one or more simple compact codes this element is often placed adjacent to the result value in reports and flow sheets to signal the meaning/normalcy status of the result.","requirements":"For some results, particularly numeric results, an interpretation is necessary to fully understand the significance of a result.","alias":["Abnormal Flag"],"min":0,"max":"*","base":{"path":"Observation.interpretation","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationInterpretation"}],"strength":"extensible","description":"Codes identifying interpretations of observations.","valueSet":"http://hl7.org/fhir/ValueSet/observation-interpretation"}},{"id":"Observation.note","path":"Observation.note","short":"Comments about the observation","definition":"Comments about the observation or the results.","comment":"May include general statements about the observation, or statements about significant, unexpected or unreliable results values, or information about its source when relevant to its interpretation.","requirements":"Need to be able to provide free text additional information.","min":0,"max":"*","base":{"path":"Observation.note","min":0,"max":"*"},"type":[{"code":"Annotation"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.bodySite","path":"Observation.bodySite","short":"Observed body part","definition":"Indicates the site on the subject's body where the observation was made (i.e. the target site).","comment":"Only used if not implicit in code found in Observation.code. In many systems, this may be represented as a related observation instead of an inline component. \n\nIf the use case requires BodySite to be handled as a separate resource (e.g. to identify and track separately) then use the standard extension[ bodySite](http://hl7.org/fhir/extension-bodysite.html).","min":0,"max":"1","base":{"path":"Observation.bodySite","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"BodySite"}],"strength":"example","description":"Codes describing anatomical locations. May include laterality.","valueSet":"http://hl7.org/fhir/ValueSet/body-site"}},{"id":"Observation.method","path":"Observation.method","short":"How it was done","definition":"Indicates the mechanism used to perform the observation.","comment":"Only used if not implicit in code for Observation.code.","requirements":"In some cases, method can impact results and is thus used for determining whether results can be compared or determining significance of results.","min":0,"max":"1","base":{"path":"Observation.method","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationMethod"}],"strength":"example","description":"Methods for simple observations.","valueSet":"http://hl7.org/fhir/ValueSet/observation-methods"}},{"id":"Observation.specimen","path":"Observation.specimen","short":"Specimen used for this observation","definition":"The specimen that was used when this observation was made.","comment":"Should only be used if not implicit in code found in `Observation.code`. Observations are not made on specimens themselves; they are made on a subject, but in many cases by the means of a specimen. Note that although specimens are often involved, they are not always tracked and reported explicitly. Also note that observation resources may be used in contexts that track the specimen explicitly (e.g. Diagnostic Report).","min":0,"max":"1","base":{"path":"Observation.specimen","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Specimen"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.device","path":"Observation.device","short":"(Measurement) Device","definition":"The device used to generate the observation data.","comment":"Note that this is not meant to represent a device involved in the transmission of the result, e.g., a gateway. Such devices may be documented using the Provenance resource where relevant.","min":0,"max":"1","base":{"path":"Observation.device","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Device","http://hl7.org/fhir/StructureDefinition/DeviceMetric"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange","path":"Observation.referenceRange","short":"Provides guide for interpretation","definition":"Guidance on how to interpret the value by comparison to a normal or recommended range. Multiple reference ranges are interpreted as an \"OR\". In other words, to represent two distinct target populations, two `referenceRange` elements would be used.","comment":"Most observations only have one generic reference range. Systems MAY choose to restrict to only supplying the relevant reference range based on knowledge about the patient (e.g., specific to the patient's age, gender, weight and other factors), but this might not be possible or appropriate. Whenever more than one reference range is supplied, the differences between them SHOULD be provided in the reference range and/or age properties.","requirements":"Knowing what values are considered \"normal\" can help evaluate the significance of a particular result. Need to be able to provide multiple reference ranges for different contexts.","min":0,"max":"*","base":{"path":"Observation.referenceRange","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"obs-3","severity":"error","human":"Must have at least a low or a high or text","expression":"low.exists() or high.exists() or text.exists()","xpath":"(exists(f:low) or exists(f:high)or exists(f:text))","source":"http://hl7.org/fhir/StructureDefinition/vitalsigns"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange.id","path":"Observation.referenceRange.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange.extension","path":"Observation.referenceRange.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange.modifierExtension","path":"Observation.referenceRange.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Observation.referenceRange.low","path":"Observation.referenceRange.low","short":"Low Range, if relevant","definition":"The value of the low bound of the reference range. The low bound of the reference range endpoint is inclusive of the value (e.g. reference range is >=5 - <=9). If the low bound is omitted, it is assumed to be meaningless (e.g. reference range is <=2.3).","min":0,"max":"1","base":{"path":"Observation.referenceRange.low","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"condition":["obs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange.high","path":"Observation.referenceRange.high","short":"High Range, if relevant","definition":"The value of the high bound of the reference range. The high bound of the reference range endpoint is inclusive of the value (e.g. reference range is >=5 - <=9). If the high bound is omitted, it is assumed to be meaningless (e.g. reference range is >= 2.3).","min":0,"max":"1","base":{"path":"Observation.referenceRange.high","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"condition":["obs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange.type","path":"Observation.referenceRange.type","short":"Reference range qualifier","definition":"Codes to indicate the what part of the targeted reference population it applies to. For example, the normal or therapeutic range.","comment":"This SHOULD be populated if there is more than one range. If this element is not present then the normal range is assumed.","requirements":"Need to be able to say what kind of reference range this is - normal, recommended, therapeutic, etc., - for proper interpretation.","min":0,"max":"1","base":{"path":"Observation.referenceRange.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationRangeMeaning"}],"strength":"preferred","description":"Code for the meaning of a reference range.","valueSet":"http://hl7.org/fhir/ValueSet/referencerange-meaning"}},{"id":"Observation.referenceRange.appliesTo","path":"Observation.referenceRange.appliesTo","short":"Reference range population","definition":"Codes to indicate the target population this reference range applies to. For example, a reference range may be based on the normal population or a particular sex or race. Multiple `appliesTo` are interpreted as an \"AND\" of the target populations. For example, to represent a target population of African American females, both a code of female and a code for African American would be used.","comment":"This SHOULD be populated if there is more than one range. If this element is not present then the normal population is assumed.","requirements":"Need to be able to identify the target population for proper interpretation.","min":0,"max":"*","base":{"path":"Observation.referenceRange.appliesTo","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationRangeType"}],"strength":"example","description":"Codes identifying the population the reference range applies to.","valueSet":"http://hl7.org/fhir/ValueSet/referencerange-appliesto"}},{"id":"Observation.referenceRange.age","path":"Observation.referenceRange.age","short":"Applicable age range, if relevant","definition":"The age at which this reference range is applicable. This is a neonatal age (e.g. number of weeks at term) if the meaning says so.","requirements":"Some analytes vary greatly over age.","min":0,"max":"1","base":{"path":"Observation.referenceRange.age","min":0,"max":"1"},"type":[{"code":"Range"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange.text","path":"Observation.referenceRange.text","short":"Text based reference range in an observation","definition":"Text based reference range in an observation which may be used when a quantitative range is not appropriate for an observation. An example would be a reference value of \"Negative\" or a list or table of \"normals\".","min":0,"max":"1","base":{"path":"Observation.referenceRange.text","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.hasMember","path":"Observation.hasMember","short":"Used when reporting vital signs panel components","definition":"Used when reporting vital signs panel components.","comment":"When using this element, an observation will typically have either a value or a set of related resources, although both may be present in some cases. For a discussion on the ways Observations can assembled in groups together, see [Notes](http://hl7.org/fhir/observation.html#obsgrouping) below. Note that a system may calculate results from [QuestionnaireResponse](http://hl7.org/fhir/questionnaireresponse.html) into a final score and represent the score as an Observation.","min":0,"max":"*","base":{"path":"Observation.hasMember","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/QuestionnaireResponse","http://hl7.org/fhir/StructureDefinition/MolecularSequence","http://hl7.org/fhir/StructureDefinition/vitalsigns"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.derivedFrom","path":"Observation.derivedFrom","short":"Related measurements the observation is made from","definition":"The target resource that represents a measurement from which this observation value is derived. For example, a calculated anion gap or a fetal measurement based on an ultrasound image.","comment":"All the reference choices that are listed in this element can represent clinical observations and other measurements that may be the source for a derived value. The most common reference will be another Observation. For a discussion on the ways Observations can assembled in groups together, see [Notes](http://hl7.org/fhir/observation.html#obsgrouping) below.","min":0,"max":"*","base":{"path":"Observation.derivedFrom","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/DocumentReference","http://hl7.org/fhir/StructureDefinition/ImagingStudy","http://hl7.org/fhir/StructureDefinition/Media","http://hl7.org/fhir/StructureDefinition/QuestionnaireResponse","http://hl7.org/fhir/StructureDefinition/MolecularSequence","http://hl7.org/fhir/StructureDefinition/vitalsigns"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.component","path":"Observation.component","slicing":{"discriminator":[{"type":"pattern","path":"code"}],"ordered":false,"rules":"open"},"short":"Component observations","definition":"Used when reporting component observation such as systolic and diastolic blood pressure.","comment":"For a discussion on the ways Observations can be assembled in groups together see [Notes](http://hl7.org/fhir/observation.html#notes) below.","requirements":"Component observations share the same attributes in the Observation resource as the primary observation and are always treated a part of a single observation (they are not separable). However, the reference range for the primary observation value is not inherited by the component values and is required when appropriate for each component observation.","min":2,"max":"*","base":{"path":"Observation.component","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"vs-3","severity":"error","human":"If there is no a value a data absent reason must be present","expression":"value.exists() or dataAbsentReason.exists()","xpath":"f:*[starts-with(local-name(.), 'value')] or f:dataAbsentReason","source":"http://hl7.org/fhir/StructureDefinition/vitalsigns"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component.id","path":"Observation.component.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.component.extension","path":"Observation.component.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.component.modifierExtension","path":"Observation.component.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Observation.component.code","path":"Observation.component.code","short":"Type of component observation (code / type)","definition":"Describes what was observed. Sometimes this is called the observation \"code\".","comment":"*All* code-value and component.code-component.value pairs need to be taken into account to correctly understand the meaning of the observation.","requirements":"Knowing what kind of observation is being made is essential to understanding the observation.","min":1,"max":"1","base":{"path":"Observation.component.code","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"The vital sign codes from the base FHIR and US Core Vital Signs.","valueSet":"http://hl7.org/fhir/us/core/ValueSet/us-core-vital-signs"}},{"id":"Observation.component.value[x]","path":"Observation.component.value[x]","short":"Vital Sign Component Value","definition":"Vital Signs value are typically recorded using the Quantity data type. For supporting observations such as cuff size could use other datatypes such as CodeableConcept.","comment":"Used when observation has a set of component observations. An observation may have both a value (e.g. an Apgar score) and component observations (the observations from which the Apgar score was derived). If a value is present, the datatype for this element should be determined by Observation.code. A CodeableConcept with just a text would be used instead of a string if the field was usually coded, or if the type associated with the Observation.code defines a coded value. For additional guidance, see the [Notes section](http://hl7.org/fhir/observation.html#notes) below.","requirements":"9. SHALL contain exactly one [1..1] value with @xsi:type=\"PQ\" (CONF:7305).","min":0,"max":"1","base":{"path":"Observation.component.value[x]","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"Quantity"},{"code":"CodeableConcept"},{"code":"string"},{"code":"boolean"},{"code":"integer"},{"code":"Range"},{"code":"Ratio"},{"code":"SampledData"},{"code":"time"},{"code":"dateTime"},{"code":"Period"}],"condition":["vs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"Common UCUM units for recording Vital Signs.","valueSet":"http://hl7.org/fhir/ValueSet/ucum-vitals-common|4.0.1"}},{"id":"Observation.component.dataAbsentReason","path":"Observation.component.dataAbsentReason","short":"Why the component result is missing","definition":"Provides a reason why the expected value in the element Observation.component.value[x] is missing.","comment":"\"Null\" or exceptional values can be represented two ways in FHIR Observations. One way is to simply include them in the value set and represent the exceptions in the value. For example, measurement values for a serology test could be \"detected\", \"not detected\", \"inconclusive\", or \"test not done\". \n\nThe alternate way is to use the value element for actual observations and use the explicit dataAbsentReason element to record exceptional values. For example, the dataAbsentReason code \"error\" could be used when the measurement was not completed. Because of these options, use-case agreements are required to interpret general observations for exceptional values.","requirements":"For many results it is necessary to handle exceptional values in measurements.","min":0,"max":"1","base":{"path":"Observation.component.dataAbsentReason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"condition":["obs-6","vs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationValueAbsentReason"}],"strength":"extensible","description":"Codes specifying why the result (`Observation.value[x]`) is missing.","valueSet":"http://hl7.org/fhir/ValueSet/data-absent-reason"}},{"id":"Observation.component.interpretation","path":"Observation.component.interpretation","short":"High, low, normal, etc.","definition":"A categorical assessment of an observation value. For example, high, low, normal.","comment":"Historically used for laboratory results (known as 'abnormal flag' ), its use extends to other use cases where coded interpretations are relevant. Often reported as one or more simple compact codes this element is often placed adjacent to the result value in reports and flow sheets to signal the meaning/normalcy status of the result.","requirements":"For some results, particularly numeric results, an interpretation is necessary to fully understand the significance of a result.","alias":["Abnormal Flag"],"min":0,"max":"*","base":{"path":"Observation.component.interpretation","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationInterpretation"}],"strength":"extensible","description":"Codes identifying interpretations of observations.","valueSet":"http://hl7.org/fhir/ValueSet/observation-interpretation"}},{"id":"Observation.component.referenceRange","path":"Observation.component.referenceRange","short":"Provides guide for interpretation of component result","definition":"Guidance on how to interpret the value by comparison to a normal or recommended range.","comment":"Most observations only have one generic reference range. Systems MAY choose to restrict to only supplying the relevant reference range based on knowledge about the patient (e.g., specific to the patient's age, gender, weight and other factors), but this might not be possible or appropriate. Whenever more than one reference range is supplied, the differences between them SHOULD be provided in the reference range and/or age properties.","requirements":"Knowing what values are considered \"normal\" can help evaluate the significance of a particular result. Need to be able to provide multiple reference ranges for different contexts.","min":0,"max":"*","base":{"path":"Observation.component.referenceRange","min":0,"max":"*"},"contentReference":"http://hl7.org/fhir/StructureDefinition/Observation#Observation.referenceRange","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:systolic","path":"Observation.component","sliceName":"systolic","short":"Systolic Blood Pressure","definition":"Used when reporting component observation such as systolic and diastolic blood pressure.","comment":"For a discussion on the ways Observations can be assembled in groups together see [Notes](http://hl7.org/fhir/observation.html#notes) below.","requirements":"Component observations share the same attributes in the Observation resource as the primary observation and are always treated a part of a single observation (they are not separable). However, the reference range for the primary observation value is not inherited by the component values and is required when appropriate for each component observation.","min":1,"max":"1","base":{"path":"Observation.component","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"vs-3","severity":"error","human":"If there is no a value a data absent reason must be present","expression":"value.exists() or dataAbsentReason.exists()","xpath":"f:*[starts-with(local-name(.), 'value')] or f:dataAbsentReason","source":"http://hl7.org/fhir/StructureDefinition/vitalsigns"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:systolic.id","path":"Observation.component.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:systolic.extension","path":"Observation.component.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:systolic.modifierExtension","path":"Observation.component.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Observation.component:systolic.code","path":"Observation.component.code","short":"Systolic Blood Pressure Code","definition":"Describes what was observed. Sometimes this is called the observation \"code\".","comment":"*All* code-value and component.code-component.value pairs need to be taken into account to correctly understand the meaning of the observation.","requirements":"Knowing what kind of observation is being made is essential to understanding the observation.","min":1,"max":"1","base":{"path":"Observation.component.code","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"patternCodeableConcept":{"coding":[{"system":"http://loinc.org","code":"8480-6"}]},"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"The vital sign codes from the base FHIR and US Core Vital Signs.","valueSet":"http://hl7.org/fhir/us/core/ValueSet/us-core-vital-signs"}},{"id":"Observation.component:systolic.value[x]","path":"Observation.component.value[x]","short":"Vital Sign Component Value","definition":"Vital Signs value are typically recorded using the Quantity data type. For supporting observations such as cuff size could use other datatypes such as CodeableConcept.","comment":"Used when observation has a set of component observations. An observation may have both a value (e.g. an Apgar score) and component observations (the observations from which the Apgar score was derived). If a value is present, the datatype for this element should be determined by Observation.code. A CodeableConcept with just a text would be used instead of a string if the field was usually coded, or if the type associated with the Observation.code defines a coded value. For additional guidance, see the [Notes section](http://hl7.org/fhir/observation.html#notes) below.","requirements":"9. SHALL contain exactly one [1..1] value with @xsi:type=\"PQ\" (CONF:7305).","min":0,"max":"1","base":{"path":"Observation.component.value[x]","min":0,"max":"1"},"type":[{"code":"Quantity"}],"condition":["vs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"Common UCUM units for recording Vital Signs.","valueSet":"http://hl7.org/fhir/ValueSet/ucum-vitals-common|4.0.1"}},{"id":"Observation.component:systolic.value[x].id","path":"Observation.component.value[x].id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:systolic.value[x].extension","path":"Observation.component.value[x].extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:systolic.value[x].value","path":"Observation.component.value[x].value","short":"Numerical value (with implicit precision)","definition":"The value of the measured amount. The value includes an implicit precision in the presentation of the value.","comment":"The implicit precision in the value should always be honored. Monetary values have their own rules for handling precision (refer to standard accounting text books).","requirements":"Precision is handled implicitly in almost all cases of measurement.","min":1,"max":"1","base":{"path":"Quantity.value","min":0,"max":"1"},"type":[{"code":"decimal"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:systolic.value[x].comparator","path":"Observation.component.value[x].comparator","short":"< | <= | >= | > - how to understand the value","definition":"How the value should be understood and represented - whether the actual value is greater or less than the stated value due to measurement issues; e.g. if the comparator is \"<\" , then the real value is < stated value.","requirements":"Need a framework for handling measures where the value is <5ug/L or >400mg/L due to the limitations of measuring methodology.","min":0,"max":"1","base":{"path":"Quantity.comparator","min":0,"max":"1"},"type":[{"code":"code"}],"meaningWhenMissing":"If there is no comparator, then there is no modification of the value","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because the comparator modifies the interpretation of the value significantly. If there is no comparator, then there is no modification of the value","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"QuantityComparator"}],"strength":"required","description":"How the Quantity should be understood and represented.","valueSet":"http://hl7.org/fhir/ValueSet/quantity-comparator|4.0.1"}},{"id":"Observation.component:systolic.value[x].unit","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-translatable","valueBoolean":true}],"path":"Observation.component.value[x].unit","short":"Unit representation","definition":"A human-readable form of the unit.","requirements":"There are many representations for units of measure and in many contexts, particular representations are fixed and required. I.e. mcg for micrograms.","min":1,"max":"1","base":{"path":"Quantity.unit","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:systolic.value[x].system","path":"Observation.component.value[x].system","short":"System that defines coded unit form","definition":"The identification of the system that provides the coded form of the unit.","requirements":"Need to know the system that defines the coded form of the unit.","min":1,"max":"1","base":{"path":"Quantity.system","min":0,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"http://unitsofmeasure.org","condition":["qty-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:systolic.value[x].code","path":"Observation.component.value[x].code","short":"Coded form of the unit","definition":"A computer processable form of the unit in some unit representation system.","comment":"The preferred system is UCUM, but SNOMED CT can also be used (for customary units) or ISO 4217 for currency. The context of use may additionally require a code from a particular system.","requirements":"Need a computable form of the unit that is fixed across all forms. UCUM provides this for quantities, but SNOMED CT provides many units of interest.","min":1,"max":"1","base":{"path":"Quantity.code","min":0,"max":"1"},"type":[{"code":"code"}],"fixedCode":"mm[Hg]","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:systolic.dataAbsentReason","path":"Observation.component.dataAbsentReason","short":"Why the component result is missing","definition":"Provides a reason why the expected value in the element Observation.component.value[x] is missing.","comment":"\"Null\" or exceptional values can be represented two ways in FHIR Observations. One way is to simply include them in the value set and represent the exceptions in the value. For example, measurement values for a serology test could be \"detected\", \"not detected\", \"inconclusive\", or \"test not done\". \n\nThe alternate way is to use the value element for actual observations and use the explicit dataAbsentReason element to record exceptional values. For example, the dataAbsentReason code \"error\" could be used when the measurement was not completed. Because of these options, use-case agreements are required to interpret general observations for exceptional values.","requirements":"For many results it is necessary to handle exceptional values in measurements.","min":0,"max":"1","base":{"path":"Observation.component.dataAbsentReason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"condition":["obs-6","vs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationValueAbsentReason"}],"strength":"extensible","description":"Codes specifying why the result (`Observation.value[x]`) is missing.","valueSet":"http://hl7.org/fhir/ValueSet/data-absent-reason"}},{"id":"Observation.component:systolic.interpretation","path":"Observation.component.interpretation","short":"High, low, normal, etc.","definition":"A categorical assessment of an observation value. For example, high, low, normal.","comment":"Historically used for laboratory results (known as 'abnormal flag' ), its use extends to other use cases where coded interpretations are relevant. Often reported as one or more simple compact codes this element is often placed adjacent to the result value in reports and flow sheets to signal the meaning/normalcy status of the result.","requirements":"For some results, particularly numeric results, an interpretation is necessary to fully understand the significance of a result.","alias":["Abnormal Flag"],"min":0,"max":"*","base":{"path":"Observation.component.interpretation","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationInterpretation"}],"strength":"extensible","description":"Codes identifying interpretations of observations.","valueSet":"http://hl7.org/fhir/ValueSet/observation-interpretation"}},{"id":"Observation.component:systolic.referenceRange","path":"Observation.component.referenceRange","short":"Provides guide for interpretation of component result","definition":"Guidance on how to interpret the value by comparison to a normal or recommended range.","comment":"Most observations only have one generic reference range. Systems MAY choose to restrict to only supplying the relevant reference range based on knowledge about the patient (e.g., specific to the patient's age, gender, weight and other factors), but this might not be possible or appropriate. Whenever more than one reference range is supplied, the differences between them SHOULD be provided in the reference range and/or age properties.","requirements":"Knowing what values are considered \"normal\" can help evaluate the significance of a particular result. Need to be able to provide multiple reference ranges for different contexts.","min":0,"max":"*","base":{"path":"Observation.component.referenceRange","min":0,"max":"*"},"contentReference":"http://hl7.org/fhir/StructureDefinition/Observation#Observation.referenceRange","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:diastolic","path":"Observation.component","sliceName":"diastolic","short":"Diastolic Blood Pressure","definition":"Used when reporting component observation such as systolic and diastolic blood pressure.","comment":"For a discussion on the ways Observations can be assembled in groups together see [Notes](http://hl7.org/fhir/observation.html#notes) below.","requirements":"Component observations share the same attributes in the Observation resource as the primary observation and are always treated a part of a single observation (they are not separable). However, the reference range for the primary observation value is not inherited by the component values and is required when appropriate for each component observation.","min":1,"max":"1","base":{"path":"Observation.component","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"vs-3","severity":"error","human":"If there is no a value a data absent reason must be present","expression":"value.exists() or dataAbsentReason.exists()","xpath":"f:*[starts-with(local-name(.), 'value')] or f:dataAbsentReason","source":"http://hl7.org/fhir/StructureDefinition/vitalsigns"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:diastolic.id","path":"Observation.component.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:diastolic.extension","path":"Observation.component.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:diastolic.modifierExtension","path":"Observation.component.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Observation.component:diastolic.code","path":"Observation.component.code","short":"Diastolic Blood Pressure Code","definition":"Describes what was observed. Sometimes this is called the observation \"code\".","comment":"*All* code-value and component.code-component.value pairs need to be taken into account to correctly understand the meaning of the observation.","requirements":"Knowing what kind of observation is being made is essential to understanding the observation.","min":1,"max":"1","base":{"path":"Observation.component.code","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"patternCodeableConcept":{"coding":[{"system":"http://loinc.org","code":"8462-4"}]},"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"The vital sign codes from the base FHIR and US Core Vital Signs.","valueSet":"http://hl7.org/fhir/us/core/ValueSet/us-core-vital-signs"}},{"id":"Observation.component:diastolic.value[x]","path":"Observation.component.value[x]","short":"Vital Sign Component Value","definition":"Vital Signs value are typically recorded using the Quantity data type. For supporting observations such as cuff size could use other datatypes such as CodeableConcept.","comment":"Used when observation has a set of component observations. An observation may have both a value (e.g. an Apgar score) and component observations (the observations from which the Apgar score was derived). If a value is present, the datatype for this element should be determined by Observation.code. A CodeableConcept with just a text would be used instead of a string if the field was usually coded, or if the type associated with the Observation.code defines a coded value. For additional guidance, see the [Notes section](http://hl7.org/fhir/observation.html#notes) below.","requirements":"9. SHALL contain exactly one [1..1] value with @xsi:type=\"PQ\" (CONF:7305).","min":0,"max":"1","base":{"path":"Observation.component.value[x]","min":0,"max":"1"},"type":[{"code":"Quantity"}],"condition":["vs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"Common UCUM units for recording Vital Signs.","valueSet":"http://hl7.org/fhir/ValueSet/ucum-vitals-common|4.0.1"}},{"id":"Observation.component:diastolic.value[x].id","path":"Observation.component.value[x].id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:diastolic.value[x].extension","path":"Observation.component.value[x].extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:diastolic.value[x].value","path":"Observation.component.value[x].value","short":"Numerical value (with implicit precision)","definition":"The value of the measured amount. The value includes an implicit precision in the presentation of the value.","comment":"The implicit precision in the value should always be honored. Monetary values have their own rules for handling precision (refer to standard accounting text books).","requirements":"Precision is handled implicitly in almost all cases of measurement.","min":1,"max":"1","base":{"path":"Quantity.value","min":0,"max":"1"},"type":[{"code":"decimal"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:diastolic.value[x].comparator","path":"Observation.component.value[x].comparator","short":"< | <= | >= | > - how to understand the value","definition":"How the value should be understood and represented - whether the actual value is greater or less than the stated value due to measurement issues; e.g. if the comparator is \"<\" , then the real value is < stated value.","requirements":"Need a framework for handling measures where the value is <5ug/L or >400mg/L due to the limitations of measuring methodology.","min":0,"max":"1","base":{"path":"Quantity.comparator","min":0,"max":"1"},"type":[{"code":"code"}],"meaningWhenMissing":"If there is no comparator, then there is no modification of the value","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because the comparator modifies the interpretation of the value significantly. If there is no comparator, then there is no modification of the value","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"QuantityComparator"}],"strength":"required","description":"How the Quantity should be understood and represented.","valueSet":"http://hl7.org/fhir/ValueSet/quantity-comparator|4.0.1"}},{"id":"Observation.component:diastolic.value[x].unit","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-translatable","valueBoolean":true}],"path":"Observation.component.value[x].unit","short":"Unit representation","definition":"A human-readable form of the unit.","requirements":"There are many representations for units of measure and in many contexts, particular representations are fixed and required. I.e. mcg for micrograms.","min":1,"max":"1","base":{"path":"Quantity.unit","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:diastolic.value[x].system","path":"Observation.component.value[x].system","short":"System that defines coded unit form","definition":"The identification of the system that provides the coded form of the unit.","requirements":"Need to know the system that defines the coded form of the unit.","min":1,"max":"1","base":{"path":"Quantity.system","min":0,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"http://unitsofmeasure.org","condition":["qty-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:diastolic.value[x].code","path":"Observation.component.value[x].code","short":"Coded form of the unit","definition":"A computer processable form of the unit in some unit representation system.","comment":"The preferred system is UCUM, but SNOMED CT can also be used (for customary units) or ISO 4217 for currency. The context of use may additionally require a code from a particular system.","requirements":"Need a computable form of the unit that is fixed across all forms. UCUM provides this for quantities, but SNOMED CT provides many units of interest.","min":1,"max":"1","base":{"path":"Quantity.code","min":0,"max":"1"},"type":[{"code":"code"}],"fixedCode":"mm[Hg]","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:diastolic.dataAbsentReason","path":"Observation.component.dataAbsentReason","short":"Why the component result is missing","definition":"Provides a reason why the expected value in the element Observation.component.value[x] is missing.","comment":"\"Null\" or exceptional values can be represented two ways in FHIR Observations. One way is to simply include them in the value set and represent the exceptions in the value. For example, measurement values for a serology test could be \"detected\", \"not detected\", \"inconclusive\", or \"test not done\". \n\nThe alternate way is to use the value element for actual observations and use the explicit dataAbsentReason element to record exceptional values. For example, the dataAbsentReason code \"error\" could be used when the measurement was not completed. Because of these options, use-case agreements are required to interpret general observations for exceptional values.","requirements":"For many results it is necessary to handle exceptional values in measurements.","min":0,"max":"1","base":{"path":"Observation.component.dataAbsentReason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"condition":["obs-6","vs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationValueAbsentReason"}],"strength":"extensible","description":"Codes specifying why the result (`Observation.value[x]`) is missing.","valueSet":"http://hl7.org/fhir/ValueSet/data-absent-reason"}},{"id":"Observation.component:diastolic.interpretation","path":"Observation.component.interpretation","short":"High, low, normal, etc.","definition":"A categorical assessment of an observation value. For example, high, low, normal.","comment":"Historically used for laboratory results (known as 'abnormal flag' ), its use extends to other use cases where coded interpretations are relevant. Often reported as one or more simple compact codes this element is often placed adjacent to the result value in reports and flow sheets to signal the meaning/normalcy status of the result.","requirements":"For some results, particularly numeric results, an interpretation is necessary to fully understand the significance of a result.","alias":["Abnormal Flag"],"min":0,"max":"*","base":{"path":"Observation.component.interpretation","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationInterpretation"}],"strength":"extensible","description":"Codes identifying interpretations of observations.","valueSet":"http://hl7.org/fhir/ValueSet/observation-interpretation"}},{"id":"Observation.component:diastolic.referenceRange","path":"Observation.component.referenceRange","short":"Provides guide for interpretation of component result","definition":"Guidance on how to interpret the value by comparison to a normal or recommended range.","comment":"Most observations only have one generic reference range. Systems MAY choose to restrict to only supplying the relevant reference range based on knowledge about the patient (e.g., specific to the patient's age, gender, weight and other factors), but this might not be possible or appropriate. Whenever more than one reference range is supplied, the differences between them SHOULD be provided in the reference range and/or age properties.","requirements":"Knowing what values are considered \"normal\" can help evaluate the significance of a particular result. Need to be able to provide multiple reference ranges for different contexts.","min":0,"max":"*","base":{"path":"Observation.component.referenceRange","min":0,"max":"*"},"contentReference":"http://hl7.org/fhir/StructureDefinition/Observation#Observation.referenceRange","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false}]}},{"resourceType":"StructureDefinition","id":"us-core-medicationrequest","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-medicationrequest","version":"5.0.1","name":"USCoreMedicationRequestProfile","title":"US Core MedicationRequest Profile","status":"active","experimental":false,"date":"2020-06-26","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"The US Core Medication Request Profile is based upon the core FHIR MedicationRequest Resource and meets the U.S. Core Data for Interoperability (USCDI) v2 'Medications' requirements. The MedicationRequest resource can be used to record a patient’s medication prescription or order. This profile sets minimum expectations for the MedicationRequest resource to record, search, and fetch a patient's medication to promote interoperability and adoption through common implementation. It identifies which core elements, extensions, vocabularies and value sets **SHALL** be present in the resource when using this profile. It provides the floor for standards development for specific uses cases.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"resource","abstract":false,"type":"MedicationRequest","baseDefinition":"http://hl7.org/fhir/StructureDefinition/MedicationRequest","derivation":"constraint","snapshot":{"element":[{"id":"MedicationRequest","path":"MedicationRequest","short":"Ordering of medication for patient or group","definition":"\\-","comment":"\\-","alias":["Prescription","Order"],"min":0,"max":"*","base":{"path":"MedicationRequest","min":0,"max":"*"},"constraint":[{"key":"dom-2","severity":"error","human":"If the resource is contained in another resource, it SHALL NOT contain nested Resources","expression":"contained.contained.empty()","xpath":"not(parent::f:contained and f:contained)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-3","severity":"error","human":"If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource","expression":"contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()","xpath":"not(exists(for $id in f:contained/*/f:id/@value return $contained[not(parent::*/descendant::f:reference/@value=concat('#', $contained/*/id/@value) or descendant::f:reference[@value='#'])]))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-4","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated","expression":"contained.meta.versionId.empty() and contained.meta.lastUpdated.empty()","xpath":"not(exists(f:contained/*/f:meta/f:versionId)) and not(exists(f:contained/*/f:meta/f:lastUpdated))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-5","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a security label","expression":"contained.meta.security.empty()","xpath":"not(exists(f:contained/*/f:meta/f:security))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice","valueBoolean":true},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice-explanation","valueMarkdown":"When a resource has no narrative, only systems that fully understand the data can display the resource to a human safely. Including a human readable representation in the resource makes for a much more robust eco-system and cheaper handling of resources by intermediary systems. Some ecosystems restrict distribution of resources to only those systems that do fully understand the resources, and as a consequence implementers may believe that the narrative is superfluous. However experience shows that such eco-systems often open up to new participants over time."}],"key":"dom-6","severity":"warning","human":"A resource should have narrative for robust management","expression":"text.`div`.exists()","xpath":"exists(f:text/h:div)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"}],"mustSupport":false,"isModifier":false,"isSummary":false},{"id":"MedicationRequest.id","path":"MedicationRequest.id","short":"Logical id of this artifact","definition":"The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.","comment":"The only time that a resource does not have an id is when it is being submitted to the server using a create operation.","min":0,"max":"1","base":{"path":"Resource.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.meta","path":"MedicationRequest.meta","short":"Metadata about the resource","definition":"The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource.","min":0,"max":"1","base":{"path":"Resource.meta","min":0,"max":"1"},"type":[{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.implicitRules","path":"MedicationRequest.implicitRules","short":"A set of rules under which this content was created","definition":"A reference to a set of rules that were followed when the resource was constructed, and which must be understood when processing the content. Often, this is a reference to an implementation guide that defines the special rules along with other profiles etc.","comment":"Asserting this rule set restricts the content to be only understood by a limited set of trading partners. This inherently limits the usefulness of the data in the long term. However, the existing health eco-system is highly fractured, and not yet ready to define, collect, and exchange data in a generally computable sense. Wherever possible, implementers and/or specification writers should avoid using this element. Often, when used, the URL is a reference to an implementation guide that defines these special rules as part of it's narrative along with other profiles, value sets, etc.","min":0,"max":"1","base":{"path":"Resource.implicitRules","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because the implicit rules may provide additional knowledge about the resource that modifies it's meaning or interpretation","isSummary":true},{"id":"MedicationRequest.language","path":"MedicationRequest.language","short":"Language of the resource content","definition":"The base language in which the resource is written.","comment":"Language is provided to support indexing and accessibility (typically, services such as text to speech use the language tag). The html language tag in the narrative applies to the narrative. The language tag on the resource may be used to specify the language of other presentations generated from the data in the resource. Not all the content has to be in the base language. The Resource.language should not be assumed to apply to the narrative automatically. If a language is specified, it should it also be specified on the div element in the html (see rules in HTML5 for information about the relationship between xml:lang and the html lang attribute).","min":0,"max":"1","base":{"path":"Resource.language","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet","valueCanonical":"http://hl7.org/fhir/ValueSet/all-languages"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"Language"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"preferred","description":"A human language.","valueSet":"http://hl7.org/fhir/ValueSet/languages"}},{"id":"MedicationRequest.text","path":"MedicationRequest.text","short":"Text summary of the resource, for human interpretation","definition":"A human-readable narrative that contains a summary of the resource and can be used to represent the content of the resource to a human. The narrative need not encode all the structured data, but is required to contain sufficient detail to make it \"clinically safe\" for a human to just read the narrative. Resource definitions may define what content should be represented in the narrative to ensure clinical safety.","comment":"Contained resources do not have narrative. Resources that are not contained SHOULD have a narrative. In some cases, a resource may only have text with little or no additional discrete data (as long as all minOccurs=1 elements are satisfied). This may be necessary for data from legacy systems where information is captured as a \"text blob\" or where text is additionally entered raw or narrated and encoded information is added later.","alias":["narrative","html","xhtml","display"],"min":0,"max":"1","base":{"path":"DomainResource.text","min":0,"max":"1"},"type":[{"code":"Narrative"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.contained","path":"MedicationRequest.contained","short":"Contained, inline Resources","definition":"These resources do not have an independent existence apart from the resource that contains them - they cannot be identified independently, and nor can they have their own independent transaction scope.","comment":"This should never be done when the content can be identified properly, as once identification is lost, it is extremely difficult (and context dependent) to restore it again. Contained resources may have profiles and tags In their meta elements, but SHALL NOT have security labels.","alias":["inline resources","anonymous resources","contained resources"],"min":0,"max":"*","base":{"path":"DomainResource.contained","min":0,"max":"*"},"type":[{"code":"Resource"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.extension","path":"MedicationRequest.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the resource. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.modifierExtension","path":"MedicationRequest.modifierExtension","short":"Extensions that cannot be ignored","definition":"May be used to represent additional information that is not part of the basic definition of the resource and that modifies the understanding of the element that contains it and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the resource that contains them","isSummary":false},{"id":"MedicationRequest.identifier","path":"MedicationRequest.identifier","short":"External ids for this request","definition":"Identifiers associated with this medication request that are defined by business processes and/or used to refer to it when a direct URL reference to the resource itself is not appropriate. They are business identifiers assigned to this resource by the performer or other systems and remain constant as the resource is updated and propagates from server to server.","comment":"This is a business identifier, not a resource identifier.","min":0,"max":"*","base":{"path":"MedicationRequest.identifier","min":0,"max":"*"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.status","path":"MedicationRequest.status","short":"active | on-hold | cancelled | completed | entered-in-error | stopped | draft | unknown","definition":"A code specifying the current state of the order. Generally, this will be active or completed state.","comment":"This element is labeled as a modifier because the status contains codes that mark the resource as not currently valid.","min":1,"max":"1","base":{"path":"MedicationRequest.status","min":1,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":true,"isModifierReason":"This element is labeled as a modifier because it is a status element that contains status entered-in-error which means that the resource should not be treated as valid","isSummary":true,"binding":{"strength":"required","description":"A code specifying the state of the prescribing event. Describes the lifecycle of the prescription.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-status"}},{"id":"MedicationRequest.statusReason","path":"MedicationRequest.statusReason","short":"Reason for current status","definition":"Captures the reason for the current state of the MedicationRequest.","comment":"This is generally only used for \"exception\" statuses such as \"suspended\" or \"cancelled\". The reason why the MedicationRequest was created at all is captured in reasonCode, not here.","min":0,"max":"1","base":{"path":"MedicationRequest.statusReason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestStatusReason"}],"strength":"example","description":"Identifies the reasons for a given status.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-status-reason"}},{"id":"MedicationRequest.intent","path":"MedicationRequest.intent","short":"proposal | plan | order | original-order | reflex-order | filler-order | instance-order | option","definition":"Whether the request is a proposal, plan, or an original order.","comment":"It is expected that the type of requester will be restricted for different stages of a MedicationRequest. For example, Proposals can be created by a patient, relatedPerson, Practitioner or Device. Plans can be created by Practitioners, Patients, RelatedPersons and Devices. Original orders can be created by a Practitioner only.\r\rAn instance-order is an instantiation of a request or order and may be used to populate Medication Administration Record.\r\rThis element is labeled as a modifier because the intent alters when and how the resource is actually applicable.","min":1,"max":"1","base":{"path":"MedicationRequest.intent","min":1,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":true,"isModifierReason":"This element changes the interpretation of all descriptive attributes. For example \"the time the request is recommended to occur\" vs. \"the time the request is authorized to occur\" or \"who is recommended to perform the request\" vs. \"who is authorized to perform the request","isSummary":true,"binding":{"strength":"required","description":"The kind of medication order.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-intent"}},{"id":"MedicationRequest.category","path":"MedicationRequest.category","slicing":{"discriminator":[{"type":"pattern","path":"$this"}],"rules":"open"},"short":"Type of medication usage","definition":"Indicates the type of medication request (for example, where the medication is expected to be consumed or administered (i.e. inpatient or outpatient)).","comment":"The category can be used to include where the medication is expected to be consumed or other types of requests.","min":0,"max":"*","base":{"path":"MedicationRequest.category","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestCategory"}],"strength":"example","description":"A coded concept identifying the category of medication request. For example, where the medication is to be consumed or administered, or the type of medication treatment.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-category"}},{"id":"MedicationRequest.category:us-core","path":"MedicationRequest.category","sliceName":"us-core","short":"Type of medication usage","definition":"Indicates the type of medication request (for example, where the medication is expected to be consumed or administered (i.e. inpatient or outpatient)).","comment":"The category can be used to include where the medication is expected to be consumed or other types of requests.","min":0,"max":"*","base":{"path":"MedicationRequest.category","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"strength":"required","description":"The type of medication order.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-category"}},{"id":"MedicationRequest.priority","path":"MedicationRequest.priority","short":"routine | urgent | asap | stat","definition":"Indicates how quickly the Medication Request should be addressed with respect to other requests.","min":0,"max":"1","base":{"path":"MedicationRequest.priority","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestPriority"}],"strength":"required","description":"Identifies the level of importance to be assigned to actioning the request.","valueSet":"http://hl7.org/fhir/ValueSet/request-priority|4.0.1"}},{"id":"MedicationRequest.doNotPerform","path":"MedicationRequest.doNotPerform","short":"True if request is prohibiting action","definition":"If true indicates that the provider is asking for the medication request not to occur.","comment":"If do not perform is not specified, the request is a positive request e.g. \"do perform\".","min":0,"max":"1","base":{"path":"MedicationRequest.doNotPerform","min":0,"max":"1"},"type":[{"code":"boolean"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because this element negates the request to occur (ie, this is a request for the medication not to be ordered or prescribed, etc)","isSummary":true},{"id":"MedicationRequest.reported[x]","path":"MedicationRequest.reported[x]","short":"Reported rather than primary record","definition":"Indicates if this record was captured as a secondary 'reported' record rather than as an original primary source-of-truth record. It may also indicate the source of the report.","min":0,"max":"1","base":{"path":"MedicationRequest.reported[x]","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"boolean"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner","http://hl7.org/fhir/us/core/StructureDefinition/us-core-organization","http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient","http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitionerrole","http://hl7.org/fhir/us/core/StructureDefinition/us-core-relatedperson"],"_targetProfile":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]}]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"MedicationRequest.medication[x]","path":"MedicationRequest.medication[x]","short":"Medication to be taken","definition":"Identifies the medication being requested. This is a link to a resource that represents the medication which may be the details of the medication or simply an attribute carrying a code that identifies the medication from a known list of medications.","comment":"If only a code is specified, then it needs to be a code for a specific product. If more information is required, then the use of the Medication resource is recommended. For example, if you require form or lot number or if the medication is compounded or extemporaneously prepared, then you must reference the Medication resource.","min":1,"max":"1","base":{"path":"MedicationRequest.medication[x]","min":1,"max":"1"},"type":[{"code":"CodeableConcept"},{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-medication"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","valueSet":"http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1010.4"}},{"id":"MedicationRequest.subject","path":"MedicationRequest.subject","short":"Who or group medication request is for","definition":"A link to a resource representing the person or set of individuals to whom the medication will be given.","comment":"The subject on a medication request is mandatory. For the secondary use case where the actual subject is not provided, there still must be an anonymized subject specified.","min":1,"max":"1","base":{"path":"MedicationRequest.subject","min":1,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"MedicationRequest.encounter","path":"MedicationRequest.encounter","short":"Encounter created as part of encounter/admission/stay","definition":"The Encounter during which this [x] was created or to which the creation of this record is tightly associated.","comment":"This will typically be the encounter the event occurred within, but some activities may be initiated prior to or after the official completion of an encounter but still be tied to the context of the encounter.\" If there is a need to link to episodes of care they will be handled with an extension.","min":0,"max":"1","base":{"path":"MedicationRequest.encounter","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-encounter"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"MedicationRequest.supportingInformation","path":"MedicationRequest.supportingInformation","short":"Information to support ordering of the medication","definition":"Include additional information (for example, patient height and weight) that supports the ordering of the medication.","min":0,"max":"*","base":{"path":"MedicationRequest.supportingInformation","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Resource"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.authoredOn","path":"MedicationRequest.authoredOn","short":"When request was initially authored","definition":"The date (and perhaps time) when the prescription was initially written or authored on.","min":0,"max":"1","base":{"path":"MedicationRequest.authoredOn","min":0,"max":"1"},"type":[{"code":"dateTime"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"MedicationRequest.requester","path":"MedicationRequest.requester","short":"Who/What requested the Request","definition":"The individual, organization, or device that initiated the request and has responsibility for its activation.","min":1,"max":"1","base":{"path":"MedicationRequest.requester","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner","http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient","http://hl7.org/fhir/us/core/StructureDefinition/us-core-organization","http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitionerrole","http://hl7.org/fhir/us/core/StructureDefinition/us-core-relatedperson","http://hl7.org/fhir/StructureDefinition/Device"],"_targetProfile":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]}]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"MedicationRequest.performer","path":"MedicationRequest.performer","short":"Intended performer of administration","definition":"The specified desired performer of the medication treatment (e.g. the performer of the medication administration).","min":0,"max":"1","base":{"path":"MedicationRequest.performer","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Practitioner","http://hl7.org/fhir/StructureDefinition/PractitionerRole","http://hl7.org/fhir/StructureDefinition/Organization","http://hl7.org/fhir/StructureDefinition/Patient","http://hl7.org/fhir/StructureDefinition/Device","http://hl7.org/fhir/StructureDefinition/RelatedPerson","http://hl7.org/fhir/StructureDefinition/CareTeam"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.performerType","path":"MedicationRequest.performerType","short":"Desired kind of performer of the medication administration","definition":"Indicates the type of performer of the administration of the medication.","comment":"If specified without indicating a performer, this indicates that the performer must be of the specified type. If specified with a performer then it indicates the requirements of the performer if the designated performer is not available.","min":0,"max":"1","base":{"path":"MedicationRequest.performerType","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestPerformerType"}],"strength":"example","description":"Identifies the type of individual that is desired to administer the medication.","valueSet":"http://hl7.org/fhir/ValueSet/performer-role"}},{"id":"MedicationRequest.recorder","path":"MedicationRequest.recorder","short":"Person who entered the request","definition":"The person who entered the order on behalf of another individual for example in the case of a verbal or a telephone order.","min":0,"max":"1","base":{"path":"MedicationRequest.recorder","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Practitioner","http://hl7.org/fhir/StructureDefinition/PractitionerRole"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.reasonCode","path":"MedicationRequest.reasonCode","short":"Reason or indication for ordering or not ordering the medication","definition":"The reason or the indication for ordering or not ordering the medication.","comment":"This could be a diagnosis code. If a full condition record exists or additional detail is needed, use reasonReference.","min":0,"max":"*","base":{"path":"MedicationRequest.reasonCode","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestReason"}],"strength":"example","description":"A coded concept indicating why the medication was ordered.","valueSet":"http://hl7.org/fhir/ValueSet/condition-code"}},{"id":"MedicationRequest.reasonReference","path":"MedicationRequest.reasonReference","short":"Condition or observation that supports why the prescription is being written","definition":"Condition or observation that supports why the medication was ordered.","comment":"This is a reference to a condition or observation that is the reason for the medication order. If only a code exists, use reasonCode.","min":0,"max":"*","base":{"path":"MedicationRequest.reasonReference","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Condition","http://hl7.org/fhir/StructureDefinition/Observation"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.instantiatesCanonical","path":"MedicationRequest.instantiatesCanonical","short":"Instantiates FHIR protocol or definition","definition":"The URL pointing to a protocol, guideline, orderset, or other definition that is adhered to in whole or in part by this MedicationRequest.","min":0,"max":"*","base":{"path":"MedicationRequest.instantiatesCanonical","min":0,"max":"*"},"type":[{"code":"canonical"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.instantiatesUri","path":"MedicationRequest.instantiatesUri","short":"Instantiates external protocol or definition","definition":"The URL pointing to an externally maintained protocol, guideline, orderset or other definition that is adhered to in whole or in part by this MedicationRequest.","min":0,"max":"*","base":{"path":"MedicationRequest.instantiatesUri","min":0,"max":"*"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.basedOn","path":"MedicationRequest.basedOn","short":"What request fulfills","definition":"A plan or request that is fulfilled in whole or in part by this medication request.","min":0,"max":"*","base":{"path":"MedicationRequest.basedOn","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/CarePlan","http://hl7.org/fhir/StructureDefinition/MedicationRequest","http://hl7.org/fhir/StructureDefinition/ServiceRequest","http://hl7.org/fhir/StructureDefinition/ImmunizationRecommendation"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.groupIdentifier","path":"MedicationRequest.groupIdentifier","short":"Composite request this is part of","definition":"A shared identifier common to all requests that were authorized more or less simultaneously by a single author, representing the identifier of the requisition or prescription.","requirements":"Requests are linked either by a \"basedOn\" relationship (i.e. one request is fulfilling another) or by having a common requisition. Requests that are part of the same requisition are generally treated independently from the perspective of changing their state or maintaining them after initial creation.","min":0,"max":"1","base":{"path":"MedicationRequest.groupIdentifier","min":0,"max":"1"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.courseOfTherapyType","path":"MedicationRequest.courseOfTherapyType","short":"Overall pattern of medication administration","definition":"The description of the overall patte3rn of the administration of the medication to the patient.","comment":"This attribute should not be confused with the protocol of the medication.","min":0,"max":"1","base":{"path":"MedicationRequest.courseOfTherapyType","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestCourseOfTherapy"}],"strength":"example","description":"Identifies the overall pattern of medication administratio.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-course-of-therapy"}},{"id":"MedicationRequest.insurance","path":"MedicationRequest.insurance","short":"Associated insurance coverage","definition":"Insurance plans, coverage extensions, pre-authorizations and/or pre-determinations that may be required for delivering the requested service.","min":0,"max":"*","base":{"path":"MedicationRequest.insurance","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Coverage","http://hl7.org/fhir/StructureDefinition/ClaimResponse"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.note","path":"MedicationRequest.note","short":"Information about the prescription","definition":"Extra information about the prescription that could not be conveyed by the other attributes.","min":0,"max":"*","base":{"path":"MedicationRequest.note","min":0,"max":"*"},"type":[{"code":"Annotation"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dosageInstruction","path":"MedicationRequest.dosageInstruction","short":"How the medication should be taken","definition":"Indicates how the medication is to be used by the patient.","comment":"There are examples where a medication request may include the option of an oral dose or an Intravenous or Intramuscular dose. For example, \"Ondansetron 8mg orally or IV twice a day as needed for nausea\" or \"Compazine® (prochlorperazine) 5-10mg PO or 25mg PR bid prn nausea or vomiting\". In these cases, two medication requests would be created that could be grouped together. The decision on which dose and route of administration to use is based on the patient's condition at the time the dose is needed.","min":0,"max":"*","base":{"path":"MedicationRequest.dosageInstruction","min":0,"max":"*"},"type":[{"code":"Dosage"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dosageInstruction.id","path":"MedicationRequest.dosageInstruction.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dosageInstruction.extension","path":"MedicationRequest.dosageInstruction.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dosageInstruction.modifierExtension","path":"MedicationRequest.dosageInstruction.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"MedicationRequest.dosageInstruction.sequence","path":"MedicationRequest.dosageInstruction.sequence","short":"The order of the dosage instructions","definition":"Indicates the order in which the dosage instructions should be applied or interpreted.","requirements":"If the sequence number of multiple Dosages is the same, then it is implied that the instructions are to be treated as concurrent. If the sequence number is different, then the Dosages are intended to be sequential.","min":0,"max":"1","base":{"path":"Dosage.sequence","min":0,"max":"1"},"type":[{"code":"integer"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.text","path":"MedicationRequest.dosageInstruction.text","short":"Free text dosage instructions e.g. SIG","definition":"Free text dosage instructions e.g. SIG.","requirements":"Free text dosage instructions can be used for cases where the instructions are too complex to code. The content of this attribute does not include the name or description of the medication. When coded instructions are present, the free text instructions may still be present for display to humans taking or administering the medication. It is expected that the text instructions will always be populated. If the dosage.timing attribute is also populated, then the dosage.text should reflect the same information as the timing. Additional information about administration or preparation of the medication should be included as text.","min":0,"max":"1","base":{"path":"Dosage.text","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.additionalInstruction","path":"MedicationRequest.dosageInstruction.additionalInstruction","short":"Supplemental instruction or warnings to the patient - e.g. \"with meals\", \"may cause drowsiness\"","definition":"Supplemental instructions to the patient on how to take the medication (e.g. \"with meals\" or\"take half to one hour before food\") or warnings for the patient about the medication (e.g. \"may cause drowsiness\" or \"avoid exposure of skin to direct sunlight or sunlamps\").","comment":"Information about administration or preparation of the medication (e.g. \"infuse as rapidly as possibly via intraperitoneal port\" or \"immediately following drug x\") should be populated in dosage.text.","requirements":"Additional instruction is intended to be coded, but where no code exists, the element could include text. For example, \"Swallow with plenty of water\" which might or might not be coded.","min":0,"max":"*","base":{"path":"Dosage.additionalInstruction","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"AdditionalInstruction"}],"strength":"example","description":"A coded concept identifying additional instructions such as \"take with water\" or \"avoid operating heavy machinery\".","valueSet":"http://hl7.org/fhir/ValueSet/additional-instruction-codes"}},{"id":"MedicationRequest.dosageInstruction.patientInstruction","path":"MedicationRequest.dosageInstruction.patientInstruction","short":"Patient or consumer oriented instructions","definition":"Instructions in terms that are understood by the patient or consumer.","min":0,"max":"1","base":{"path":"Dosage.patientInstruction","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.timing","path":"MedicationRequest.dosageInstruction.timing","short":"When medication should be administered","definition":"When medication should be administered.","comment":"This attribute might not always be populated while the Dosage.text is expected to be populated. If both are populated, then the Dosage.text should reflect the content of the Dosage.timing.","requirements":"The timing schedule for giving the medication to the patient. This data type allows many different expressions. For example: \"Every 8 hours\"; \"Three times a day\"; \"1/2 an hour before breakfast for 10 days from 23-Dec 2011:\"; \"15 Oct 2013, 17 Oct 2013 and 1 Nov 2013\". Sometimes, a rate can imply duration when expressed as total volume / duration (e.g. 500mL/2 hours implies a duration of 2 hours). However, when rate doesn't imply duration (e.g. 250mL/hour), then the timing.repeat.duration is needed to convey the infuse over time period.","min":0,"max":"1","base":{"path":"Dosage.timing","min":0,"max":"1"},"type":[{"code":"Timing"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.asNeeded[x]","path":"MedicationRequest.dosageInstruction.asNeeded[x]","short":"Take \"as needed\" (for x)","definition":"Indicates whether the Medication is only taken when needed within a specific dosing schedule (Boolean option), or it indicates the precondition for taking the Medication (CodeableConcept).","comment":"Can express \"as needed\" without a reason by setting the Boolean = True. In this case the CodeableConcept is not populated. Or you can express \"as needed\" with a reason by including the CodeableConcept. In this case the Boolean is assumed to be True. If you set the Boolean to False, then the dose is given according to the schedule and is not \"prn\" or \"as needed\".","min":0,"max":"1","base":{"path":"Dosage.asNeeded[x]","min":0,"max":"1"},"type":[{"code":"boolean"},{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationAsNeededReason"}],"strength":"example","description":"A coded concept identifying the precondition that should be met or evaluated prior to consuming or administering a medication dose. For example \"pain\", \"30 minutes prior to sexual intercourse\", \"on flare-up\" etc.","valueSet":"http://hl7.org/fhir/ValueSet/medication-as-needed-reason"}},{"id":"MedicationRequest.dosageInstruction.site","path":"MedicationRequest.dosageInstruction.site","short":"Body site to administer to","definition":"Body site to administer to.","comment":"If the use case requires attributes from the BodySite resource (e.g. to identify and track separately) then use the standard extension [bodySite](http://hl7.org/fhir/R4/extension-bodysite.html). May be a summary code, or a reference to a very precise definition of the location, or both.","requirements":"A coded specification of the anatomic site where the medication first enters the body.","min":0,"max":"1","base":{"path":"Dosage.site","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationAdministrationSite"}],"strength":"example","description":"A coded concept describing the site location the medicine enters into or onto the body.","valueSet":"http://hl7.org/fhir/ValueSet/approach-site-codes"}},{"id":"MedicationRequest.dosageInstruction.route","path":"MedicationRequest.dosageInstruction.route","short":"How drug should enter body","definition":"How drug should enter body.","requirements":"A code specifying the route or physiological path of administration of a therapeutic agent into or onto a patient's body.","min":0,"max":"1","base":{"path":"Dosage.route","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"RouteOfAdministration"}],"strength":"example","description":"A coded concept describing the route or physiological path of administration of a therapeutic agent into or onto the body of a subject.","valueSet":"http://hl7.org/fhir/ValueSet/route-codes"}},{"id":"MedicationRequest.dosageInstruction.method","path":"MedicationRequest.dosageInstruction.method","short":"Technique for administering medication","definition":"Technique for administering medication.","comment":"Terminologies used often pre-coordinate this term with the route and or form of administration.","requirements":"A coded value indicating the method by which the medication is introduced into or onto the body. Most commonly used for injections. For examples, Slow Push; Deep IV.","min":0,"max":"1","base":{"path":"Dosage.method","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationAdministrationMethod"}],"strength":"example","description":"A coded concept describing the technique by which the medicine is administered.","valueSet":"http://hl7.org/fhir/ValueSet/administration-method-codes"}},{"id":"MedicationRequest.dosageInstruction.doseAndRate","path":"MedicationRequest.dosageInstruction.doseAndRate","short":"Amount of medication administered","definition":"The amount of medication administered.","min":0,"max":"*","base":{"path":"Dosage.doseAndRate","min":0,"max":"*"},"type":[{"code":"Element"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.doseAndRate.id","path":"MedicationRequest.dosageInstruction.doseAndRate.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dosageInstruction.doseAndRate.extension","path":"MedicationRequest.dosageInstruction.doseAndRate.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dosageInstruction.doseAndRate.type","path":"MedicationRequest.dosageInstruction.doseAndRate.type","short":"The kind of dose or rate specified","definition":"The kind of dose or rate specified, for example, ordered or calculated.","requirements":"If the type is not populated, assume to be \"ordered\".","min":0,"max":"1","base":{"path":"Dosage.doseAndRate.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"DoseAndRateType"}],"strength":"example","description":"The kind of dose or rate specified.","valueSet":"http://hl7.org/fhir/ValueSet/dose-rate-type"}},{"id":"MedicationRequest.dosageInstruction.doseAndRate.dose[x]","path":"MedicationRequest.dosageInstruction.doseAndRate.dose[x]","short":"Amount of medication per dose","definition":"Amount of medication per dose.","comment":"Note that this specifies the quantity of the specified medication, not the quantity for each active ingredient(s). Each ingredient amount can be communicated in the Medication resource. For example, if one wants to communicate that a tablet was 375 mg, where the dose was one tablet, you can use the Medication resource to document that the tablet was comprised of 375 mg of drug XYZ. Alternatively if the dose was 375 mg, then you may only need to use the Medication resource to indicate this was a tablet. If the example were an IV such as dopamine and you wanted to communicate that 400mg of dopamine was mixed in 500 ml of some IV solution, then this would all be communicated in the Medication resource. If the administration is not intended to be instantaneous (rate is present or timing has a duration), this can be specified to convey the total amount to be administered over the period of time as indicated by the schedule e.g. 500 ml in dose, with timing used to convey that this should be done over 4 hours.","requirements":"The amount of therapeutic or other substance given at one administration event.","min":0,"max":"1","base":{"path":"Dosage.doseAndRate.dose[x]","min":0,"max":"1"},"type":[{"code":"Range"},{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.doseAndRate.rate[x]","path":"MedicationRequest.dosageInstruction.doseAndRate.rate[x]","short":"Amount of medication per unit of time","definition":"Amount of medication per unit of time.","comment":"It is possible to supply both a rate and a doseQuantity to provide full details about how the medication is to be administered and supplied. If the rate is intended to change over time, depending on local rules/regulations, each change should be captured as a new version of the MedicationRequest with an updated rate, or captured with a new MedicationRequest with the new rate.\r\rIt is possible to specify a rate over time (for example, 100 ml/hour) using either the rateRatio and rateQuantity. The rateQuantity approach requires systems to have the capability to parse UCUM grammer where ml/hour is included rather than a specific ratio where the time is specified as the denominator. Where a rate such as 500ml over 2 hours is specified, the use of rateRatio may be more semantically correct than specifying using a rateQuantity of 250 mg/hour.","requirements":"Identifies the speed with which the medication was or will be introduced into the patient. Typically the rate for an infusion e.g. 100 ml per 1 hour or 100 ml/hr. May also be expressed as a rate per unit of time e.g. 500 ml per 2 hours. Other examples: 200 mcg/min or 200 mcg/1 minute; 1 liter/8 hours. Sometimes, a rate can imply duration when expressed as total volume / duration (e.g. 500mL/2 hours implies a duration of 2 hours). However, when rate doesn't imply duration (e.g. 250mL/hour), then the timing.repeat.duration is needed to convey the infuse over time period.","min":0,"max":"1","base":{"path":"Dosage.doseAndRate.rate[x]","min":0,"max":"1"},"type":[{"code":"Ratio"},{"code":"Range"},{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.maxDosePerPeriod","path":"MedicationRequest.dosageInstruction.maxDosePerPeriod","short":"Upper limit on medication per unit of time","definition":"Upper limit on medication per unit of time.","comment":"This is intended for use as an adjunct to the dosage when there is an upper cap. For example \"2 tablets every 4 hours to a maximum of 8/day\".","requirements":"The maximum total quantity of a therapeutic substance that may be administered to a subject over the period of time. For example, 1000mg in 24 hours.","min":0,"max":"1","base":{"path":"Dosage.maxDosePerPeriod","min":0,"max":"1"},"type":[{"code":"Ratio"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.maxDosePerAdministration","path":"MedicationRequest.dosageInstruction.maxDosePerAdministration","short":"Upper limit on medication per administration","definition":"Upper limit on medication per administration.","comment":"This is intended for use as an adjunct to the dosage when there is an upper cap. For example, a body surface area related dose with a maximum amount, such as 1.5 mg/m2 (maximum 2 mg) IV over 5 – 10 minutes would have doseQuantity of 1.5 mg/m2 and maxDosePerAdministration of 2 mg.","requirements":"The maximum total quantity of a therapeutic substance that may be administered to a subject per administration.","min":0,"max":"1","base":{"path":"Dosage.maxDosePerAdministration","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.maxDosePerLifetime","path":"MedicationRequest.dosageInstruction.maxDosePerLifetime","short":"Upper limit on medication per lifetime of the patient","definition":"Upper limit on medication per lifetime of the patient.","requirements":"The maximum total quantity of a therapeutic substance that may be administered per lifetime of the subject.","min":0,"max":"1","base":{"path":"Dosage.maxDosePerLifetime","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dispenseRequest","path":"MedicationRequest.dispenseRequest","short":"Medication supply authorization","definition":"Indicates the specific details for the dispense or medication supply part of a medication request (also known as a Medication Prescription or Medication Order). Note that this information is not always sent with the order. There may be in some settings (e.g. hospitals) institutional or system support for completing the dispense details in the pharmacy department.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest","min":0,"max":"1"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.id","path":"MedicationRequest.dispenseRequest.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.extension","path":"MedicationRequest.dispenseRequest.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.modifierExtension","path":"MedicationRequest.dispenseRequest.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"MedicationRequest.dispenseRequest.initialFill","path":"MedicationRequest.dispenseRequest.initialFill","short":"First fill details","definition":"Indicates the quantity or duration for the first dispense of the medication.","comment":"If populating this element, either the quantity or the duration must be included.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.initialFill","min":0,"max":"1"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.initialFill.id","path":"MedicationRequest.dispenseRequest.initialFill.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.initialFill.extension","path":"MedicationRequest.dispenseRequest.initialFill.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.initialFill.modifierExtension","path":"MedicationRequest.dispenseRequest.initialFill.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"MedicationRequest.dispenseRequest.initialFill.quantity","path":"MedicationRequest.dispenseRequest.initialFill.quantity","short":"First fill quantity","definition":"The amount or quantity to provide as part of the first dispense.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.initialFill.quantity","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.initialFill.duration","path":"MedicationRequest.dispenseRequest.initialFill.duration","short":"First fill duration","definition":"The length of time that the first dispense is expected to last.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.initialFill.duration","min":0,"max":"1"},"type":[{"code":"Duration"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.dispenseInterval","path":"MedicationRequest.dispenseRequest.dispenseInterval","short":"Minimum period of time between dispenses","definition":"The minimum period of time that must occur between dispenses of the medication.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.dispenseInterval","min":0,"max":"1"},"type":[{"code":"Duration"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.validityPeriod","path":"MedicationRequest.dispenseRequest.validityPeriod","short":"Time period supply is authorized for","definition":"This indicates the validity period of a prescription (stale dating the Prescription).","comment":"It reflects the prescribers' perspective for the validity of the prescription. Dispenses must not be made against the prescription outside of this period. The lower-bound of the Dispensing Window signifies the earliest date that the prescription can be filled for the first time. If an upper-bound is not specified then the Prescription is open-ended or will default to a stale-date based on regulations.","requirements":"Indicates when the Prescription becomes valid, and when it ceases to be a dispensable Prescription.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.validityPeriod","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.numberOfRepeatsAllowed","path":"MedicationRequest.dispenseRequest.numberOfRepeatsAllowed","short":"Number of refills authorized","definition":"An integer indicating the number of times, in addition to the original dispense, (aka refills or repeats) that the patient can receive the prescribed medication. Usage Notes: This integer does not include the original order dispense. This means that if an order indicates dispense 30 tablets plus \"3 repeats\", then the order can be dispensed a total of 4 times and the patient can receive a total of 120 tablets. A prescriber may explicitly say that zero refills are permitted after the initial dispense.","comment":"If displaying \"number of authorized fills\", add 1 to this number.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.numberOfRepeatsAllowed","min":0,"max":"1"},"type":[{"code":"unsignedInt"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.quantity","path":"MedicationRequest.dispenseRequest.quantity","short":"Amount of medication to supply per dispense","definition":"The amount that is to be dispensed for one fill.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.quantity","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.expectedSupplyDuration","path":"MedicationRequest.dispenseRequest.expectedSupplyDuration","short":"Number of days supply per dispense","definition":"Identifies the period time over which the supplied product is expected to be used, or the length of time the dispense is expected to last.","comment":"In some situations, this attribute may be used instead of quantity to identify the amount supplied by how long it is expected to last, rather than the physical quantity issued, e.g. 90 days supply of medication (based on an ordered dosage). When possible, it is always better to specify quantity, as this tends to be more precise. expectedSupplyDuration will always be an estimate that can be influenced by external factors.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.expectedSupplyDuration","min":0,"max":"1"},"type":[{"code":"Duration"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.performer","path":"MedicationRequest.dispenseRequest.performer","short":"Intended dispenser","definition":"Indicates the intended dispensing Organization specified by the prescriber.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.performer","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.substitution","path":"MedicationRequest.substitution","short":"Any restrictions on medication substitution","definition":"Indicates whether or not substitution can or should be part of the dispense. In some cases, substitution must happen, in other cases substitution must not happen. This block explains the prescriber's intent. If nothing is specified substitution may be done.","min":0,"max":"1","base":{"path":"MedicationRequest.substitution","min":0,"max":"1"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.substitution.id","path":"MedicationRequest.substitution.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.substitution.extension","path":"MedicationRequest.substitution.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.substitution.modifierExtension","path":"MedicationRequest.substitution.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"MedicationRequest.substitution.allowed[x]","path":"MedicationRequest.substitution.allowed[x]","short":"Whether substitution is allowed or not","definition":"True if the prescriber allows a different drug to be dispensed from what was prescribed.","comment":"This element is labeled as a modifier because whether substitution is allow or not, it cannot be ignored.","min":1,"max":"1","base":{"path":"MedicationRequest.substitution.allowed[x]","min":1,"max":"1"},"type":[{"code":"boolean"},{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestSubstitution"}],"strength":"example","description":"Identifies the type of substitution allowed.","valueSet":"http://terminology.hl7.org/ValueSet/v3-ActSubstanceAdminSubstitutionCode"}},{"id":"MedicationRequest.substitution.reason","path":"MedicationRequest.substitution.reason","short":"Why should (not) substitution be made","definition":"Indicates the reason for the substitution, or why substitution must or must not be performed.","min":0,"max":"1","base":{"path":"MedicationRequest.substitution.reason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationIntendedSubstitutionReason"}],"strength":"example","description":"A coded concept describing the reason that a different medication should (or should not) be substituted from what was prescribed.","valueSet":"http://terminology.hl7.org/ValueSet/v3-SubstanceAdminSubstitutionReason"}},{"id":"MedicationRequest.priorPrescription","path":"MedicationRequest.priorPrescription","short":"An order/prescription that is being replaced","definition":"A link to a resource representing an earlier order related order or prescription.","min":0,"max":"1","base":{"path":"MedicationRequest.priorPrescription","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/MedicationRequest"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.detectedIssue","path":"MedicationRequest.detectedIssue","short":"Clinical Issue with action","definition":"Indicates an actual or potential clinical issue with or between one or more active or proposed clinical actions for a patient; e.g. Drug-drug interaction, duplicate therapy, dosage alert etc.","comment":"This element can include a detected issue that has been identified either by a decision support system or by a clinician and may include information on the steps that were taken to address the issue.","alias":["Contraindication","Drug Utilization Review (DUR)","Alert"],"min":0,"max":"*","base":{"path":"MedicationRequest.detectedIssue","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/DetectedIssue"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.eventHistory","path":"MedicationRequest.eventHistory","short":"A list of events of interest in the lifecycle","definition":"Links to Provenance records for past versions of this resource or fulfilling request or event resources that identify key state transitions or updates that are likely to be relevant to a user looking at the current version of the resource.","comment":"This might not include provenances for all versions of the request – only those deemed “relevant” or important. This SHALL NOT include the provenance associated with this current version of the resource. (If that provenance is deemed to be a “relevant” change, it will need to be added as part of a later update. Until then, it can be queried directly as the provenance that points to this version using _revinclude All Provenances should have some historical version of this Request as their subject.).","min":0,"max":"*","base":{"path":"MedicationRequest.eventHistory","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Provenance"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false}]}}] \ No newline at end of file diff --git a/packages/generator/src/storybook.ts b/packages/generator/src/storybook.ts index a83cd44a09..90a6f4d353 100644 --- a/packages/generator/src/storybook.ts +++ b/packages/generator/src/storybook.ts @@ -97,10 +97,10 @@ export function main(): void { // To build USCore, download and expand a USCore Implementation Guide package file, // such as https://hl7.org/fhir/us/core/STU5.0.1/package.tgz which is linked to // from https://hl7.org/fhir/us/core/STU5.0.1/downloads.html - buildUSCoreStructureDefinitions( - '/absolute/path/to/expanded/package-file', - resolve(__dirname, '../../mock/src/mocks/uscore/uscore-v5.0.1-structuredefinitions.json') - ); + buildUSCoreStructureDefinitions('/absolute/path/to/expanded/package-file', [ + resolve(__dirname, '../../mock/src/mocks/uscore/uscore-v5.0.1-structuredefinitions.json'), + resolve(__dirname, '../../definitions/dist/fhir/r4/testing/uscore-v5.0.1-structuredefinitions.json'), + ]); } } @@ -169,12 +169,14 @@ function cleanStructureDefinition(sd: StructureDefinition): void { } } -function buildUSCoreStructureDefinitions(inputDirectory: string, outputFilename: string): void { +function buildUSCoreStructureDefinitions(inputDirectory: string, outputFilenames: string[]): void { const sds = []; for (const file of USCoreStructureDefinitionFiles) { const sd = JSON.parse(readFileSync(resolve(inputDirectory, file), 'utf8')); cleanStructureDefinition(sd); sds.push(sd); } - writeFileSync(outputFilename, JSON.stringify(sds)); + for (const outputFilename of outputFilenames) { + writeFileSync(outputFilename, JSON.stringify(sds)); + } } diff --git a/packages/react/src/AddressInput/AddressInput.tsx b/packages/react/src/AddressInput/AddressInput.tsx index 8b2d268b89..96fdd7f2c3 100644 --- a/packages/react/src/AddressInput/AddressInput.tsx +++ b/packages/react/src/AddressInput/AddressInput.tsx @@ -24,10 +24,6 @@ export function AddressInput(props: AddressInputProps): JSX.Element { const valueRef = useRef
(); valueRef.current = value; - // const stateElement = useMemo( - // () => getModifiedNestedElement(props.path + '.state'), - // [getModifiedNestedElement, props.path] - // ); // TODO{profiles} is it worth the complexity of subbing in an autocomplete input when // a binding is defined in a profile? If so, it should go in a new wrapper around TextInput // e.g. US Core Patient Profile diff --git a/packages/react/src/BackboneElementInput/BackboneElementInput.tsx b/packages/react/src/BackboneElementInput/BackboneElementInput.tsx index e1dbd216e6..60dcb21524 100644 --- a/packages/react/src/BackboneElementInput/BackboneElementInput.tsx +++ b/packages/react/src/BackboneElementInput/BackboneElementInput.tsx @@ -1,8 +1,9 @@ -import { tryGetDataType } from '@medplum/core'; +import { ElementsContextType, buildElementsContext, tryGetDataType } from '@medplum/core'; import { OperationOutcome } from '@medplum/fhirtypes'; import { useContext, useMemo, useState } from 'react'; import { ElementsInput } from '../ElementsInput/ElementsInput'; -import { ElementsContext, buildElementsContext } from '../ElementsInput/ElementsInput.utils'; +import { ElementsContext } from '../ElementsInput/ElementsInput.utils'; +import { maybeWrapWithContext } from '../utils/maybeWrapWithContext'; export interface BackboneElementInputProps { /** Type name the backbone element represents */ @@ -22,33 +23,35 @@ export interface BackboneElementInputProps { export function BackboneElementInput(props: BackboneElementInputProps): JSX.Element { const [defaultValue] = useState(() => props.defaultValue ?? {}); const parentElementsContext = useContext(ElementsContext); - const profileUrl = props.profileUrl ?? parentElementsContext.profileUrl; + const profileUrl = props.profileUrl ?? parentElementsContext?.profileUrl; const typeSchema = useMemo(() => tryGetDataType(props.typeName, profileUrl), [props.typeName, profileUrl]); const type = typeSchema?.type ?? props.typeName; - const elementsContext = useMemo(() => { + const contextValue: ElementsContextType | undefined = useMemo(() => { + if (!typeSchema) { + return undefined; + } return buildElementsContext({ parentContext: parentElementsContext, - elements: typeSchema?.elements, - parentPath: props.path, - parentType: type, - profileUrl, + elements: typeSchema.elements, + path: props.path, + profileUrl: typeSchema.url, }); - }, [parentElementsContext, typeSchema?.elements, props.path, type, profileUrl]); + }, [typeSchema, props.path, parentElementsContext]); if (!typeSchema) { return
{type} not implemented
; } - return ( - - - + return maybeWrapWithContext( + ElementsContext.Provider, + contextValue, + ); } diff --git a/packages/react/src/ContactPointInput/ContactPointInput.tsx b/packages/react/src/ContactPointInput/ContactPointInput.tsx index adbea9ea6a..8b5b10a89a 100644 --- a/packages/react/src/ContactPointInput/ContactPointInput.tsx +++ b/packages/react/src/ContactPointInput/ContactPointInput.tsx @@ -11,15 +11,15 @@ export type ContactPointInputProps = ComplexTypeInputProps & { export function ContactPointInput(props: ContactPointInputProps): JSX.Element { const { path, outcome } = props; - const { getModifiedNestedElement } = useContext(ElementsContext); + const { elementsByPath } = useContext(ElementsContext); const [contactPoint, setContactPoint] = useState(props.defaultValue); const ref = useRef(); ref.current = contactPoint; const [systemElement, useElement, valueElement] = useMemo( - () => ['system', 'use', 'value'].map((field) => getModifiedNestedElement(path + '.' + field)), - [getModifiedNestedElement, path] + () => ['system', 'use', 'value'].map((field) => elementsByPath[path + '.' + field]), + [elementsByPath, path] ); function setContactPointWrapper(newValue: ContactPoint | undefined): void { diff --git a/packages/react/src/ElementsInput/ElementsInput.tsx b/packages/react/src/ElementsInput/ElementsInput.tsx index 9cb782b7d2..fd8f2fad95 100644 --- a/packages/react/src/ElementsInput/ElementsInput.tsx +++ b/packages/react/src/ElementsInput/ElementsInput.tsx @@ -31,12 +31,6 @@ export function ElementsInput(props: ElementsInputProps): JSX.Element { }, [elementsContext.elements]); function setValueWrapper(newValue: any): void { - for (const [key, prop] of Object.entries(elementsContext.fixedProperties)) { - // setPropertyValue cannot set nested properties - if (!key.includes('.')) { - setPropertyValue(newValue, key, key, prop, prop.fixed.value); - } - } setValue(newValue); if (props.onChange) { props.onChange(newValue); diff --git a/packages/react/src/ElementsInput/ElementsInput.utils.test.ts b/packages/react/src/ElementsInput/ElementsInput.utils.test.ts deleted file mode 100644 index c25c6f9862..0000000000 --- a/packages/react/src/ElementsInput/ElementsInput.utils.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { HTTP_HL7_ORG, isPopulated, parseStructureDefinition } from '@medplum/core'; -import { buildElementsContext } from './ElementsInput.utils'; -import { USCoreStructureDefinitionList } from '@medplum/mock'; - -describe('buildElementsContext', () => { - test('deeply nested schema', () => { - const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-medicationrequest`; - const sd = USCoreStructureDefinitionList.find((sd) => sd.url === profileUrl); - if (!isPopulated(sd)) { - fail(`Expected structure definition for ${profileUrl} to be found`); - } - const schema = parseStructureDefinition(sd); - - const context = buildElementsContext({ - elements: schema.elements, - parentPath: 'MedicationRequest', - parentContext: undefined, - parentType: schema.type, - profileUrl, - }); - - expect(context.profileUrl).toEqual(sd.url); - expect(context.getModifiedNestedElement('MedicationRequest.dosageInstruction.method')).toBeDefined(); - }); -}); diff --git a/packages/react/src/ElementsInput/ElementsInput.utils.ts b/packages/react/src/ElementsInput/ElementsInput.utils.ts index 77159dd852..bebb89d420 100644 --- a/packages/react/src/ElementsInput/ElementsInput.utils.ts +++ b/packages/react/src/ElementsInput/ElementsInput.utils.ts @@ -1,155 +1,11 @@ -import { InternalSchemaElement, InternalTypeSchema, TypedValue, isPopulated } from '@medplum/core'; +import { ElementsContextType } from '@medplum/core'; import React from 'react'; -/** - * Splits a string on the last occurrence of the delimiter - * @param str - The string to split - * @param delim - The delimiter string - * @returns An array of two strings; the first consisting of the beginning of the - * string up to the last occurrence of the delimiter. the second is the remainder of the - * string after the last occurrence of the delimiter. If the delimiter is not present - * in the string, the first element is empty and the second is the input string. - */ -function splitOnceRight(str: string, delim: string): [string, string] { - const delimIndex = str.lastIndexOf(delim); - if (delimIndex === -1) { - return ['', str]; - } - const beginning = str.substring(0, delimIndex); - const last = str.substring(delimIndex + delim.length); - return [beginning, last]; -} - -export type ElementsContextType = { - debugMode: boolean; - profileUrl: string | undefined; - /** - * Get the element definition for the specified path if it has been modified by a profile. - * @param nestedElementPath - The path of the nested element - * @returns The modified element definition if it has been modified by the active profile or undefined. If undefined, - * the element has the default definition for the given type. - */ - getModifiedNestedElement: (nestedElementPath: string) => InternalSchemaElement | undefined; - getElementByPath: (path: string) => InternalSchemaElement | undefined; - elements: Record; - elementsByPath: Record; - fixedProperties: { [key: string]: InternalSchemaElement & { fixed: TypedValue } }; -}; - export const ElementsContext = React.createContext({ + path: '', profileUrl: undefined, - debugMode: false, - getModifiedNestedElement: () => undefined, - getElementByPath: () => undefined, elements: Object.create(null), elementsByPath: Object.create(null), - fixedProperties: Object.create(null), + debugMode: false, }); ElementsContext.displayName = 'ElementsContext'; - -export type BuildElementsContextArgs = { - elements: InternalTypeSchema['elements'] | undefined; - parentPath: string; - parentContext: ElementsContextType | undefined; - parentType: string | undefined; - profileUrl?: string; - debugMode?: boolean; -}; - -function hasFixed(element: InternalSchemaElement): element is InternalSchemaElement & { fixed: TypedValue } { - return isPopulated(element.fixed?.type) && 'value' in element.fixed; -} - -export function buildElementsContext({ - parentContext, - elements, - parentPath, - parentType, - profileUrl, - debugMode, -}: BuildElementsContextArgs): ElementsContextType { - if (debugMode) { - console.debug('Building ElementsContext', { parentPath, profileUrl, elements }); - } - const mergedElements: ElementsContextType['elements'] = mergeElementsForContext( - parentPath, - elements, - parentContext, - Boolean(debugMode) - ); - - const nestedPaths: Record = Object.create(null); - const elementsByPath: ElementsContextType['elementsByPath'] = Object.create(null); - const fixedProperties: ElementsContextType['fixedProperties'] = Object.create(null); - - const seenKeys = new Set(); - for (const [key, property] of Object.entries(mergedElements)) { - elementsByPath[parentPath + '.' + key] = property; - - if (hasFixed(property)) { - fixedProperties[key] = property; - } - - const [beginning, _last] = splitOnceRight(key, '.'); - // assume paths are hierarchically sorted, e.g. identifier comes before identifier.id - if (seenKeys.has(beginning)) { - nestedPaths[parentType + '.' + key] = property; - } - seenKeys.add(key); - } - - function getElementByPath(path: string): InternalSchemaElement | undefined { - return elementsByPath[path]; - } - - function getModifiedNestedElement(nestedElementPath: string): InternalSchemaElement | undefined { - return nestedPaths[nestedElementPath]; - } - - return { - debugMode: debugMode ?? parentContext?.debugMode ?? false, - profileUrl: profileUrl ?? parentContext?.profileUrl, - getModifiedNestedElement, - getElementByPath, - elements: mergedElements, - elementsByPath, - fixedProperties, - }; -} - -function mergeElementsForContext( - parentPath: string, - elements: BuildElementsContextArgs['elements'], - parentContext: BuildElementsContextArgs['parentContext'], - debugMode: boolean -): ElementsContextType['elements'] { - const result: ElementsContextType['elements'] = Object.create(null); - - if (parentContext) { - const parentPathPrefix = parentPath + '.'; - for (const [path, element] of Object.entries(parentContext.elementsByPath)) { - if (path.startsWith(parentPathPrefix)) { - const key = path.slice(parentPathPrefix.length); - result[key] = element; - } - } - } - - let usedNewElements = false; - if (elements) { - for (const [key, element] of Object.entries(elements)) { - if (!(key in result)) { - result[key] = element; - usedNewElements = true; - } - } - } - - // TODO if no new elements are used, the elementscontext is very likely not necessary; - // there could be an optimization where buildElementsContext returns undefined in this case - // to avoid needless contexts - if (debugMode && parentContext && !usedNewElements) { - console.debug('ElementsContext elements same as parent context'); - } - return result; -} diff --git a/packages/react/src/ExtensionInput/ExtensionInput.stories.tsx b/packages/react/src/ExtensionInput/ExtensionInput.stories.tsx index ff912d45b3..08c0efbe7e 100644 --- a/packages/react/src/ExtensionInput/ExtensionInput.stories.tsx +++ b/packages/react/src/ExtensionInput/ExtensionInput.stories.tsx @@ -16,7 +16,7 @@ export const Basic = (): JSX.Element => ( defaultValue={ { url: 'http://hl7.org/fhir/StructureDefinition/patient-interpreterRequired', valueBoolean: true } as Extension } - path={''} + path="Patient.interpreterRequired" onChange={undefined} outcome={undefined} propertyType={{ code: 'Extension' }} diff --git a/packages/react/src/ExtensionInput/ExtensionInput.tsx b/packages/react/src/ExtensionInput/ExtensionInput.tsx index d49802a2f0..0ca3ad9217 100644 --- a/packages/react/src/ExtensionInput/ExtensionInput.tsx +++ b/packages/react/src/ExtensionInput/ExtensionInput.tsx @@ -1,4 +1,4 @@ -import { InternalTypeSchema, tryGetProfile, isProfileLoaded } from '@medplum/core'; +import { InternalTypeSchema, tryGetProfile, isProfileLoaded, isPopulated } from '@medplum/core'; import { ElementDefinitionType, Extension } from '@medplum/fhirtypes'; import { useMedplum } from '@medplum/react-hooks'; import { useEffect, useMemo, useState } from 'react'; @@ -13,15 +13,15 @@ export function ExtensionInput(props: ExtensionInputProps): JSX.Element | null { const { propertyType } = props; const medplum = useMedplum(); - const [loadingProfile, setLoadingProfile] = useState(false); const [typeSchema, setTypeSchema] = useState(); const profileUrl: string | undefined = useMemo(() => { - if (!propertyType.profile || propertyType.profile.length === 0) { + if (!isPopulated(propertyType.profile)) { return undefined; } return propertyType.profile[0] satisfies string; }, [propertyType]); + const [loadingProfile, setLoadingProfile] = useState(profileUrl !== undefined); useEffect(() => { if (profileUrl) { diff --git a/packages/react/src/IdentifierInput/IdentifierInput.tsx b/packages/react/src/IdentifierInput/IdentifierInput.tsx index ad2e6b8840..a0a6fb22f9 100644 --- a/packages/react/src/IdentifierInput/IdentifierInput.tsx +++ b/packages/react/src/IdentifierInput/IdentifierInput.tsx @@ -10,11 +10,11 @@ export type IdentifierInputProps = ComplexTypeInputProps; export function IdentifierInput(props: IdentifierInputProps): JSX.Element { const { path, outcome } = props; const [value, setValue] = useState(props.defaultValue); - const { getModifiedNestedElement } = useContext(ElementsContext); + const { elementsByPath } = useContext(ElementsContext); const [systemElement, valueElement] = useMemo( - () => ['system', 'value'].map((field) => getModifiedNestedElement(path + '.' + field)), - [getModifiedNestedElement, path] + () => ['system', 'value'].map((field) => elementsByPath[path + '.' + field]), + [elementsByPath, path] ); function setValueWrapper(newValue: Identifier): void { diff --git a/packages/react/src/ResourceArrayInput/ResourceArrayInput.test.tsx b/packages/react/src/ResourceArrayInput/ResourceArrayInput.test.tsx index 84b4469d15..41806f585e 100644 --- a/packages/react/src/ResourceArrayInput/ResourceArrayInput.test.tsx +++ b/packages/react/src/ResourceArrayInput/ResourceArrayInput.test.tsx @@ -195,31 +195,20 @@ describe('ResourceArrayInput', () => { onChange, }); - const shouldExist = [ + ['slice-chocolateVariety-add', 'slice-vanillaVariety-add', 'nonsliced-add'].forEach((testId) => { + expect(screen.getByTestId(testId)).toBeInTheDocument(); + }); + + [ 'slice-chocolateVariety-elements-0', 'slice-chocolateVariety-remove-0', - 'slice-chocolateVariety-add', 'slice-vanillaVariety-elements-0', 'slice-vanillaVariety-remove-0', - 'nonsliced-add', - ]; - shouldExist.forEach((testId) => { - expect(screen.getByTestId(testId)).toBeInTheDocument(); - }); - - const shouldNotExist = ['slice-vanillaVariety-add', 'nonsliced-remove-0']; - shouldNotExist.forEach((testId) => { + 'nonsliced-remove-0', + ].forEach((testId) => { expect(screen.queryByTestId(testId)).toBeNull(); }); - await act(async () => { - fireEvent.click(screen.getByTestId('slice-chocolateVariety-remove-0')); - }); - - expect(screen.queryByTestId('slice-chocolateVariety-add')).toBeInTheDocument(); - expect(screen.queryByTestId('slice-chocolateVariety-remove-0')).toBeNull(); - expect(screen.queryByTestId('slice-chocolateVariety-elements-0')).toBeNull(); - await act(async () => { fireEvent.click(screen.getByTestId('slice-chocolateVariety-add')); }); diff --git a/packages/react/src/ResourceArrayInput/ResourceArrayInput.tsx b/packages/react/src/ResourceArrayInput/ResourceArrayInput.tsx index 06dc7dd526..a66c4ab7c4 100644 --- a/packages/react/src/ResourceArrayInput/ResourceArrayInput.tsx +++ b/packages/react/src/ResourceArrayInput/ResourceArrayInput.tsx @@ -1,12 +1,11 @@ import { Group, Stack } from '@mantine/core'; -import { InternalSchemaElement, getPathDisplayName } from '@medplum/core'; +import { InternalSchemaElement, SliceDefinitionWithTypes, getPathDisplayName } from '@medplum/core'; import { OperationOutcome } from '@medplum/fhirtypes'; import { useMedplum } from '@medplum/react-hooks'; import { MouseEvent, useContext, useEffect, useState } from 'react'; import { ElementsContext } from '../ElementsInput/ElementsInput.utils'; import { ResourcePropertyInput } from '../ResourcePropertyInput/ResourcePropertyInput'; import { SliceInput } from '../SliceInput/SliceInput'; -import { SupportedSliceDefinition } from '../SliceInput/SliceInput.utils'; import { ArrayAddButton } from '../buttons/ArrayAddButton'; import { ArrayRemoveButton } from '../buttons/ArrayRemoveButton'; import { killEvent } from '../utils/dom'; @@ -28,7 +27,7 @@ export function ResourceArrayInput(props: ResourceArrayInputProps): JSX.Element const { property } = props; const medplum = useMedplum(); const [loading, setLoading] = useState(true); - const [slices, setSlices] = useState([]); + const [slices, setSlices] = useState([]); // props.defaultValue should NOT be used after this; prefer the defaultValue state const [defaultValue] = useState(() => (Array.isArray(props.defaultValue) ? props.defaultValue : [])); const [slicedValues, setSlicedValues] = useState(() => [defaultValue]); diff --git a/packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.test.ts b/packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.test.ts index 568e1be7e4..5887d5d79c 100644 --- a/packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.test.ts +++ b/packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.test.ts @@ -1,12 +1,25 @@ -import { HTTP_HL7_ORG, InternalTypeSchema, isProfileLoaded, loadDataType, tryGetProfile } from '@medplum/core'; +import { + HTTP_HL7_ORG, + InternalTypeSchema, + buildElementsContext, + isProfileLoaded, + loadDataType, + tryGetProfile, +} from '@medplum/core'; import { assignValuesIntoSlices, prepareSlices } from './ResourceArrayInput.utils'; -import { MockClient, USCoreStructureDefinitionList } from '@medplum/mock'; +import { MockClient } from '@medplum/mock'; import { StructureDefinition } from '@medplum/fhirtypes'; -import { buildElementsContext } from '../ElementsInput/ElementsInput.utils'; +import { readJson } from '@medplum/definitions'; const medplum = new MockClient(); describe('assignValuesIntoSlices', () => { + let USCoreStructureDefinitions: StructureDefinition[]; + + beforeAll(() => { + USCoreStructureDefinitions = readJson('fhir/r4/testing/uscore-v5.0.1-structuredefinitions.json'); + }); + describe('US Core Patient', () => { const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-patient`; const profilesToLoad = [ @@ -16,13 +29,14 @@ describe('assignValuesIntoSlices', () => { `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-birthsex`, `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-genderIdentity`, ]; - const patientSD = USCoreStructureDefinitionList.find((sd) => sd.url === profileUrl) as StructureDefinition; + let patientSD: StructureDefinition; let patientSchema: InternalTypeSchema; beforeAll(() => { + patientSD = USCoreStructureDefinitions.find((sd) => sd.url === profileUrl) as StructureDefinition; expect(patientSD).toBeDefined(); for (const url of profilesToLoad) { - const sd = USCoreStructureDefinitionList.find((sd) => sd.url === url); + const sd = USCoreStructureDefinitions.find((sd) => sd.url === url); if (!sd) { fail(`could not find structure definition for ${url}`); } @@ -80,10 +94,12 @@ describe('assignValuesIntoSlices', () => { const elementsContext = buildElementsContext({ parentContext: undefined, elements: patientSchema.elements, - parentPath: 'Patient', - parentType: 'Patient', + path: 'Patient', profileUrl, }); + if (!elementsContext) { + fail('elementsContext should be defined'); + } const slices = await prepareSlices({ medplum, @@ -103,10 +119,11 @@ describe('assignValuesIntoSlices', () => { describe('US Core Blood Pressure', () => { const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-blood-pressure`; - const bpSD = USCoreStructureDefinitionList.find((sd) => sd.url === profileUrl) as StructureDefinition; + let bpSD: StructureDefinition; let bpSchema: InternalTypeSchema; beforeAll(() => { + bpSD = USCoreStructureDefinitions.find((sd) => sd.url === profileUrl) as StructureDefinition; expect(bpSD).toBeDefined(); loadDataType(bpSD, bpSD.url); expect(isProfileLoaded(profileUrl)).toBe(true); @@ -133,10 +150,12 @@ describe('assignValuesIntoSlices', () => { const elementsContext = buildElementsContext({ parentContext: undefined, elements: bpSchema.elements, - parentPath: 'Observation', - parentType: 'Observation', + path: 'Observation', profileUrl, }); + if (!elementsContext) { + fail('elementsContext should be defined'); + } const slices = await prepareSlices({ medplum, @@ -198,11 +217,14 @@ describe('assignValuesIntoSlices', () => { const elementsContext = buildElementsContext({ parentContext: undefined, elements: bpSchema.elements, - parentPath: 'Observation', - parentType: 'Observation', + path: 'Observation', profileUrl, }); + if (!elementsContext) { + fail('elementsContext should be defined'); + } + const slices = await prepareSlices({ medplum, property, diff --git a/packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.ts b/packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.ts index 926c451769..3a36e18db5 100644 --- a/packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.ts +++ b/packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.ts @@ -1,62 +1,17 @@ import { InternalSchemaElement, MedplumClient, - SliceDiscriminator, + SliceDefinitionWithTypes, SlicingRules, - TypedValue, - arrayify, - getNestedProperty, + getValueSliceName, isPopulated, - matchDiscriminant, + isSliceDefinitionWithTypes, tryGetProfile, } from '@medplum/core'; -import { SupportedSliceDefinition, isSupportedSliceDefinition } from '../SliceInput/SliceInput.utils'; - -function isDiscriminatorComponentMatch( - typedValue: TypedValue, - discriminator: SliceDiscriminator, - slice: SupportedSliceDefinition, - profileUrl: string | undefined -): boolean { - const nestedProp = getNestedProperty(typedValue, discriminator.path, { profileUrl }); - - if (nestedProp) { - const elementList = slice.typeSchema?.elements ?? slice.elements; - return arrayify(nestedProp)?.some((v: any) => matchDiscriminant(v, discriminator, slice, elementList)) ?? false; - } - - console.assert(false, 'getNestedProperty[%s] in isDiscriminatorComponentMatch missed', discriminator.path); - return false; -} -function getValueSliceName( - value: any, - slices: SupportedSliceDefinition[], - discriminators: SliceDiscriminator[], - profileUrl?: string -): string | undefined { - if (!value) { - return undefined; - } - - for (const slice of slices) { - const typedValue: TypedValue = { - value, - type: slice.typeSchema?.name ?? slice.type[0].code, - }; - if ( - discriminators.every((d) => - isDiscriminatorComponentMatch(typedValue, d, slice, slice.typeSchema?.url ?? profileUrl) - ) - ) { - return slice.name; - } - } - return undefined; -} export function assignValuesIntoSlices( values: any[], - slices: SupportedSliceDefinition[], + slices: SliceDefinitionWithTypes[], slicing: SlicingRules | undefined, profileUrl: string | undefined ): any[][] { @@ -81,16 +36,12 @@ export function assignValuesIntoSlices( slicedValues[sliceIndex].push(value); } - // add placeholder empty values + // add placeholder empty values for required slices for (let sliceIndex = 0; sliceIndex < slices.length; sliceIndex++) { const slice = slices[sliceIndex]; const sliceValues = slicedValues[sliceIndex]; - if (sliceValues.length < slice.min) { - while (sliceValues.length < slice.min) { - sliceValues.push(undefined); - } - } else if (sliceValues.length === 0) { + while (sliceValues.length < slice.min) { sliceValues.push(undefined); } } @@ -104,18 +55,18 @@ export async function prepareSlices({ }: { medplum: MedplumClient; property: InternalSchemaElement; -}): Promise { +}): Promise { return new Promise((resolve, reject) => { if (!property.slicing) { resolve([]); return; } - const supportedSlices: SupportedSliceDefinition[] = []; + const supportedSlices: SliceDefinitionWithTypes[] = []; const profileUrls: (string | undefined)[] = []; const promises: Promise[] = []; for (const slice of property.slicing.slices) { - if (!isSupportedSliceDefinition(slice)) { + if (!isSliceDefinitionWithTypes(slice)) { console.debug('Unsupported slice definition', slice); continue; } diff --git a/packages/react/src/ResourceForm/ResourceForm.stories.tsx b/packages/react/src/ResourceForm/ResourceForm.stories.tsx index 17e84c743a..8e9bc25d8d 100644 --- a/packages/react/src/ResourceForm/ResourceForm.stories.tsx +++ b/packages/react/src/ResourceForm/ResourceForm.stories.tsx @@ -151,7 +151,7 @@ function useUSCoreDataTypes({ medplum }: { medplum: MedplumClient }): { loaded: const [loaded, setLoaded] = useState(false); useEffect(() => { (async (): Promise => { - for (const sd of USCoreStructureDefinitionList as StructureDefinition[]) { + for (const sd of USCoreStructureDefinitionList) { loadDataType(sd, sd.url); } return true; @@ -192,7 +192,7 @@ function useFakeRequestProfileSchema(medplum: MedplumClient): void { function useUSCoreProfile(profileName: string): StructureDefinition { const profileSD = useMemo(() => { - const result = (USCoreStructureDefinitionList as StructureDefinition[]).find((sd) => sd.name === profileName); + const result = USCoreStructureDefinitionList.find((sd) => sd.name === profileName); if (!result) { throw new Error(`Could not find ${profileName}`); } diff --git a/packages/react/src/ResourceForm/ResourceForm.test.tsx b/packages/react/src/ResourceForm/ResourceForm.test.tsx index 60bedfd1ba..81d8da2c8f 100644 --- a/packages/react/src/ResourceForm/ResourceForm.test.tsx +++ b/packages/react/src/ResourceForm/ResourceForm.test.tsx @@ -1,14 +1,31 @@ -import { HTTP_HL7_ORG, createReference, loadDataType } from '@medplum/core'; -import { Observation, Patient, Specimen } from '@medplum/fhirtypes'; -import { HomerObservation1, MockClient, USCoreStructureDefinitionList } from '@medplum/mock'; +import { HTTP_HL7_ORG, createReference, deepClone, loadDataType } from '@medplum/core'; +import { Observation, Patient, Specimen, StructureDefinition } from '@medplum/fhirtypes'; +import { HomerObservation1, MockClient } from '@medplum/mock'; import { MedplumProvider } from '@medplum/react-hooks'; import { convertIsoToLocal, convertLocalToIso } from '../DateTimeInput/DateTimeInput.utils'; -import { act, fireEvent, render, screen, waitFor } from '../test-utils/render'; +import { act, fireEvent, render, screen, waitFor, within } from '../test-utils/render'; import { ResourceForm, ResourceFormProps } from './ResourceForm'; +import { readJson } from '@medplum/definitions'; const medplum = new MockClient(); describe('ResourceForm', () => { + let USCoreStructureDefinitions: StructureDefinition[]; + beforeAll(() => { + USCoreStructureDefinitions = readJson('fhir/r4/testing/uscore-v5.0.1-structuredefinitions.json'); + }); + + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(async () => { + await act(async () => { + jest.runOnlyPendingTimers(); + }); + jest.useRealTimers(); + }); + async function setup(props: ResourceFormProps, medplumClient?: MockClient): Promise { await act(async () => { render( @@ -228,7 +245,7 @@ describe('ResourceForm', () => { const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-implantable-device`; const profilesToLoad = [profileUrl, `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-patient`]; for (const url of profilesToLoad) { - const sd = USCoreStructureDefinitionList.find((sd) => sd.url === url); + const sd = USCoreStructureDefinitions.find((sd) => sd.url === url); if (!sd) { fail(`could not find structure definition for ${url}`); } @@ -246,4 +263,116 @@ describe('ResourceForm', () => { expect(fakeRequestProfileSchema).toHaveBeenCalledTimes(1); }); + + test('US Core Patient add extensions', async () => { + const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-patient`; + const raceExtensionUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-race`; + const ethnicityExtensionUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-ethnicity`; + const profileUrls = [ + profileUrl, + raceExtensionUrl, + ethnicityExtensionUrl, + `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-birthsex`, + `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-genderIdentity`, + ]; + for (const url of profileUrls) { + const sd = USCoreStructureDefinitions.find((sd) => sd.url === url); + if (!sd) { + fail(`could not find structure definition for ${url}`); + } + loadDataType(sd, sd.url); + } + + const onSubmit = jest.fn(); + const mockedMedplum = new MockClient(); + const fakeRequestProfileSchema = jest.fn(async (profileUrl: string) => { + return [profileUrl]; + }); + mockedMedplum.requestProfileSchema = fakeRequestProfileSchema; + + const initialValue: Patient = { + resourceType: 'Patient', + name: [ + { + given: ['Lisa'], + family: 'Simpson', + use: 'usual', + }, + ], + gender: 'female', + identifier: [ + { + system: 'http://name.ly', + value: 'lisa-123', + }, + ], + }; + const expectedValue = deepClone(initialValue); + expectedValue.extension = []; + + await setup({ defaultValue: initialValue, profileUrl, onSubmit }, mockedMedplum); + + const raceExtension = screen.getByTestId('slice-race'); + + await act(async () => { + fireEvent.click(within(raceExtension).getByText('Add Race')); + }); + + await act(async () => { + fireEvent.click(within(raceExtension).getByText('Add OMB Category')); + }); + + const ombCategoryInput = within(within(raceExtension).getByTestId('slice-ombCategory')).getByRole('searchbox'); + + await act(async () => { + fireEvent.focus(ombCategoryInput); + }); + + await act(async () => { + fireEvent.change(ombCategoryInput, { target: { value: 'custom-omb-category-value' } }); + }); + + await waitFor(() => screen.getByText('+ Create custom-omb-category-value')); + + await act(async () => { + fireEvent.click(screen.getByText('+ Create custom-omb-category-value')); + }); + + await waitFor(() => screen.getByText('custom-omb-category-value')); + + await act(async () => { + const textInput = within(within(raceExtension).getByTestId('slice-text')).getByTestId('value[x]'); + fireEvent.change(textInput, { + target: { value: 'This is a text value' }, + }); + }); + + // Just clicking add, but not filling in a value should not add it to the final value + await act(async () => { + fireEvent.click(within(raceExtension).getByText('Add Detailed')); + }); + + expectedValue.extension.push({ + extension: [ + { + url: 'ombCategory', + valueCoding: { + code: 'custom-omb-category-value', + display: 'custom-omb-category-value', + }, + }, + { + url: 'text', + valueString: 'This is a text value', + }, + ], + url: raceExtensionUrl, + }); + + await act(async () => { + fireEvent.click(screen.getByText('OK')); + }); + + expect(onSubmit).toHaveBeenCalledWith(expectedValue); + }); }); diff --git a/packages/react/src/ResourceForm/ResourceForm.tsx b/packages/react/src/ResourceForm/ResourceForm.tsx index 5511969d94..0f074c13fb 100644 --- a/packages/react/src/ResourceForm/ResourceForm.tsx +++ b/packages/react/src/ResourceForm/ResourceForm.tsx @@ -1,5 +1,5 @@ import { Button, Group, Stack, TextInput } from '@mantine/core'; -import { deepClone, tryGetProfile } from '@medplum/core'; +import { applyDefaultValuesToResource, tryGetProfile } from '@medplum/core'; import { OperationOutcome, Reference, Resource } from '@medplum/fhirtypes'; import { useMedplum, useResource } from '@medplum/react-hooks'; import { FormEvent, useEffect, useState } from 'react'; @@ -25,7 +25,6 @@ export function ResourceForm(props: ResourceFormProps): JSX.Element { useEffect(() => { if (defaultValue) { - setValue(deepClone(defaultValue)); if (props.profileUrl) { const profileUrl: string = props.profileUrl; medplum @@ -34,6 +33,8 @@ export function ResourceForm(props: ResourceFormProps): JSX.Element { const profile = tryGetProfile(profileUrl); if (profile) { setSchemaLoaded(profile.name); + const modifiedDefaultValue = applyDefaultValuesToResource(defaultValue, profile); + setValue(modifiedDefaultValue); } else { console.error(`Schema not found for ${profileUrl}`); } @@ -45,7 +46,10 @@ export function ResourceForm(props: ResourceFormProps): JSX.Element { const schemaName = props.schemaName ?? defaultValue?.resourceType; medplum .requestSchema(schemaName) - .then(() => setSchemaLoaded(schemaName)) + .then(() => { + setValue(defaultValue); + setSchemaLoaded(schemaName); + }) .catch(console.log); } } diff --git a/packages/react/src/ResourceInput/ResourceInput.tsx b/packages/react/src/ResourceInput/ResourceInput.tsx index 0cc8613186..f72c59dad7 100644 --- a/packages/react/src/ResourceInput/ResourceInput.tsx +++ b/packages/react/src/ResourceInput/ResourceInput.tsx @@ -1,5 +1,5 @@ import { Group, Text } from '@mantine/core'; -import { getDisplayString, getReferenceString } from '@medplum/core'; +import { getDisplayString, getReferenceString, isPopulated } from '@medplum/core'; import { OperationOutcome, Patient, Reference, Resource } from '@medplum/fhirtypes'; import { useMedplum, useResource } from '@medplum/react-hooks'; import { forwardRef, useCallback, useState } from 'react'; @@ -121,7 +121,7 @@ export function ResourceInput(props: ResourceInpu [onChange] ); - if (props.defaultValue && !outcome && !defaultValue) { + if (isPopulated(props.defaultValue) && !outcome && !defaultValue) { // If a default value was specified, but the default resource is not loaded yet, // then return null to avoid rendering the input until the default resource is loaded. // The Mantine component does not reliably handle changes to defaultValue. diff --git a/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.tsx b/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.tsx index 520ebeb0ac..77a3160438 100644 --- a/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.tsx +++ b/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.tsx @@ -1,7 +1,17 @@ import { Checkbox, Group, NativeSelect, Textarea, TextInput } from '@mantine/core'; -import { capitalize, HTTP_HL7_ORG, InternalSchemaElement, PropertyType } from '@medplum/core'; +import { + applyDefaultValuesToElement, + capitalize, + getPathDifference, + HTTP_HL7_ORG, + InternalSchemaElement, + isComplexTypeCode, + isEmpty, + isPopulated, + PropertyType, +} from '@medplum/core'; import { ElementDefinitionBinding, ElementDefinitionType, OperationOutcome } from '@medplum/fhirtypes'; -import { useState } from 'react'; +import { useContext, useMemo, useState } from 'react'; import { AddressInput } from '../AddressInput/AddressInput'; import { AnnotationInput } from '../AnnotationInput/AnnotationInput'; import { AttachmentArrayInput } from '../AttachmentArrayInput/AttachmentArrayInput'; @@ -13,6 +23,7 @@ import { CodingInput } from '../CodingInput/CodingInput'; import { ContactDetailInput } from '../ContactDetailInput/ContactDetailInput'; import { ContactPointInput } from '../ContactPointInput/ContactPointInput'; import { DateTimeInput } from '../DateTimeInput/DateTimeInput'; +import { ElementsContext } from '../ElementsInput/ElementsInput.utils'; import { ExtensionInput } from '../ExtensionInput/ExtensionInput'; import { HumanNameInput } from '../HumanNameInput/HumanNameInput'; import { IdentifierInput } from '../IdentifierInput/IdentifierInput'; @@ -41,12 +52,11 @@ export interface ResourcePropertyInputProps { } export function ResourcePropertyInput(props: ResourcePropertyInputProps): JSX.Element { - const { property, name, defaultValue, onChange } = props; + const { property, name, onChange, defaultValue } = props; const defaultPropertyType = props.defaultPropertyType && props.defaultPropertyType !== 'undefined' ? props.defaultPropertyType : property.type[0].code; - const propertyTypes = property.type as ElementDefinitionType[]; if ((property.isArray || property.max > 1) && !props.arrayElement) { @@ -67,9 +77,7 @@ export function ResourcePropertyInput(props: ResourcePropertyInputProps): JSX.El outcome={props.outcome} /> ); - } - - if (propertyTypes.length > 1) { + } else if (propertyTypes.length > 1) { return ; } else { return ( @@ -154,11 +162,39 @@ export interface ElementDefinitionTypeInputProps } export function ElementDefinitionTypeInput(props: ElementDefinitionTypeInputProps): JSX.Element { - const { name, defaultValue, onChange, outcome, binding, path } = props; + const { name, onChange, outcome, binding, path } = props; const required = props.min !== undefined && props.min > 0; const propertyType = props.elementDefinitionType.code; + const elementsContext = useContext(ElementsContext); + const defaultValue = useMemo(() => { + if (!isComplexTypeCode(propertyType)) { + return props.defaultValue; + } + + if (!isEmpty(props.defaultValue)) { + return props.defaultValue; + } + + const withDefaults = Object.create(null); + if (elementsContext.path === props.path) { + applyDefaultValuesToElement(withDefaults, elementsContext.elements); + } else { + const key = getPathDifference(elementsContext.path, props.path); + if (key === undefined) { + return props.defaultValue; + } + applyDefaultValuesToElement(withDefaults, elementsContext.elements, key); + } + + if (isPopulated(withDefaults)) { + return withDefaults; + } + + return props.defaultValue; + }, [propertyType, elementsContext.path, elementsContext.elements, props.path, props.defaultValue]); + if (!propertyType) { return
Property type not specified
; } diff --git a/packages/react/src/SliceInput/SliceInput.tsx b/packages/react/src/SliceInput/SliceInput.tsx index eaa149b278..bec86703a0 100644 --- a/packages/react/src/SliceInput/SliceInput.tsx +++ b/packages/react/src/SliceInput/SliceInput.tsx @@ -1,19 +1,27 @@ import { Group, Stack } from '@mantine/core'; -import { InternalSchemaElement, getPropertyDisplayName, isEmpty, isPopulated } from '@medplum/core'; +import { + ElementsContextType, + InternalSchemaElement, + SliceDefinitionWithTypes, + buildElementsContext, + getPropertyDisplayName, + isEmpty, + isPopulated, +} from '@medplum/core'; import { OperationOutcome } from '@medplum/fhirtypes'; import { useContext, useMemo, useState } from 'react'; -import { ElementsContext, ElementsContextType, buildElementsContext } from '../ElementsInput/ElementsInput.utils'; +import { ElementsContext } from '../ElementsInput/ElementsInput.utils'; import { FormSection } from '../FormSection/FormSection'; import { ElementDefinitionTypeInput } from '../ResourcePropertyInput/ResourcePropertyInput'; import { ArrayAddButton } from '../buttons/ArrayAddButton'; import { ArrayRemoveButton } from '../buttons/ArrayRemoveButton'; import { killEvent } from '../utils/dom'; import classes from '../ResourceArrayInput/ResourceArrayInput.module.css'; -import { SupportedSliceDefinition } from './SliceInput.utils'; +import { maybeWrapWithContext } from '../utils/maybeWrapWithContext'; export interface SliceInputProps { readonly path: string; - readonly slice: SupportedSliceDefinition; + readonly slice: SliceDefinitionWithTypes; readonly property: InternalSchemaElement; readonly defaultValue: any[]; readonly onChange: (newValue: any[]) => void; @@ -21,37 +29,26 @@ export interface SliceInputProps { readonly testId?: string; } -function maybeWrapWithContext(contextValue: ElementsContextType | undefined, contents: JSX.Element): JSX.Element { - if (contextValue) { - return {contents}; - } - - return contents; -} - export function SliceInput(props: SliceInputProps): JSX.Element | null { const { slice, property } = props; - const [values, setValues] = useState(() => { - return props.defaultValue.map((v) => v ?? {}); - }); + const [values, setValues] = useState(props.defaultValue); - const sliceType = slice.typeSchema?.type ?? slice.type[0].code; const sliceElements = slice.typeSchema?.elements ?? slice.elements; const parentElementsContextValue = useContext(ElementsContext); - const contextValue = useMemo(() => { + const contextValue: ElementsContextType | undefined = useMemo(() => { if (isPopulated(sliceElements)) { return buildElementsContext({ parentContext: parentElementsContextValue, elements: sliceElements, - parentPath: props.path, - parentType: sliceType, + path: props.path, + profileUrl: slice.typeSchema?.url, }); } console.assert(false, 'Expected sliceElements to always be populated', props.path); return undefined; - }, [parentElementsContextValue, props.path, sliceElements, sliceType]); + }, [parentElementsContextValue, props.path, slice.typeSchema?.url, sliceElements]); function setValuesWrapper(newValues: any[]): void { setValues(newValues); @@ -67,6 +64,7 @@ export function SliceInput(props: SliceInputProps): JSX.Element | null { const indentedStack = isEmpty(slice.elements); const propertyDisplayName = getPropertyDisplayName(slice.name); return maybeWrapWithContext( + ElementsContext.Provider, contextValue, ; - typeSchema?: InternalTypeSchema; -}; - -export function isSupportedSliceDefinition(slice: SliceDefinition): slice is SupportedSliceDefinition { - return slice.type !== undefined && slice.type.length > 0; -} diff --git a/packages/react/src/utils/maybeWrapWithContext.tsx b/packages/react/src/utils/maybeWrapWithContext.tsx new file mode 100644 index 0000000000..c21d70acbb --- /dev/null +++ b/packages/react/src/utils/maybeWrapWithContext.tsx @@ -0,0 +1,11 @@ +export function maybeWrapWithContext( + ContextProvider: React.Context['Provider'], + contextValue: T | undefined, + contents: JSX.Element +): JSX.Element { + if (contextValue !== undefined) { + return {contents}; + } + + return contents; +} diff --git a/packages/server/src/fhir/operations/structuredefinitionexpandprofile.test.ts b/packages/server/src/fhir/operations/structuredefinitionexpandprofile.test.ts index 00a735bc8a..013e7c8b9b 100644 --- a/packages/server/src/fhir/operations/structuredefinitionexpandprofile.test.ts +++ b/packages/server/src/fhir/operations/structuredefinitionexpandprofile.test.ts @@ -1,4 +1,3 @@ -import { USCoreStructureDefinitionList } from '@medplum/mock'; import { ContentType, HTTP_HL7_ORG } from '@medplum/core'; import express from 'express'; import request from 'supertest'; @@ -6,31 +5,34 @@ import { loadTestConfig } from '../../config'; import { initApp, shutdownApp } from '../../app'; import { createTestProject } from '../../test.setup'; import { Bundle, ElementDefinition, StructureDefinition, StructureDefinitionSnapshot } from '@medplum/fhirtypes'; +import { readJson } from '@medplum/definitions'; jest.mock('node-fetch'); const app = express(); -async function createSDs(profileUrls: string[], accessToken: string): Promise { - for (const profileUrl of profileUrls) { - const sd = USCoreStructureDefinitionList.find((sd) => sd.url === profileUrl); +describe('StructureDefinition $expand-profile', () => { + let USCoreStructureDefinitions: StructureDefinition[]; + let accessToken: string; - if (!sd) { - fail(`could not find structure definition for ${profileUrl}`); + async function createSDs(profileUrls: string[], accessToken: string): Promise { + for (const profileUrl of profileUrls) { + const sd = USCoreStructureDefinitions.find((sd) => sd.url === profileUrl); + + if (!sd) { + fail(`could not find structure definition for ${profileUrl}`); + } + const res = await request(app) + .post(`/fhir/R4/StructureDefinition`) + .set('Authorization', 'Bearer ' + accessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send(sd); + expect(res.status).toEqual(201); } - const res = await request(app) - .post(`/fhir/R4/StructureDefinition`) - .set('Authorization', 'Bearer ' + accessToken) - .set('Content-Type', ContentType.FHIR_JSON) - .send(sd); - expect(res.status).toEqual(201); } -} - -describe('StructureDefinition $expand-profile', () => { - let accessToken: string; beforeAll(async () => { + USCoreStructureDefinitions = readJson('fhir/r4/testing/uscore-v5.0.1-structuredefinitions.json'); const config = await loadTestConfig(); await initApp(app, config); }); From ee8169b659238442b699f5b1f79816ee11080f5e Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Thu, 15 Feb 2024 13:56:55 -0800 Subject: [PATCH 47/81] Search and paging in ChooseProfileForm (#3962) * Search and paging in ChooseProfileForm * Tests and stories * Fixed storybook error --- .../src/auth/ChooseProfileForm.stories.tsx | 46 +++++++++ .../react/src/auth/ChooseProfileForm.test.tsx | 84 ++++++++++++++++ packages/react/src/auth/ChooseProfileForm.tsx | 98 +++++++++++++------ .../react/src/auth/RegisterForm.stories.tsx | 2 +- .../react/src/auth/SignInForm.stories.tsx | 2 +- 5 files changed, 200 insertions(+), 32 deletions(-) create mode 100644 packages/react/src/auth/ChooseProfileForm.stories.tsx create mode 100644 packages/react/src/auth/ChooseProfileForm.test.tsx diff --git a/packages/react/src/auth/ChooseProfileForm.stories.tsx b/packages/react/src/auth/ChooseProfileForm.stories.tsx new file mode 100644 index 0000000000..e9ec8306bc --- /dev/null +++ b/packages/react/src/auth/ChooseProfileForm.stories.tsx @@ -0,0 +1,46 @@ +import { ProjectMembership } from '@medplum/fhirtypes'; +import { Meta } from '@storybook/react'; +import { Document } from '../Document/Document'; +import { ChooseProfileForm } from './ChooseProfileForm'; + +export default { + title: 'Medplum/Auth/ChooseProfileForm', + component: ChooseProfileForm, +} as Meta; + +export function FewMemberships(): JSX.Element { + return ( + + + + ); +} + +export function ManyMemberships(): JSX.Element { + const memberships = []; + for (let i = 1; i <= 30; i++) { + memberships.push(makeMembership('membership' + i, 'Project ' + i, 'Profile ' + i)); + } + return ( + + + + ); +} + +function makeMembership(id: string, projectName: string, profileName: string): ProjectMembership { + return { + resourceType: 'ProjectMembership', + id, + project: { reference: 'Project/' + projectName, display: projectName }, + user: { reference: 'User/x', display: 'x' }, + profile: { reference: 'Practitioner/' + profileName, display: profileName }, + }; +} diff --git a/packages/react/src/auth/ChooseProfileForm.test.tsx b/packages/react/src/auth/ChooseProfileForm.test.tsx new file mode 100644 index 0000000000..e67dbb409c --- /dev/null +++ b/packages/react/src/auth/ChooseProfileForm.test.tsx @@ -0,0 +1,84 @@ +import { ProjectMembership } from '@medplum/fhirtypes'; +import { MockClient } from '@medplum/mock'; +import { MedplumProvider } from '@medplum/react-hooks'; +import { act } from 'react-dom/test-utils'; +import { fireEvent, render, screen } from '../test-utils/render'; +import { ChooseProfileForm } from './ChooseProfileForm'; + +describe('ChooseProfileForm', () => { + test('Renders', () => { + render( + + + + ); + + expect(screen.getByText('Choose profile')).toBeInTheDocument(); + expect(screen.getByText('Prod')).toBeInTheDocument(); + expect(screen.getByText('Staging')).toBeInTheDocument(); + }); + + test('Filters', async () => { + render( + + + + ); + + const input = screen.getByPlaceholderText('Search') as HTMLInputElement; + await act(async () => { + fireEvent.change(input, { target: { value: 'prod' } }); + }); + + expect(screen.getByText('Prod')).toBeInTheDocument(); + expect(screen.queryByText('Staging')).not.toBeInTheDocument(); + }); + + test('No matches', async () => { + render( + + + + ); + + const input = screen.getByPlaceholderText('Search') as HTMLInputElement; + await act(async () => { + fireEvent.change(input, { target: { value: 'xyz' } }); + }); + + expect(screen.queryByText('Prod')).not.toBeInTheDocument(); + expect(screen.queryByText('Staging')).not.toBeInTheDocument(); + expect(screen.getByText('Nothing found...')).toBeInTheDocument(); + }); +}); + +function makeMembership(id: string, projectName: string, profileName: string): ProjectMembership { + return { + resourceType: 'ProjectMembership', + id, + project: { reference: 'Project/' + projectName, display: projectName }, + user: { reference: 'User/x', display: 'x' }, + profile: { reference: 'Practitioner/' + profileName, display: profileName }, + }; +} diff --git a/packages/react/src/auth/ChooseProfileForm.tsx b/packages/react/src/auth/ChooseProfileForm.tsx index 7e97ef2563..2478a991c2 100644 --- a/packages/react/src/auth/ChooseProfileForm.tsx +++ b/packages/react/src/auth/ChooseProfileForm.tsx @@ -1,9 +1,9 @@ -import { Avatar, Center, Group, Stack, Text, Title, UnstyledButton } from '@mantine/core'; +import { Avatar, Combobox, Flex, Group, Stack, Text, TextInput, Title, useCombobox } from '@mantine/core'; import { LoginAuthenticationResponse, normalizeOperationOutcome } from '@medplum/core'; import { OperationOutcome, ProjectMembership } from '@medplum/fhirtypes'; +import { useMedplum } from '@medplum/react-hooks'; import { useState } from 'react'; import { Logo } from '../Logo/Logo'; -import { useMedplum } from '@medplum/react-hooks'; import { OperationOutcomeAlert } from '../OperationOutcomeAlert/OperationOutcomeAlert'; export interface ChooseProfileFormProps { @@ -14,40 +14,78 @@ export interface ChooseProfileFormProps { export function ChooseProfileForm(props: ChooseProfileFormProps): JSX.Element { const medplum = useMedplum(); + const combobox = useCombobox(); + const [search, setSearch] = useState(''); const [outcome, setOutcome] = useState(); + + function filterDisplay(display: string | undefined): boolean { + return !!display?.toLowerCase()?.includes(search.toLowerCase()); + } + + function filterMembership(membership: ProjectMembership): boolean { + return filterDisplay(membership.profile?.display) || filterDisplay(membership.project?.display); + } + + function handleValueSelect(membershipId: string): void { + medplum + .post('auth/profile', { + login: props.login, + profile: membershipId, + }) + .then(props.handleAuthResponse) + .catch((err) => setOutcome(normalizeOperationOutcome(err))); + } + + const options = props.memberships + .filter(filterMembership) + .slice(0, 10) + .map((item) => ( + + + + )); + return ( -
+ Choose profile -
+ - {props.memberships.map((membership: ProjectMembership) => ( - { - medplum - .post('auth/profile', { - login: props.login, - profile: membership.id, - }) - .then(props.handleAuthResponse) - .catch((err) => setOutcome(normalizeOperationOutcome(err))); - }} - > - - -
- - {membership.profile?.display} - - - {membership.project?.display} - -
-
-
- ))} + + + { + setSearch(event.currentTarget.value); + combobox.updateSelectedOptionIndex(); + }} + /> + + +
+ + {options.length > 0 ? options : Nothing found...} + +
+
); } + +function SelectOption(membership: ProjectMembership): JSX.Element { + return ( + + +
+ + {membership.profile?.display} + + + {membership.project?.display} + +
+
+ ); +} diff --git a/packages/react/src/auth/RegisterForm.stories.tsx b/packages/react/src/auth/RegisterForm.stories.tsx index 69f404870b..b56a677e94 100644 --- a/packages/react/src/auth/RegisterForm.stories.tsx +++ b/packages/react/src/auth/RegisterForm.stories.tsx @@ -4,7 +4,7 @@ import { RegisterForm } from '../auth/RegisterForm'; import { Logo } from '../Logo/Logo'; export default { - title: 'Medplum/RegisterForm', + title: 'Medplum/Auth/RegisterForm', component: RegisterForm, } as Meta; diff --git a/packages/react/src/auth/SignInForm.stories.tsx b/packages/react/src/auth/SignInForm.stories.tsx index f9b1e7c78d..b38b58c9e8 100644 --- a/packages/react/src/auth/SignInForm.stories.tsx +++ b/packages/react/src/auth/SignInForm.stories.tsx @@ -4,7 +4,7 @@ import { SignInForm } from './SignInForm'; import { Logo } from '../Logo/Logo'; export default { - title: 'Medplum/SignInForm', + title: 'Medplum/Auth/SignInForm', component: SignInForm, } as Meta; From 7fdb50c28e8211733ef69b7d9a20cae67d3d2a07 Mon Sep 17 00:00:00 2001 From: Matt Long Date: Thu, 15 Feb 2024 16:22:06 -0800 Subject: [PATCH 48/81] More precise clearing of resource.meta in bundle conversion (#3973) * More precise clearing of resource.meta, Fixes 3664 * Use toEqual instead of toMatchObject --- packages/core/src/bundle.test.ts | 67 +++++++++++++++++++++++++++++++- packages/core/src/bundle.ts | 8 +++- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/packages/core/src/bundle.test.ts b/packages/core/src/bundle.test.ts index 84ae7be53e..2a73d5859a 100644 --- a/packages/core/src/bundle.test.ts +++ b/packages/core/src/bundle.test.ts @@ -1,6 +1,7 @@ import { Bundle, BundleEntry, DiagnosticReport, Patient, Resource, Specimen } from '@medplum/fhirtypes'; import { convertContainedResourcesToBundle, convertToTransactionBundle } from './bundle'; -import { isUUID } from './utils'; +import { deepClone, isUUID } from './utils'; +import { getDataType } from './typeschema/types'; let jsonFile: any; @@ -214,6 +215,70 @@ describe('Bundle tests', () => { ], }); }); + + test('Preserve resource.meta', () => { + const patient: Patient = { + resourceType: 'Patient', + meta: { + account: { + reference: 'Organization/33333333-3333-3333-3333-333333333333', + display: 'Organization #3', + }, + author: { + reference: 'Practitioner/22222222-2222-2222-2222-222222222222', + display: 'Doctor', + }, + compartment: [ + { + reference: 'Project/11111111-2222-3333-4444-555555555555', + }, + { + reference: 'Patient/00000000-0000-0000-0000-000000000000', + }, + ], + extension: [{ url: 'https://example.com/Extension/meta-1', valueBoolean: true }], + id: 'some-id', + lastUpdated: '2024-02-14T21:47:11.777Z', + profile: ['http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient'], + project: '11111111-2222-3333-4444-555555555555', + security: [{ system: 'http://hl7.org/fhir/v3/Confidentiality', code: 'N' }], + source: 'https://example.com/source', + tag: [{ system: 'http://hl7.org/fhir/v3/ObservationValue', code: 'SUBSETTED' }], + versionId: '55555555-5555-5555-5555-555555555555', + }, + active: true, + }; + + const expected = deepClone(patient); + const meta = expected.meta; + if (meta === undefined) { + fail('Expected meta to be defined'); + } + + const removedKeys = ['project', 'versionId', 'lastUpdated', 'compartment', 'author']; + for (const key of Object.keys(getDataType('Meta').elements)) { + // make sure every possible element is defined in the test + expect((meta as any)[key]).toBeDefined(); + + if (removedKeys.includes(key)) { + delete (meta as any)[key]; + } + } + + const inputBundle: Bundle = { + resourceType: 'Bundle', + type: 'searchset', + entry: [ + { + fullUrl: 'https://example.com/Patient/00000000-0000-0000-0000-000000000000', + resource: patient, + }, + ], + }; + + const result = convertToTransactionBundle(inputBundle); + expect(result?.entry?.[0]?.resource).toEqual(expected); + }); }); describe('convertContainedResourcesToBundle', () => { diff --git a/packages/core/src/bundle.ts b/packages/core/src/bundle.ts index 5efae1ce93..b46ea6167c 100644 --- a/packages/core/src/bundle.ts +++ b/packages/core/src/bundle.ts @@ -17,7 +17,13 @@ export function convertToTransactionBundle(bundle: Bundle): Bundle { const idToUuid: Record = {}; bundle = deepClone(bundle); for (const entry of bundle.entry || []) { - delete entry.resource?.meta; + if (entry.resource?.meta !== undefined) { + delete entry.resource.meta.author; + delete entry.resource.meta.compartment; + delete entry.resource.meta.lastUpdated; + delete entry.resource.meta.project; + delete entry.resource.meta.versionId; + } const id = entry.resource?.id; if (id) { idToUuid[id] = generateId(); From de46769e05b3b30778a8e3f7d70895b00ec88ac2 Mon Sep 17 00:00:00 2001 From: dillonstreator Date: Fri, 16 Feb 2024 13:28:40 -0600 Subject: [PATCH 49/81] fix-3976 fully propagate traceId to user executed code and into bot-injected medplum client (#3977) * fix-3976 fully propagate traceId to user executed code and into bot-injected medplum client * refactor/simplify --- packages/core/src/client.ts | 1 + packages/server/src/fhir/operations/deploy.ts | 11 ++++++++--- packages/server/src/fhir/operations/execute.ts | 11 ++++++++--- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 0c9572a261..3b8537936e 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -409,6 +409,7 @@ export interface BotEvent; + readonly traceId?: string; } export interface InviteRequest { diff --git a/packages/server/src/fhir/operations/deploy.ts b/packages/server/src/fhir/operations/deploy.ts index b93df768d2..d961432447 100644 --- a/packages/server/src/fhir/operations/deploy.ts +++ b/packages/server/src/fhir/operations/deploy.ts @@ -35,10 +35,15 @@ const PdfPrinter = require("pdfmake"); const userCode = require("./user.js"); exports.handler = async (event, context) => { - const { baseUrl, accessToken, contentType, secrets } = event; + const { baseUrl, accessToken, contentType, secrets, traceId } = event; const medplum = new MedplumClient({ baseUrl, - fetch, + fetch: function(url, options = {}) { + options.headers ||= {}; + options.headers['X-Trace-Id'] = traceId; + options.headers['traceparent'] = traceId; + return fetch(url, options); + }, createPdf, }); medplum.setAccessToken(accessToken); @@ -47,7 +52,7 @@ exports.handler = async (event, context) => { if (contentType === ContentType.HL7_V2 && input) { input = Hl7Message.parse(input); } - let result = await userCode.handler(medplum, { input, contentType, secrets }); + let result = await userCode.handler(medplum, { input, contentType, secrets, traceId }); if (contentType === ContentType.HL7_V2 && result) { result = result.toString(); } diff --git a/packages/server/src/fhir/operations/execute.ts b/packages/server/src/fhir/operations/execute.ts index bab04ec337..713995aa38 100644 --- a/packages/server/src/fhir/operations/execute.ts +++ b/packages/server/src/fhir/operations/execute.ts @@ -429,10 +429,15 @@ async function runInVmContext(request: BotExecutionRequest): Promise { - const { baseUrl, accessToken, contentType, secrets } = event; + const { baseUrl, accessToken, contentType, secrets, traceId } = event; const medplum = new MedplumClient({ baseUrl, - fetch, + fetch: function(url, options = {}) { + options.headers ||= {}; + options.headers['X-Trace-Id'] = traceId; + options.headers['traceparent'] = traceId; + return fetch(url, options); + }, }); medplum.setAccessToken(accessToken); try { @@ -440,7 +445,7 @@ async function runInVmContext(request: BotExecutionRequest): Promise Date: Fri, 16 Feb 2024 17:04:42 -0500 Subject: [PATCH 50/81] Add Agent Dockerfile. Fixes #3912 (#3913) --- packages/agent/.dockerignore | 9 +++++++++ packages/agent/Dockerfile | 17 +++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 packages/agent/.dockerignore create mode 100644 packages/agent/Dockerfile diff --git a/packages/agent/.dockerignore b/packages/agent/.dockerignore new file mode 100644 index 0000000000..4de047158c --- /dev/null +++ b/packages/agent/.dockerignore @@ -0,0 +1,9 @@ +# Ignore everything +* + +# But not these files... +!.gitignore +!Dockerfile + +# ...even if they are in subdirectories +!*/ diff --git a/packages/agent/Dockerfile b/packages/agent/Dockerfile new file mode 100644 index 0000000000..4aea49a8cf --- /dev/null +++ b/packages/agent/Dockerfile @@ -0,0 +1,17 @@ +FROM --platform=linux/amd64 debian:bullseye-slim + +ARG GIT_SHA +ARG MEDPLUM_VERSION + +ENV GIT_SHA ${GIT_SHA} +ENV MEDPLUM_VERSION ${MEDPLUM_VERSION} + +RUN adduser -u 5678 --disabled-password --gecos "" app + +COPY bin/medplum-agent-${MEDPLUM_VERSION}-linux /srv/medplum-agent + +WORKDIR /srv + +USER app + +CMD ./medplum-agent $MEDPLUM_BASE_URL $MEDPLUM_CLIENT_ID $MEDPLUM_CLIENT_SECRET $MEDPLUM_AGENT_ID From 1b2bdcc104e16c9687e91975f8d814bf8a9da489 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Fri, 16 Feb 2024 14:03:42 -0800 Subject: [PATCH 51/81] Websocket demo updates (#3981) --- .../src/components/BundleDisplay.tsx | 73 +++++++++++ .../src/main.tsx | 4 +- .../src/pages/HomePage.tsx | 123 ++++-------------- 3 files changed, 104 insertions(+), 96 deletions(-) create mode 100644 examples/medplum-websocket-subscriptions-demo/src/components/BundleDisplay.tsx diff --git a/examples/medplum-websocket-subscriptions-demo/src/components/BundleDisplay.tsx b/examples/medplum-websocket-subscriptions-demo/src/components/BundleDisplay.tsx new file mode 100644 index 0000000000..cc3b97f916 --- /dev/null +++ b/examples/medplum-websocket-subscriptions-demo/src/components/BundleDisplay.tsx @@ -0,0 +1,73 @@ +import { Accordion, ActionIcon, Chip, Group } from '@mantine/core'; +import { Bundle, Communication, Reference } from '@medplum/fhirtypes'; +import { useMedplum } from '@medplum/react'; +import { IconArrowNarrowRight, IconCheck } from '@tabler/icons-react'; +import { useCallback } from 'react'; + +export interface BundleDisplayProps { + readonly bundle: Bundle; +} + +export function BundleDisplay(props: BundleDisplayProps): JSX.Element { + const medplum = useMedplum(); + const { bundle } = props; + const communication = bundle?.entry?.[1].resource as Communication; + const [senderType, senderId] = ((communication.sender as Reference).reference as string).split('/'); + const [recipientType, recipientId] = ((communication.recipient?.[0] as Reference).reference as string).split('/'); + + const markAsCompleted = useCallback( + (e: React.SyntheticEvent) => { + e.stopPropagation(); + e.preventDefault(); + medplum + .updateResource({ + ...communication, + received: new Date().toISOString(), // Mark as received + status: 'completed', // Mark as read + // See: https://www.medplum.com/docs/communications/organizing-communications#:~:text=THE%20Communication%20LIFECYCLE + // for more info about recommended `Communication` lifecycle + }) + .catch(console.error); + }, + [medplum, communication] + ); + + return ( + + + + {bundle.timestamp}{' '} + + {senderType}/{senderId.slice(0, 8)} + + + + {recipientType}/{recipientId.slice(0, 8)} + + + {communication.status} + + {communication.status !== 'completed' && ( + + + + )} + + + +
+
{JSON.stringify(bundle, null, 2)}
+
+
+
+ ); +} diff --git a/examples/medplum-websocket-subscriptions-demo/src/main.tsx b/examples/medplum-websocket-subscriptions-demo/src/main.tsx index 4b930bd0d6..9b72e3f021 100644 --- a/examples/medplum-websocket-subscriptions-demo/src/main.tsx +++ b/examples/medplum-websocket-subscriptions-demo/src/main.tsx @@ -1,6 +1,8 @@ import { MantineProvider, MantineThemeOverride } from '@mantine/core'; +import '@mantine/core/styles.css'; import { MedplumClient } from '@medplum/core'; import { MedplumProvider } from '@medplum/react'; +import '@medplum/react/styles.css'; import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; @@ -8,7 +10,7 @@ import { App } from './App'; const medplum = new MedplumClient({ onUnauthenticated: () => (window.location.href = '/'), - baseUrl: 'http://localhost:8103/', //Uncomment this to run against the server on your localhost; also change `googleClientId` in `./pages/SignInPage.tsx` + // baseUrl: 'http://localhost:8103/', // Uncomment this to run against the server on your localhost; also change `googleClientId` in `./pages/SignInPage.tsx` }); const theme: MantineThemeOverride = { diff --git a/examples/medplum-websocket-subscriptions-demo/src/pages/HomePage.tsx b/examples/medplum-websocket-subscriptions-demo/src/pages/HomePage.tsx index 8d187d805f..093943873c 100644 --- a/examples/medplum-websocket-subscriptions-demo/src/pages/HomePage.tsx +++ b/examples/medplum-websocket-subscriptions-demo/src/pages/HomePage.tsx @@ -1,63 +1,10 @@ -import { Accordion, Button, Chip, Group, Title } from '@mantine/core'; -import { createReference } from '@medplum/core'; -import { - Bundle, - BundleEntry, - Communication, - Parameters, - Patient, - Practitioner, - Reference, - Subscription, -} from '@medplum/fhirtypes'; -import { DrAliceSmith, HomerSimpson, MargeSimpson } from '@medplum/mock'; +import { Accordion, Button, Group, Title } from '@mantine/core'; +import { createReference, getReferenceString } from '@medplum/core'; +import { Bundle, Communication, Parameters, Patient, Practitioner, Subscription } from '@medplum/fhirtypes'; +import { HomerSimpson, MargeSimpson } from '@medplum/mock'; import { Document, ResourceName, useMedplum, useMedplumProfile } from '@medplum/react'; -import { IconArrowNarrowRight } from '@tabler/icons-react'; import { useState } from 'react'; - -interface BundleDisplayProps { - readonly bundle: Bundle; -} - -function BundleDisplay(props: BundleDisplayProps): JSX.Element { - const { bundle } = props; - const communication = bundle?.entry?.[1].resource as Communication; - const [senderType, senderId] = ((communication.sender as Reference).reference as string).split('/'); - const [recipientType, recipientId] = ((communication.recipient?.[0] as Reference).reference as string).split('/'); - return ( - - - - {bundle.timestamp}{' '} - - {senderType}/{senderId.slice(0, 8)} - - - - {recipientType}/{recipientId.slice(0, 8)} - - - {communication.status === 'in-progress' ? 'Sent' : 'Received'} - - - - -
-
{JSON.stringify(bundle, null, 2)}
-
-
-
- ); -} +import { BundleDisplay } from '../components/BundleDisplay'; /** * Home page that greets the user and displays a list of patients. @@ -77,7 +24,6 @@ export function HomePage(): JSX.Element { const [bundles, setBundles] = useState([]); const [patient, setPatient] = useState(); const [anotherPatient, setAnotherPatient] = useState(); - const [practitioner, setPractitioner] = useState(); async function createSubscriptions(): Promise { if (working) { @@ -88,25 +34,24 @@ export function HomePage(): JSX.Element { const homer = await medplum.createResourceIfNoneExist(HomerSimpson, 'name="Homer Simpson"'); const marge = await medplum.createResourceIfNoneExist(MargeSimpson, 'name="Marge Simpson"'); - const drAlice = await medplum.createResourceIfNoneExist(DrAliceSmith, 'name="Alice Smith"'); - const drRefString = `${drAlice.resourceType}/${drAlice.id as string}`; - const homerRefString = `${homer.resourceType}/${homer.id as string}`; + const meRefString = getReferenceString(profile); + const homerRefString = getReferenceString(homer); const subscription1 = await medplum.createResource({ resourceType: 'Subscription', - criteria: `Communication?_compartment=${homerRefString}&recipient=${drRefString}`, + criteria: `Communication?_compartment=${homerRefString}&recipient=${meRefString}`, status: 'active', - reason: `Watch for outgoing Communications for ${homerRefString} to ${drRefString}.`, + reason: `Watch for outgoing Communications for ${homerRefString} to ${meRefString}.`, channel: { type: 'websocket', }, }); const subscription2 = await medplum.createResource({ resourceType: 'Subscription', - criteria: `Communication?_compartment=${homerRefString}&sender=${drRefString}`, + criteria: `Communication?_compartment=${homerRefString}&sender=${meRefString}`, status: 'active', - reason: `Watch for incoming Communications from ${drRefString} to ${homerRefString}.`, + reason: `Watch for incoming Communications from ${meRefString} to ${homerRefString}.`, channel: { type: 'websocket', }, @@ -115,7 +60,6 @@ export function HomePage(): JSX.Element { setSubscriptions([subscription1, subscription2]); setPatient(homer); setAnotherPatient(marge); - setPractitioner(drAlice); setWorking(false); } @@ -157,23 +101,13 @@ export function HomePage(): JSX.Element { ws.addEventListener('message', (event: MessageEvent) => { const bundle = JSON.parse(event.data) as Bundle; - for (const entry of bundle.entry as BundleEntry[]) { - const entryResource = entry?.resource; - if ( - entryResource?.resourceType === 'Communication' && - !(entryResource.received && entryResource.status === 'completed') - ) { - medplum - .updateResource({ - ...entryResource, - received: new Date().toISOString(), // Mark as received - status: 'completed', // Mark as read - // See: https://www.medplum.com/docs/communications/organizing-communications#:~:text=THE%20Communication%20LIFECYCLE - // for more info about recommended `Communication` lifecycle - }) - .catch(console.error); - } + + const firstResource = bundle.entry?.[0]?.resource; + if (firstResource?.resourceType === 'SubscriptionStatus' && firstResource.type === 'heartbeat') { + // Ignore heartbeat bundles + return; } + setBundles((s) => [bundle, ...s]); }); @@ -182,29 +116,29 @@ export function HomePage(): JSX.Element { } async function createOutgoingMessage(): Promise { - if (!(patient && practitioner)) { + if (!patient) { return; } await medplum.createResource({ resourceType: 'Communication', status: 'in-progress', - sender: createReference({ resourceType: 'Patient', id: patient.id as string }), - recipient: [createReference({ resourceType: 'Practitioner', id: practitioner.id as string })], - payload: [{ contentString: "I'm not feeling great, and not sure if the medicine is working" }], + sender: createReference(profile), + recipient: [createReference(patient)], + payload: [{ contentString: 'Can you come in tomorrow for a follow-up?' }], sent: new Date().toISOString(), }); } async function createIncomingMessage(): Promise { - if (!(patient && practitioner)) { + if (!patient) { return; } await medplum.createResource({ resourceType: 'Communication', status: 'in-progress', - sender: createReference({ resourceType: 'Practitioner', id: practitioner.id as string }), - recipient: [createReference({ resourceType: 'Patient', id: patient.id as string })], - payload: [{ contentString: 'Can you come in tomorrow for a follow-up?' }], + sender: createReference(patient), + recipient: [createReference(profile)], + payload: [{ contentString: "I'm not feeling great, and not sure if the medicine is working" }], sent: new Date().toISOString(), }); } @@ -213,14 +147,14 @@ export function HomePage(): JSX.Element { // Important when tweaking criteria with advanced queries, such as the inclusion of `_filter` expressions // How can we make this more natural in this example? async function createMessageForAnotherPatient(): Promise { - if (!(anotherPatient && practitioner)) { + if (!anotherPatient) { return; } await medplum.createResource({ resourceType: 'Communication', status: 'in-progress', - sender: createReference({ resourceType: 'Practitioner', id: practitioner.id as string }), - recipient: [createReference({ resourceType: 'Patient', id: anotherPatient.id as string })], + sender: createReference(profile), + recipient: [createReference(anotherPatient)], payload: [{ contentString: 'Are you going to be able to make it to your appointment today?' }], sent: new Date().toISOString(), }); @@ -245,7 +179,6 @@ export function HomePage(): JSX.Element { } setSubscriptions(undefined); setPatient(undefined); - setPractitioner(undefined); setWorking(false); } From 1361639292284eeac5e473e3891a5aded73a21bd Mon Sep 17 00:00:00 2001 From: Matt Long Date: Fri, 16 Feb 2024 16:43:28 -0800 Subject: [PATCH 52/81] Use same pattern as ResourcePage for default tab (#3972) --- packages/app/src/CreateResourcePage.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/app/src/CreateResourcePage.tsx b/packages/app/src/CreateResourcePage.tsx index c5b20f5785..5a6854d64f 100644 --- a/packages/app/src/CreateResourcePage.tsx +++ b/packages/app/src/CreateResourcePage.tsx @@ -10,8 +10,10 @@ export function CreateResourcePage(): JSX.Element { const navigate = useNavigate(); const theme = useMantineTheme(); const { resourceType } = useParams(); - const tab = window.location.pathname.split('/').pop(); - const [currentTab, setCurrentTab] = useState(tab ?? defaultTab); + const [currentTab, setCurrentTab] = useState(() => { + const tab = window.location.pathname.split('/').pop(); + return tab && tabs.map((t) => t.toLowerCase()).includes(tab) ? tab : defaultTab; + }); /** * Handles a tab change event. From 5336c0fc7237e8e693af495e5ac9cdd167eda3f0 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Sat, 17 Feb 2024 10:29:46 -0800 Subject: [PATCH 53/81] Add scheduled job to upgrade dependencies (#3984) --- .github/workflows/upgrade-dependencies.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/upgrade-dependencies.yml b/.github/workflows/upgrade-dependencies.yml index 0802f0cfe4..3448ab6b5c 100644 --- a/.github/workflows/upgrade-dependencies.yml +++ b/.github/workflows/upgrade-dependencies.yml @@ -1,6 +1,11 @@ name: Upgrade dependencies -on: workflow_dispatch +on: + workflow_dispatch: + schedule: + # Every Monday at 9:00 AM UTC + # Every Monday at 1:00 AM PST (2:00 AM PDT) + - cron: '0 9 * * 1' jobs: upgrade-dependencies: From 1188e82ab132afdb9d15d9dbb8484c2393e9cc3e Mon Sep 17 00:00:00 2001 From: Reshma Khilnani Date: Sat, 17 Feb 2024 10:30:12 -0800 Subject: [PATCH 54/81] Content update for case study page (#3983) Co-authored-by: Reshma Khilnani --- .../docs/blog/2024-01-26-develo-case-study.md | 2 +- packages/docs/src/pages/case-studies.tsx | 43 ++++++++++++++---- packages/docs/static/img/blog/codex-logo.jpeg | Bin 0 -> 9971 bytes packages/docs/static/img/blog/develo.jpeg | Bin 0 -> 8903 bytes 4 files changed, 35 insertions(+), 10 deletions(-) create mode 100644 packages/docs/static/img/blog/codex-logo.jpeg create mode 100644 packages/docs/static/img/blog/develo.jpeg diff --git a/packages/docs/blog/2024-01-26-develo-case-study.md b/packages/docs/blog/2024-01-26-develo-case-study.md index a7e36c5584..19ef10f3cf 100644 --- a/packages/docs/blog/2024-01-26-develo-case-study.md +++ b/packages/docs/blog/2024-01-26-develo-case-study.md @@ -6,7 +6,7 @@ authors: title: Medplum Core Team url: https://github.com/reshmakh image_url: https://github.com/reshmakh.png -tags: [pediatrics, fhir-datastore, self-host] +tags: [pediatrics, fhir-datastore, self-host, ai] --- # Develo Pediatric EHR diff --git a/packages/docs/src/pages/case-studies.tsx b/packages/docs/src/pages/case-studies.tsx index 9dbf944bb6..210a7b589b 100644 --- a/packages/docs/src/pages/case-studies.tsx +++ b/packages/docs/src/pages/case-studies.tsx @@ -82,18 +82,18 @@ export default function CaseStudiesPage(): JSX.Element { youtubeUrl="https://youtu.be/q-22Y7Ox2jY" /> @@ -117,6 +117,31 @@ export default function CaseStudiesPage(): JSX.Element { >
+
+ + + + + +
); diff --git a/packages/docs/static/img/blog/codex-logo.jpeg b/packages/docs/static/img/blog/codex-logo.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..f47fdbcdc577056cc38f0377c7be6c9439d70d16 GIT binary patch literal 9971 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 Date: Sat, 17 Feb 2024 17:45:29 -0800 Subject: [PATCH 55/81] Add withHelpText prop to ValueSetAutocomplete (#3985) * Add withHelpText prop to ValueSetAutocomplete * Cleanup --- packages/react/src/CodeInput/CodeInput.tsx | 11 +++++++++-- .../src/CodeableConceptInput/CodeableConceptInput.tsx | 5 +++-- packages/react/src/CodingInput/CodingInput.tsx | 3 ++- .../react/src/ResourceTypeInput/ResourceTypeInput.tsx | 1 + .../src/ValueSetAutocomplete/ValueSetAutocomplete.tsx | 5 +++-- 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/packages/react/src/CodeInput/CodeInput.tsx b/packages/react/src/CodeInput/CodeInput.tsx index ea129af96f..cf89f4e4f3 100644 --- a/packages/react/src/CodeInput/CodeInput.tsx +++ b/packages/react/src/CodeInput/CodeInput.tsx @@ -8,7 +8,7 @@ export interface CodeInputProps extends Omit(defaultValue); function handleChange(newValues: ValueSetExpansionContains[]): void { @@ -20,7 +20,14 @@ export function CodeInput(props: CodeInputProps): JSX.Element { } } - return ; + return ( + + ); } function codeToValueSetElement(code: string | undefined): ValueSetExpansionContains | undefined { diff --git a/packages/react/src/CodeableConceptInput/CodeableConceptInput.tsx b/packages/react/src/CodeableConceptInput/CodeableConceptInput.tsx index 238ee4c25e..a15c03dbad 100644 --- a/packages/react/src/CodeableConceptInput/CodeableConceptInput.tsx +++ b/packages/react/src/CodeableConceptInput/CodeableConceptInput.tsx @@ -1,7 +1,7 @@ import { CodeableConcept, ValueSetExpansionContains } from '@medplum/fhirtypes'; import { useState } from 'react'; -import { ValueSetAutocomplete, ValueSetAutocompleteProps } from '../ValueSetAutocomplete/ValueSetAutocomplete'; import { ComplexTypeInputProps } from '../ResourcePropertyInput/ResourcePropertyInput.utils'; +import { ValueSetAutocomplete, ValueSetAutocompleteProps } from '../ValueSetAutocomplete/ValueSetAutocomplete'; export interface CodeableConceptInputProps extends Omit, @@ -10,7 +10,7 @@ export interface CodeableConceptInputProps } export function CodeableConceptInput(props: CodeableConceptInputProps): JSX.Element { - const { defaultValue, onChange, ...rest } = props; + const { defaultValue, onChange, withHelpText, ...rest } = props; const [value, setValue] = useState(defaultValue); function handleChange(newValues: ValueSetExpansionContains[]): void { @@ -25,6 +25,7 @@ export function CodeableConceptInput(props: CodeableConceptInputProps): JSX.Elem ); diff --git a/packages/react/src/CodingInput/CodingInput.tsx b/packages/react/src/CodingInput/CodingInput.tsx index 27cd0c5ffa..f230a452b4 100644 --- a/packages/react/src/CodingInput/CodingInput.tsx +++ b/packages/react/src/CodingInput/CodingInput.tsx @@ -8,7 +8,7 @@ export interface CodingInputProps extends Omit(defaultValue); function handleChange(newValues: ValueSetExpansionContains[]): void { @@ -25,6 +25,7 @@ export function CodingInput(props: CodingInputProps): JSX.Element { defaultValue={value && codingToValueSetElement(value)} maxValues={1} onChange={handleChange} + withHelpText={withHelpText ?? true} {...rest} /> ); diff --git a/packages/react/src/ResourceTypeInput/ResourceTypeInput.tsx b/packages/react/src/ResourceTypeInput/ResourceTypeInput.tsx index 195588473c..816353cb03 100644 --- a/packages/react/src/ResourceTypeInput/ResourceTypeInput.tsx +++ b/packages/react/src/ResourceTypeInput/ResourceTypeInput.tsx @@ -38,6 +38,7 @@ export function ResourceTypeInput(props: ResourceTypeInputProps): JSX.Element { creatable={false} maxValues={props.maxValues ?? 1} clearable={false} + withHelpText={false} /> ); } diff --git a/packages/react/src/ValueSetAutocomplete/ValueSetAutocomplete.tsx b/packages/react/src/ValueSetAutocomplete/ValueSetAutocomplete.tsx index 3a3e609cd2..3dae42846e 100644 --- a/packages/react/src/ValueSetAutocomplete/ValueSetAutocomplete.tsx +++ b/packages/react/src/ValueSetAutocomplete/ValueSetAutocomplete.tsx @@ -15,6 +15,7 @@ export interface ValueSetAutocompleteProps readonly creatable?: boolean; readonly clearable?: boolean; readonly expandParams?: Partial; + readonly withHelpText?: boolean; } function toKey(element: ValueSetExpansionContains): string { @@ -54,7 +55,7 @@ function createValue(input: string): ValueSetExpansionContains { */ export function ValueSetAutocomplete(props: ValueSetAutocompleteProps): JSX.Element { const medplum = useMedplum(); - const { binding, creatable, clearable, expandParams, ...rest } = props; + const { binding, creatable, clearable, expandParams, withHelpText, ...rest } = props; const loadValues = useCallback( async (input: string, signal: AbortSignal): Promise => { @@ -90,7 +91,7 @@ export function ValueSetAutocomplete(props: ValueSetAutocompleteProps): JSX.Elem toOption={toOption} loadOptions={loadValues} onCreate={createValue} - itemComponent={ItemComponent} + itemComponent={withHelpText ? ItemComponent : undefined} /> ); } From 1d10e8e96764b3cd66043bfbe6d88abadf68e44f Mon Sep 17 00:00:00 2001 From: Rahul Agarwal Date: Sat, 17 Feb 2024 17:45:45 -0800 Subject: [PATCH 56/81] Added note about required scopes for refresh token (#3988) --- packages/docs/docs/api/oauth/token.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/docs/docs/api/oauth/token.md b/packages/docs/docs/api/oauth/token.md index 2294086fe6..6d8023a2dc 100644 --- a/packages/docs/docs/api/oauth/token.md +++ b/packages/docs/docs/api/oauth/token.md @@ -94,7 +94,7 @@ Content-Type: application/json ``` **Note** -The token endpoint returns `refresh_token` only when the `grant_type` is `authorization_code`. +The token endpoint returns `refresh_token` only when the `grant_type` is `authorization_code` and the requested scopes during login included `offline` or `offline_access`. ### Exchanging client credentials for an access token From 2d79acdaa7a41bd703ed5b86becf31e5a640ae68 Mon Sep 17 00:00:00 2001 From: ksmith94 <102421938+ksmith94@users.noreply.github.com> Date: Mon, 19 Feb 2024 11:15:32 -0500 Subject: [PATCH 57/81] Document the admin page of the Medplum App (#3968) * Document the admin page of the Medplum App * Add link to user management guide --- .../docs/docs/app/admin-page/admin-page.md | 35 ++++++++++++++++++ .../docs/docs/app/admin-page/admin-page.png | Bin 0 -> 190203 bytes 2 files changed, 35 insertions(+) create mode 100644 packages/docs/docs/app/admin-page/admin-page.md create mode 100644 packages/docs/docs/app/admin-page/admin-page.png diff --git a/packages/docs/docs/app/admin-page/admin-page.md b/packages/docs/docs/app/admin-page/admin-page.md new file mode 100644 index 0000000000..aa3cd60cf8 --- /dev/null +++ b/packages/docs/docs/app/admin-page/admin-page.md @@ -0,0 +1,35 @@ +# The Admin Page + +The [Admin Page](https://app.medplum.com/admin/project) of the Medplum App allows admin users to view and edit details of their project that normal users do not have access to. + +At the top of the page, there is an array of tabs as shown below. In this guide, we will briefly go over the content of each tab as well as any functionality they provide. + +![App Admin Page](./admin-page.png) + +## Details + +The Details tab displays all of the populated elements of the current [`Project`](/docs/api/fhir/medplum/project) resource. For more details on the [`Project`](/docs/api/fhir/medplum/project) resource, see the [User Management Guide](/docs/auth/user-management-guide). + +## Users + +The Users tab displays all of the [`Practitioner`](/docs/api/fhir/resources/practitioner) resources that are also a [`User`](/docs/api/fhir/medplum/user) in your project. It also allows you to invite new users. For more details on the [`User`](/docs/api/fhir/medplum/user) resource, see the [User Management Guide](/docs/auth/user-management-guide). + +## Patients + +The Patients tab displays all of the [`Patient`](/docs/api/fhir/resources/patient) resources that are also a [`User`](/docs/api/fhir/medplum/user) in your project. It also allows you to invite new patients. For more details on the [`User`](/docs/api/fhir/medplum/user) and [`Patient`](/docs/api/fhir/resources/patient) resources see the [User Management Guide](/docs/auth/user-management-guide#project-scoped-users). + +## Clients + +The Clients tab displays all of the [`ClientApplication`](/docs/api/fhir/medplum/clientapplication) resources that are associated with your project. It also allows you to create new [`ClientApplication`](/docs/api/fhir/medplum/clientapplication). For more details, see the [Authentication Method docs](/docs/auth/methods/token-exchange#set-up-your-clientapplication). + +## Bots + +The Bots tab dispalys all of the [`Bots`](/docs/api/fhir/medplum/bot) that are a part of your project and allows you to create new ones. For more details on [`Bots`](/docs/api/fhir/medplum/bot) see the [Bot Basics docs](/docs/bots/bot-basics). + +## Secrets + +The Secrets tab displays all of your project secrets as well as allowing you to create new ones. Secrets are used to store sensitive information and as access controls. For example, API keys, [`Bot`](/docs/api/fhir/medplum/bot) secrets, and reCAPTCHA secrets would all appear here. For more details see the [Project Secrets docs](/docs/access/projects#project-secrets). + +## Sites + +The Sites tab allows you to view and edit any custom domains that your project is configured for. diff --git a/packages/docs/docs/app/admin-page/admin-page.png b/packages/docs/docs/app/admin-page/admin-page.png new file mode 100644 index 0000000000000000000000000000000000000000..362f2cbd40096547713ad74f995a745e8cf1f623 GIT binary patch literal 190203 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�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 literal 0 HcmV?d00001 From 192f4acc1fce01d50aa01577f4494baf78cb859f Mon Sep 17 00:00:00 2001 From: Medplum Bot <152649536+medplumbot@users.noreply.github.com> Date: Mon, 19 Feb 2024 09:06:47 -0800 Subject: [PATCH 58/81] Dependency upgrades (#3990) --- examples/foomedical/package.json | 14 +- examples/medplum-chart-demo/package.json | 14 +- examples/medplum-demo-bots/package.json | 12 +- examples/medplum-fhircast-demo/package.json | 12 +- examples/medplum-hello-world/package.json | 14 +- examples/medplum-live-chat-demo/package.json | 14 +- examples/medplum-nextjs-demo/package.json | 10 +- examples/medplum-provider/package.json | 14 +- .../medplum-react-native-example/package.json | 2 +- examples/medplum-task-demo/package.json | 14 +- .../package.json | 14 +- package-lock.json | 4352 ++++++++--------- package.json | 12 +- packages/agent/package.json | 2 +- packages/app/package.json | 14 +- packages/bot-layer/package.json | 2 +- packages/cdk/package.json | 10 +- packages/cli/package.json | 18 +- packages/docs/package.json | 4 +- packages/eslint-config/package.json | 6 +- packages/expo-polyfills/package.json | 6 +- packages/graphiql/package.json | 10 +- packages/react-hooks/package.json | 4 +- packages/react/package.json | 26 +- packages/server/package.json | 24 +- 25 files changed, 2099 insertions(+), 2525 deletions(-) diff --git a/examples/foomedical/package.json b/examples/foomedical/package.json index 82101c210a..01e94bd6d6 100644 --- a/examples/foomedical/package.json +++ b/examples/foomedical/package.json @@ -25,9 +25,9 @@ "@babel/preset-env": "7.23.9", "@babel/preset-react": "7.23.3", "@babel/preset-typescript": "7.23.3", - "@mantine/core": "7.5.2", - "@mantine/hooks": "7.5.2", - "@mantine/notifications": "7.5.2", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", "@medplum/core": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", @@ -37,8 +37,8 @@ "@testing-library/jest-dom": "6.4.2", "@testing-library/react": "14.2.1", "@types/jest": "29.5.12", - "@types/node": "20.11.17", - "@types/react": "18.2.55", + "@types/node": "20.11.19", + "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "babel-jest": "29.7.0", @@ -53,8 +53,8 @@ "react": "18.2.0", "react-chartjs-2": "5.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.22.0", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.1.1" + "vite": "5.1.3" } } diff --git a/examples/medplum-chart-demo/package.json b/examples/medplum-chart-demo/package.json index 8c3bea61f0..1845d5d1ee 100644 --- a/examples/medplum-chart-demo/package.json +++ b/examples/medplum-chart-demo/package.json @@ -19,24 +19,24 @@ ] }, "devDependencies": { - "@mantine/core": "7.5.2", - "@mantine/hooks": "7.5.2", - "@mantine/notifications": "7.5.2", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", "@medplum/core": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/react": "3.0.3", "@tabler/icons-react": "2.47.0", - "@types/node": "20.11.17", - "@types/react": "18.2.55", + "@types/node": "20.11.19", + "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.22.0", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.1.1" + "vite": "5.1.3" } } diff --git a/examples/medplum-demo-bots/package.json b/examples/medplum-demo-bots/package.json index 7275d7f7c9..7f217f4b97 100644 --- a/examples/medplum-demo-bots/package.json +++ b/examples/medplum-demo-bots/package.json @@ -30,19 +30,19 @@ "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/mock": "3.0.3", - "@types/node": "20.11.17", + "@types/node": "20.11.19", "@types/node-fetch": "2.6.11", "@types/ssh2-sftp-client": "9.0.3", - "@vitest/coverage-v8": "1.2.2", - "@vitest/ui": "1.2.2", - "esbuild": "0.20.0", + "@vitest/coverage-v8": "1.3.0", + "@vitest/ui": "1.3.0", + "esbuild": "0.20.1", "form-data": "4.0.0", "glob": "^10.3.10", "node-fetch": "2.7.0", "pdfmake": "0.2.9", "ssh2-sftp-client": "10.0.3", - "stripe": "14.16.0", + "stripe": "14.17.0", "typescript": "5.3.3", - "vitest": "1.2.2" + "vitest": "1.3.0" } } diff --git a/examples/medplum-fhircast-demo/package.json b/examples/medplum-fhircast-demo/package.json index f90de0476c..33284d63db 100644 --- a/examples/medplum-fhircast-demo/package.json +++ b/examples/medplum-fhircast-demo/package.json @@ -15,23 +15,23 @@ ] }, "devDependencies": { - "@mantine/core": "7.5.2", - "@mantine/hooks": "7.5.2", - "@mantine/notifications": "7.5.2", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", "@medplum/core": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/react": "3.0.3", "@tabler/icons-react": "2.47.0", - "@types/react": "18.2.55", + "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.22.0", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.1.1" + "vite": "5.1.3" } } diff --git a/examples/medplum-hello-world/package.json b/examples/medplum-hello-world/package.json index 8e63e6ca03..1a37b4e1a7 100644 --- a/examples/medplum-hello-world/package.json +++ b/examples/medplum-hello-world/package.json @@ -19,24 +19,24 @@ ] }, "devDependencies": { - "@mantine/core": "7.5.2", - "@mantine/hooks": "7.5.2", - "@mantine/notifications": "7.5.2", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", "@medplum/core": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/react": "3.0.3", "@tabler/icons-react": "2.47.0", - "@types/node": "20.11.17", - "@types/react": "18.2.55", + "@types/node": "20.11.19", + "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.22.0", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.1.1" + "vite": "5.1.3" } } diff --git a/examples/medplum-live-chat-demo/package.json b/examples/medplum-live-chat-demo/package.json index ffc0864c77..6d010bb88a 100644 --- a/examples/medplum-live-chat-demo/package.json +++ b/examples/medplum-live-chat-demo/package.json @@ -19,24 +19,24 @@ ] }, "devDependencies": { - "@mantine/core": "7.5.2", - "@mantine/hooks": "7.5.2", - "@mantine/notifications": "7.5.2", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", "@medplum/core": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/react": "3.0.3", "@tabler/icons-react": "2.47.0", - "@types/node": "20.11.17", - "@types/react": "18.2.55", + "@types/node": "20.11.19", + "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.22.0", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.1.1" + "vite": "5.1.3" } } diff --git a/examples/medplum-nextjs-demo/package.json b/examples/medplum-nextjs-demo/package.json index 57ef1b3c66..9b0486bbd0 100644 --- a/examples/medplum-nextjs-demo/package.json +++ b/examples/medplum-nextjs-demo/package.json @@ -10,9 +10,9 @@ "start": "next start" }, "dependencies": { - "@mantine/core": "7.5.2", - "@mantine/hooks": "7.5.2", - "@mantine/notifications": "7.5.2", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", "@medplum/core": "3.0.3", "@medplum/react": "3.0.3", "next": "14.1.0", @@ -22,8 +22,8 @@ }, "devDependencies": { "@medplum/fhirtypes": "3.0.3", - "@types/node": "20.11.17", - "@types/react": "18.2.55", + "@types/node": "20.11.19", + "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "eslint": "8.56.0", "eslint-config-next": "14.1.0", diff --git a/examples/medplum-provider/package.json b/examples/medplum-provider/package.json index 472513055c..38f5b38181 100644 --- a/examples/medplum-provider/package.json +++ b/examples/medplum-provider/package.json @@ -19,24 +19,24 @@ ] }, "devDependencies": { - "@mantine/core": "7.5.2", - "@mantine/hooks": "7.5.2", - "@mantine/notifications": "7.5.2", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", "@medplum/core": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/react": "3.0.3", "@tabler/icons-react": "2.47.0", - "@types/node": "20.11.17", - "@types/react": "18.2.55", + "@types/node": "20.11.19", + "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.22.0", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.1.1" + "vite": "5.1.3" } } diff --git a/examples/medplum-react-native-example/package.json b/examples/medplum-react-native-example/package.json index f53088adea..9f20951c00 100644 --- a/examples/medplum-react-native-example/package.json +++ b/examples/medplum-react-native-example/package.json @@ -23,7 +23,7 @@ "@medplum/core": "3.0.3", "@medplum/expo-polyfills": "3.0.3", "@medplum/react-hooks": "3.0.3", - "expo": "50.0.6", + "expo": "50.0.7", "expo-status-bar": "1.11.1", "react": "18.2.0", "react-dom": "18.2.0", diff --git a/examples/medplum-task-demo/package.json b/examples/medplum-task-demo/package.json index 34929ab3e6..535771d6a0 100644 --- a/examples/medplum-task-demo/package.json +++ b/examples/medplum-task-demo/package.json @@ -22,25 +22,25 @@ ] }, "devDependencies": { - "@mantine/core": "7.5.2", - "@mantine/hooks": "7.5.2", - "@mantine/notifications": "7.5.2", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", "@medplum/core": "3.0.3", "@medplum/definitions": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/react": "3.0.3", "@tabler/icons-react": "2.47.0", - "@types/node": "20.11.17", - "@types/react": "18.2.55", + "@types/node": "20.11.19", + "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.22.0", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.1.1" + "vite": "5.1.3" } } diff --git a/examples/medplum-websocket-subscriptions-demo/package.json b/examples/medplum-websocket-subscriptions-demo/package.json index 316f998bcd..fe2c093f3c 100644 --- a/examples/medplum-websocket-subscriptions-demo/package.json +++ b/examples/medplum-websocket-subscriptions-demo/package.json @@ -20,9 +20,9 @@ }, "devDependencies": { "@emotion/react": "11.11.3", - "@mantine/core": "7.5.2", - "@mantine/hooks": "7.5.2", - "@mantine/notifications": "7.5.2", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", "@medplum/core": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhir-router": "3.0.3", @@ -30,14 +30,14 @@ "@medplum/mock": "3.0.3", "@medplum/react": "3.0.3", "@tabler/icons-react": "2.47.0", - "@types/node": "20.11.17", - "@types/react": "18.2.55", + "@types/node": "20.11.19", + "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.22.0", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.1.1" + "vite": "5.1.3" } } diff --git a/package-lock.json b/package-lock.json index e97e4d99a3..2437b37aad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,15 +17,15 @@ "@babel/preset-react": "7.23.3", "@babel/preset-typescript": "7.23.3", "@cyclonedx/cyclonedx-npm": "1.16.1", - "@microsoft/api-documenter": "7.23.23", - "@microsoft/api-extractor": "7.40.1", + "@microsoft/api-documenter": "7.23.24", + "@microsoft/api-extractor": "7.40.2", "@types/jest": "29.5.12", - "@types/node": "20.11.17", + "@types/node": "20.11.19", "babel-jest": "29.7.0", "babel-preset-vite": "1.1.3", "cross-env": "7.0.3", "danger": "11.3.1", - "esbuild": "0.20.0", + "esbuild": "0.20.1", "identity-obj-proxy": "3.0.0", "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", @@ -37,7 +37,7 @@ "source-map-explorer": "2.5.3", "ts-node": "10.9.2", "tslib": "2.6.2", - "turbo": "1.12.3", + "turbo": "1.12.4", "typescript": "5.3.3" }, "engines": { @@ -51,9 +51,9 @@ "@babel/preset-env": "7.23.9", "@babel/preset-react": "7.23.3", "@babel/preset-typescript": "7.23.3", - "@mantine/core": "7.5.2", - "@mantine/hooks": "7.5.2", - "@mantine/notifications": "7.5.2", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", "@medplum/core": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", @@ -63,8 +63,8 @@ "@testing-library/jest-dom": "6.4.2", "@testing-library/react": "14.2.1", "@types/jest": "29.5.12", - "@types/node": "20.11.17", - "@types/react": "18.2.55", + "@types/node": "20.11.19", + "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "babel-jest": "29.7.0", @@ -79,15 +79,15 @@ "react": "18.2.0", "react-chartjs-2": "5.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.22.0", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.1.1" + "vite": "5.1.3" } }, "examples/foomedical/node_modules/rollup": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", - "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -100,26 +100,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.10.0", - "@rollup/rollup-android-arm64": "4.10.0", - "@rollup/rollup-darwin-arm64": "4.10.0", - "@rollup/rollup-darwin-x64": "4.10.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", - "@rollup/rollup-linux-arm64-gnu": "4.10.0", - "@rollup/rollup-linux-arm64-musl": "4.10.0", - "@rollup/rollup-linux-riscv64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-musl": "4.10.0", - "@rollup/rollup-win32-arm64-msvc": "4.10.0", - "@rollup/rollup-win32-ia32-msvc": "4.10.0", - "@rollup/rollup-win32-x64-msvc": "4.10.0", + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", "fsevents": "~2.3.2" } }, "examples/foomedical/node_modules/vite": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", - "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", "dev": true, "dependencies": { "esbuild": "^0.19.3", @@ -174,31 +174,31 @@ "examples/medplum-chart-demo": { "version": "3.0.3", "devDependencies": { - "@mantine/core": "7.5.2", - "@mantine/hooks": "7.5.2", - "@mantine/notifications": "7.5.2", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", "@medplum/core": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/react": "3.0.3", "@tabler/icons-react": "2.47.0", - "@types/node": "20.11.17", - "@types/react": "18.2.55", + "@types/node": "20.11.19", + "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.22.0", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.1.1" + "vite": "5.1.3" } }, "examples/medplum-chart-demo/node_modules/rollup": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", - "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -211,26 +211,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.10.0", - "@rollup/rollup-android-arm64": "4.10.0", - "@rollup/rollup-darwin-arm64": "4.10.0", - "@rollup/rollup-darwin-x64": "4.10.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", - "@rollup/rollup-linux-arm64-gnu": "4.10.0", - "@rollup/rollup-linux-arm64-musl": "4.10.0", - "@rollup/rollup-linux-riscv64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-musl": "4.10.0", - "@rollup/rollup-win32-arm64-msvc": "4.10.0", - "@rollup/rollup-win32-ia32-msvc": "4.10.0", - "@rollup/rollup-win32-x64-msvc": "4.10.0", + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", "fsevents": "~2.3.2" } }, "examples/medplum-chart-demo/node_modules/vite": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", - "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", "dev": true, "dependencies": { "esbuild": "^0.19.3", @@ -291,49 +291,49 @@ "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/mock": "3.0.3", - "@types/node": "20.11.17", + "@types/node": "20.11.19", "@types/node-fetch": "2.6.11", "@types/ssh2-sftp-client": "9.0.3", - "@vitest/coverage-v8": "1.2.2", - "@vitest/ui": "1.2.2", - "esbuild": "0.20.0", + "@vitest/coverage-v8": "1.3.0", + "@vitest/ui": "1.3.0", + "esbuild": "0.20.1", "form-data": "4.0.0", "glob": "^10.3.10", "node-fetch": "2.7.0", "pdfmake": "0.2.9", "ssh2-sftp-client": "10.0.3", - "stripe": "14.16.0", + "stripe": "14.17.0", "typescript": "5.3.3", - "vitest": "1.2.2" + "vitest": "1.3.0" } }, "examples/medplum-fhircast-demo": { "version": "3.0.3", "devDependencies": { - "@mantine/core": "7.5.2", - "@mantine/hooks": "7.5.2", - "@mantine/notifications": "7.5.2", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", "@medplum/core": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/react": "3.0.3", "@tabler/icons-react": "2.47.0", - "@types/react": "18.2.55", + "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.22.0", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.1.1" + "vite": "5.1.3" } }, "examples/medplum-fhircast-demo/node_modules/rollup": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", - "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -346,26 +346,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.10.0", - "@rollup/rollup-android-arm64": "4.10.0", - "@rollup/rollup-darwin-arm64": "4.10.0", - "@rollup/rollup-darwin-x64": "4.10.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", - "@rollup/rollup-linux-arm64-gnu": "4.10.0", - "@rollup/rollup-linux-arm64-musl": "4.10.0", - "@rollup/rollup-linux-riscv64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-musl": "4.10.0", - "@rollup/rollup-win32-arm64-msvc": "4.10.0", - "@rollup/rollup-win32-ia32-msvc": "4.10.0", - "@rollup/rollup-win32-x64-msvc": "4.10.0", + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", "fsevents": "~2.3.2" } }, "examples/medplum-fhircast-demo/node_modules/vite": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", - "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", "dev": true, "dependencies": { "esbuild": "^0.19.3", @@ -420,31 +420,31 @@ "examples/medplum-hello-world": { "version": "3.0.3", "devDependencies": { - "@mantine/core": "7.5.2", - "@mantine/hooks": "7.5.2", - "@mantine/notifications": "7.5.2", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", "@medplum/core": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/react": "3.0.3", "@tabler/icons-react": "2.47.0", - "@types/node": "20.11.17", - "@types/react": "18.2.55", + "@types/node": "20.11.19", + "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.22.0", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.1.1" + "vite": "5.1.3" } }, "examples/medplum-hello-world/node_modules/rollup": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", - "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -457,26 +457,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.10.0", - "@rollup/rollup-android-arm64": "4.10.0", - "@rollup/rollup-darwin-arm64": "4.10.0", - "@rollup/rollup-darwin-x64": "4.10.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", - "@rollup/rollup-linux-arm64-gnu": "4.10.0", - "@rollup/rollup-linux-arm64-musl": "4.10.0", - "@rollup/rollup-linux-riscv64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-musl": "4.10.0", - "@rollup/rollup-win32-arm64-msvc": "4.10.0", - "@rollup/rollup-win32-ia32-msvc": "4.10.0", - "@rollup/rollup-win32-x64-msvc": "4.10.0", + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", "fsevents": "~2.3.2" } }, "examples/medplum-hello-world/node_modules/vite": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", - "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", "dev": true, "dependencies": { "esbuild": "^0.19.3", @@ -531,31 +531,31 @@ "examples/medplum-live-chat-demo": { "version": "3.0.3", "devDependencies": { - "@mantine/core": "7.5.2", - "@mantine/hooks": "7.5.2", - "@mantine/notifications": "7.5.2", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", "@medplum/core": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/react": "3.0.3", "@tabler/icons-react": "2.47.0", - "@types/node": "20.11.17", - "@types/react": "18.2.55", + "@types/node": "20.11.19", + "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.22.0", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.1.1" + "vite": "5.1.3" } }, "examples/medplum-live-chat-demo/node_modules/rollup": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", - "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -568,26 +568,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.10.0", - "@rollup/rollup-android-arm64": "4.10.0", - "@rollup/rollup-darwin-arm64": "4.10.0", - "@rollup/rollup-darwin-x64": "4.10.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", - "@rollup/rollup-linux-arm64-gnu": "4.10.0", - "@rollup/rollup-linux-arm64-musl": "4.10.0", - "@rollup/rollup-linux-riscv64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-musl": "4.10.0", - "@rollup/rollup-win32-arm64-msvc": "4.10.0", - "@rollup/rollup-win32-ia32-msvc": "4.10.0", - "@rollup/rollup-win32-x64-msvc": "4.10.0", + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", "fsevents": "~2.3.2" } }, "examples/medplum-live-chat-demo/node_modules/vite": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", - "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", "dev": true, "dependencies": { "esbuild": "^0.19.3", @@ -642,9 +642,9 @@ "examples/medplum-nextjs-demo": { "version": "3.0.3", "dependencies": { - "@mantine/core": "7.5.2", - "@mantine/hooks": "7.5.2", - "@mantine/notifications": "7.5.2", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", "@medplum/core": "3.0.3", "@medplum/react": "3.0.3", "next": "14.1.0", @@ -654,8 +654,8 @@ }, "devDependencies": { "@medplum/fhirtypes": "3.0.3", - "@types/node": "20.11.17", - "@types/react": "18.2.55", + "@types/node": "20.11.19", + "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "eslint": "8.56.0", "eslint-config-next": "14.1.0", @@ -667,648 +667,31 @@ "examples/medplum-provider": { "version": "3.0.3", "devDependencies": { - "@mantine/core": "7.5.2", - "@mantine/hooks": "7.5.2", - "@mantine/notifications": "7.5.2", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", "@medplum/core": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/react": "3.0.3", "@tabler/icons-react": "2.47.0", - "@types/node": "20.11.17", - "@types/react": "18.2.55", + "@types/node": "20.11.19", + "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.22.0", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.1.1" - } - }, - "examples/medplum-provider/node_modules/@esbuild/aix-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", - "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "examples/medplum-provider/node_modules/@esbuild/android-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", - "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "examples/medplum-provider/node_modules/@esbuild/android-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", - "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "examples/medplum-provider/node_modules/@esbuild/android-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", - "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "examples/medplum-provider/node_modules/@esbuild/darwin-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "examples/medplum-provider/node_modules/@esbuild/darwin-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", - "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "examples/medplum-provider/node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", - "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "examples/medplum-provider/node_modules/@esbuild/freebsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", - "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "examples/medplum-provider/node_modules/@esbuild/linux-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", - "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "examples/medplum-provider/node_modules/@esbuild/linux-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", - "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "examples/medplum-provider/node_modules/@esbuild/linux-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", - "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "examples/medplum-provider/node_modules/@esbuild/linux-loong64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", - "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "examples/medplum-provider/node_modules/@esbuild/linux-mips64el": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", - "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "examples/medplum-provider/node_modules/@esbuild/linux-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", - "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "examples/medplum-provider/node_modules/@esbuild/linux-riscv64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", - "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "examples/medplum-provider/node_modules/@esbuild/linux-s390x": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", - "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "examples/medplum-provider/node_modules/@esbuild/linux-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", - "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "examples/medplum-provider/node_modules/@esbuild/netbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", - "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "examples/medplum-provider/node_modules/@esbuild/openbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", - "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "examples/medplum-provider/node_modules/@esbuild/sunos-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", - "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "examples/medplum-provider/node_modules/@esbuild/win32-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", - "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "examples/medplum-provider/node_modules/@esbuild/win32-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", - "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "examples/medplum-provider/node_modules/@esbuild/win32-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", - "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "examples/medplum-provider/node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", - "dev": true, - "optional": true, - "peer": true, - "bin": { - "detect-libc": "bin/detect-libc.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "examples/medplum-provider/node_modules/esbuild": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.12", - "@esbuild/android-arm": "0.19.12", - "@esbuild/android-arm64": "0.19.12", - "@esbuild/android-x64": "0.19.12", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", - "@esbuild/freebsd-arm64": "0.19.12", - "@esbuild/freebsd-x64": "0.19.12", - "@esbuild/linux-arm": "0.19.12", - "@esbuild/linux-arm64": "0.19.12", - "@esbuild/linux-ia32": "0.19.12", - "@esbuild/linux-loong64": "0.19.12", - "@esbuild/linux-mips64el": "0.19.12", - "@esbuild/linux-ppc64": "0.19.12", - "@esbuild/linux-riscv64": "0.19.12", - "@esbuild/linux-s390x": "0.19.12", - "@esbuild/linux-x64": "0.19.12", - "@esbuild/netbsd-x64": "0.19.12", - "@esbuild/openbsd-x64": "0.19.12", - "@esbuild/sunos-x64": "0.19.12", - "@esbuild/win32-arm64": "0.19.12", - "@esbuild/win32-ia32": "0.19.12", - "@esbuild/win32-x64": "0.19.12" - } - }, - "examples/medplum-provider/node_modules/lightningcss": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.23.0.tgz", - "integrity": "sha512-SEArWKMHhqn/0QzOtclIwH5pXIYQOUEkF8DgICd/105O+GCgd7jxjNod/QPnBCSWvpRHQBGVz5fQ9uScby03zA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "detect-libc": "^1.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-darwin-arm64": "1.23.0", - "lightningcss-darwin-x64": "1.23.0", - "lightningcss-freebsd-x64": "1.23.0", - "lightningcss-linux-arm-gnueabihf": "1.23.0", - "lightningcss-linux-arm64-gnu": "1.23.0", - "lightningcss-linux-arm64-musl": "1.23.0", - "lightningcss-linux-x64-gnu": "1.23.0", - "lightningcss-linux-x64-musl": "1.23.0", - "lightningcss-win32-x64-msvc": "1.23.0" - } - }, - "examples/medplum-provider/node_modules/lightningcss-darwin-arm64": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.23.0.tgz", - "integrity": "sha512-kl4Pk3Q2lnE6AJ7Qaij47KNEfY2/UXRZBT/zqGA24B8qwkgllr/j7rclKOf1axcslNXvvUdztjo4Xqh39Yq1aA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "examples/medplum-provider/node_modules/lightningcss-darwin-x64": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.23.0.tgz", - "integrity": "sha512-KeRFCNoYfDdcolcFXvokVw+PXCapd2yHS1Diko1z1BhRz/nQuD5XyZmxjWdhmhN/zj5sH8YvWsp0/lPLVzqKpg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "examples/medplum-provider/node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.23.0.tgz", - "integrity": "sha512-fBamf/bULvmWft9uuX+bZske236pUZEoUlaHNBjnueaCTJ/xd8eXgb0cEc7S5o0Nn6kxlauMBnqJpF70Bgq3zg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "examples/medplum-provider/node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.23.0.tgz", - "integrity": "sha512-RS7sY77yVLOmZD6xW2uEHByYHhQi5JYWmgVumYY85BfNoVI3DupXSlzbw+b45A9NnVKq45+oXkiN6ouMMtTwfg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "examples/medplum-provider/node_modules/lightningcss-linux-arm64-musl": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.23.0.tgz", - "integrity": "sha512-cU00LGb6GUXCwof6ACgSMKo3q7XYbsyTj0WsKHLi1nw7pV0NCq8nFTn6ZRBYLoKiV8t+jWl0Hv8KkgymmK5L5g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "examples/medplum-provider/node_modules/lightningcss-linux-x64-gnu": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.23.0.tgz", - "integrity": "sha512-q4jdx5+5NfB0/qMbXbOmuC6oo7caPnFghJbIAV90cXZqgV8Am3miZhC4p+sQVdacqxfd+3nrle4C8icR3p1AYw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "examples/medplum-provider/node_modules/lightningcss-linux-x64-musl": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.23.0.tgz", - "integrity": "sha512-G9Ri3qpmF4qef2CV/80dADHKXRAQeQXpQTLx7AiQrBYQHqBjB75oxqj06FCIe5g4hNCqLPnM9fsO4CyiT1sFSQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "examples/medplum-provider/node_modules/lightningcss-win32-x64-msvc": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.23.0.tgz", - "integrity": "sha512-1rcBDJLU+obPPJM6qR5fgBUiCdZwZLafZM5f9kwjFLkb/UBNIzmae39uCSmh71nzPCTXZqHbvwu23OWnWEz+eg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "vite": "5.1.3" } }, "examples/medplum-provider/node_modules/rollup": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", - "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -1321,26 +704,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.10.0", - "@rollup/rollup-android-arm64": "4.10.0", - "@rollup/rollup-darwin-arm64": "4.10.0", - "@rollup/rollup-darwin-x64": "4.10.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", - "@rollup/rollup-linux-arm64-gnu": "4.10.0", - "@rollup/rollup-linux-arm64-musl": "4.10.0", - "@rollup/rollup-linux-riscv64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-musl": "4.10.0", - "@rollup/rollup-win32-arm64-msvc": "4.10.0", - "@rollup/rollup-win32-ia32-msvc": "4.10.0", - "@rollup/rollup-win32-x64-msvc": "4.10.0", + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", "fsevents": "~2.3.2" } }, "examples/medplum-provider/node_modules/vite": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", - "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", "dev": true, "dependencies": { "esbuild": "^0.19.3", @@ -1399,7 +782,7 @@ "@medplum/core": "3.0.3", "@medplum/expo-polyfills": "3.0.3", "@medplum/react-hooks": "3.0.3", - "expo": "50.0.6", + "expo": "50.0.7", "expo-status-bar": "1.11.1", "react": "18.2.0", "react-dom": "18.2.0", @@ -1414,32 +797,32 @@ "examples/medplum-task-demo": { "version": "3.0.3", "devDependencies": { - "@mantine/core": "7.5.2", - "@mantine/hooks": "7.5.2", - "@mantine/notifications": "7.5.2", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", "@medplum/core": "3.0.3", "@medplum/definitions": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhirtypes": "3.0.3", "@medplum/react": "3.0.3", "@tabler/icons-react": "2.47.0", - "@types/node": "20.11.17", - "@types/react": "18.2.55", + "@types/node": "20.11.19", + "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.22.0", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.1.1" + "vite": "5.1.3" } }, "examples/medplum-task-demo/node_modules/rollup": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", - "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -1452,26 +835,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.10.0", - "@rollup/rollup-android-arm64": "4.10.0", - "@rollup/rollup-darwin-arm64": "4.10.0", - "@rollup/rollup-darwin-x64": "4.10.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", - "@rollup/rollup-linux-arm64-gnu": "4.10.0", - "@rollup/rollup-linux-arm64-musl": "4.10.0", - "@rollup/rollup-linux-riscv64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-musl": "4.10.0", - "@rollup/rollup-win32-arm64-msvc": "4.10.0", - "@rollup/rollup-win32-ia32-msvc": "4.10.0", - "@rollup/rollup-win32-x64-msvc": "4.10.0", + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", "fsevents": "~2.3.2" } }, "examples/medplum-task-demo/node_modules/vite": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", - "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", "dev": true, "dependencies": { "esbuild": "^0.19.3", @@ -1527,9 +910,9 @@ "version": "3.0.3", "devDependencies": { "@emotion/react": "11.11.3", - "@mantine/core": "7.5.2", - "@mantine/hooks": "7.5.2", - "@mantine/notifications": "7.5.2", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", "@medplum/core": "3.0.3", "@medplum/eslint-config": "3.0.3", "@medplum/fhir-router": "3.0.3", @@ -1537,21 +920,21 @@ "@medplum/mock": "3.0.3", "@medplum/react": "3.0.3", "@tabler/icons-react": "2.47.0", - "@types/node": "20.11.17", - "@types/react": "18.2.55", + "@types/node": "20.11.19", + "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.22.0", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.1.1" + "vite": "5.1.3" } }, "examples/medplum-websocket-subscriptions-demo/node_modules/rollup": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", - "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -1564,26 +947,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.10.0", - "@rollup/rollup-android-arm64": "4.10.0", - "@rollup/rollup-darwin-arm64": "4.10.0", - "@rollup/rollup-darwin-x64": "4.10.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", - "@rollup/rollup-linux-arm64-gnu": "4.10.0", - "@rollup/rollup-linux-arm64-musl": "4.10.0", - "@rollup/rollup-linux-riscv64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-musl": "4.10.0", - "@rollup/rollup-win32-arm64-msvc": "4.10.0", - "@rollup/rollup-win32-ia32-msvc": "4.10.0", - "@rollup/rollup-win32-x64-msvc": "4.10.0", + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", "fsevents": "~2.3.2" } }, "examples/medplum-websocket-subscriptions-demo/node_modules/vite": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", - "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", "dev": true, "dependencies": { "esbuild": "^0.19.3", @@ -1995,27 +1378,26 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@aws-sdk/client-acm": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-acm/-/client-acm-3.511.0.tgz", - "integrity": "sha512-0PdcCshsfqUZWhIHRX7fo3JG92IrQGkGYapgRJpY3gYlFF0CteQ+gFpVv6aZ9j4eIZ0iubozFDvrRmxq6TEvcw==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-acm/-/client-acm-3.515.0.tgz", + "integrity": "sha512-HXRh2mxrYD092b5Uuoe7UiVBLF5k310UpJAb1A7pj0snQTJYjr3qSrwkrRJmlbh1b7mo4pdHydKMU3NwMhIuow==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.511.0", - "@aws-sdk/core": "3.511.0", - "@aws-sdk/credential-provider-node": "3.511.0", - "@aws-sdk/middleware-host-header": "3.511.0", - "@aws-sdk/middleware-logger": "3.511.0", - "@aws-sdk/middleware-recursion-detection": "3.511.0", - "@aws-sdk/middleware-signing": "3.511.0", - "@aws-sdk/middleware-user-agent": "3.511.0", - "@aws-sdk/region-config-resolver": "3.511.0", - "@aws-sdk/types": "3.511.0", - "@aws-sdk/util-endpoints": "3.511.0", - "@aws-sdk/util-user-agent-browser": "3.511.0", - "@aws-sdk/util-user-agent-node": "3.511.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/credential-provider-node": "3.515.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", "@smithy/config-resolver": "^2.1.1", - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/fetch-http-handler": "^2.4.1", "@smithy/hash-node": "^2.1.1", "@smithy/invalid-dependency": "^2.1.1", @@ -2034,8 +1416,9 @@ "@smithy/util-body-length-browser": "^2.1.1", "@smithy/util-body-length-node": "^2.2.1", "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", "@smithy/util-retry": "^2.1.1", "@smithy/util-utf8": "^2.1.1", "@smithy/util-waiter": "^2.1.1", @@ -2046,27 +1429,26 @@ } }, "node_modules/@aws-sdk/client-cloudformation": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudformation/-/client-cloudformation-3.511.0.tgz", - "integrity": "sha512-FR41v2xzMGXKM+QiV+3XktKuCR6VwOvhoRjbCwECIhrvOaUGdXi0XMVCD1aSFocwxDN5aa96piD7gexkXRF91A==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudformation/-/client-cloudformation-3.515.0.tgz", + "integrity": "sha512-wRZTIGfKeuSlPPPb5aj3PahdDKNfLfz27VS8rAcICzRgryLg7HmTKwhxXLb6jG+AFylODedxWtpq+QflM2RghA==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.511.0", - "@aws-sdk/core": "3.511.0", - "@aws-sdk/credential-provider-node": "3.511.0", - "@aws-sdk/middleware-host-header": "3.511.0", - "@aws-sdk/middleware-logger": "3.511.0", - "@aws-sdk/middleware-recursion-detection": "3.511.0", - "@aws-sdk/middleware-signing": "3.511.0", - "@aws-sdk/middleware-user-agent": "3.511.0", - "@aws-sdk/region-config-resolver": "3.511.0", - "@aws-sdk/types": "3.511.0", - "@aws-sdk/util-endpoints": "3.511.0", - "@aws-sdk/util-user-agent-browser": "3.511.0", - "@aws-sdk/util-user-agent-node": "3.511.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/credential-provider-node": "3.515.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", "@smithy/config-resolver": "^2.1.1", - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/fetch-http-handler": "^2.4.1", "@smithy/hash-node": "^2.1.1", "@smithy/invalid-dependency": "^2.1.1", @@ -2085,42 +1467,42 @@ "@smithy/util-body-length-browser": "^2.1.1", "@smithy/util-body-length-node": "^2.2.1", "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", "@smithy/util-retry": "^2.1.1", "@smithy/util-utf8": "^2.1.1", "@smithy/util-waiter": "^2.1.1", "fast-xml-parser": "4.2.5", "tslib": "^2.5.0", - "uuid": "^8.3.2" + "uuid": "^9.0.1" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/client-cloudfront": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudfront/-/client-cloudfront-3.511.0.tgz", - "integrity": "sha512-5tqS6OZ+59N3tpJBxSQgg9+n8yZfcKtSVzIPHLtNmVVLYLxf/We4QYlOj0dZK+GlsfR2MDR//Qwlm9cFxoVrIA==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudfront/-/client-cloudfront-3.515.0.tgz", + "integrity": "sha512-aDiTeB2QEX6M9I3yqchCce4z78wRuDOh3oZq2eiBueJqk3R3RGm8zDdsiJ+U9N6NVSmcm7Xs55Ws8NUJZGwizw==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.511.0", - "@aws-sdk/core": "3.511.0", - "@aws-sdk/credential-provider-node": "3.511.0", - "@aws-sdk/middleware-host-header": "3.511.0", - "@aws-sdk/middleware-logger": "3.511.0", - "@aws-sdk/middleware-recursion-detection": "3.511.0", - "@aws-sdk/middleware-signing": "3.511.0", - "@aws-sdk/middleware-user-agent": "3.511.0", - "@aws-sdk/region-config-resolver": "3.511.0", - "@aws-sdk/types": "3.511.0", - "@aws-sdk/util-endpoints": "3.511.0", - "@aws-sdk/util-user-agent-browser": "3.511.0", - "@aws-sdk/util-user-agent-node": "3.511.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/credential-provider-node": "3.515.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", "@aws-sdk/xml-builder": "3.496.0", "@smithy/config-resolver": "^2.1.1", - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/fetch-http-handler": "^2.4.1", "@smithy/hash-node": "^2.1.1", "@smithy/invalid-dependency": "^2.1.1", @@ -2139,8 +1521,9 @@ "@smithy/util-body-length-browser": "^2.1.1", "@smithy/util-body-length-node": "^2.2.1", "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", "@smithy/util-retry": "^2.1.1", "@smithy/util-stream": "^2.1.1", "@smithy/util-utf8": "^2.1.1", @@ -2153,27 +1536,26 @@ } }, "node_modules/@aws-sdk/client-cloudwatch-logs": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.511.0.tgz", - "integrity": "sha512-MS5EiKfmEpo/y6bKBQEJZUppYrh3k/yCj67ckC+9/xzLzeT7XSRt3eM4MqNTVKt1HHWJLufuEE655hX4bJ2D+Q==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.515.0.tgz", + "integrity": "sha512-kjZU8iyi8xlN9lBAlnkWr7TCRNoiWQwmmQMSMUYUCBcQvlZCyylnFDADMRa/PR4+HIdnlFeSlPDjfsmazQ5Qig==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.511.0", - "@aws-sdk/core": "3.511.0", - "@aws-sdk/credential-provider-node": "3.511.0", - "@aws-sdk/middleware-host-header": "3.511.0", - "@aws-sdk/middleware-logger": "3.511.0", - "@aws-sdk/middleware-recursion-detection": "3.511.0", - "@aws-sdk/middleware-signing": "3.511.0", - "@aws-sdk/middleware-user-agent": "3.511.0", - "@aws-sdk/region-config-resolver": "3.511.0", - "@aws-sdk/types": "3.511.0", - "@aws-sdk/util-endpoints": "3.511.0", - "@aws-sdk/util-user-agent-browser": "3.511.0", - "@aws-sdk/util-user-agent-node": "3.511.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/credential-provider-node": "3.515.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", "@smithy/config-resolver": "^2.1.1", - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/eventstream-serde-browser": "^2.1.1", "@smithy/eventstream-serde-config-resolver": "^2.1.1", "@smithy/eventstream-serde-node": "^2.1.1", @@ -2195,39 +1577,39 @@ "@smithy/util-body-length-browser": "^2.1.1", "@smithy/util-body-length-node": "^2.2.1", "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", "@smithy/util-retry": "^2.1.1", "@smithy/util-utf8": "^2.1.1", "tslib": "^2.5.0", - "uuid": "^8.3.2" + "uuid": "^9.0.1" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/client-ecs": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ecs/-/client-ecs-3.511.0.tgz", - "integrity": "sha512-23NTodvp8P5B0RzanSYhUXsSxLr1m4r1hTQfZ/WbRz04vD1F339urxE5wmS6U8GAu+HqB/KldgGaTevc2Ik33A==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ecs/-/client-ecs-3.515.0.tgz", + "integrity": "sha512-OVdH6Q5eHBfwOL+y4XpLoeclkk1rd4+3GTxFasb6X8rUb6CAflzW3rqO8x4g04+XMPLv01UrIla1/NwNwljTjA==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.511.0", - "@aws-sdk/core": "3.511.0", - "@aws-sdk/credential-provider-node": "3.511.0", - "@aws-sdk/middleware-host-header": "3.511.0", - "@aws-sdk/middleware-logger": "3.511.0", - "@aws-sdk/middleware-recursion-detection": "3.511.0", - "@aws-sdk/middleware-signing": "3.511.0", - "@aws-sdk/middleware-user-agent": "3.511.0", - "@aws-sdk/region-config-resolver": "3.511.0", - "@aws-sdk/types": "3.511.0", - "@aws-sdk/util-endpoints": "3.511.0", - "@aws-sdk/util-user-agent-browser": "3.511.0", - "@aws-sdk/util-user-agent-node": "3.511.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/credential-provider-node": "3.515.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", "@smithy/config-resolver": "^2.1.1", - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/fetch-http-handler": "^2.4.1", "@smithy/hash-node": "^2.1.1", "@smithy/invalid-dependency": "^2.1.1", @@ -2246,40 +1628,40 @@ "@smithy/util-body-length-browser": "^2.1.1", "@smithy/util-body-length-node": "^2.2.1", "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", "@smithy/util-retry": "^2.1.1", "@smithy/util-utf8": "^2.1.1", "@smithy/util-waiter": "^2.1.1", "tslib": "^2.5.0", - "uuid": "^8.3.2" + "uuid": "^9.0.1" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/client-lambda": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.511.0.tgz", - "integrity": "sha512-zJVszZcdEOit+hxqZc3sb0MsgpYfbygjKNYsWTSj4YMjZBT+bzSqi/4dE/VJPGvvchU61jJLnqafJXc6GBPVxA==", + "version": "3.516.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.516.0.tgz", + "integrity": "sha512-VF47RCJc5q03nRjlTmMmvDrtIK7NcutzdOiUzQFKH+UcHP3XqyvgdPsN/LOiu1DqRz46bvTVT8gGUGRgrGhEFw==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.511.0", - "@aws-sdk/core": "3.511.0", - "@aws-sdk/credential-provider-node": "3.511.0", - "@aws-sdk/middleware-host-header": "3.511.0", - "@aws-sdk/middleware-logger": "3.511.0", - "@aws-sdk/middleware-recursion-detection": "3.511.0", - "@aws-sdk/middleware-signing": "3.511.0", - "@aws-sdk/middleware-user-agent": "3.511.0", - "@aws-sdk/region-config-resolver": "3.511.0", - "@aws-sdk/types": "3.511.0", - "@aws-sdk/util-endpoints": "3.511.0", - "@aws-sdk/util-user-agent-browser": "3.511.0", - "@aws-sdk/util-user-agent-node": "3.511.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/credential-provider-node": "3.515.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", "@smithy/config-resolver": "^2.1.1", - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/eventstream-serde-browser": "^2.1.1", "@smithy/eventstream-serde-config-resolver": "^2.1.1", "@smithy/eventstream-serde-node": "^2.1.1", @@ -2301,8 +1683,9 @@ "@smithy/util-body-length-browser": "^2.1.1", "@smithy/util-body-length-node": "^2.2.1", "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", "@smithy/util-retry": "^2.1.1", "@smithy/util-stream": "^2.1.1", "@smithy/util-utf8": "^2.1.1", @@ -2314,36 +1697,36 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.511.0.tgz", - "integrity": "sha512-IRUYev0KNKa5rQrpULE9IhJW6dhgGQWBmAJI+OyITHMu3uGvVHDqWKqnShV0IfMJWg1y37I3juFJ1KAti8jyHw==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.515.0.tgz", + "integrity": "sha512-K527n83hrMUdosxOYTzL63wtlJtmN5SUJZnGY1sUR6UyOrnOr9lS6t3AB6BgHqLFRFZJqSqmhflv2cOD7P1UPg==", "dependencies": { "@aws-crypto/sha1-browser": "3.0.0", "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.511.0", - "@aws-sdk/core": "3.511.0", - "@aws-sdk/credential-provider-node": "3.511.0", - "@aws-sdk/middleware-bucket-endpoint": "3.511.0", - "@aws-sdk/middleware-expect-continue": "3.511.0", - "@aws-sdk/middleware-flexible-checksums": "3.511.0", - "@aws-sdk/middleware-host-header": "3.511.0", - "@aws-sdk/middleware-location-constraint": "3.511.0", - "@aws-sdk/middleware-logger": "3.511.0", - "@aws-sdk/middleware-recursion-detection": "3.511.0", - "@aws-sdk/middleware-sdk-s3": "3.511.0", - "@aws-sdk/middleware-signing": "3.511.0", - "@aws-sdk/middleware-ssec": "3.511.0", - "@aws-sdk/middleware-user-agent": "3.511.0", - "@aws-sdk/region-config-resolver": "3.511.0", - "@aws-sdk/signature-v4-multi-region": "3.511.0", - "@aws-sdk/types": "3.511.0", - "@aws-sdk/util-endpoints": "3.511.0", - "@aws-sdk/util-user-agent-browser": "3.511.0", - "@aws-sdk/util-user-agent-node": "3.511.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/credential-provider-node": "3.515.0", + "@aws-sdk/middleware-bucket-endpoint": "3.515.0", + "@aws-sdk/middleware-expect-continue": "3.515.0", + "@aws-sdk/middleware-flexible-checksums": "3.515.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-location-constraint": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-sdk-s3": "3.515.0", + "@aws-sdk/middleware-signing": "3.515.0", + "@aws-sdk/middleware-ssec": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/signature-v4-multi-region": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", "@aws-sdk/xml-builder": "3.496.0", "@smithy/config-resolver": "^2.1.1", - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/eventstream-serde-browser": "^2.1.1", "@smithy/eventstream-serde-config-resolver": "^2.1.1", "@smithy/eventstream-serde-node": "^2.1.1", @@ -2368,7 +1751,7 @@ "@smithy/util-body-length-browser": "^2.1.1", "@smithy/util-body-length-node": "^2.2.1", "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", "@smithy/util-endpoints": "^1.1.1", "@smithy/util-retry": "^2.1.1", "@smithy/util-stream": "^2.1.1", @@ -2382,27 +1765,26 @@ } }, "node_modules/@aws-sdk/client-secrets-manager": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.511.0.tgz", - "integrity": "sha512-HI03pQK6EyBunogeGWAylSZU866yv5vOEr7cku8UGNUxDt5yP+xctXusdYIggl4jIRTwaqg/0IGmZDs8mXTFtg==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.515.0.tgz", + "integrity": "sha512-YO7SVh0mQ55COP5LcKsXE+o6wsOBBn3beAqKDZIBdjXho5rNhTdvbtaGTiaZmv0ALtu7TxtPVixhbDE0y1QseA==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.511.0", - "@aws-sdk/core": "3.511.0", - "@aws-sdk/credential-provider-node": "3.511.0", - "@aws-sdk/middleware-host-header": "3.511.0", - "@aws-sdk/middleware-logger": "3.511.0", - "@aws-sdk/middleware-recursion-detection": "3.511.0", - "@aws-sdk/middleware-signing": "3.511.0", - "@aws-sdk/middleware-user-agent": "3.511.0", - "@aws-sdk/region-config-resolver": "3.511.0", - "@aws-sdk/types": "3.511.0", - "@aws-sdk/util-endpoints": "3.511.0", - "@aws-sdk/util-user-agent-browser": "3.511.0", - "@aws-sdk/util-user-agent-node": "3.511.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/credential-provider-node": "3.515.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", "@smithy/config-resolver": "^2.1.1", - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/fetch-http-handler": "^2.4.1", "@smithy/hash-node": "^2.1.1", "@smithy/invalid-dependency": "^2.1.1", @@ -2421,39 +1803,39 @@ "@smithy/util-body-length-browser": "^2.1.1", "@smithy/util-body-length-node": "^2.2.1", "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", "@smithy/util-retry": "^2.1.1", "@smithy/util-utf8": "^2.1.1", "tslib": "^2.5.0", - "uuid": "^8.3.2" + "uuid": "^9.0.1" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/client-sesv2": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.511.0.tgz", - "integrity": "sha512-unp8zqN6QLQ2ypz0f++ydC7a+rrJe+kNISI9wlGmQ32UDlrvh2nl8YBi44ZvhzmRSpN9idvy/WYvq5L1ly5FVA==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.515.0.tgz", + "integrity": "sha512-WHx1X+Qm+IHOQ5JCDUKbPh7Q+5Q26myXKFb0yF7ApvrGd+IxjDhYM/X02YQH47KEEvhcuIPvIBwIfCTgOaMonw==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.511.0", - "@aws-sdk/core": "3.511.0", - "@aws-sdk/credential-provider-node": "3.511.0", - "@aws-sdk/middleware-host-header": "3.511.0", - "@aws-sdk/middleware-logger": "3.511.0", - "@aws-sdk/middleware-recursion-detection": "3.511.0", - "@aws-sdk/middleware-signing": "3.511.0", - "@aws-sdk/middleware-user-agent": "3.511.0", - "@aws-sdk/region-config-resolver": "3.511.0", - "@aws-sdk/types": "3.511.0", - "@aws-sdk/util-endpoints": "3.511.0", - "@aws-sdk/util-user-agent-browser": "3.511.0", - "@aws-sdk/util-user-agent-node": "3.511.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/credential-provider-node": "3.515.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", "@smithy/config-resolver": "^2.1.1", - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/fetch-http-handler": "^2.4.1", "@smithy/hash-node": "^2.1.1", "@smithy/invalid-dependency": "^2.1.1", @@ -2472,8 +1854,9 @@ "@smithy/util-body-length-browser": "^2.1.1", "@smithy/util-body-length-node": "^2.2.1", "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", "@smithy/util-retry": "^2.1.1", "@smithy/util-utf8": "^2.1.1", "tslib": "^2.5.0" @@ -2483,27 +1866,26 @@ } }, "node_modules/@aws-sdk/client-ssm": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ssm/-/client-ssm-3.511.0.tgz", - "integrity": "sha512-L7LUER0n/9yVMuCzDTY90p9a1n/kln1J720WZ3OQuckLbs7U2ibdY66SE0dxVriVP85Yq9ihnGr4dammB8PW5w==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ssm/-/client-ssm-3.515.0.tgz", + "integrity": "sha512-Ifvy6J3BbypLTbw9x25T9c2huKh4kTZLaAGqOWOjCyl0/0ySgJqDUuvvdxWl7dPn0sMBRq3OiXyjQLbVP/gAqg==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.511.0", - "@aws-sdk/core": "3.511.0", - "@aws-sdk/credential-provider-node": "3.511.0", - "@aws-sdk/middleware-host-header": "3.511.0", - "@aws-sdk/middleware-logger": "3.511.0", - "@aws-sdk/middleware-recursion-detection": "3.511.0", - "@aws-sdk/middleware-signing": "3.511.0", - "@aws-sdk/middleware-user-agent": "3.511.0", - "@aws-sdk/region-config-resolver": "3.511.0", - "@aws-sdk/types": "3.511.0", - "@aws-sdk/util-endpoints": "3.511.0", - "@aws-sdk/util-user-agent-browser": "3.511.0", - "@aws-sdk/util-user-agent-node": "3.511.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/credential-provider-node": "3.515.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", "@smithy/config-resolver": "^2.1.1", - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/fetch-http-handler": "^2.4.1", "@smithy/hash-node": "^2.1.1", "@smithy/invalid-dependency": "^2.1.1", @@ -2522,37 +1904,38 @@ "@smithy/util-body-length-browser": "^2.1.1", "@smithy/util-body-length-node": "^2.2.1", "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", "@smithy/util-retry": "^2.1.1", "@smithy/util-utf8": "^2.1.1", "@smithy/util-waiter": "^2.1.1", "tslib": "^2.5.0", - "uuid": "^8.3.2" + "uuid": "^9.0.1" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.511.0.tgz", - "integrity": "sha512-v1f5ZbuZWpad+fgTOpgFyIZT3A37wdqoSPh0hl+cKRu5kPsz96xCe9+UvLx+HdN2yJ/mV0UZcMq6ysj4xAGIEg==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.515.0.tgz", + "integrity": "sha512-4oGBLW476zmkdN98lAns3bObRNO+DLOfg4MDUSR6l6GYBV/zGAtoy2O/FhwYKgA2L5h2ZtElGopLlk/1Q0ePLw==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.511.0", - "@aws-sdk/middleware-host-header": "3.511.0", - "@aws-sdk/middleware-logger": "3.511.0", - "@aws-sdk/middleware-recursion-detection": "3.511.0", - "@aws-sdk/middleware-user-agent": "3.511.0", - "@aws-sdk/region-config-resolver": "3.511.0", - "@aws-sdk/types": "3.511.0", - "@aws-sdk/util-endpoints": "3.511.0", - "@aws-sdk/util-user-agent-browser": "3.511.0", - "@aws-sdk/util-user-agent-node": "3.511.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", "@smithy/config-resolver": "^2.1.1", - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/fetch-http-handler": "^2.4.1", "@smithy/hash-node": "^2.1.1", "@smithy/invalid-dependency": "^2.1.1", @@ -2571,8 +1954,9 @@ "@smithy/util-body-length-browser": "^2.1.1", "@smithy/util-body-length-node": "^2.2.1", "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", "@smithy/util-retry": "^2.1.1", "@smithy/util-utf8": "^2.1.1", "tslib": "^2.5.0" @@ -2582,26 +1966,25 @@ } }, "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.511.0.tgz", - "integrity": "sha512-cITRRq54eTrq7ll9li+yYnLbNHKXG2P+ovdZSDiQ6LjCYBdcD4ela30qbs87Yye9YsopdslDzBhHHtrf5oiuMw==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.515.0.tgz", + "integrity": "sha512-zACa8LNlPUdlNUBqQRf5a3MfouLNtcBfm84v2c8M976DwJrMGONPe1QjyLLsD38uESQiXiVQRruj/b000iMXNw==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.511.0", - "@aws-sdk/core": "3.511.0", - "@aws-sdk/middleware-host-header": "3.511.0", - "@aws-sdk/middleware-logger": "3.511.0", - "@aws-sdk/middleware-recursion-detection": "3.511.0", - "@aws-sdk/middleware-signing": "3.511.0", - "@aws-sdk/middleware-user-agent": "3.511.0", - "@aws-sdk/region-config-resolver": "3.511.0", - "@aws-sdk/types": "3.511.0", - "@aws-sdk/util-endpoints": "3.511.0", - "@aws-sdk/util-user-agent-browser": "3.511.0", - "@aws-sdk/util-user-agent-node": "3.511.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", "@smithy/config-resolver": "^2.1.1", - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/fetch-http-handler": "^2.4.1", "@smithy/hash-node": "^2.1.1", "@smithy/invalid-dependency": "^2.1.1", @@ -2620,8 +2003,9 @@ "@smithy/util-body-length-browser": "^2.1.1", "@smithy/util-body-length-node": "^2.2.1", "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", "@smithy/util-retry": "^2.1.1", "@smithy/util-utf8": "^2.1.1", "tslib": "^2.5.0" @@ -2630,28 +2014,28 @@ "node": ">=14.0.0" }, "peerDependencies": { - "@aws-sdk/credential-provider-node": "^3.511.0" + "@aws-sdk/credential-provider-node": "^3.515.0" } }, "node_modules/@aws-sdk/client-sts": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.511.0.tgz", - "integrity": "sha512-lwVEEXK+1auEwmBuTv35m2GvbxPthi8SjNUpU4pRetZPVbGhnhCN6H7JqeMDP6GLf81Io2eySXRsmLMt7l/fjg==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.515.0.tgz", + "integrity": "sha512-ScYuvaIDgip3atOJIA1FU2n0gJkEdveu1KrrCPathoUCV5zpK8qQmO/n+Fj/7hKFxeKdFbB+4W4CsJWYH94nlg==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.511.0", - "@aws-sdk/middleware-host-header": "3.511.0", - "@aws-sdk/middleware-logger": "3.511.0", - "@aws-sdk/middleware-recursion-detection": "3.511.0", - "@aws-sdk/middleware-user-agent": "3.511.0", - "@aws-sdk/region-config-resolver": "3.511.0", - "@aws-sdk/types": "3.511.0", - "@aws-sdk/util-endpoints": "3.511.0", - "@aws-sdk/util-user-agent-browser": "3.511.0", - "@aws-sdk/util-user-agent-node": "3.511.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", "@smithy/config-resolver": "^2.1.1", - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/fetch-http-handler": "^2.4.1", "@smithy/hash-node": "^2.1.1", "@smithy/invalid-dependency": "^2.1.1", @@ -2670,7 +2054,7 @@ "@smithy/util-body-length-browser": "^2.1.1", "@smithy/util-body-length-node": "^2.2.1", "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", "@smithy/util-endpoints": "^1.1.1", "@smithy/util-middleware": "^2.1.1", "@smithy/util-retry": "^2.1.1", @@ -2682,7 +2066,7 @@ "node": ">=14.0.0" }, "peerDependencies": { - "@aws-sdk/credential-provider-node": "^3.511.0" + "@aws-sdk/credential-provider-node": "^3.515.0" } }, "node_modules/@aws-sdk/cloudfront-signer": { @@ -2698,11 +2082,11 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.511.0.tgz", - "integrity": "sha512-0gbDvQhToyLxPyr/7KP6uavrBYKh7exld2lju1Lp65U61XgEjTVP/thJmHTvH4BAKGSqeIz/rrwJ0KrC8nwBtw==", + "version": "3.513.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.513.0.tgz", + "integrity": "sha512-L+9DL4apWuqNKVOMJ8siAuWoRM9rZf9w1iPv8S2o83WO2jVK7E/m+rNW1dFo9HsA5V1ccDl2H2qLXx24HiHmOw==", "dependencies": { - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/protocol-http": "^3.1.1", "@smithy/signature-v4": "^2.1.1", "@smithy/smithy-client": "^2.3.1", @@ -2714,11 +2098,11 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.511.0.tgz", - "integrity": "sha512-4VUsnLRox8YzxnZwnFrfZM4bL5KKLhsjjjX7oiuLyzFkhauI4HFYt7rTB8YNGphpqAg/Wzw5DBZfO3Bw1iR1HA==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.515.0.tgz", + "integrity": "sha512-45vxdyqhTAaUMERYVWOziG3K8L2TV9G4ryQS/KZ84o7NAybE9GMdoZRVmGHAO7mJJ1wQiYCM/E+i5b3NW9JfNA==", "dependencies": { - "@aws-sdk/types": "3.511.0", + "@aws-sdk/types": "3.515.0", "@smithy/property-provider": "^2.1.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -2728,11 +2112,11 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.511.0.tgz", - "integrity": "sha512-y83Gt8GPpgMe/lMFxIq+0G2rbzLTC6lhrDocHUzqcApLD6wet8Esy2iYckSRlJgYY+qsVAzpLrSMtt85DwRPTw==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.515.0.tgz", + "integrity": "sha512-Ba6FXK77vU4WyheiamNjEuTFmir0eAXuJGPO27lBaA8g+V/seXGHScsbOG14aQGDOr2P02OPwKGZrWWA7BFpfQ==", "dependencies": { - "@aws-sdk/types": "3.511.0", + "@aws-sdk/types": "3.515.0", "@smithy/fetch-http-handler": "^2.4.1", "@smithy/node-http-handler": "^2.3.1", "@smithy/property-provider": "^2.1.1", @@ -2747,16 +2131,16 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.511.0.tgz", - "integrity": "sha512-AgIOCtYzm61jbTQCY/2Vf/yu7DeLG0TLZa05a3VVRN9XE4ERtEnMn7TdbxM+hS24MTX8xI0HbMcWxCBkXRIg9w==", - "dependencies": { - "@aws-sdk/client-sts": "3.511.0", - "@aws-sdk/credential-provider-env": "3.511.0", - "@aws-sdk/credential-provider-process": "3.511.0", - "@aws-sdk/credential-provider-sso": "3.511.0", - "@aws-sdk/credential-provider-web-identity": "3.511.0", - "@aws-sdk/types": "3.511.0", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.515.0.tgz", + "integrity": "sha512-ouDlNZdv2TKeVEA/YZk2+XklTXyAAGdbWnl4IgN9ItaodWI+lZjdIoNC8BAooVH+atIV/cZgoGTGQL7j2TxJ9A==", + "dependencies": { + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/credential-provider-env": "3.515.0", + "@aws-sdk/credential-provider-process": "3.515.0", + "@aws-sdk/credential-provider-sso": "3.515.0", + "@aws-sdk/credential-provider-web-identity": "3.515.0", + "@aws-sdk/types": "3.515.0", "@smithy/credential-provider-imds": "^2.2.1", "@smithy/property-provider": "^2.1.1", "@smithy/shared-ini-file-loader": "^2.3.1", @@ -2768,17 +2152,17 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.511.0.tgz", - "integrity": "sha512-5JDZXsSluliJmxOF+lYYFgJdSKQfVLQyic5NxScHULTERGoEwEHMgucFGwJ9MV9FoINjNTQLfAiWlJL/kGkCEQ==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.511.0", - "@aws-sdk/credential-provider-http": "3.511.0", - "@aws-sdk/credential-provider-ini": "3.511.0", - "@aws-sdk/credential-provider-process": "3.511.0", - "@aws-sdk/credential-provider-sso": "3.511.0", - "@aws-sdk/credential-provider-web-identity": "3.511.0", - "@aws-sdk/types": "3.511.0", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.515.0.tgz", + "integrity": "sha512-Y4kHSpbxksiCZZNcvsiKUd8Fb2XlyUuONEwqWFNL82ZH6TCCjBGS31wJQCSxBHqYcOL3tiORUEJkoO7uS30uQA==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.515.0", + "@aws-sdk/credential-provider-http": "3.515.0", + "@aws-sdk/credential-provider-ini": "3.515.0", + "@aws-sdk/credential-provider-process": "3.515.0", + "@aws-sdk/credential-provider-sso": "3.515.0", + "@aws-sdk/credential-provider-web-identity": "3.515.0", + "@aws-sdk/types": "3.515.0", "@smithy/credential-provider-imds": "^2.2.1", "@smithy/property-provider": "^2.1.1", "@smithy/shared-ini-file-loader": "^2.3.1", @@ -2790,11 +2174,11 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.511.0.tgz", - "integrity": "sha512-88hLUPqcTwjSubPS+34ZfmglnKeLny8GbmZsyllk96l26PmDTAqo5RScSA8BWxL0l5pRRWGtcrFyts+oibHIuQ==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.515.0.tgz", + "integrity": "sha512-pSjiOA2FM63LHRKNDvEpBRp80FVGT0Mw/gzgbqFXP+sewk0WVonYbEcMDTJptH3VsLPGzqH/DQ1YL/aEIBuXFQ==", "dependencies": { - "@aws-sdk/types": "3.511.0", + "@aws-sdk/types": "3.515.0", "@smithy/property-provider": "^2.1.1", "@smithy/shared-ini-file-loader": "^2.3.1", "@smithy/types": "^2.9.1", @@ -2805,13 +2189,13 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.511.0.tgz", - "integrity": "sha512-aEei9UdXYEE2e0Htf28/IcuHcWk3VkUkpcg3KDR/AyzXA3i/kxmixtAgRmHOForC5CMqoJjzVPFUITNkAscyag==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.515.0.tgz", + "integrity": "sha512-j7vUkiSmuhpBvZYoPTRTI4ePnQbiZMFl6TNhg9b9DprC1zHkucsZnhRhqjOVlrw/H6J4jmcPGcHHTZ5WQNI5xQ==", "dependencies": { - "@aws-sdk/client-sso": "3.511.0", - "@aws-sdk/token-providers": "3.511.0", - "@aws-sdk/types": "3.511.0", + "@aws-sdk/client-sso": "3.515.0", + "@aws-sdk/token-providers": "3.515.0", + "@aws-sdk/types": "3.515.0", "@smithy/property-provider": "^2.1.1", "@smithy/shared-ini-file-loader": "^2.3.1", "@smithy/types": "^2.9.1", @@ -2822,12 +2206,12 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.511.0.tgz", - "integrity": "sha512-/3XMyN7YYefAsES/sMMY5zZGRmZ5QJisJw798DdMYmYMsb1dt0Qy8kZTu+59ZzOiVIcznsjSTCEB81QmGtDKcA==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.515.0.tgz", + "integrity": "sha512-66+2g4z3fWwdoGReY8aUHvm6JrKZMTRxjuizljVmMyOBttKPeBYXvUTop/g3ZGUx1f8j+C5qsGK52viYBvtjuQ==", "dependencies": { - "@aws-sdk/client-sts": "3.511.0", - "@aws-sdk/types": "3.511.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/types": "3.515.0", "@smithy/property-provider": "^2.1.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -2837,9 +2221,9 @@ } }, "node_modules/@aws-sdk/lib-storage": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.511.0.tgz", - "integrity": "sha512-inEbSyqzGxiQs8aEnkGdxw9ZDn370mRHOdE1TB/GvVe9buQVyZ2hQvOY5WBVOaIGDIxGpuUzVvr4o89XreU19w==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.515.0.tgz", + "integrity": "sha512-/7z/3KnMs1ODNS9c8Skj/DFTsy6/v7n17clh1IGOcTYhhioCMA3MIzIZecWFeLjPYcUSkNQHIIjKFQt1nhZkwA==", "dependencies": { "@smithy/abort-controller": "^2.1.1", "@smithy/middleware-endpoint": "^2.4.1", @@ -2857,11 +2241,11 @@ } }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.511.0.tgz", - "integrity": "sha512-G4dAAHPUZbpDCVBaCcAOlFoctO9lcecSs0EZYrvzQc/9d4XJvNWGd1C7GSdf204VPOCPZCjNpTkdWGm25r00wA==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.515.0.tgz", + "integrity": "sha512-Vm423j3udFrhKPaKiXtie+6aF05efjX8lhAu5VOruIvbam7olvdWNdkH7sGWlz1ko3CVa7PwOYjGHiOOhxpEOA==", "dependencies": { - "@aws-sdk/types": "3.511.0", + "@aws-sdk/types": "3.515.0", "@aws-sdk/util-arn-parser": "3.495.0", "@smithy/node-config-provider": "^2.2.1", "@smithy/protocol-http": "^3.1.1", @@ -2874,11 +2258,11 @@ } }, "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.511.0.tgz", - "integrity": "sha512-zjDzrJV9PFCkEqhNLKKK+9PB1vPveVZLJbcY71V3PZFvPII1bhlgwvI1e99MhEiaiH2a9I2PnS56bGwEKuNTrw==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.515.0.tgz", + "integrity": "sha512-TWCXulivab4reOMx/vxa/IwnPX78fLwI9NUoAxjsqB6W9qjmSnPD43BSVeGvbbl/YNmgk7XfMbZb6IgxW7RyzA==", "dependencies": { - "@aws-sdk/types": "3.511.0", + "@aws-sdk/types": "3.515.0", "@smithy/protocol-http": "^3.1.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -2888,13 +2272,13 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.511.0.tgz", - "integrity": "sha512-oI8zULi6VXLXJ3zA6aCdbOoceSNOxGITosB7EKDsLllzAQFV1WlzmQCtjFY8DLLYZ521atgJNcVbzjxPQnrnJA==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.515.0.tgz", + "integrity": "sha512-ydGjnqNeYlJaAkmQeQnS4pZRAAvzefdm8c234Qh0Fg55xRwHTNLp7uYsdfkTjrdAlj6YIO3Zr6vK6VJ6MGCwug==", "dependencies": { "@aws-crypto/crc32": "3.0.0", "@aws-crypto/crc32c": "3.0.0", - "@aws-sdk/types": "3.511.0", + "@aws-sdk/types": "3.515.0", "@smithy/is-array-buffer": "^2.1.1", "@smithy/protocol-http": "^3.1.1", "@smithy/types": "^2.9.1", @@ -2906,11 +2290,11 @@ } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.511.0.tgz", - "integrity": "sha512-DbBzQP/6woSHR/+g9dHN3YiYaLIqFw9u8lQFMxi3rT3hqITFVYLzzXtEaHjDD6/is56pNT84CIKbyJ6/gY5d1Q==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.515.0.tgz", + "integrity": "sha512-I1MwWPzdRKM1luvdDdjdGsDjNVPhj9zaIytEchjTY40NcKOg+p2evLD2y69ozzg8pyXK63r8DdvDGOo9QPuh0A==", "dependencies": { - "@aws-sdk/types": "3.511.0", + "@aws-sdk/types": "3.515.0", "@smithy/protocol-http": "^3.1.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -2920,11 +2304,11 @@ } }, "node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.511.0.tgz", - "integrity": "sha512-PKHnOT3oBo41NELq3Esz3K9JuV1l9E+SrCcfr/07yU4EbqhS4UGPb22Yf5JakQu4fGbTFlAftcc8PXcE2zLr4g==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.515.0.tgz", + "integrity": "sha512-ORFC5oijjTJsHhUXy9o52/vl5Irf6e83bE/8tBp+sVVx81+E8zTTWZbysoa41c0B5Ycd0H3wCWutvjdXT16ydQ==", "dependencies": { - "@aws-sdk/types": "3.511.0", + "@aws-sdk/types": "3.515.0", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" }, @@ -2933,11 +2317,11 @@ } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.511.0.tgz", - "integrity": "sha512-EYU9dBlJXvQcCsM2Tfgi0NQoXrqovfDv/fDy8oGJgZFrgNuHDti8tdVVxeJTUJNEAF67xlDl5o+rWEkKthkYGQ==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.515.0.tgz", + "integrity": "sha512-qXomJzg2m/5seQOxHi/yOXOKfSjwrrJSmEmfwJKJyQgdMbBcjz3Cz0H/1LyC6c5hHm6a/SZgSTzDAbAoUmyL+Q==", "dependencies": { - "@aws-sdk/types": "3.511.0", + "@aws-sdk/types": "3.515.0", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" }, @@ -2946,11 +2330,11 @@ } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.511.0.tgz", - "integrity": "sha512-PlNPCV/6zpDVdNx1K69xDTh/wPNU4WyP4qa6hUo2/+4/PNG5HI9xbCWtpb4RjhdTRw6qDtkBNcPICHbtWx5aHg==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.515.0.tgz", + "integrity": "sha512-dokHLbTV3IHRIBrw9mGoxcNTnQsjlm7TpkJhPdGT9T4Mq399EyQo51u6IsVMm07RXLl2Zw7u+u9p+qWBFzmFRA==", "dependencies": { - "@aws-sdk/types": "3.511.0", + "@aws-sdk/types": "3.515.0", "@smithy/protocol-http": "^3.1.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -2960,11 +2344,11 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.511.0.tgz", - "integrity": "sha512-SKJr8mKaqjcGpu0xxRPXZiKrJmyetDfgzvWuZ7QOgdnPa+6jk5fmEUTFoPb3VCarMkf8xo/l6cTZ5lei7Lbflw==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.515.0.tgz", + "integrity": "sha512-vB8JwiTEAqm1UT9xfugnCgl0H0dtBLUQQK99JwQEWjHPZmQ3HQuVkykmJRY3X0hzKMEgqXodz0hZOvf3Hq1mvQ==", "dependencies": { - "@aws-sdk/types": "3.511.0", + "@aws-sdk/types": "3.515.0", "@aws-sdk/util-arn-parser": "3.495.0", "@smithy/node-config-provider": "^2.2.1", "@smithy/protocol-http": "^3.1.1", @@ -2979,11 +2363,11 @@ } }, "node_modules/@aws-sdk/middleware-signing": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.511.0.tgz", - "integrity": "sha512-IMijFLfm+QQHD6NNDX9k3op9dpBSlWKnqjcMU38Tytl2nbqV4gktkarOK1exHAmH7CdoYR5BufVtBzbASNSF/A==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.515.0.tgz", + "integrity": "sha512-SdjCyQCL702I07KhCiBFcoh6+NYtnruHJQIzWwMpBteuYHnCHW1k9uZ6pqacsS+Y6qpAKfTVNpQx2zP2s6QoHA==", "dependencies": { - "@aws-sdk/types": "3.511.0", + "@aws-sdk/types": "3.515.0", "@smithy/property-provider": "^2.1.1", "@smithy/protocol-http": "^3.1.1", "@smithy/signature-v4": "^2.1.1", @@ -2996,11 +2380,11 @@ } }, "node_modules/@aws-sdk/middleware-ssec": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.511.0.tgz", - "integrity": "sha512-8pfgBard9pj7oWJ79R6dbXHUGr7JPP/OmAsKBYZA0r/91a1XdFUDtRYZadstjcOv/X3QbeG3QqWOtNco+XgM7Q==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.515.0.tgz", + "integrity": "sha512-0qLjKiorosVBzzaV/o7MEyS9xqLLu02qGbP564Z/FZY74JUQEpBNedgveMUbb6lqr85RnOuwZ0GZ0cBRfH2brQ==", "dependencies": { - "@aws-sdk/types": "3.511.0", + "@aws-sdk/types": "3.515.0", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" }, @@ -3009,12 +2393,12 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.511.0.tgz", - "integrity": "sha512-eLs+CxP2QCXh3tCGYCdAml3oyWj8MSIwKbH+8rKw0k/5vmY1YJDBy526whOxx61ivhz2e0muuijN4X5EZZ2Pnw==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.515.0.tgz", + "integrity": "sha512-nOqZjGA/GkjuJ5fUshec9Fv6HFd7ovOTxMJbw3MfAhqXuVZ6dKF41lpVJ4imNsgyFt3shUg9WDY8zGFjlYMB3g==", "dependencies": { - "@aws-sdk/types": "3.511.0", - "@aws-sdk/util-endpoints": "3.511.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", "@smithy/protocol-http": "^3.1.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -3024,11 +2408,11 @@ } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.511.0.tgz", - "integrity": "sha512-RzBLSNaRd4iEkQyEGfiSNvSnWU/x23rsiFgA9tqYFA0Vqx7YmzSWC8QBUxpwybB8HkbbL9wNVKQqTbhI3mYneQ==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.515.0.tgz", + "integrity": "sha512-RIRx9loxMgEAc/r1wPfnfShOuzn4RBi8pPPv6/jhhITEeMnJe6enAh2k5y9DdiVDDgCWZgVFSv0YkAIfzAFsnQ==", "dependencies": { - "@aws-sdk/types": "3.511.0", + "@aws-sdk/types": "3.515.0", "@smithy/node-config-provider": "^2.2.1", "@smithy/types": "^2.9.1", "@smithy/util-config-provider": "^2.2.1", @@ -3040,12 +2424,12 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.511.0.tgz", - "integrity": "sha512-lwbU3LX5TpYu1DHBMH2Wz+2MWGccn5G3psu1Y9WTPc+1bubVQHWf8UD2lzON5L2QirT9tQheQjTke1u5JC7FTQ==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.515.0.tgz", + "integrity": "sha512-5lrCn4DSE0zL41k0L6moqcdExZhWdAnV0/oMEagrISzQYoia+aNTEeyVD3xqJhRbEW4gCj3Uoyis6c8muf7b9g==", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.511.0", - "@aws-sdk/types": "3.511.0", + "@aws-sdk/middleware-sdk-s3": "3.515.0", + "@aws-sdk/types": "3.515.0", "@smithy/protocol-http": "^3.1.1", "@smithy/signature-v4": "^2.1.1", "@smithy/types": "^2.9.1", @@ -3056,12 +2440,12 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.511.0.tgz", - "integrity": "sha512-92dXjMHBJcRoUkJHc0Bvtsz7Sal8t6VASRJ5vfs5c2ZpTVgLpVnM4dBmwUgGUdnvHov0cZTXbbadTJ/qOWx5Zw==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.515.0.tgz", + "integrity": "sha512-MQuf04rIcTXqwDzmyHSpFPF1fKEzRl64oXtCRUF3ddxTdK6wxXkePfK6wNCuL+GEbEcJAoCtIGIRpzGPJvQjHA==", "dependencies": { - "@aws-sdk/client-sso-oidc": "3.511.0", - "@aws-sdk/types": "3.511.0", + "@aws-sdk/client-sso-oidc": "3.515.0", + "@aws-sdk/types": "3.515.0", "@smithy/property-provider": "^2.1.1", "@smithy/shared-ini-file-loader": "^2.3.1", "@smithy/types": "^2.9.1", @@ -3072,9 +2456,9 @@ } }, "node_modules/@aws-sdk/types": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.511.0.tgz", - "integrity": "sha512-P03ufufxmkvd7nO46oOeEqYIMPJ8qMCKxAsfJk1JBVPQ1XctVntbail4/UFnrnzij8DTl4Mk/D62uGo7+RolXA==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.515.0.tgz", + "integrity": "sha512-B3gUpiMlpT6ERaLvZZ61D0RyrQPsFYDkCncLPVkZOKkCOoFU46zi1o6T5JcYiz8vkx1q9RGloQ5exh79s5pU/w==", "dependencies": { "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -3095,11 +2479,11 @@ } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.511.0.tgz", - "integrity": "sha512-J/5hsscJkg2pAOdLx1YKlyMCk5lFRxRxEtup9xipzOxVBlqOIE72Tuu31fbxSlF8XzO/AuCJcZL4m1v098K9oA==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.515.0.tgz", + "integrity": "sha512-UJi+jdwcGFV/F7d3+e2aQn5yZOVpDiAgfgNhPnEtgV0WozJ5/ZUeZBgWvSc/K415N4A4D/9cbBc7+I+35qzcDQ==", "dependencies": { - "@aws-sdk/types": "3.511.0", + "@aws-sdk/types": "3.515.0", "@smithy/types": "^2.9.1", "@smithy/util-endpoints": "^1.1.1", "tslib": "^2.5.0" @@ -3120,22 +2504,22 @@ } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.511.0.tgz", - "integrity": "sha512-5LuESdwtIcA10aHcX7pde7aCIijcyTPBXFuXmFlDTgm/naAayQxelQDpvgbzuzGLgePf8eTyyhDKhzwPZ2EqiQ==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.515.0.tgz", + "integrity": "sha512-pTWQb0JCafTmLHLDv3Qqs/nAAJghcPdGQIBpsCStb0YEzg3At/dOi2AIQ683yYnXmeOxLXJDzmlsovfVObJScw==", "dependencies": { - "@aws-sdk/types": "3.511.0", + "@aws-sdk/types": "3.515.0", "@smithy/types": "^2.9.1", "bowser": "^2.11.0", "tslib": "^2.5.0" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.511.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.511.0.tgz", - "integrity": "sha512-UopdlRvYY5mxlS4wwFv+QAWL6/T302wmoQj7i+RY+c/D3Ej3PKBb/mW3r2wEOgZLJmPpeeM1SYMk+rVmsW1rqw==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.515.0.tgz", + "integrity": "sha512-A/KJ+/HTohHyVXLH+t/bO0Z2mPrQgELbQO8tX+B2nElo8uklj70r5cT7F8ETsI9oOy+HDVpiL5/v45ZgpUOiPg==", "dependencies": { - "@aws-sdk/types": "3.511.0", + "@aws-sdk/types": "3.515.0", "@smithy/node-config-provider": "^2.2.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -7616,9 +7000,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.0.tgz", - "integrity": "sha512-fGFDEctNh0CcSwsiRPxiaqX0P5rq+AqE0SRhYGZ4PX46Lg1FNR6oCxJghf8YgY0WQEgQuh3lErUFE4KxLeRmmw==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.1.tgz", + "integrity": "sha512-m55cpeupQ2DbuRGQMMZDzbv9J9PgVelPjlcmM5kxHnrBdBx6REaEd7LamYV7Dm8N7rCyR/XwU6rVP8ploKtIkA==", "cpu": [ "ppc64" ], @@ -7632,9 +7016,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.0.tgz", - "integrity": "sha512-3bMAfInvByLHfJwYPJRlpTeaQA75n8C/QKpEaiS4HrFWFiJlNI0vzq/zCjBrhAYcPyVPG7Eo9dMrcQXuqmNk5g==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.1.tgz", + "integrity": "sha512-4j0+G27/2ZXGWR5okcJi7pQYhmkVgb4D7UKwxcqrjhvp5TKWx3cUjgB1CGj1mfdmJBQ9VnUGgUhign+FPF2Zgw==", "cpu": [ "arm" ], @@ -7648,9 +7032,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.0.tgz", - "integrity": "sha512-aVpnM4lURNkp0D3qPoAzSG92VXStYmoVPOgXveAUoQBWRSuQzt51yvSju29J6AHPmwY1BjH49uR29oyfH1ra8Q==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.1.tgz", + "integrity": "sha512-hCnXNF0HM6AjowP+Zou0ZJMWWa1VkD77BXe959zERgGJBBxB+sV+J9f/rcjeg2c5bsukD/n17RKWXGFCO5dD5A==", "cpu": [ "arm64" ], @@ -7664,9 +7048,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.0.tgz", - "integrity": "sha512-uK7wAnlRvjkCPzh8jJ+QejFyrP8ObKuR5cBIsQZ+qbMunwR8sbd8krmMbxTLSrDhiPZaJYKQAU5Y3iMDcZPhyQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.1.tgz", + "integrity": "sha512-MSfZMBoAsnhpS+2yMFYIQUPs8Z19ajwfuaSZx+tSl09xrHZCjbeXXMsUF/0oq7ojxYEpsSo4c0SfjxOYXRbpaA==", "cpu": [ "x64" ], @@ -7680,9 +7064,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.0.tgz", - "integrity": "sha512-AjEcivGAlPs3UAcJedMa9qYg9eSfU6FnGHJjT8s346HSKkrcWlYezGE8VaO2xKfvvlZkgAhyvl06OJOxiMgOYQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.1.tgz", + "integrity": "sha512-Ylk6rzgMD8klUklGPzS414UQLa5NPXZD5tf8JmQU8GQrj6BrFA/Ic9tb2zRe1kOZyCbGl+e8VMbDRazCEBqPvA==", "cpu": [ "arm64" ], @@ -7696,9 +7080,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.0.tgz", - "integrity": "sha512-bsgTPoyYDnPv8ER0HqnJggXK6RyFy4PH4rtsId0V7Efa90u2+EifxytE9pZnsDgExgkARy24WUQGv9irVbTvIw==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.1.tgz", + "integrity": "sha512-pFIfj7U2w5sMp52wTY1XVOdoxw+GDwy9FsK3OFz4BpMAjvZVs0dT1VXs8aQm22nhwoIWUmIRaE+4xow8xfIDZA==", "cpu": [ "x64" ], @@ -7712,9 +7096,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.0.tgz", - "integrity": "sha512-kQ7jYdlKS335mpGbMW5tEe3IrQFIok9r84EM3PXB8qBFJPSc6dpWfrtsC/y1pyrz82xfUIn5ZrnSHQQsd6jebQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.1.tgz", + "integrity": "sha512-UyW1WZvHDuM4xDz0jWun4qtQFauNdXjXOtIy7SYdf7pbxSWWVlqhnR/T2TpX6LX5NI62spt0a3ldIIEkPM6RHw==", "cpu": [ "arm64" ], @@ -7728,9 +7112,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.0.tgz", - "integrity": "sha512-uG8B0WSepMRsBNVXAQcHf9+Ko/Tr+XqmK7Ptel9HVmnykupXdS4J7ovSQUIi0tQGIndhbqWLaIL/qO/cWhXKyQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.1.tgz", + "integrity": "sha512-itPwCw5C+Jh/c624vcDd9kRCCZVpzpQn8dtwoYIt2TJF3S9xJLiRohnnNrKwREvcZYx0n8sCSbvGH349XkcQeg==", "cpu": [ "x64" ], @@ -7744,9 +7128,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.0.tgz", - "integrity": "sha512-2ezuhdiZw8vuHf1HKSf4TIk80naTbP9At7sOqZmdVwvvMyuoDiZB49YZKLsLOfKIr77+I40dWpHVeY5JHpIEIg==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.1.tgz", + "integrity": "sha512-LojC28v3+IhIbfQ+Vu4Ut5n3wKcgTu6POKIHN9Wpt0HnfgUGlBuyDDQR4jWZUZFyYLiz4RBBBmfU6sNfn6RhLw==", "cpu": [ "arm" ], @@ -7760,9 +7144,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.0.tgz", - "integrity": "sha512-uTtyYAP5veqi2z9b6Gr0NUoNv9F/rOzI8tOD5jKcCvRUn7T60Bb+42NDBCWNhMjkQzI0qqwXkQGo1SY41G52nw==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.1.tgz", + "integrity": "sha512-cX8WdlF6Cnvw/DO9/X7XLH2J6CkBnz7Twjpk56cshk9sjYVcuh4sXQBy5bmTwzBjNVZze2yaV1vtcJS04LbN8w==", "cpu": [ "arm64" ], @@ -7776,9 +7160,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.0.tgz", - "integrity": "sha512-c88wwtfs8tTffPaoJ+SQn3y+lKtgTzyjkD8NgsyCtCmtoIC8RDL7PrJU05an/e9VuAke6eJqGkoMhJK1RY6z4w==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.1.tgz", + "integrity": "sha512-4H/sQCy1mnnGkUt/xszaLlYJVTz3W9ep52xEefGtd6yXDQbz/5fZE5dFLUgsPdbUOQANcVUa5iO6g3nyy5BJiw==", "cpu": [ "ia32" ], @@ -7792,9 +7176,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.0.tgz", - "integrity": "sha512-lR2rr/128/6svngnVta6JN4gxSXle/yZEZL3o4XZ6esOqhyR4wsKyfu6qXAL04S4S5CgGfG+GYZnjFd4YiG3Aw==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.1.tgz", + "integrity": "sha512-c0jgtB+sRHCciVXlyjDcWb2FUuzlGVRwGXgI+3WqKOIuoo8AmZAddzeOHeYLtD+dmtHw3B4Xo9wAUdjlfW5yYA==", "cpu": [ "loong64" ], @@ -7808,9 +7192,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.0.tgz", - "integrity": "sha512-9Sycc+1uUsDnJCelDf6ZNqgZQoK1mJvFtqf2MUz4ujTxGhvCWw+4chYfDLPepMEvVL9PDwn6HrXad5yOrNzIsQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.1.tgz", + "integrity": "sha512-TgFyCfIxSujyuqdZKDZ3yTwWiGv+KnlOeXXitCQ+trDODJ+ZtGOzLkSWngynP0HZnTsDyBbPy7GWVXWaEl6lhA==", "cpu": [ "mips64el" ], @@ -7824,9 +7208,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.0.tgz", - "integrity": "sha512-CoWSaaAXOZd+CjbUTdXIJE/t7Oz+4g90A3VBCHLbfuc5yUQU/nFDLOzQsN0cdxgXd97lYW/psIIBdjzQIwTBGw==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.1.tgz", + "integrity": "sha512-b+yuD1IUeL+Y93PmFZDZFIElwbmFfIKLKlYI8M6tRyzE6u7oEP7onGk0vZRh8wfVGC2dZoy0EqX1V8qok4qHaw==", "cpu": [ "ppc64" ], @@ -7840,9 +7224,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.0.tgz", - "integrity": "sha512-mlb1hg/eYRJUpv8h/x+4ShgoNLL8wgZ64SUr26KwglTYnwAWjkhR2GpoKftDbPOCnodA9t4Y/b68H4J9XmmPzA==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.1.tgz", + "integrity": "sha512-wpDlpE0oRKZwX+GfomcALcouqjjV8MIX8DyTrxfyCfXxoKQSDm45CZr9fanJ4F6ckD4yDEPT98SrjvLwIqUCgg==", "cpu": [ "riscv64" ], @@ -7856,9 +7240,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.0.tgz", - "integrity": "sha512-fgf9ubb53xSnOBqyvWEY6ukBNRl1mVX1srPNu06B6mNsNK20JfH6xV6jECzrQ69/VMiTLvHMicQR/PgTOgqJUQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.1.tgz", + "integrity": "sha512-5BepC2Au80EohQ2dBpyTquqGCES7++p7G+7lXe1bAIvMdXm4YYcEfZtQrP4gaoZ96Wv1Ute61CEHFU7h4FMueQ==", "cpu": [ "s390x" ], @@ -7872,9 +7256,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.0.tgz", - "integrity": "sha512-H9Eu6MGse++204XZcYsse1yFHmRXEWgadk2N58O/xd50P9EvFMLJTQLg+lB4E1cF2xhLZU5luSWtGTb0l9UeSg==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.1.tgz", + "integrity": "sha512-5gRPk7pKuaIB+tmH+yKd2aQTRpqlf1E4f/mC+tawIm/CGJemZcHZpp2ic8oD83nKgUPMEd0fNanrnFljiruuyA==", "cpu": [ "x64" ], @@ -7888,9 +7272,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.0.tgz", - "integrity": "sha512-lCT675rTN1v8Fo+RGrE5KjSnfY0x9Og4RN7t7lVrN3vMSjy34/+3na0q7RIfWDAj0e0rCh0OL+P88lu3Rt21MQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.1.tgz", + "integrity": "sha512-4fL68JdrLV2nVW2AaWZBv3XEm3Ae3NZn/7qy2KGAt3dexAgSVT+Hc97JKSZnqezgMlv9x6KV0ZkZY7UO5cNLCg==", "cpu": [ "x64" ], @@ -7904,9 +7288,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.0.tgz", - "integrity": "sha512-HKoUGXz/TOVXKQ+67NhxyHv+aDSZf44QpWLa3I1lLvAwGq8x1k0T+e2HHSRvxWhfJrFxaaqre1+YyzQ99KixoA==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.1.tgz", + "integrity": "sha512-GhRuXlvRE+twf2ES+8REbeCb/zeikNqwD3+6S5y5/x+DYbAQUNl0HNBs4RQJqrechS4v4MruEr8ZtAin/hK5iw==", "cpu": [ "x64" ], @@ -7920,9 +7304,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.0.tgz", - "integrity": "sha512-GDwAqgHQm1mVoPppGsoq4WJwT3vhnz/2N62CzhvApFD1eJyTroob30FPpOZabN+FgCjhG+AgcZyOPIkR8dfD7g==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.1.tgz", + "integrity": "sha512-ZnWEyCM0G1Ex6JtsygvC3KUUrlDXqOihw8RicRuQAzw+c4f1D66YlPNNV3rkjVW90zXVsHwZYWbJh3v+oQFM9Q==", "cpu": [ "x64" ], @@ -7936,9 +7320,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.0.tgz", - "integrity": "sha512-0vYsP8aC4TvMlOQYozoksiaxjlvUcQrac+muDqj1Fxy6jh9l9CZJzj7zmh8JGfiV49cYLTorFLxg7593pGldwQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.1.tgz", + "integrity": "sha512-QZ6gXue0vVQY2Oon9WyLFCdSuYbXSoxaZrPuJ4c20j6ICedfsDilNPYfHLlMH7vGfU5DQR0czHLmJvH4Nzis/A==", "cpu": [ "arm64" ], @@ -7952,9 +7336,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.0.tgz", - "integrity": "sha512-p98u4rIgfh4gdpV00IqknBD5pC84LCub+4a3MO+zjqvU5MVXOc3hqR2UgT2jI2nh3h8s9EQxmOsVI3tyzv1iFg==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.1.tgz", + "integrity": "sha512-HzcJa1NcSWTAU0MJIxOho8JftNp9YALui3o+Ny7hCh0v5f90nprly1U3Sj1Ldj/CvKKdvvFsCRvDkpsEMp4DNw==", "cpu": [ "ia32" ], @@ -7968,9 +7352,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.0.tgz", - "integrity": "sha512-NgJnesu1RtWihtTtXGFMU5YSE6JyyHPMxCwBZK7a6/8d31GuSo9l0Ss7w1Jw5QnKUawG6UEehs883kcXf5fYwg==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.1.tgz", + "integrity": "sha512-0MBh53o6XtI6ctDnRMeQ+xoCN8kD2qI1rY1KgF/xdWQwoFeKou7puvDfV8/Wv4Ctx2rRpET/gGdz3YlNtNACSA==", "cpu": [ "x64" ], @@ -8143,6 +7527,14 @@ "safe-json-stringify": "~1" } }, + "node_modules/@expo/bunyan/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@expo/cli": { "version": "0.17.5", "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-0.17.5.tgz", @@ -10265,6 +9657,14 @@ "resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-4.1.1.tgz", "integrity": "sha512-e6eB7zN6UBSwGVwrbWVH+gdLnkW9WwHhmq2YDK1Sh30pzx1onRVGBvogTlUeWxwTa+L86NYdo4hFkh7O8ZjSnA==" }, + "node_modules/@expo/rudder-sdk-node/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@expo/sdk-runtime-versions": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@expo/sdk-runtime-versions/-/sdk-runtime-versions-1.0.0.tgz", @@ -10796,12 +10196,12 @@ } }, "node_modules/@floating-ui/dom": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.1.tgz", - "integrity": "sha512-iA8qE43/H5iGozC3W0YSnVSW42Vh522yyM1gj+BqRwVsTNOyr231PsXDaV04yT39PsO0QL2QpbI/M0ZaLUQgRQ==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", + "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", "dependencies": { - "@floating-ui/core": "^1.6.0", - "@floating-ui/utils": "^0.2.1" + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.0" } }, "node_modules/@floating-ui/react": { @@ -11020,9 +10420,9 @@ } }, "node_modules/@grpc/grpc-js": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.0.tgz", - "integrity": "sha512-tx+eoEsqkMkLCHR4OOplwNIaJ7SVZWzeVKzEMBz8VR+TbssgBYOP4a0P+KQiQ6LaTG4SGaIEu7YTS8xOmkOWLA==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.1.tgz", + "integrity": "sha512-55ONqFytZExfOIjF1RjXPcVmT/jJqFzbbDqxK9jmRV4nxiYWtL9hENSW1Jfx0SdZfrvoqd44YJ/GJTqfRrawSQ==", "dependencies": { "@grpc/proto-loader": "^0.7.8", "@types/node": ">=12.12.47" @@ -12096,9 +11496,9 @@ } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "engines": { "node": ">=6.0.0" } @@ -12179,9 +11579,9 @@ } }, "node_modules/@lhncbc/ucum-lhc": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@lhncbc/ucum-lhc/-/ucum-lhc-5.0.0.tgz", - "integrity": "sha512-r1LRxZG/JVZV6daWQZWyg8EiykLYndMuK39/TbUx71b09bjp09He2aNHfBSBMlxj3r0CKT8nnuFIRrznvSjJYw==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@lhncbc/ucum-lhc/-/ucum-lhc-5.0.3.tgz", + "integrity": "sha512-FlWyCOE6+Oc73zwRiFaiNSYQD8xpMYe9f4Qzy/tvnM3j5tXUwM3U5W/aXh/znJmHZr+lu3Hx697Sefp/3efOog==", "dev": true, "dependencies": { "coffeescript": "^2.7.0", @@ -12197,9 +11597,9 @@ } }, "node_modules/@mantine/core": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.5.2.tgz", - "integrity": "sha512-e58qTiLEp9qLxQ5JZlPNykJWBR+oi0q2J8JPKTjTJD+4UM58uh9oL4wuMEdUyBgiYGvDc/cVYF+rftMpuCt+KQ==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.5.3.tgz", + "integrity": "sha512-Wvv6DJXI+GX9mmKG5HITTh/24sCZ0RoYQHdTHh0tOfGnEy+RleyhA82UjnMsp0n2NjfCISBwbiKgfya6b2iaFw==", "dependencies": { "@floating-ui/react": "^0.24.8", "clsx": "2.0.0", @@ -12209,53 +11609,53 @@ "type-fest": "^3.13.1" }, "peerDependencies": { - "@mantine/hooks": "7.5.2", + "@mantine/hooks": "7.5.3", "react": "^18.2.0", "react-dom": "^18.2.0" } }, "node_modules/@mantine/dropzone": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/@mantine/dropzone/-/dropzone-7.5.2.tgz", - "integrity": "sha512-YjzaBEppLhb5F+lKpBSVeIGneCdisC+0nSertcjjo7QXPc++NR+WbVuTiPMYZoUuRbpJ2GRRTFZndmK1FvO+7w==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@mantine/dropzone/-/dropzone-7.5.3.tgz", + "integrity": "sha512-NbMnLN9B07WHkdrpxNTC0k0vzR6prONF+/awFPidphsXGAgRSZhiZXv06l9Wp9wVp6xPhVMTqGphh0i4Ey+AYg==", "dev": true, "dependencies": { "react-dropzone-esm": "15.0.1" }, "peerDependencies": { - "@mantine/core": "7.5.2", - "@mantine/hooks": "7.5.2", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", "react": "^18.2.0", "react-dom": "^18.2.0" } }, "node_modules/@mantine/hooks": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.5.2.tgz", - "integrity": "sha512-4POujH5Xx84VB/xfM6EttDq725dqqL2DntoeMjHz3Ua94aK5Y0VSc1IwlETxCuF7yNV5/w/Z8kgeVVa5cDgrPg==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.5.3.tgz", + "integrity": "sha512-mFI448mAs12v8FrgSVhytqlhTVrEjIfd/PqPEfwJu5YcZIq4YZdqpzJIUbANnRrFSvmoQpDb1PssdKx7Ds35hw==", "peerDependencies": { "react": "^18.2.0" } }, "node_modules/@mantine/notifications": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-7.5.2.tgz", - "integrity": "sha512-wGRDeOG2NGng202k/vZPfkRJMHtflvIORYe4cJaQAd9EhP646xr/ji8zzdXeXvUO5PrKlBpu+K+HSF1bQQY4og==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-7.5.3.tgz", + "integrity": "sha512-08mWoGBfc8sGDTRthBg/HYPD8dRHyugZpeUH1U7RjWQmYD4ktdkT8bdBocStTSJkCQIvtP7OPJ1MiKln1idt5w==", "dependencies": { - "@mantine/store": "7.5.2", + "@mantine/store": "7.5.3", "react-transition-group": "4.4.5" }, "peerDependencies": { - "@mantine/core": "7.5.2", - "@mantine/hooks": "7.5.2", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", "react": "^18.2.0", "react-dom": "^18.2.0" } }, "node_modules/@mantine/store": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/@mantine/store/-/store-7.5.2.tgz", - "integrity": "sha512-au53HLLH/LfHLOR2Zb00TgG11xAXZNJb4jqsEhAr90axCIw6mlBDIAhbb+Yu6OwDaV7TscE/sonkUstmRjLh/w==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@mantine/store/-/store-7.5.3.tgz", + "integrity": "sha512-jLTIaChJr9rWtzSRp2HQGHfuoFcr3ylD0JW/vsE5gpFvhoZFfkrxx5QsM1kn5wV34rZo0nQNMIJ8hvBVvfJtLQ==", "peerDependencies": { "react": "^18.2.0" } @@ -12379,9 +11779,9 @@ "optional": true }, "node_modules/@mdx-js/mdx": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.0.0.tgz", - "integrity": "sha512-Icm0TBKBLYqroYbNW3BPnzMGn+7mwpQOK310aZ7+fkCtiU3aqv2cdcX+nd0Ydo3wI5Rx8bX2Z2QmGb/XcAClCw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.0.1.tgz", + "integrity": "sha512-eIQ4QTrOWyL3LWEe/bu6Taqzq2HQvHcyTMaOrI95P2/LmJE7AsfPfgJGuFLPVqBUE1BC1rik3VIhU+s9u72arA==", "dev": true, "dependencies": { "@types/estree": "^1.0.0", @@ -12423,9 +11823,9 @@ } }, "node_modules/@mdx-js/react": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.0.0.tgz", - "integrity": "sha512-nDctevR9KyYFyV+m+/+S4cpzCWHqj+iHDHq3QrsWezcC+B17uZdIWgCguESUkwFhM3n/56KxWVE3V6EokrmONQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.0.1.tgz", + "integrity": "sha512-9ZrPIU4MGf6et1m1ov3zKf+q9+deetI51zprKB1D/z3NOb+rUxxtEl3mCjW5wTGh6VhRdwPueh1oRzi6ezkA8A==", "dev": true, "dependencies": { "@types/mdx": "^2.0.0" @@ -12524,15 +11924,15 @@ "link": true }, "node_modules/@microsoft/api-documenter": { - "version": "7.23.23", - "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.23.23.tgz", - "integrity": "sha512-77upYNmm6h9+8hdNWL7R1+vW1QaExkwKuOuSWR2v3Tdk2JHCVf+s341jybgYEnxRhIQIIxtLeiwQ/xzMFOizwQ==", + "version": "7.23.24", + "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.23.24.tgz", + "integrity": "sha512-mig/L2g3geTN+rkJynyu2yFBvS6s/v5Q5OqAjwzYE/tNiBDHj+81qKTB/3ib94FNU0g5U4l+DsVkDtHwoN37Hw==", "dev": true, "dependencies": { - "@microsoft/api-extractor-model": "7.28.9", + "@microsoft/api-extractor-model": "7.28.10", "@microsoft/tsdoc": "0.14.2", - "@rushstack/node-core-library": "3.66.0", - "@rushstack/ts-command-line": "4.17.1", + "@rushstack/node-core-library": "3.66.1", + "@rushstack/ts-command-line": "4.17.2", "colors": "~1.2.1", "js-yaml": "~3.13.1", "resolve": "~1.22.1" @@ -12542,17 +11942,17 @@ } }, "node_modules/@microsoft/api-extractor": { - "version": "7.40.1", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.40.1.tgz", - "integrity": "sha512-xHn2Zkh6s5JIjP94SG6VtIlIeRJcASgfZpDKV+bgoddMt1X4ujSZFOz7uEGNYNO7mEtdVOvpNKBpC4CDytD8KQ==", + "version": "7.40.2", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.40.2.tgz", + "integrity": "sha512-BCK+a9r0Nl/fd9fGhotaXJBt9IHBtuvEf/a8YS2UXwcqI4lnGcrvT3pAt3rrziS/dc5+0W/7TDZorULSj6N1Aw==", "dev": true, "dependencies": { - "@microsoft/api-extractor-model": "7.28.9", + "@microsoft/api-extractor-model": "7.28.10", "@microsoft/tsdoc": "0.14.2", "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "3.66.0", - "@rushstack/rig-package": "0.5.1", - "@rushstack/ts-command-line": "4.17.1", + "@rushstack/node-core-library": "3.66.1", + "@rushstack/rig-package": "0.5.2", + "@rushstack/ts-command-line": "4.17.2", "colors": "~1.2.1", "lodash": "~4.17.15", "resolve": "~1.22.1", @@ -12565,14 +11965,14 @@ } }, "node_modules/@microsoft/api-extractor-model": { - "version": "7.28.9", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.28.9.tgz", - "integrity": "sha512-lM77dV+VO46MGp5lu4stUBnO3jyr+CrDzU+DtapcOQEZUqJxPYUoK5zjeD+gRZ9ckgGMZC94ch6FBkpmsjwQgw==", + "version": "7.28.10", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.28.10.tgz", + "integrity": "sha512-5ThitnV04Jbo0337Q0/VOjeGdx0OiduGgx4aGzfD6gsTSppYCPQjm2eIygRfc7w+XIP33osAZsWHAOo419PGQg==", "dev": true, "dependencies": { "@microsoft/tsdoc": "0.14.2", "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "3.66.0" + "@rushstack/node-core-library": "3.66.1" } }, "node_modules/@microsoft/api-extractor/node_modules/lru-cache": { @@ -13794,6 +13194,14 @@ "@opentelemetry/api": "^1.3.0" } }, + "node_modules/@opentelemetry/instrumentation-connect/node_modules/@types/connect": { + "version": "3.4.36", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz", + "integrity": "sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@opentelemetry/instrumentation-cucumber": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cucumber/-/instrumentation-cucumber-0.3.0.tgz", @@ -16425,9 +15833,9 @@ } }, "node_modules/@react-native-community/cli-doctor/node_modules/ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", + "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==" }, "node_modules/@react-native-community/cli-doctor/node_modules/lru-cache": { "version": "6.0.0", @@ -16539,9 +15947,9 @@ } }, "node_modules/@react-native-community/cli-hermes/node_modules/ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", + "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==" }, "node_modules/@react-native-community/cli-hermes/node_modules/supports-color": { "version": "7.2.0", @@ -17650,9 +17058,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.0.tgz", - "integrity": "sha512-HOil5aFtme37dVQTB6M34G95kPM3MMuqSmIRVCC52eKV+Y/tGSqw9P3rWhlAx6A+mz+MoX+XxsGsNJbaI5qCgQ==", + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.1.tgz", + "integrity": "sha512-zcU0gM3z+3iqj8UX45AmWY810l3oUmXM7uH4dt5xtzvMhRtYVhKGOmgOd1877dOPPepfCjUv57w+syamWIYe7w==", "dev": true, "engines": { "node": ">=14.0.0" @@ -17738,9 +17146,9 @@ "dev": true }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.10.0.tgz", - "integrity": "sha512-/MeDQmcD96nVoRumKUljsYOLqfv1YFJps+0pTrb2Z9Nl/w5qNUysMaWQsrd1mvAlNT4yza1iVyIu4Q4AgF6V3A==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz", + "integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==", "cpu": [ "arm" ], @@ -17751,9 +17159,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.10.0.tgz", - "integrity": "sha512-lvu0jK97mZDJdpZKDnZI93I0Om8lSDaiPx3OiCk0RXn3E8CMPJNS/wxjAvSJJzhhZpfjXsjLWL8LnS6qET4VNQ==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz", + "integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==", "cpu": [ "arm64" ], @@ -17764,9 +17172,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.10.0.tgz", - "integrity": "sha512-uFpayx8I8tyOvDkD7X6n0PriDRWxcqEjqgtlxnUA/G9oS93ur9aZ8c8BEpzFmsed1TH5WZNG5IONB8IiW90TQg==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz", + "integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==", "cpu": [ "arm64" ], @@ -17777,9 +17185,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.10.0.tgz", - "integrity": "sha512-nIdCX03qFKoR/MwQegQBK+qZoSpO3LESurVAC6s6jazLA1Mpmgzo3Nj3H1vydXp/JM29bkCiuF7tDuToj4+U9Q==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz", + "integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==", "cpu": [ "x64" ], @@ -17790,9 +17198,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.10.0.tgz", - "integrity": "sha512-Fz7a+y5sYhYZMQFRkOyCs4PLhICAnxRX/GnWYReaAoruUzuRtcf+Qnw+T0CoAWbHCuz2gBUwmWnUgQ67fb3FYw==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz", + "integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==", "cpu": [ "arm" ], @@ -17803,9 +17211,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.10.0.tgz", - "integrity": "sha512-yPtF9jIix88orwfTi0lJiqINnlWo6p93MtZEoaehZnmCzEmLL0eqjA3eGVeyQhMtxdV+Mlsgfwhh0+M/k1/V7Q==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz", + "integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==", "cpu": [ "arm64" ], @@ -17816,9 +17224,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.10.0.tgz", - "integrity": "sha512-9GW9yA30ib+vfFiwjX+N7PnjTnCMiUffhWj4vkG4ukYv1kJ4T9gHNg8zw+ChsOccM27G9yXrEtMScf1LaCuoWQ==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz", + "integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==", "cpu": [ "arm64" ], @@ -17829,9 +17237,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.10.0.tgz", - "integrity": "sha512-X1ES+V4bMq2ws5fF4zHornxebNxMXye0ZZjUrzOrf7UMx1d6wMQtfcchZ8SqUnQPPHdOyOLW6fTcUiFgHFadRA==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz", + "integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==", "cpu": [ "riscv64" ], @@ -17842,9 +17250,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.10.0.tgz", - "integrity": "sha512-w/5OpT2EnI/Xvypw4FIhV34jmNqU5PZjZue2l2Y3ty1Ootm3SqhI+AmfhlUYGBTd9JnpneZCDnt3uNOiOBkMyw==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz", + "integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==", "cpu": [ "x64" ], @@ -17855,9 +17263,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.10.0.tgz", - "integrity": "sha512-q/meftEe3QlwQiGYxD9rWwB21DoKQ9Q8wA40of/of6yGHhZuGfZO0c3WYkN9dNlopHlNT3mf5BPsUSxoPuVQaw==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz", + "integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==", "cpu": [ "x64" ], @@ -17868,9 +17276,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.10.0.tgz", - "integrity": "sha512-NrR6667wlUfP0BHaEIKgYM/2va+Oj+RjZSASbBMnszM9k+1AmliRjHc3lJIiOehtSSjqYiO7R6KLNrWOX+YNSQ==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz", + "integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==", "cpu": [ "arm64" ], @@ -17881,9 +17289,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.10.0.tgz", - "integrity": "sha512-FV0Tpt84LPYDduIDcXvEC7HKtyXxdvhdAOvOeWMWbQNulxViH2O07QXkT/FffX4FqEI02jEbCJbr+YcuKdyyMg==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz", + "integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==", "cpu": [ "ia32" ], @@ -17894,9 +17302,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.10.0.tgz", - "integrity": "sha512-OZoJd+o5TaTSQeFFQ6WjFCiltiYVjIdsXxwu/XZ8qRpsvMQr4UsVrE5UyT9RIvsnuF47DqkJKhhVZ2Q9YW9IpQ==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz", + "integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==", "cpu": [ "x64" ], @@ -17913,9 +17321,9 @@ "dev": true }, "node_modules/@rushstack/node-core-library": { - "version": "3.66.0", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.66.0.tgz", - "integrity": "sha512-nXyddNe3T9Ph14TrIfjtLZ+GDzC7HL/wF+ZKC18qmRVtz2xXLd1ZzreVgiAgGDwn8ZUWZ/7q//gQJk96iWjSrg==", + "version": "3.66.1", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.66.1.tgz", + "integrity": "sha512-ker69cVKAoar7MMtDFZC4CzcDxjwqIhFzqEnYI5NRN/8M3om6saWCVx/A7vL2t/jFCJsnzQplRDqA7c78pytng==", "dev": true, "dependencies": { "colors": "~1.2.1", @@ -17969,9 +17377,9 @@ "dev": true }, "node_modules/@rushstack/rig-package": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.5.1.tgz", - "integrity": "sha512-pXRYSe29TjRw7rqxD4WS3HN/sRSbfr+tJs4a9uuaSIBAITbUggygdhuG0VrO0EO+QqH91GhYMN4S6KRtOEmGVA==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.5.2.tgz", + "integrity": "sha512-mUDecIJeH3yYGZs2a48k+pbhM6JYwWlgjs2Ca5f2n1G2/kgdgP9D/07oglEGf6mRyXEnazhEENeYTSNDRCwdqA==", "dev": true, "dependencies": { "resolve": "~1.22.1", @@ -17979,9 +17387,9 @@ } }, "node_modules/@rushstack/ts-command-line": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.17.1.tgz", - "integrity": "sha512-2jweO1O57BYP5qdBGl6apJLB+aRIn5ccIRTPDyULh0KMwVzFqWtw6IZWt1qtUoZD/pD2RNkIOosH6Cq45rIYeg==", + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.17.2.tgz", + "integrity": "sha512-QS2S2nJo9zXq/+9Dk10LmvIFugMizI9IeQUH4jnhIcoaeqYlsv2fK830U+/gMKpI5vomXz19XMXfkUfZzO4R3A==", "dev": true, "dependencies": { "@types/argparse": "1.0.38", @@ -18427,6 +17835,14 @@ "node": ">=14.0.0" } }, + "node_modules/@smithy/middleware-retry/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@smithy/middleware-serde": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-2.1.1.tgz", @@ -18797,12 +18213,12 @@ } }, "node_modules/@storybook/addon-actions": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-7.6.14.tgz", - "integrity": "sha512-hFVB/ejxBdE6J3wOEPSx6aFB51PqDfQ/YR4ik5GCGJb3cmUX7d/FY8zH0TKJLXcG/Hw3XoxNiEo5AaMVxtGVGA==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-7.6.16.tgz", + "integrity": "sha512-wCpZljLXnu08TZzp+qL5AXousfUBzY6TgHVwn4yoZkMhPg3WLxZTceKYnc+XAxoMmdTrDjwanEF7v/uQ9eu64Q==", "dev": true, "dependencies": { - "@storybook/core-events": "7.6.14", + "@storybook/core-events": "7.6.16", "@storybook/global": "^5.0.0", "@types/uuid": "^9.0.1", "dequal": "^2.0.2", @@ -18814,23 +18230,10 @@ "url": "https://opencollective.com/storybook" } }, - "node_modules/@storybook/addon-actions/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@storybook/addon-backgrounds": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-7.6.14.tgz", - "integrity": "sha512-R6OblK71iKIwpxTZQhuOpbktIT5pNrfMNe4/lkIP2F6Dv9HgHwvg95Bpt0ebHKlRvD7KNwj1whKjJh1fO3yLgQ==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-7.6.16.tgz", + "integrity": "sha512-q9985hjtoX3ytvReV2YC4UY0FVASXFq2fW6RNOrrivw81UbW2SWxVG01vh7ZXjMrWbQ6r3yC05X9vVAmCa7TdQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0", @@ -18843,12 +18246,12 @@ } }, "node_modules/@storybook/addon-controls": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-7.6.14.tgz", - "integrity": "sha512-KJRPdzbXjitqCixMzMjkcRYJGIts9wrx2Qk7NCSXCbE0LDdT+U7//25luLp5DrRiPdqIVEQjNcLF10frljaA9g==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-7.6.16.tgz", + "integrity": "sha512-WeIuwyGxaMMClWSHhSH0ibwPSarEFtxE6SPQxCTmGIeD11bn5vQ6UUrmm9A2xbFqHOJBoB60TJhw69alnI0AHA==", "dev": true, "dependencies": { - "@storybook/blocks": "7.6.14", + "@storybook/blocks": "7.6.16", "lodash": "^4.17.21", "ts-dedent": "^2.0.0" }, @@ -18858,26 +18261,26 @@ } }, "node_modules/@storybook/addon-docs": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-7.6.14.tgz", - "integrity": "sha512-fH3voEcHuJmMXNIT6Lxs5ve+dM6P74gwhdyMj21WIp8DnYM99RrmjvT1k/3+tGknL/7oGM+4Y2DLyy2KYFc6HQ==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-7.6.16.tgz", + "integrity": "sha512-X4WLAwwxGq9ki49FtERT5VHstGeZYca+l+8lxVXW6NQYuQ1xCeSy5puwknDv5p5u4thIVW2Fa4Uvma7wCfddtg==", "dev": true, "dependencies": { "@jest/transform": "^29.3.1", "@mdx-js/react": "^2.1.5", - "@storybook/blocks": "7.6.14", - "@storybook/client-logger": "7.6.14", - "@storybook/components": "7.6.14", - "@storybook/csf-plugin": "7.6.14", - "@storybook/csf-tools": "7.6.14", + "@storybook/blocks": "7.6.16", + "@storybook/client-logger": "7.6.16", + "@storybook/components": "7.6.16", + "@storybook/csf-plugin": "7.6.16", + "@storybook/csf-tools": "7.6.16", "@storybook/global": "^5.0.0", "@storybook/mdx2-csf": "^1.0.0", - "@storybook/node-logger": "7.6.14", - "@storybook/postinstall": "7.6.14", - "@storybook/preview-api": "7.6.14", - "@storybook/react-dom-shim": "7.6.14", - "@storybook/theming": "7.6.14", - "@storybook/types": "7.6.14", + "@storybook/node-logger": "7.6.16", + "@storybook/postinstall": "7.6.16", + "@storybook/preview-api": "7.6.16", + "@storybook/react-dom-shim": "7.6.16", + "@storybook/theming": "7.6.16", + "@storybook/types": "7.6.16", "fs-extra": "^11.1.0", "remark-external-links": "^8.0.0", "remark-slug": "^6.0.0", @@ -18945,24 +18348,24 @@ } }, "node_modules/@storybook/addon-essentials": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-7.6.14.tgz", - "integrity": "sha512-1CcpLvzmvXyRhxbc2FgVbchpu7EMEeAjNY2lQ8ejn4cwLuIeWvYI61Cq4swiEmcEOEzi9Uvrq9q1bua9N1fPqw==", - "dev": true, - "dependencies": { - "@storybook/addon-actions": "7.6.14", - "@storybook/addon-backgrounds": "7.6.14", - "@storybook/addon-controls": "7.6.14", - "@storybook/addon-docs": "7.6.14", - "@storybook/addon-highlight": "7.6.14", - "@storybook/addon-measure": "7.6.14", - "@storybook/addon-outline": "7.6.14", - "@storybook/addon-toolbars": "7.6.14", - "@storybook/addon-viewport": "7.6.14", - "@storybook/core-common": "7.6.14", - "@storybook/manager-api": "7.6.14", - "@storybook/node-logger": "7.6.14", - "@storybook/preview-api": "7.6.14", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-7.6.16.tgz", + "integrity": "sha512-LTrsud7yphxA7dpbk8TvIsHXqk5Wkq3JAwby3yQDEOFakpgNeXj8b6rlr9CHJja2p13pB4LuXokLk8t+qJGnQQ==", + "dev": true, + "dependencies": { + "@storybook/addon-actions": "7.6.16", + "@storybook/addon-backgrounds": "7.6.16", + "@storybook/addon-controls": "7.6.16", + "@storybook/addon-docs": "7.6.16", + "@storybook/addon-highlight": "7.6.16", + "@storybook/addon-measure": "7.6.16", + "@storybook/addon-outline": "7.6.16", + "@storybook/addon-toolbars": "7.6.16", + "@storybook/addon-viewport": "7.6.16", + "@storybook/core-common": "7.6.16", + "@storybook/manager-api": "7.6.16", + "@storybook/node-logger": "7.6.16", + "@storybook/preview-api": "7.6.16", "ts-dedent": "^2.0.0" }, "funding": { @@ -18975,9 +18378,9 @@ } }, "node_modules/@storybook/addon-highlight": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-7.6.14.tgz", - "integrity": "sha512-VQTgLm6jPKN7DOhrx0mY5yrhQxOiidQt4yoazJTgzn+aV7zBFKn+GtF1W38QrnFtq5Mr8VJsEByEdtVCqMcmyw==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-7.6.16.tgz", + "integrity": "sha512-DJtUBiButx6cz55eaRe5JFVBORVtp3Htr9PnxWVGEy4Ki5aoYCYWxMcPOuXVFvtWgBmh6d3HO0pEd888qPr60g==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -18988,9 +18391,9 @@ } }, "node_modules/@storybook/addon-links": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-7.6.14.tgz", - "integrity": "sha512-xzDWQEzntia9ArFQC95TEw/Tqp/cNFq0SSuQQ6d9/ryQczuSdRGFHRmEd99/92ufNgCGPaRZOB7sweiKG0bkzA==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-7.6.16.tgz", + "integrity": "sha512-+582ePJxvweYZB5s133Uou6YRzZtnXGMRtKMJVovy/P5cWtq8FS5wzyMJPeK4z6ioR6BQJQVF2NV5lfrjoxpKQ==", "dev": true, "dependencies": { "@storybook/csf": "^0.1.2", @@ -19011,9 +18414,9 @@ } }, "node_modules/@storybook/addon-measure": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-7.6.14.tgz", - "integrity": "sha512-bRy3SEv4uf1csDe5H8Lg3wUDg1uMZo6/j2FwNjvUmW+vcasj3VsqPKQjT6KO+LjCXsQ1pIAHu1HcUh2v/Qoitw==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-7.6.16.tgz", + "integrity": "sha512-lQw7WXEeLuvDe3bfi7699WnHMryLIRnoT/w7oHqvS19UHp2HR0TKqYiPPppI6Yy4RoWHx+qFhKZJlajFyKDGfg==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0", @@ -19025,9 +18428,9 @@ } }, "node_modules/@storybook/addon-outline": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-7.6.14.tgz", - "integrity": "sha512-RH3arZYMBBxoqif4pnKN8m8Vt8setpeh0kz6oA+Ilhf/Z8Wz5jWiYDvTL5WW3+E+XGLrIFwH87wmJLN0egKqtA==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-7.6.16.tgz", + "integrity": "sha512-bG9KN10ANLUDIsm4e6RXRsCZ++b8pyfYTyu0MlSNXf6KdYcuDvdTY59gj6RIeVGKqWeX5yYCYUm2oPLtkms1NQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0", @@ -19039,12 +18442,12 @@ } }, "node_modules/@storybook/addon-storysource": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/addon-storysource/-/addon-storysource-7.6.14.tgz", - "integrity": "sha512-hDJ8MHokSH4CiUn6a5HoPUjTmDfkYiCqKkRgMbVkNIHaLNttfqqSCm8FLMZT/XEOw4RJeh8g4CqEu2NTCf63CA==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/addon-storysource/-/addon-storysource-7.6.16.tgz", + "integrity": "sha512-F/t0Y8bDF0C5m0h9Tui0ODCVAhp4Qicq6wonsZovEoSdhUE+Fq+dBtQ9K+zUYpQYf2alnvFexhy6xdAwBXFZAw==", "dev": true, "dependencies": { - "@storybook/source-loader": "7.6.14", + "@storybook/source-loader": "7.6.16", "estraverse": "^5.2.0", "tiny-invariant": "^1.3.1" }, @@ -19054,9 +18457,9 @@ } }, "node_modules/@storybook/addon-toolbars": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-7.6.14.tgz", - "integrity": "sha512-/Zea9XgmxJp/5pQ+PKw+FGj2s2POIur/9uCUmLBWPDAMIW+kugOYZ/i8krrcHDPJ7nG2rtUJbeSliod9h2tpfw==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-7.6.16.tgz", + "integrity": "sha512-6wSNXe50auEVwHCcupYPrJkpzQFugumEBfgYuQ6ICW9k2xJtGtahy7TyM9sZbYgnDkoTm2ba7UhML6Noy3JuUg==", "dev": true, "funding": { "type": "opencollective", @@ -19064,9 +18467,9 @@ } }, "node_modules/@storybook/addon-viewport": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-7.6.14.tgz", - "integrity": "sha512-7GbJyXFP3QCZezUQ+75VdjBpyXWutdFY0YMM/3JTjU+Khutbph3RurMTi4dRiBndAIPXlReNm1AnnYX5w+jd9w==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-7.6.16.tgz", + "integrity": "sha512-WkvixYHncLXpAeEnktjfYIffJ3b6poymB+wDbHKK/tg7m3N8llLlys64nvyeb7DbZ/+1yJls3K1DVbk1AIEHrQ==", "dev": true, "dependencies": { "memoizerific": "^1.11.3" @@ -19077,22 +18480,22 @@ } }, "node_modules/@storybook/blocks": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-7.6.14.tgz", - "integrity": "sha512-DZOSEWSNptAhaeNiOG0BqidJxqi/KaAZ2ZnlygpswDDT9vOCGoc7edZEgrq/i83M55KZFD4IXVLYFdfpjRcirQ==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-7.6.16.tgz", + "integrity": "sha512-rWG9a7BbK0qYvge1oJTIpAbcQ4eOSxetKqgeZc7jxQGeJw0Xvq7C/CmkBY4ZrdP8nj7M7R1Yw49u6OV4aXlyOg==", "dev": true, "dependencies": { - "@storybook/channels": "7.6.14", - "@storybook/client-logger": "7.6.14", - "@storybook/components": "7.6.14", - "@storybook/core-events": "7.6.14", + "@storybook/channels": "7.6.16", + "@storybook/client-logger": "7.6.16", + "@storybook/components": "7.6.16", + "@storybook/core-events": "7.6.16", "@storybook/csf": "^0.1.2", - "@storybook/docs-tools": "7.6.14", + "@storybook/docs-tools": "7.6.16", "@storybook/global": "^5.0.0", - "@storybook/manager-api": "7.6.14", - "@storybook/preview-api": "7.6.14", - "@storybook/theming": "7.6.14", - "@storybook/types": "7.6.14", + "@storybook/manager-api": "7.6.16", + "@storybook/preview-api": "7.6.16", + "@storybook/theming": "7.6.16", + "@storybook/types": "7.6.16", "@types/lodash": "^4.14.167", "color-convert": "^2.0.1", "dequal": "^2.0.2", @@ -19116,15 +18519,15 @@ } }, "node_modules/@storybook/builder-manager": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/builder-manager/-/builder-manager-7.6.14.tgz", - "integrity": "sha512-pID/g2Bnr3tjmkh8c+O6TZei3f1TWHW/UWi/skNQ3wGJ+9dqJIK2vQY5SwnXBWkmJdUqGVXaW5BvzR8jjfpTxQ==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/builder-manager/-/builder-manager-7.6.16.tgz", + "integrity": "sha512-QTmvjmk49tpPe5IFM3SwHvRb1P6G0PTip4mCO7ab/zKiWaXlg9QZF5su+2e3KSil4ATssr3ybUlKlkqSubaCyQ==", "dev": true, "dependencies": { "@fal-works/esbuild-plugin-global-externals": "^2.1.2", - "@storybook/core-common": "7.6.14", - "@storybook/manager": "7.6.14", - "@storybook/node-logger": "7.6.14", + "@storybook/core-common": "7.6.16", + "@storybook/manager": "7.6.16", + "@storybook/node-logger": "7.6.16", "@types/ejs": "^3.1.1", "@types/find-cache-dir": "^3.2.1", "@yarnpkg/esbuild-plugin-pnp": "^3.0.0-rc.10", @@ -19179,19 +18582,19 @@ } }, "node_modules/@storybook/builder-vite": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-7.6.14.tgz", - "integrity": "sha512-GhIuK0Xu+HZK4K3NW0PlPpY3wQQ6Ay8WQp9Ea8UZn+ixop4wAV+dLFEJ0B8fXrpSNqsmjUim7rIfMePzXkfucQ==", - "dev": true, - "dependencies": { - "@storybook/channels": "7.6.14", - "@storybook/client-logger": "7.6.14", - "@storybook/core-common": "7.6.14", - "@storybook/csf-plugin": "7.6.14", - "@storybook/node-logger": "7.6.14", - "@storybook/preview": "7.6.14", - "@storybook/preview-api": "7.6.14", - "@storybook/types": "7.6.14", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-7.6.16.tgz", + "integrity": "sha512-i0nL6ajWpcThnTP4ndUXdIKdOatYC6vWE4HxMjuRjfEgKBXn4pJbDBbQZ+L5jFau7aLGOTXiSQ69ONEhSjhllg==", + "dev": true, + "dependencies": { + "@storybook/channels": "7.6.16", + "@storybook/client-logger": "7.6.16", + "@storybook/core-common": "7.6.16", + "@storybook/csf-plugin": "7.6.16", + "@storybook/node-logger": "7.6.16", + "@storybook/preview": "7.6.16", + "@storybook/preview-api": "7.6.16", + "@storybook/types": "7.6.16", "@types/find-cache-dir": "^3.2.1", "browser-assert": "^1.2.1", "es-module-lexer": "^0.9.3", @@ -19259,13 +18662,13 @@ } }, "node_modules/@storybook/channels": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.14.tgz", - "integrity": "sha512-tyrnnXTh7Ca6HbtzYtZGZmbUkC+eYPdot41+YDERMxXCnejd18BnsH/pyGW66GwgY079Q7uhdDFyM63ynZrt/A==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.16.tgz", + "integrity": "sha512-LKB0t4OGISez1O4TRJ/CDPxlb2wAW7gg8YRL91VVUHeffVyr4bnpklvMbLbuEcYrysM82Q2UMB9ipQdyK6Issg==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.14", - "@storybook/core-events": "7.6.14", + "@storybook/client-logger": "7.6.16", + "@storybook/core-events": "7.6.16", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -19277,23 +18680,23 @@ } }, "node_modules/@storybook/cli": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/cli/-/cli-7.6.14.tgz", - "integrity": "sha512-2xqcGRPtj/OE+9ro92C5MFCT8VHdMCDDuZZRnmgPi83iqSZtYbO8xHZwz78j4TvmouHstOV1SedeWv0IsFIxLw==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/cli/-/cli-7.6.16.tgz", + "integrity": "sha512-bFEiAXv69ZLqFnxAMCEBTxZqLnPG0GAEpGqwpPbt2lk6lLtro8g+//OR9RiztZt0YFHpp0YK5WCy6Xq0gwXcPw==", "dev": true, "dependencies": { "@babel/core": "^7.23.2", "@babel/preset-env": "^7.23.2", "@babel/types": "^7.23.0", "@ndelangen/get-tarball": "^3.0.7", - "@storybook/codemod": "7.6.14", - "@storybook/core-common": "7.6.14", - "@storybook/core-events": "7.6.14", - "@storybook/core-server": "7.6.14", - "@storybook/csf-tools": "7.6.14", - "@storybook/node-logger": "7.6.14", - "@storybook/telemetry": "7.6.14", - "@storybook/types": "7.6.14", + "@storybook/codemod": "7.6.16", + "@storybook/core-common": "7.6.16", + "@storybook/core-events": "7.6.16", + "@storybook/core-server": "7.6.16", + "@storybook/csf-tools": "7.6.16", + "@storybook/node-logger": "7.6.16", + "@storybook/telemetry": "7.6.16", + "@storybook/types": "7.6.16", "@types/semver": "^7.3.4", "@yarnpkg/fslib": "2.10.3", "@yarnpkg/libzip": "2.3.0", @@ -19486,9 +18889,9 @@ "dev": true }, "node_modules/@storybook/client-logger": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.14.tgz", - "integrity": "sha512-rHa2hLU+80BN5E58Shf1g09YS6QEEOk5hwMuJ4WJfAypMDYPjnIsOYUboHClkCA9TDCH/iVhyRSPy83NWN2MZg==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.16.tgz", + "integrity": "sha512-Vquhmgk/SO0VeAkojcA1juuicBHoTST+f4XwBvyUNiebOSOdGIkxHVxpDFXu2kS0aKflFBEutX2IgoysDup+fQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -19499,18 +18902,18 @@ } }, "node_modules/@storybook/codemod": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-7.6.14.tgz", - "integrity": "sha512-Sq/Q12KmvzaSUtmbtD26cEEGVmZLUA+iiNHbl0n65MMka6QBGG/VgSPvSgu+GEpKowbVoqfMpH4Ic16A6XsNFg==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-7.6.16.tgz", + "integrity": "sha512-RlL2I7UV+ef3j+6NaFa1Y6j/hU9KDKssync1GfKypUKlFAP76ozfpRWdDVEkc/29JruEEkbvMiUxQdP7CE3PMQ==", "dev": true, "dependencies": { "@babel/core": "^7.23.2", "@babel/preset-env": "^7.23.2", "@babel/types": "^7.23.0", "@storybook/csf": "^0.1.2", - "@storybook/csf-tools": "7.6.14", - "@storybook/node-logger": "7.6.14", - "@storybook/types": "7.6.14", + "@storybook/csf-tools": "7.6.16", + "@storybook/node-logger": "7.6.16", + "@storybook/types": "7.6.16", "@types/cross-spawn": "^6.0.2", "cross-spawn": "^7.0.3", "globby": "^11.0.2", @@ -19540,18 +18943,18 @@ } }, "node_modules/@storybook/components": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/components/-/components-7.6.14.tgz", - "integrity": "sha512-kukLj6B2xaIbKAq8E2WUcU0KZ+keuvIo0VcfrtSNHFbNvrNzHshajPC1dTO4NbgI3ey2SmD0rp71eh06TUQ9ng==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/components/-/components-7.6.16.tgz", + "integrity": "sha512-5KZQqxFiVEGM485ceF/7PmiNEkHgouEa8ZUJvDGrW9Ap5MfN0xqAuyTTveHvZzGrKp0YlOcOnpqwu/cSk0HQKA==", "dev": true, "dependencies": { "@radix-ui/react-select": "^1.2.2", "@radix-ui/react-toolbar": "^1.0.4", - "@storybook/client-logger": "7.6.14", + "@storybook/client-logger": "7.6.16", "@storybook/csf": "^0.1.2", "@storybook/global": "^5.0.0", - "@storybook/theming": "7.6.14", - "@storybook/types": "7.6.14", + "@storybook/theming": "7.6.16", + "@storybook/types": "7.6.16", "memoizerific": "^1.11.3", "use-resize-observer": "^9.1.0", "util-deprecate": "^1.0.2" @@ -19566,13 +18969,13 @@ } }, "node_modules/@storybook/core-client": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/core-client/-/core-client-7.6.14.tgz", - "integrity": "sha512-2q+R6olHLS5GJBTZNdKscTKJ8YwKOatKx6QjktFTfxfLRfBfOGSepignYy8JnEGuU4iTOwBekmUDm5dWAUjnQg==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/core-client/-/core-client-7.6.16.tgz", + "integrity": "sha512-ogVwvjpNPrcv8Lk9oSa38b7X4NNgYIgVnCjvvr9FCmhh4xAuA4XNUyB+vyAdK4JOT0/CoMKRpTp+VL5e2+BZbg==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.14", - "@storybook/preview-api": "7.6.14" + "@storybook/client-logger": "7.6.16", + "@storybook/preview-api": "7.6.16" }, "funding": { "type": "opencollective", @@ -19580,14 +18983,14 @@ } }, "node_modules/@storybook/core-common": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.14.tgz", - "integrity": "sha512-0CIfwdjY5+OO6B+WxeCx3fZou1wk50RU9hFOMGwJ2yj/5ilV06xVHt0HNrA2x37zaK7r370PjOuny0Xudba03g==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.16.tgz", + "integrity": "sha512-Xn3Fbo4k9RRKgYzOBx9CeJFpWgS9gkcdo3J9XMMzmUqdZ+MUGT74kl2sMmzSypcH5aI1AUl5vZIKvLwloliejw==", "dev": true, "dependencies": { - "@storybook/core-events": "7.6.14", - "@storybook/node-logger": "7.6.14", - "@storybook/types": "7.6.14", + "@storybook/core-events": "7.6.16", + "@storybook/node-logger": "7.6.16", + "@storybook/types": "7.6.16", "@types/find-cache-dir": "^3.2.1", "@types/node": "^18.0.0", "@types/node-fetch": "^2.6.4", @@ -19615,9 +19018,9 @@ } }, "node_modules/@storybook/core-common/node_modules/@types/node": { - "version": "18.19.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.15.tgz", - "integrity": "sha512-AMZ2UWx+woHNfM11PyAEQmfSxi05jm9OlkxczuHeEqmvwPkYj6MWv44gbzDPefYOLysTOFyI3ziiy2ONmUZfpA==", + "version": "18.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.17.tgz", + "integrity": "sha512-SzyGKgwPzuWp2SHhlpXKzCX0pIOfcI4V2eF37nNBJOhwlegQ83omtVQ1XxZpDE06V/d6AQvfQdPfnw0tRC//Ng==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -19711,9 +19114,9 @@ } }, "node_modules/@storybook/core-events": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.14.tgz", - "integrity": "sha512-zuSMjOgju7WLFL+okTXVvOKKNzwqVGRVp5UhXeSikT4aXuVdpfepCfikkjntn12G1ybL7mfFCsBU2DV1lwwp6Q==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.16.tgz", + "integrity": "sha512-mkBqzrbp6vmdjo0fBZGrFQQ4YdvMFxF6AesdKTf8EzPa69FoxnhQLrmQ4aXF+9vXkxfXVJF2HfpoTEdfqqAo+w==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -19724,26 +19127,26 @@ } }, "node_modules/@storybook/core-server": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-7.6.14.tgz", - "integrity": "sha512-OSUunvjXyUiyfGet8ZBz7/Lka6dSgbbVMH7lU6wELIYCd2ZUxU5HQMl9JPesl61wWB4L3JaWFAoMRaCVI7q0xQ==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-7.6.16.tgz", + "integrity": "sha512-Sj8j45XMg1bI7ktMqj9gxXHsZ4d1KgR+2A2eaxR7Heho7253WkUltLYxhu3hdH01rRJXYFxn/zZBxYfEib94Vg==", "dev": true, "dependencies": { "@aw-web-design/x-default-browser": "1.4.126", "@discoveryjs/json-ext": "^0.5.3", - "@storybook/builder-manager": "7.6.14", - "@storybook/channels": "7.6.14", - "@storybook/core-common": "7.6.14", - "@storybook/core-events": "7.6.14", + "@storybook/builder-manager": "7.6.16", + "@storybook/channels": "7.6.16", + "@storybook/core-common": "7.6.16", + "@storybook/core-events": "7.6.16", "@storybook/csf": "^0.1.2", - "@storybook/csf-tools": "7.6.14", + "@storybook/csf-tools": "7.6.16", "@storybook/docs-mdx": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/manager": "7.6.14", - "@storybook/node-logger": "7.6.14", - "@storybook/preview-api": "7.6.14", - "@storybook/telemetry": "7.6.14", - "@storybook/types": "7.6.14", + "@storybook/manager": "7.6.16", + "@storybook/node-logger": "7.6.16", + "@storybook/preview-api": "7.6.16", + "@storybook/telemetry": "7.6.16", + "@storybook/types": "7.6.16", "@types/detect-port": "^1.3.0", "@types/node": "^18.0.0", "@types/pretty-hrtime": "^1.0.0", @@ -19777,9 +19180,9 @@ } }, "node_modules/@storybook/core-server/node_modules/@types/node": { - "version": "18.19.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.15.tgz", - "integrity": "sha512-AMZ2UWx+woHNfM11PyAEQmfSxi05jm9OlkxczuHeEqmvwPkYj6MWv44gbzDPefYOLysTOFyI3ziiy2ONmUZfpA==", + "version": "18.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.17.tgz", + "integrity": "sha512-SzyGKgwPzuWp2SHhlpXKzCX0pIOfcI4V2eF37nNBJOhwlegQ83omtVQ1XxZpDE06V/d6AQvfQdPfnw0tRC//Ng==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -19915,12 +19318,12 @@ } }, "node_modules/@storybook/csf-plugin": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-7.6.14.tgz", - "integrity": "sha512-TYmtuLCzdWGy4/T6KYUBGdzRy/4cJzDQrDzWRWD7a+xcy1Z7wlKkXw+zWfxbNheEnxb146q5lIkRpvhevKgpGA==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-7.6.16.tgz", + "integrity": "sha512-hslhGtnijMpL7HAcYYgIuo6acVLP7BDptflMwIyGFWKK3MHjMxqWTZ3Sj+BV1yg/pYZdqC2NYyUypeuuSpivSA==", "dev": true, "dependencies": { - "@storybook/csf-tools": "7.6.14", + "@storybook/csf-tools": "7.6.16", "unplugin": "^1.3.1" }, "funding": { @@ -19929,9 +19332,9 @@ } }, "node_modules/@storybook/csf-tools": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.14.tgz", - "integrity": "sha512-s7XFIi823HhcKxTqHY/uU1QZCujLBjFt6OJa5y3XvwIMoLJWZtuT1PF/QPR0K7iYb9gQnGHwO9lZBfMraUywrQ==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.16.tgz", + "integrity": "sha512-8kVBq3UKDrEQq7rTHlNMoe1TDOTdO8iL8Jtv/FMDu/Qzj6AoT8/bjrtPsGjGMfVjP7QwBDeiLn6rStT4TlVGog==", "dev": true, "dependencies": { "@babel/generator": "^7.23.0", @@ -19939,7 +19342,7 @@ "@babel/traverse": "^7.23.2", "@babel/types": "^7.23.0", "@storybook/csf": "^0.1.2", - "@storybook/types": "7.6.14", + "@storybook/types": "7.6.16", "fs-extra": "^11.1.0", "recast": "^0.23.1", "ts-dedent": "^2.0.0" @@ -20003,14 +19406,14 @@ "dev": true }, "node_modules/@storybook/docs-tools": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/docs-tools/-/docs-tools-7.6.14.tgz", - "integrity": "sha512-8FCuVnty2d74cgF+qjhI/LTbGlf3mvu1OkKpLMp9xqouPy3X+yo9N8mpe2tIhgpRMTDzDScIeIBUpLrIpjHaXA==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/docs-tools/-/docs-tools-7.6.16.tgz", + "integrity": "sha512-meuq5uLGBLOSJXKeCt9iEH0uVKgGqwfEBi2T4E2w3BcubC/6oQ3VeZl25/KO+l1XcLmOg9LkN2ZOtLV9TEiVLQ==", "dev": true, "dependencies": { - "@storybook/core-common": "7.6.14", - "@storybook/preview-api": "7.6.14", - "@storybook/types": "7.6.14", + "@storybook/core-common": "7.6.16", + "@storybook/preview-api": "7.6.16", + "@storybook/types": "7.6.16", "@types/doctrine": "^0.0.3", "assert": "^2.1.0", "doctrine": "^3.0.0", @@ -20028,9 +19431,9 @@ "dev": true }, "node_modules/@storybook/manager": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/manager/-/manager-7.6.14.tgz", - "integrity": "sha512-lgowunC/pm2y6d+3j7UJ/CkHpWC0o+nZ9b7mDbkJ6PmezW5Hpy83kbeCxbwRGosYoPQ0izBzVB5ZqGgKrNNDjA==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/manager/-/manager-7.6.16.tgz", + "integrity": "sha512-CPDhgT4jjF0CDgLDxT/R+amMJXpXxSsVp+XzahPbEB9Yu4v0W0HW3f2vSuNJXwpfofrPSkbJweO/oC4ioOtavw==", "dev": true, "funding": { "type": "opencollective", @@ -20038,19 +19441,19 @@ } }, "node_modules/@storybook/manager-api": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.6.14.tgz", - "integrity": "sha512-kZbcudrpQaYgUCrnBumDBPOvaEcvFBrZjM5v3AvMenVMXTjwlAHF8mZswE/ptpDsico2iSN96nMhd97OyaAuqA==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.6.16.tgz", + "integrity": "sha512-pX3xw4DsPhYTWEDspsnJiZSoakn0z3Rdt9YmHU0/NaFBLn64EClzd9XMDnGXnZzW1DtdG6T6l2CwDNDCNIVkWg==", "dev": true, "dependencies": { - "@storybook/channels": "7.6.14", - "@storybook/client-logger": "7.6.14", - "@storybook/core-events": "7.6.14", + "@storybook/channels": "7.6.16", + "@storybook/client-logger": "7.6.16", + "@storybook/core-events": "7.6.16", "@storybook/csf": "^0.1.2", "@storybook/global": "^5.0.0", - "@storybook/router": "7.6.14", - "@storybook/theming": "7.6.14", - "@storybook/types": "7.6.14", + "@storybook/router": "7.6.16", + "@storybook/theming": "7.6.16", + "@storybook/types": "7.6.16", "dequal": "^2.0.2", "lodash": "^4.17.21", "memoizerific": "^1.11.3", @@ -20070,9 +19473,9 @@ "dev": true }, "node_modules/@storybook/node-logger": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.14.tgz", - "integrity": "sha512-prKUMGxGzeX3epdlin1UU6M1//CoAJM1GrffrFeNntnPr3h6GMTgxNzl85flUhWd4ky/wjC/36dGOI8QRYVtoA==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.16.tgz", + "integrity": "sha512-s18wgtLynLWnunz47lkVIpjk8J6LxT/OmfzkggieU8cG2XYRbf//t7/EOUpOqK77+Xqm3epSwgDAxOXGfjOjAA==", "dev": true, "funding": { "type": "opencollective", @@ -20080,9 +19483,9 @@ } }, "node_modules/@storybook/postinstall": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/postinstall/-/postinstall-7.6.14.tgz", - "integrity": "sha512-ya3e5jvW1eSw4l3lhiGH2g+Gk8py2Tr3PW5ecnH/x1rD8Tt43OHXRQqiFfl7QzOudHxQGKQsO3lhWe8FJXvdbA==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/postinstall/-/postinstall-7.6.16.tgz", + "integrity": "sha512-axWxj8e90+iLUZPGU9Zvn2Jc/GQrWspu8DpwRCS7N23epTVW6n6OWp31GAShdSx8Oh5lmCMXGegTd1v2Mwc61A==", "dev": true, "funding": { "type": "opencollective", @@ -20090,9 +19493,9 @@ } }, "node_modules/@storybook/preview": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/preview/-/preview-7.6.14.tgz", - "integrity": "sha512-6Y873pNsJBQuCeR3YDMlRgRW+4Tf+Rj4VdujjvRw/H7ES1+pO8qgcI3VJCcoxqDY9ZNPT/riLh8YOddpLNCgNg==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/preview/-/preview-7.6.16.tgz", + "integrity": "sha512-q4DbLn9kEK8JM9s+2oIjXBPHQhY0tQzsZ5hFeq833vNFcmuHnXS+WYk20b+UkmzL6j+E8pLm8WpI7rdbi0ZUVA==", "dev": true, "funding": { "type": "opencollective", @@ -20100,17 +19503,17 @@ } }, "node_modules/@storybook/preview-api": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.6.14.tgz", - "integrity": "sha512-CnUEkTUK3ei3vw4Ypa9EOxEO9lCKc3HvVHxXu4z6Caoe/hRUc10Q6Nj1A7brqok1QLZ304qc715XdYFMahDhyA==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.6.16.tgz", + "integrity": "sha512-V9x9HOhi4CJuiX+0a7GU0JlfRAp6txStGMkV0DrCATbxSWpK+6d5x2Te521z16V3RIMMmYn33aEyarOp5WjTqw==", "dev": true, "dependencies": { - "@storybook/channels": "7.6.14", - "@storybook/client-logger": "7.6.14", - "@storybook/core-events": "7.6.14", + "@storybook/channels": "7.6.16", + "@storybook/client-logger": "7.6.16", + "@storybook/core-events": "7.6.16", "@storybook/csf": "^0.1.2", "@storybook/global": "^5.0.0", - "@storybook/types": "7.6.14", + "@storybook/types": "7.6.16", "@types/qs": "^6.9.5", "dequal": "^2.0.2", "lodash": "^4.17.21", @@ -20126,18 +19529,18 @@ } }, "node_modules/@storybook/react": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/react/-/react-7.6.14.tgz", - "integrity": "sha512-esWjMgVkYaIyS4ZvEkTrHUDLu9KkTE+wyiyRBINoZLeczAw1YHI5iNqKDMOAN+pOyCyM6iEYSZasAzsJTAFWYA==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-7.6.16.tgz", + "integrity": "sha512-3vzjtEHu9xXLz827JiwC448ZVattzAR5qkfVg3dVOD1MtLH8LTJ/gOqv/8Kq0fOtEgOdlcAF4jQV/XAL6pEAkQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.14", - "@storybook/core-client": "7.6.14", - "@storybook/docs-tools": "7.6.14", + "@storybook/client-logger": "7.6.16", + "@storybook/core-client": "7.6.16", + "@storybook/docs-tools": "7.6.16", "@storybook/global": "^5.0.0", - "@storybook/preview-api": "7.6.14", - "@storybook/react-dom-shim": "7.6.14", - "@storybook/types": "7.6.14", + "@storybook/preview-api": "7.6.16", + "@storybook/react-dom-shim": "7.6.16", + "@storybook/types": "7.6.16", "@types/escodegen": "^0.0.6", "@types/estree": "^0.0.51", "@types/node": "^18.0.0", @@ -20172,9 +19575,9 @@ } }, "node_modules/@storybook/react-dom-shim": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-7.6.14.tgz", - "integrity": "sha512-Ldmc2tKj1N3vNYZpI791xgTbk0XdqJDm1a09fSRM4CeBu4BI7M9IjnNS4cHNdTeqtK9MbCSzCr1nxfxNCtrtiA==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-7.6.16.tgz", + "integrity": "sha512-F6pGgL2pWy5utn6m2YAVz1PYZO3pdlNHfT85g5Om3q7CR4msWpMQ1O/oEVYgqfJ9UfOqCV/mHeDWICzUa7pv6g==", "dev": true, "funding": { "type": "opencollective", @@ -20186,15 +19589,15 @@ } }, "node_modules/@storybook/react-vite": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-7.6.14.tgz", - "integrity": "sha512-KYJtXWCVZbDnSpiqKe2zmt3CDXbc7JRGoLVWB3T+/SBl7aaOmAcFxVRM7yDPMLxVzs9GTNH3gCj2CD64LZMHjg==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-7.6.16.tgz", + "integrity": "sha512-6Qu04cnKtpHyIWHt/1pjcRYS3ROoiF9gV1jgE8bubMODbFuT38w0Hu6JIcyH2FdAvhujjt4gQT5oXJRvXX1cqQ==", "dev": true, "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "0.3.0", "@rollup/pluginutils": "^5.0.2", - "@storybook/builder-vite": "7.6.14", - "@storybook/react": "7.6.14", + "@storybook/builder-vite": "7.6.16", + "@storybook/react": "7.6.16", "@vitejs/plugin-react": "^3.0.1", "magic-string": "^0.30.0", "react-docgen": "^7.0.0" @@ -20250,9 +19653,9 @@ "dev": true }, "node_modules/@storybook/react/node_modules/@types/node": { - "version": "18.19.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.15.tgz", - "integrity": "sha512-AMZ2UWx+woHNfM11PyAEQmfSxi05jm9OlkxczuHeEqmvwPkYj6MWv44gbzDPefYOLysTOFyI3ziiy2ONmUZfpA==", + "version": "18.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.17.tgz", + "integrity": "sha512-SzyGKgwPzuWp2SHhlpXKzCX0pIOfcI4V2eF37nNBJOhwlegQ83omtVQ1XxZpDE06V/d6AQvfQdPfnw0tRC//Ng==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -20271,12 +19674,12 @@ } }, "node_modules/@storybook/router": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.6.14.tgz", - "integrity": "sha512-eVD7jVZeM8mppEtHsvkKIEN92stsdbiXDHG49iNVnw+ojOSjJ1HR8+Pm8wy5Cc2pcyoZEHeU356kaP9gXOhuOQ==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.6.16.tgz", + "integrity": "sha512-PgVuzs83g4dq2r1qdcc0wvS1Pe1UpKdq54uy4TkBrrei7hBzB/+POztPXs0rVXXBXdCQT/jomLmRo/yC45bsGg==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.14", + "@storybook/client-logger": "7.6.16", "memoizerific": "^1.11.3", "qs": "^6.10.0" }, @@ -20286,13 +19689,13 @@ } }, "node_modules/@storybook/source-loader": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/source-loader/-/source-loader-7.6.14.tgz", - "integrity": "sha512-Ii/APRpuqkve1NQhAX9QKzcsvp/TkhkkLUZK2PCsRaxAZkSLPqK/TrsmctOACtkkg5HP+Mb9I7LHytHFsnhfZg==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/source-loader/-/source-loader-7.6.16.tgz", + "integrity": "sha512-HPZOFw0eAi5l5nXfewo7jMC2IRqhwF2TQfXvceNKk9ohr1bZXWz6+/Axs7ZGmdNjT0izgiOnq3vUTDsGLURHYQ==", "dev": true, "dependencies": { "@storybook/csf": "^0.1.2", - "@storybook/types": "7.6.14", + "@storybook/types": "7.6.16", "estraverse": "^5.2.0", "lodash": "^4.17.21", "prettier": "^2.8.0" @@ -20318,14 +19721,14 @@ } }, "node_modules/@storybook/telemetry": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/telemetry/-/telemetry-7.6.14.tgz", - "integrity": "sha512-F+a9Q4dHCpuBLQmB05DOLosU8p1Otj3Vd+/5EF9QUFSn4C64z1gmMc3jzF3iUgktY53HdoUqR871w3GoOJ7g9A==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/telemetry/-/telemetry-7.6.16.tgz", + "integrity": "sha512-5Uaz6zSRBEio89ScrAN7KKz+mBTJ5Jc/8Uf0uUHIhAxiHprs16PhIBo6MtBeWPQoiNwytN884sAtiUFAP4zFQQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.14", - "@storybook/core-common": "7.6.14", - "@storybook/csf-tools": "7.6.14", + "@storybook/client-logger": "7.6.16", + "@storybook/core-common": "7.6.16", + "@storybook/csf-tools": "7.6.16", "chalk": "^4.1.0", "detect-package-manager": "^2.0.1", "fetch-retry": "^5.0.2", @@ -20425,13 +19828,13 @@ } }, "node_modules/@storybook/theming": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.6.14.tgz", - "integrity": "sha512-jpryYjBAGLkFauSyNEoflSfYqO3srn98llNxhgxpc1P1ocmOzeDwdg7PUWDI9DCuJC+OWaXa1zzLO6uRLyEJAQ==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.6.16.tgz", + "integrity": "sha512-ZiUyakApTzAiAR28JwqbqY426U1OlJPG/Y7ddQgYgTsdoRFR1iMewAxWW1LId1q3B1dtiIHAccqhocEMNcYkLA==", "dev": true, "dependencies": { "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@storybook/client-logger": "7.6.14", + "@storybook/client-logger": "7.6.16", "@storybook/global": "^5.0.0", "memoizerific": "^1.11.3" }, @@ -20445,12 +19848,12 @@ } }, "node_modules/@storybook/types": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.14.tgz", - "integrity": "sha512-sJ3qn45M2XLXlOi+wkhXK5xsXbSVzi8YGrusux//DttI3s8wCP3BQSnEgZkBiEktloxPferINHT1er8/9UK7Xw==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.16.tgz", + "integrity": "sha512-Ld4dKbgSbvqThdBNwNlOxQu5AiS6U9DXI5evf/j83eWs6skO3OBdQp+GWa6sUCI9eRqH8tFsw/YmMcIZ4uZrBQ==", "dev": true, "dependencies": { - "@storybook/channels": "7.6.14", + "@storybook/channels": "7.6.16", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -20765,12 +20168,12 @@ } }, "node_modules/@tanstack/react-virtual": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.0.4.tgz", - "integrity": "sha512-tiqKW/e2MJVCr7/pRUXulpkyxllaOclkHNfhKTo4pmHjJIqnhMfwIjc1Q1R0Un3PI3kQywywu/791c8z9u0qeA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.1.1.tgz", + "integrity": "sha512-9tW9xwEW7exSa/8bxu29IPCcB5c9Xlq+whETixIIgYZYKuUY4ZOr000q3oLpL4bkOkolQbB4WXM0MoQGgJXqDg==", "dev": true, "dependencies": { - "@tanstack/virtual-core": "3.0.0" + "@tanstack/virtual-core": "3.1.1" }, "funding": { "type": "github", @@ -20782,9 +20185,9 @@ } }, "node_modules/@tanstack/virtual-core": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.0.0.tgz", - "integrity": "sha512-SYXOBTjJb05rXa2vl55TTwO40A6wKu0R5i1qQwhJYNDIqaIGF7D0HsLw+pJAyi2OvntlEIVusx3xtbbgSUi6zg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.1.1.tgz", + "integrity": "sha512-I5lerX+RWxLM+zw35gwwQIoLvtkOm0ecuQUlEjNey+Ga6TnR66WKLBnSHre59onugxhpDLT2nofRYzxf+izDFQ==", "dev": true, "funding": { "type": "github", @@ -21255,9 +20658,9 @@ } }, "node_modules/@types/connect": { - "version": "3.4.36", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz", - "integrity": "sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==", + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "dependencies": { "@types/node": "*" } @@ -21756,9 +21159,9 @@ } }, "node_modules/@types/node": { - "version": "20.11.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.17.tgz", - "integrity": "sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==", + "version": "20.11.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz", + "integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==", "dependencies": { "undici-types": "~5.26.4" } @@ -21887,9 +21290,9 @@ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" }, "node_modules/@types/react": { - "version": "18.2.55", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.55.tgz", - "integrity": "sha512-Y2Tz5P4yz23brwm2d7jNon39qoAtMMmalOQv6+fEFt1mT+FcM3D841wDpoUvFXhaYenuROCy3FZYqdTjM7qVyA==", + "version": "18.2.56", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.56.tgz", + "integrity": "sha512-NpwHDMkS/EFZF2dONFQHgkPRwhvgq/OAvIaGQzxGSBmaeR++kTg6njr15Vatz0/2VcCEwJQFi6Jf4Q0qBu0rLA==", "devOptional": true, "dependencies": { "@types/prop-types": "*", @@ -21974,9 +21377,9 @@ "devOptional": true }, "node_modules/@types/semver": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", - "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.7.tgz", + "integrity": "sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==", "dev": true }, "node_modules/@types/send": { @@ -22060,9 +21463,9 @@ } }, "node_modules/@types/ssh2/node_modules/@types/node": { - "version": "18.19.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.15.tgz", - "integrity": "sha512-AMZ2UWx+woHNfM11PyAEQmfSxi05jm9OlkxczuHeEqmvwPkYj6MWv44gbzDPefYOLysTOFyI3ziiy2ONmUZfpA==", + "version": "18.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.17.tgz", + "integrity": "sha512-SzyGKgwPzuWp2SHhlpXKzCX0pIOfcI4V2eF37nNBJOhwlegQ83omtVQ1XxZpDE06V/d6AQvfQdPfnw0tRC//Ng==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -22205,16 +21608,16 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", - "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.0.1.tgz", + "integrity": "sha512-OLvgeBv3vXlnnJGIAgCLYKjgMEU+wBGj07MQ/nxAaON+3mLzX7mJbhRYrVGiVvFiXtwFlkcBa/TtmglHy0UbzQ==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/type-utils": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/scope-manager": "7.0.1", + "@typescript-eslint/type-utils": "7.0.1", + "@typescript-eslint/utils": "7.0.1", + "@typescript-eslint/visitor-keys": "7.0.1", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -22230,8 +21633,8 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -22273,15 +21676,15 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", - "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.0.1.tgz", + "integrity": "sha512-8GcRRZNzaHxKzBPU3tKtFNing571/GwPBeCvmAUw0yBtfE2XVd0zFKJIMSWkHJcPQi0ekxjIts6L/rrZq5cxGQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/scope-manager": "7.0.1", + "@typescript-eslint/types": "7.0.1", + "@typescript-eslint/typescript-estree": "7.0.1", + "@typescript-eslint/visitor-keys": "7.0.1", "debug": "^4.3.4" }, "engines": { @@ -22292,7 +21695,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -22301,13 +21704,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.0.1.tgz", + "integrity": "sha512-v7/T7As10g3bcWOOPAcbnMDuvctHzCFYCG/8R4bK4iYzdFqsZTbXGln0cZNVcwQcwewsYU2BJLay8j0/4zOk4w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" + "@typescript-eslint/types": "7.0.1", + "@typescript-eslint/visitor-keys": "7.0.1" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -22318,13 +21721,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", - "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.0.1.tgz", + "integrity": "sha512-YtT9UcstTG5Yqy4xtLiClm1ZpM/pWVGFnkAa90UfdkkZsR1eP2mR/1jbHeYp8Ay1l1JHPyGvoUYR6o3On5Nhmw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/typescript-estree": "7.0.1", + "@typescript-eslint/utils": "7.0.1", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -22336,7 +21739,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -22345,9 +21748,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.0.1.tgz", + "integrity": "sha512-uJDfmirz4FHib6ENju/7cz9SdMSkeVvJDK3VcMFvf/hAShg8C74FW+06MaQPODHfDJp/z/zHfgawIJRjlu0RLg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -22358,13 +21761,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.0.1.tgz", + "integrity": "sha512-SO9wHb6ph0/FN5OJxH4MiPscGah5wjOd0RRpaLvuBv9g8565Fgu0uMySFEPqwPHiQU90yzJ2FjRYKGrAhS1xig==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/types": "7.0.1", + "@typescript-eslint/visitor-keys": "7.0.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -22419,17 +21822,17 @@ "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", - "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.0.1.tgz", + "integrity": "sha512-oe4his30JgPbnv+9Vef1h48jm0S6ft4mNwi9wj7bX10joGn07QRfqIqFHoMiajrtoU88cIhXf8ahwgrcbNLgPA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/scope-manager": "7.0.1", + "@typescript-eslint/types": "7.0.1", + "@typescript-eslint/typescript-estree": "7.0.1", "semver": "^7.5.4" }, "engines": { @@ -22440,7 +21843,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" } }, "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { @@ -22477,12 +21880,12 @@ "dev": true }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.0.1.tgz", + "integrity": "sha512-hwAgrOyk++RTXrP4KzCg7zB2U0xt7RUU0ZdMSCsqF3eKUwkdXUMyTb0qdCuji7VIbcpG62kKTU9M1J1c9UpFBw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/types": "7.0.1", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -22531,9 +21934,9 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.2.2.tgz", - "integrity": "sha512-IHyKnDz18SFclIEEAHb9Y4Uxx0sPKC2VO1kdDCs1BF6Ip4S8rQprs971zIsooLUn7Afs71GRxWMWpkCGZpRMhw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.3.0.tgz", + "integrity": "sha512-e5Y5uK5NNoQMQaNitGQQjo9FoA5ZNcu7Bn6pH+dxUf48u6po1cX38kFBYUHZ9GNVkF4JLbncE0WeWwTw+nLrxg==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.1", @@ -22554,17 +21957,17 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "^1.0.0" + "vitest": "1.3.0" } }, "node_modules/@vitest/expect": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.2.2.tgz", - "integrity": "sha512-3jpcdPAD7LwHUUiT2pZTj2U82I2Tcgg2oVPvKxhn6mDI2On6tfvPQTjAI4628GUGDZrCm4Zna9iQHm5cEexOAg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.3.0.tgz", + "integrity": "sha512-7bWt0vBTZj08B+Ikv70AnLRicohYwFgzNjFqo9SxxqHHxSlUJGSXmCRORhOnRMisiUryKMdvsi1n27Bc6jL9DQ==", "dev": true, "dependencies": { - "@vitest/spy": "1.2.2", - "@vitest/utils": "1.2.2", + "@vitest/spy": "1.3.0", + "@vitest/utils": "1.3.0", "chai": "^4.3.10" }, "funding": { @@ -22572,12 +21975,12 @@ } }, "node_modules/@vitest/runner": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.2.2.tgz", - "integrity": "sha512-JctG7QZ4LSDXr5CsUweFgcpEvrcxOV1Gft7uHrvkQ+fsAVylmWQvnaAr/HDp3LAH1fztGMQZugIheTWjaGzYIg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.3.0.tgz", + "integrity": "sha512-1Jb15Vo/Oy7mwZ5bXi7zbgszsdIBNjc4IqP8Jpr/8RdBC4nF1CTzIAn2dxYvpF1nGSseeL39lfLQ2uvs5u1Y9A==", "dev": true, "dependencies": { - "@vitest/utils": "1.2.2", + "@vitest/utils": "1.3.0", "p-limit": "^5.0.0", "pathe": "^1.1.1" }, @@ -22601,9 +22004,9 @@ } }, "node_modules/@vitest/snapshot": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.2.2.tgz", - "integrity": "sha512-SmGY4saEw1+bwE1th6S/cZmPxz/Q4JWsl7LvbQIky2tKE35US4gd0Mjzqfr84/4OD0tikGWaWdMja/nWL5NIPA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.3.0.tgz", + "integrity": "sha512-swmktcviVVPYx9U4SEQXLV6AEY51Y6bZ14jA2yo6TgMxQ3h+ZYiO0YhAHGJNp0ohCFbPAis1R9kK0cvN6lDPQA==", "dev": true, "dependencies": { "magic-string": "^0.30.5", @@ -22615,9 +22018,9 @@ } }, "node_modules/@vitest/spy": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.2.2.tgz", - "integrity": "sha512-k9Gcahssw8d7X3pSLq3e3XEu/0L78mUkCjivUqCQeXJm9clfXR/Td8+AP+VC1O6fKPIDLcHDTAmBOINVuv6+7g==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.3.0.tgz", + "integrity": "sha512-AkCU0ThZunMvblDpPKgjIi025UxR8V7MZ/g/EwmAGpjIujLVV2X6rGYGmxE2D4FJbAy0/ijdROHMWa2M/6JVMw==", "dev": true, "dependencies": { "tinyspy": "^2.2.0" @@ -22627,12 +22030,12 @@ } }, "node_modules/@vitest/ui": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-1.2.2.tgz", - "integrity": "sha512-CG+5fa8lyoBr+9i+UZGS31Qw81v33QlD10uecHxN2CLJVN+jLnqx4pGzGvFFeJ7jSnUCT0AlbmVWY6fU6NJZmw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-1.3.0.tgz", + "integrity": "sha512-gDGEBUddrPOJvF4e0nIeKBz1whiDthBxBB0OUAbUqnY0HxJwqlKg9R257Sxoeh1Q7ZDSzc7qY96n4hrEPy1NaQ==", "dev": true, "dependencies": { - "@vitest/utils": "1.2.2", + "@vitest/utils": "1.3.0", "fast-glob": "^3.3.2", "fflate": "^0.8.1", "flatted": "^3.2.9", @@ -22644,13 +22047,13 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "^1.0.0" + "vitest": "1.3.0" } }, "node_modules/@vitest/utils": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.2.2.tgz", - "integrity": "sha512-WKITBHLsBHlpjnDQahr+XK6RE7MiAsgrIkr0pGhQ9ygoxBfUeG0lUG5iLlzqjmKSlBv3+j5EGsriBzh+C3Tq9g==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.3.0.tgz", + "integrity": "sha512-/LibEY/fkaXQufi4GDlQZhikQsPO2entBKtfuyIpr1jV4DpaeasqkeHjhdOhU24vSHshcSuEyVlWdzvv2XmYCw==", "dev": true, "dependencies": { "diff-sequences": "^29.6.3", @@ -23846,9 +23249,9 @@ } }, "node_modules/aws-cdk": { - "version": "2.127.0", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.127.0.tgz", - "integrity": "sha512-0yPiN+/VFVc/NpOryO+1S7b4DBgRSs4JdQ64jhV4QbwaoWZo7KISxdN2cK4pmcVH67BSNCJCjjlf10cYhmMvwA==", + "version": "2.128.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.128.0.tgz", + "integrity": "sha512-epOAr/0WKqmyaKqBc7N0Ky5++93pu+v6yVN9jNOa4JYkAkGbeTS3vR9bj/W0o94jnlgWevG3HNHr83jtRvw/4A==", "bin": { "cdk": "bin/cdk" }, @@ -23860,9 +23263,9 @@ } }, "node_modules/aws-cdk-lib": { - "version": "2.127.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.127.0.tgz", - "integrity": "sha512-pEdp2TqgNLYY+kAo68oVzMDEHJevYoRArZJoH+bjM9YTwqRJJiwF1k6tc78e3jca4sCNDZAgX2ytOgqW6lVTWQ==", + "version": "2.128.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.128.0.tgz", + "integrity": "sha512-cAU1L4jtPXPQXpa9kS2/HhHdkg3xGc5GCqwRgivdoj/iLQF3dDwIouOkwDBY/S5pXMqOUC7IoVdIPPbIgfGlsQ==", "bundleDependencies": [ "@balena/dockerignore", "case", @@ -25200,12 +24603,12 @@ "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==" }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.5", + "content-type": "~1.0.4", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -25213,7 +24616,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.2", + "raw-body": "2.5.1", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -25479,9 +24882,9 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.22.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", - "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "funding": [ { "type": "opencollective", @@ -25497,8 +24900,8 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001580", - "electron-to-chromium": "^1.4.648", + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, @@ -25687,9 +25090,9 @@ "dev": true }, "node_modules/bullmq": { - "version": "5.1.10", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.1.10.tgz", - "integrity": "sha512-53CpB/ALaFYTJof7lqRNvKlJyYejMCOo6Jv1IbQxustKqZGRKqbaD2o8FSfaSWsJtGTN7bHjVbvxVD0g8EUtgQ==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.2.1.tgz", + "integrity": "sha512-uImDp9k4hiitA1Ve5W/7AlOAaHMXwLtM2gPay7X7O6Do0LlXHW5fD4M0SthBtZLhECBQtZCCvzJcd2COoCi40g==", "dependencies": { "cron-parser": "^4.6.0", "glob": "^8.0.3", @@ -25756,18 +25159,6 @@ "node": ">=10" } }, - "node_modules/bullmq/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/bullmq/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -25928,14 +25319,15 @@ } }, "node_modules/call-bind": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.6.tgz", - "integrity": "sha512-Mj50FLHtlsoVfRfnHaZvyrooHcrlceNZdL/QBvJJVd9Ta55qCQK0gs4ss2oZDeV9zFCs6ewzYgVE5yfVmfFpVg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { + "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.3", - "set-function-length": "^1.2.0" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -26024,9 +25416,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001585", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001585.tgz", - "integrity": "sha512-yr2BWR1yLXQ8fMpdS/4ZZXpseBgE7o4g41x3a6AJOqZuOi+iE/WdJYAuZ6Y95i4Ohd2Y+9MzIWRR+uGABH4s3Q==", + "version": "1.0.30001588", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001588.tgz", + "integrity": "sha512-+hVY9jE44uKLkH0SrUTqxjxqNTOWHsbnQDIKjwkZ3lNTzUUVdBLBGXtj/q5Mp5u98r3droaZAewQuEDzjQdZlQ==", "funding": [ { "type": "opencollective", @@ -26053,11 +25445,11 @@ } }, "node_modules/cdk": { - "version": "2.127.0", - "resolved": "https://registry.npmjs.org/cdk/-/cdk-2.127.0.tgz", - "integrity": "sha512-HHH/miv1r8My2vub7GP0JKyWKz50oiDrmrJfPmG0KXTPD4FSczGEmVrbIC5eN5asvf8exRvVcIoH0ShahavwQA==", + "version": "2.128.0", + "resolved": "https://registry.npmjs.org/cdk/-/cdk-2.128.0.tgz", + "integrity": "sha512-h0OOqV4GICpH9senjuKCbeYsEifa0hOyb3bLEpXGTdj73UWDHZo+YEvr9H+mt8mBrKxbrF/dS42Ook9/PZNQlA==", "dependencies": { - "aws-cdk": "2.127.0" + "aws-cdk": "2.128.0" }, "bin": { "cdk": "bin/cdk" @@ -26067,18 +25459,18 @@ } }, "node_modules/cdk-nag": { - "version": "2.28.30", - "resolved": "https://registry.npmjs.org/cdk-nag/-/cdk-nag-2.28.30.tgz", - "integrity": "sha512-aGwztQoMikzErJRUp/obeGDt3x+OAmJLEDGuHou/5CSsXTd45siZrrFEMeNL7VVoeLIN3TaQGjdNlcytPDwGqA==", + "version": "2.28.39", + "resolved": "https://registry.npmjs.org/cdk-nag/-/cdk-nag-2.28.39.tgz", + "integrity": "sha512-eC+QDhuAYgvGTtOsTNKAmVvW/d1VA6I3cfOLmdTFlclLRJLscjvE2GhfBJqB5ogxvkLwQWb9U8PZtp5/zo/xHA==", "peerDependencies": { "aws-cdk-lib": "^2.116.0", "constructs": "^10.0.5" } }, "node_modules/cdk-serverless-clamscan": { - "version": "2.6.91", - "resolved": "https://registry.npmjs.org/cdk-serverless-clamscan/-/cdk-serverless-clamscan-2.6.91.tgz", - "integrity": "sha512-n758zaRt166NIXMRsrp7ppgnD67IcSh/HuHnKa/QF0OWSceny61w9zju9FPWU2B1rG8k5LCWRApGnx/tSKxPKw==", + "version": "2.6.100", + "resolved": "https://registry.npmjs.org/cdk-serverless-clamscan/-/cdk-serverless-clamscan-2.6.100.tgz", + "integrity": "sha512-ylJoUGxwz25sCTYhOhlnh3ktVS1pavW/9R9QwcpIjiV5nOGKxy+SOZQyfM1+WHyPsceJ5ysMk+nOW98+V6CnLg==", "bin": { "0": "assets" }, @@ -26416,9 +25808,9 @@ } }, "node_modules/citty": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.5.tgz", - "integrity": "sha512-AS7n5NSc0OQVMV9v6wt3ByujNIrne0/cTjiC2MYqhvao57VNfiuVksTSr2p17nVOhEr2KtqiAkGwHcgMC/qUuQ==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", "dev": true, "dependencies": { "consola": "^3.2.3" @@ -27089,6 +26481,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -27308,25 +26705,6 @@ "node": ">= 0.6" } }, - "node_modules/content-disposition/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", @@ -27341,9 +26719,9 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", "engines": { "node": ">= 0.6" } @@ -27360,6 +26738,14 @@ "node": ">= 0.8.0" } }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -27460,9 +26846,9 @@ } }, "node_modules/core-js": { - "version": "3.35.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.35.1.tgz", - "integrity": "sha512-IgdsbxNyMskrTFxa9lWHyMwAJU5gXOPP+1yO+K59d50VLVAIDAbs7gIv705KzALModfK3ZrSZTPNpC0PQgIZuw==", + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.36.0.tgz", + "integrity": "sha512-mt7+TUBbTFg5+GngsAxeKBTl5/VS0guFeJacYge9OmHb+m058UwwIm41SE9T4Den7ClatV57B6TYTuJ0CX1MAw==", "dev": true, "hasInstallScript": true, "funding": { @@ -27471,11 +26857,11 @@ } }, "node_modules/core-js-compat": { - "version": "3.35.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.1.tgz", - "integrity": "sha512-sftHa5qUJY3rs9Zht1WEnmkvXputCyDBczPnr7QDgL8n3qrF3CMXY4VPSYtOLLiOUJcah2WNXREd48iOl6mQIw==", + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.0.tgz", + "integrity": "sha512-iV9Pd/PsgjNWBXeq8XRtWVSgz2tKAfhfvBs7qxYty+RlRd+OCksaWmOnc4JKrTc1cToXL1N0s3l/vwlxPtdElw==", "dependencies": { - "browserslist": "^4.22.2" + "browserslist": "^4.22.3" }, "funding": { "type": "opencollective", @@ -27483,9 +26869,9 @@ } }, "node_modules/core-js-pure": { - "version": "3.35.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.35.1.tgz", - "integrity": "sha512-zcIdi/CL3MWbBJYo5YCeVAAx+Sy9yJE9I3/u9LkFABwbeaPhTMRWraM8mYFp9jW5Z50hOy7FVzCc8dCrpZqtIQ==", + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.36.0.tgz", + "integrity": "sha512-cN28qmhRNgbMZZMc/RFu5w8pK9VJzpb2rJVR/lHuZJKwmXnoWOpXmMkxqBB514igkp1Hu8WGROsiOAzUcKdHOQ==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -28720,9 +28106,9 @@ } }, "node_modules/dcmjs-dimse": { - "version": "0.1.24", - "resolved": "https://registry.npmjs.org/dcmjs-dimse/-/dcmjs-dimse-0.1.24.tgz", - "integrity": "sha512-j+EG7GbOfHAkeSDmCrECo5iFctdjnwrMKeeLe9SKre+EX1McNe62XCO15EOzhB5uEiunPTKGE2yVa0auJZi6HQ==", + "version": "0.1.25", + "resolved": "https://registry.npmjs.org/dcmjs-dimse/-/dcmjs-dimse-0.1.25.tgz", + "integrity": "sha512-QvSXJPzweSqc7x5SlLeEf+DQW7K6D5C+y2F7Ic2CI3TjBIu3PWvK6WAWCQVnQoFhHfcowOuxocff2Np9jGajlg==", "dependencies": { "async-eventemitter": "^0.2.4", "dcmjs": "^0.29.10", @@ -28966,17 +28352,19 @@ } }, "node_modules/define-data-property": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.2.tgz", - "integrity": "sha512-SRtsSqsDbgpJBbW3pABMCOt6rQyeM8s8RiyeSN8jYG8sYmt/kGJejbydttUsnDs1tadr19tvhT4ShwMyoqAm4g==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dependencies": { + "es-define-property": "^1.0.0", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.2", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.1" + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-lazy-prop": { @@ -29300,9 +28688,9 @@ "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==" }, "node_modules/diff": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", - "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "engines": { "node": ">=0.3.1" } @@ -29486,9 +28874,9 @@ } }, "node_modules/dotenv": { - "version": "16.4.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.2.tgz", - "integrity": "sha512-rZSSFxke7d9nYQ5NeMIwp5PP+f8wXgKNljpOb7KtH6SKW1cEqcXAz9VSJYVLKe7Jhup/gUYOkaeSVyK8GJ+nBg==", + "version": "16.4.4", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.4.tgz", + "integrity": "sha512-XvPXc8XAQThSjAbY6cQ/9PcBXmFoWuw1sQ3b8HqUCR6ziGXjkTi//kB9SWa2UwqlgdAIuRqAa/9hVljzPehbYg==", "engines": { "node": ">=12" }, @@ -29575,9 +28963,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.665", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.665.tgz", - "integrity": "sha512-UpyCWObBoD+nSZgOC2ToaIdZB0r9GhqT2WahPKiSki6ckkSuKhQNso8V2PrFcHBMleI/eqbKgVQgVC4Wni4ilw==" + "version": "1.4.673", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.673.tgz", + "integrity": "sha512-zjqzx4N7xGdl5468G+vcgzDhaHkaYgVcf9MqgexcTqsl2UHSCmOj/Bi3HAprg4BZCpC7HyD8a6nZl6QAZf72gw==" }, "node_modules/elkjs": { "version": "0.9.1", @@ -29766,50 +29154,52 @@ } }, "node_modules/es-abstract": { - "version": "1.22.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", - "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.4.tgz", + "integrity": "sha512-vZYJlk2u6qHYxBOTjAeg7qUxHdNfih64Uu2J8QqWgXZ2cri0ZpJAkzDUK/q593+mvKwlxyaxr6F1Q+3LKoQRgg==", "dev": true, "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "arraybuffer.prototype.slice": "^1.0.2", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.5", - "es-set-tostringtag": "^2.0.1", + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.7", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.2", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.2", - "get-symbol-description": "^1.0.0", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", "globalthis": "^1.0.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0", + "has-property-descriptors": "^1.0.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", - "hasown": "^2.0.0", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", + "hasown": "^2.0.1", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "is-typed-array": "^1.1.12", + "is-typed-array": "^1.1.13", "is-weakref": "^1.0.2", "object-inspect": "^1.13.1", "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "safe-array-concat": "^1.0.1", - "safe-regex-test": "^1.0.0", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.0", + "safe-regex-test": "^1.0.3", "string.prototype.trim": "^1.2.8", "string.prototype.trimend": "^1.0.7", "string.prototype.trimstart": "^1.0.7", - "typed-array-buffer": "^1.0.0", + "typed-array-buffer": "^1.0.1", "typed-array-byte-length": "^1.0.0", "typed-array-byte-offset": "^1.0.0", "typed-array-length": "^1.0.4", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.13" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -29824,6 +29214,17 @@ "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", "dev": true }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-errors": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", @@ -29853,21 +29254,21 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.16.tgz", - "integrity": "sha512-CREG2A9Vq7bpDRnldhFcMKuKArvkZtsH6Y0DHOHVg49qhf+LD8uEdUM3OkOAICv0EziGtDEnQtqY2/mfBILpFw==", + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.17.tgz", + "integrity": "sha512-lh7BsUqelv4KUbR5a/ZTaGGIMLCjPGPqJ6q+Oq24YP0RdyptX1uzm4vvaqzk7Zx3bpl/76YLTTDj9L7uYQ92oQ==", "dev": true, "dependencies": { "asynciterator.prototype": "^1.0.0", - "call-bind": "^1.0.6", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", + "es-abstract": "^1.22.4", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.0.2", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "globalthis": "^1.0.3", - "has-property-descriptors": "^1.0.1", + "has-property-descriptors": "^1.0.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "internal-slot": "^1.0.7", @@ -29994,9 +29395,9 @@ } }, "node_modules/esbuild": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.0.tgz", - "integrity": "sha512-6iwE3Y2RVYCME1jLpBqq7LQWK3MW6vjV2bZy6gt/WrqkY+WE74Spyc0ThAOYpMtITvnjX09CrC6ym7A/m9mebA==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.1.tgz", + "integrity": "sha512-OJwEgrpWm/PCMsLVWXKqvcjme3bHNpOgN7Tb6cQnR5n0TPbQx1/Xrn7rqM+wn17bYeT6MGB5sn1Bh5YiGi70nA==", "dev": true, "hasInstallScript": true, "bin": { @@ -30006,35 +29407,35 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.0", - "@esbuild/android-arm": "0.20.0", - "@esbuild/android-arm64": "0.20.0", - "@esbuild/android-x64": "0.20.0", - "@esbuild/darwin-arm64": "0.20.0", - "@esbuild/darwin-x64": "0.20.0", - "@esbuild/freebsd-arm64": "0.20.0", - "@esbuild/freebsd-x64": "0.20.0", - "@esbuild/linux-arm": "0.20.0", - "@esbuild/linux-arm64": "0.20.0", - "@esbuild/linux-ia32": "0.20.0", - "@esbuild/linux-loong64": "0.20.0", - "@esbuild/linux-mips64el": "0.20.0", - "@esbuild/linux-ppc64": "0.20.0", - "@esbuild/linux-riscv64": "0.20.0", - "@esbuild/linux-s390x": "0.20.0", - "@esbuild/linux-x64": "0.20.0", - "@esbuild/netbsd-x64": "0.20.0", - "@esbuild/openbsd-x64": "0.20.0", - "@esbuild/sunos-x64": "0.20.0", - "@esbuild/win32-arm64": "0.20.0", - "@esbuild/win32-ia32": "0.20.0", - "@esbuild/win32-x64": "0.20.0" + "@esbuild/aix-ppc64": "0.20.1", + "@esbuild/android-arm": "0.20.1", + "@esbuild/android-arm64": "0.20.1", + "@esbuild/android-x64": "0.20.1", + "@esbuild/darwin-arm64": "0.20.1", + "@esbuild/darwin-x64": "0.20.1", + "@esbuild/freebsd-arm64": "0.20.1", + "@esbuild/freebsd-x64": "0.20.1", + "@esbuild/linux-arm": "0.20.1", + "@esbuild/linux-arm64": "0.20.1", + "@esbuild/linux-ia32": "0.20.1", + "@esbuild/linux-loong64": "0.20.1", + "@esbuild/linux-mips64el": "0.20.1", + "@esbuild/linux-ppc64": "0.20.1", + "@esbuild/linux-riscv64": "0.20.1", + "@esbuild/linux-s390x": "0.20.1", + "@esbuild/linux-x64": "0.20.1", + "@esbuild/netbsd-x64": "0.20.1", + "@esbuild/openbsd-x64": "0.20.1", + "@esbuild/sunos-x64": "0.20.1", + "@esbuild/win32-arm64": "0.20.1", + "@esbuild/win32-ia32": "0.20.1", + "@esbuild/win32-x64": "0.20.1" } }, "node_modules/esbuild-node-externals": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/esbuild-node-externals/-/esbuild-node-externals-1.12.0.tgz", - "integrity": "sha512-0rQM4N9QZwnLetzkUCOHj7Dj+YkD2IlHJIO/+3bb/AOAyDPG4D4tSTdli4QjrXRfPIffS5zAUrhxSbeyqmXhAg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/esbuild-node-externals/-/esbuild-node-externals-1.13.0.tgz", + "integrity": "sha512-EAd32LMfUajIbLZphERyDVltTn/jir55B40xND5ro6VpCiv5/pum+s51cQf3LBFSVgEFznVJYMJtfVCJiSb32w==", "dev": true, "dependencies": { "find-up": "^5.0.0", @@ -30044,7 +29445,7 @@ "node": ">=12" }, "peerDependencies": { - "esbuild": "0.12 - 0.19" + "esbuild": "0.12 - 0.20" } }, "node_modules/esbuild-plugin-alias": { @@ -30202,6 +29603,142 @@ } } }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-next/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-config-next/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-config-next/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/eslint-import-resolver-node": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", @@ -30348,9 +29885,9 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "48.0.6", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.0.6.tgz", - "integrity": "sha512-LgwXOX6TWxxFYcbdVe+BJ94Kl/pgjSPYHLzqEdAMXTA1BH9WDx7iJ+9/iDajPF64LtzWX8C1mCfpbMZjJGhAOw==", + "version": "48.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.1.0.tgz", + "integrity": "sha512-g9S8ukmTd1DVcV/xeBYPPXOZ6rc8WJ4yi0+MVxJ1jBOrz5kmxV9gJJQ64ltCqIWFnBChLIhLVx3tbTSarqVyFA==", "dev": true, "dependencies": { "@es-joy/jsdoccomment": "~0.42.0", @@ -31285,9 +30822,9 @@ } }, "node_modules/expo": { - "version": "50.0.6", - "resolved": "https://registry.npmjs.org/expo/-/expo-50.0.6.tgz", - "integrity": "sha512-CVg0h9bmYeTWtw4EOL0HKNL+zu84YZl5nLWRPKrcpt8jox1VQQAYmvJGMdM5gSRxq5CFNLlWGxq9O8Zvfi1SOQ==", + "version": "50.0.7", + "resolved": "https://registry.npmjs.org/expo/-/expo-50.0.7.tgz", + "integrity": "sha512-lTqIrKOUTKHLdTuAaJzZihi1v7F8Ix1dOXVWMpToDy9zPC/s+fet0fbyXdFUxYsCUyuEDIB9tvejrTYZk8Hm0Q==", "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "0.17.5", @@ -31298,10 +30835,10 @@ "babel-preset-expo": "~10.0.1", "expo-asset": "~9.0.2", "expo-file-system": "~16.0.6", - "expo-font": "~11.10.2", + "expo-font": "~11.10.3", "expo-keep-awake": "~12.8.2", "expo-modules-autolinking": "1.10.3", - "expo-modules-core": "1.11.8", + "expo-modules-core": "1.11.9", "fbemitter": "^3.0.0", "whatwg-url-without-unicode": "8.0.0-3" }, @@ -31334,9 +30871,9 @@ } }, "node_modules/expo-crypto": { - "version": "12.8.0", - "resolved": "https://registry.npmjs.org/expo-crypto/-/expo-crypto-12.8.0.tgz", - "integrity": "sha512-67CoxXz+b4VU1zMo/2kUp+9t6TiVs8HvCvHsW8zoLLAZkVNa3YW1l0arLtQ4oR4HQpEr1i9rAZhP0/mvo+fg5A==", + "version": "12.8.1", + "resolved": "https://registry.npmjs.org/expo-crypto/-/expo-crypto-12.8.1.tgz", + "integrity": "sha512-EJEzmfBUSkGfALTlZRKUbh1RMKF7mWI12vkhO2w6bhGO4bjgGB8XzUHgLfrvSjphDFMx/lwaR6bAQDmXKO9UkQ==", "peer": true, "dependencies": { "base64-js": "^1.3.0" @@ -31354,9 +30891,9 @@ } }, "node_modules/expo-font": { - "version": "11.10.2", - "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-11.10.2.tgz", - "integrity": "sha512-AE0Q0LiWiVosQ/jlKUPoWoob7p3GwYM2xmLoUkuopO9RYh9NL1hZKHiMKcWBZyDG8Gww1GtBQwh7ZREST8+jjQ==", + "version": "11.10.3", + "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-11.10.3.tgz", + "integrity": "sha512-q1Td2zUvmLbCA9GV4OG4nLPw5gJuNY1VrPycsnemN1m8XWTzzs8nyECQQqrcBhgulCgcKZZJJ6U0kC2iuSoQHQ==", "dependencies": { "fontfaceobserver": "^2.1.0" }, @@ -31478,9 +31015,9 @@ } }, "node_modules/expo-modules-core": { - "version": "1.11.8", - "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-1.11.8.tgz", - "integrity": "sha512-rlctE3nCNLCGv3LosGQNaTuwGrr2SyQA+hOgci/0l+VRc0gFNtvl0gskph9C0tnN1jzBeb8rRZQYVj5ih1yxcA==", + "version": "1.11.9", + "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-1.11.9.tgz", + "integrity": "sha512-GTUb81vcPaF+5MtlBI1u9IjrZbGdF1ZUwz3u8Gc+rOLBblkZ7pYsj2mU/tu+k0khTckI9vcH4ZBksXWvE1ncjQ==", "dependencies": { "invariant": "^2.2.4" } @@ -31785,37 +31322,6 @@ "node": ">= 8.0.0" } }, - "node_modules/express/node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/express/node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -31824,17 +31330,6 @@ "ms": "2.0.0" } }, - "node_modules/express/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -31854,39 +31349,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/express/node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/ext": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", @@ -32573,9 +32035,9 @@ "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==" }, "node_modules/flow-parser": { - "version": "0.228.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.228.0.tgz", - "integrity": "sha512-xPWkzCO07AnS8X+fQFpWm+tJ+C7aeaiVzJ+rSepbkCXUvUJ6l6squEl63axoMcixyH4wLjmypOzq/+zTD0O93w==", + "version": "0.229.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.229.0.tgz", + "integrity": "sha512-mOYmMuvJwAo/CvnMFEq4SHftq7E5188hYMTTxJyQOXk2nh+sgslRdYMw3wTthH+FMcFaZLtmBPuMu6IwztdoUQ==", "dev": true, "engines": { "node": ">=0.4.0" @@ -33076,7 +32538,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -33090,7 +32551,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -33353,9 +32813,9 @@ } }, "node_modules/gaxios/node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", "dependencies": { "agent-base": "^7.0.2", "debug": "4" @@ -33848,9 +33308,9 @@ } }, "node_modules/graphql-ws": { - "version": "5.14.3", - "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.14.3.tgz", - "integrity": "sha512-F/i2xNIVbaEF2xWggID0X/UZQa2V8kqKDPO8hwmu53bVOcTL7uNkxnexeEgSCVxYBQUTUNEI8+e4LO1FOhKPKQ==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.15.0.tgz", + "integrity": "sha512-xWGAtm3fig9TIhSaNsg0FaDZ8Pyn/3re3RFlP4rhQcmjRDIPpk1EhRuNB+YSJtLzttyuToaDiNhwT1OMoGnJnw==", "dev": true, "engines": { "node": ">=10" @@ -33964,11 +33424,11 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -34054,9 +33514,9 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", "dependencies": { "function-bind": "^1.1.2" }, @@ -35234,9 +34694,28 @@ "integrity": "sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==" }, "node_modules/ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", + "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==", + "dev": true + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "dev": true }, "node_modules/ip-regex": { @@ -36161,6 +35640,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/istanbul-lib-processinfo/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -38134,9 +37622,9 @@ "integrity": "sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ==" }, "node_modules/jose": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/jose/-/jose-5.2.1.tgz", - "integrity": "sha512-qiaQhtQRw6YrOaOj0v59h3R6hUY9NvxBmmnMfKemkqYmBB0tEc97NbLP7ix44VP5p9/0YHG8Vyhzuo5YBNwviA==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.2.2.tgz", + "integrity": "sha512-/WByRr4jDcsKlvMd1dRJnPfS1GVO3WuKyaurJ/vvXcOaUQO8rnNObCQMlv/5uCceVQIq5Q4WLF44ohsdiTohdg==", "funding": { "url": "https://github.com/sponsors/panva" } @@ -38158,6 +37646,12 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true + }, "node_modules/jsc-android": { "version": "250231.0.0", "resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-250231.0.0.tgz", @@ -40970,19 +40464,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/mermaid/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/meros": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/meros/-/meros-1.3.0.tgz", @@ -41009,9 +40490,9 @@ } }, "node_modules/metro": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.80.5.tgz", - "integrity": "sha512-OE/CGbOgbi8BlTN1QqJgKOBaC27dS0JBQw473JcivrpgVnqIsluROA7AavEaTVUrB9wPUZvoNVDROn5uiM2jfw==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.80.6.tgz", + "integrity": "sha512-f6Nhnht9TxVRP6zdBq9J2jNdeDBxRmJFnjxhQS1GeCpokBvI6fTXq+wHTLz5jZA+75fwbkPSzBxBJzQa6xi0AQ==", "dependencies": { "@babel/code-frame": "^7.0.0", "@babel/core": "^7.20.0", @@ -41028,24 +40509,24 @@ "denodeify": "^1.2.1", "error-stack-parser": "^2.0.6", "graceful-fs": "^4.2.4", - "hermes-parser": "0.18.2", + "hermes-parser": "0.19.1", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.6.3", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.80.5", - "metro-cache": "0.80.5", - "metro-cache-key": "0.80.5", - "metro-config": "0.80.5", - "metro-core": "0.80.5", - "metro-file-map": "0.80.5", - "metro-resolver": "0.80.5", - "metro-runtime": "0.80.5", - "metro-source-map": "0.80.5", - "metro-symbolicate": "0.80.5", - "metro-transform-plugins": "0.80.5", - "metro-transform-worker": "0.80.5", + "metro-babel-transformer": "0.80.6", + "metro-cache": "0.80.6", + "metro-cache-key": "0.80.6", + "metro-config": "0.80.6", + "metro-core": "0.80.6", + "metro-file-map": "0.80.6", + "metro-resolver": "0.80.6", + "metro-runtime": "0.80.6", + "metro-source-map": "0.80.6", + "metro-symbolicate": "0.80.6", + "metro-transform-plugins": "0.80.6", + "metro-transform-worker": "0.80.6", "mime-types": "^2.1.27", "node-fetch": "^2.2.0", "nullthrows": "^1.1.1", @@ -41065,12 +40546,12 @@ } }, "node_modules/metro-babel-transformer": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.80.5.tgz", - "integrity": "sha512-sxH6hcWCorhTbk4kaShCWsadzu99WBL4Nvq4m/sDTbp32//iGuxtAnUK+ZV+6IEygr2u9Z0/4XoZ8Sbcl71MpA==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.80.6.tgz", + "integrity": "sha512-ssuoVC4OzqaOt3LpwfUbDfBlFGRu9v1Yf2JJnKPz0ROYHNjSBws4aUesqQQ/Ea8DbiH7TK4j4cJmm+XjdHmgqA==", "dependencies": { "@babel/core": "^7.20.0", - "hermes-parser": "0.18.2", + "hermes-parser": "0.19.1", "nullthrows": "^1.1.1" }, "engines": { @@ -41078,24 +40559,24 @@ } }, "node_modules/metro-babel-transformer/node_modules/hermes-estree": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.18.2.tgz", - "integrity": "sha512-KoLsoWXJ5o81nit1wSyEZnWUGy9cBna9iYMZBR7skKh7okYAYKqQ9/OczwpMHn/cH0hKDyblulGsJ7FknlfVxQ==" + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.19.1.tgz", + "integrity": "sha512-daLGV3Q2MKk8w4evNMKwS8zBE/rcpA800nu1Q5kM08IKijoSnPe9Uo1iIxzPKRkn95IxxsgBMPeYHt3VG4ej2g==" }, "node_modules/metro-babel-transformer/node_modules/hermes-parser": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.18.2.tgz", - "integrity": "sha512-1eQfvib+VPpgBZ2zYKQhpuOjw1tH+Emuib6QmjkJWJMhyjM8xnXMvA+76o9LhF0zOAJDZgPfQhg43cyXEyl5Ew==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.19.1.tgz", + "integrity": "sha512-Vp+bXzxYJWrpEuJ/vXxUsLnt0+y4q9zyi4zUlkLqD8FKv4LjIfOvP69R/9Lty3dCyKh0E2BU7Eypqr63/rKT/A==", "dependencies": { - "hermes-estree": "0.18.2" + "hermes-estree": "0.19.1" } }, "node_modules/metro-cache": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.80.5.tgz", - "integrity": "sha512-2u+dQ4PZwmC7eZo9uMBNhQQMig9f+w4QWBZwXCdVy/RYOHM0eObgGdMEOwODo73uxie82T9lWzxr3aZOZ+Nqtw==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.80.6.tgz", + "integrity": "sha512-NP81pHSPkzs+iNlpVkJqijrpcd6lfuDAunYH9/Rn8oLNz0yLfkl8lt+xOdUU4IkFt3oVcTBEFCnzAzv4B8YhyA==", "dependencies": { - "metro-core": "0.80.5", + "metro-core": "0.80.6", "rimraf": "^3.0.2" }, "engines": { @@ -41103,9 +40584,9 @@ } }, "node_modules/metro-cache-key": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.80.5.tgz", - "integrity": "sha512-fr3QLZUarsB3tRbVcmr34kCBsTHk0Sh9JXGvBY/w3b2lbre+Lq5gtgLyFElHPecGF7o4z1eK9r3ubxtScHWcbA==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.80.6.tgz", + "integrity": "sha512-DFmjQacC8m/S3HpELklLMWkPGP/fZPX3BSgjd0xQvwIvWyFwk8Nn/lfp/uWdEVDtDSIr64/anXU5uWohGwlWXw==", "engines": { "node": ">=18" } @@ -41164,17 +40645,17 @@ } }, "node_modules/metro-config": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.80.5.tgz", - "integrity": "sha512-elqo/lwvF+VjZ1OPyvmW/9hSiGlmcqu+rQvDKw5F5WMX48ZC+ySTD1WcaD7e97pkgAlJHVYqZ98FCjRAYOAFRQ==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.80.6.tgz", + "integrity": "sha512-vHYYvJpRTWYbmvqlR7i04xQpZCHJ6yfZ/xIcPdz2ssbdJGGJbiT1Aar9wr8RAhsccSxdJgfE5B1DB8Mo+DnhIg==", "dependencies": { "connect": "^3.6.5", "cosmiconfig": "^5.0.5", "jest-validate": "^29.6.3", - "metro": "0.80.5", - "metro-cache": "0.80.5", - "metro-core": "0.80.5", - "metro-runtime": "0.80.5" + "metro": "0.80.6", + "metro-cache": "0.80.6", + "metro-core": "0.80.6", + "metro-runtime": "0.80.6" }, "engines": { "node": ">=18" @@ -41227,21 +40708,21 @@ } }, "node_modules/metro-core": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.80.5.tgz", - "integrity": "sha512-vkLuaBhnZxTVpaZO8ZJVEHzjaqSXpOdpAiztSZ+NDaYM6jEFgle3/XIbLW91jTSf2+T8Pj5yB1G7KuOX+BcVwg==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.80.6.tgz", + "integrity": "sha512-fn4rryTUAwzFJWj7VIPDH4CcW/q7MV4oGobqR6NsuxZoIGYrVpK7pBasumu5YbCqifuErMs5s23BhmrDNeZURw==", "dependencies": { "lodash.throttle": "^4.1.1", - "metro-resolver": "0.80.5" + "metro-resolver": "0.80.6" }, "engines": { "node": ">=18" } }, "node_modules/metro-file-map": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.80.5.tgz", - "integrity": "sha512-bKCvJ05drjq6QhQxnDUt3I8x7bTcHo3IIKVobEr14BK++nmxFGn/BmFLRzVBlghM6an3gqwpNEYxS5qNc+VKcg==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.80.6.tgz", + "integrity": "sha512-S3CUqvpXpc+q3q+hCEWvFKhVqgq0VmXdZQDF6u7ue86E2elq1XLnfLOt9JSpwyhpMQRyysjSCnd/Yh6GZMNHoQ==", "dependencies": { "anymatch": "^3.0.3", "debug": "^2.2.0", @@ -41275,9 +40756,9 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/metro-minify-terser": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.80.5.tgz", - "integrity": "sha512-S7oZLLcab6YXUT6jYFX/ZDMN7Fq6xBGGAG8liMFU1UljX6cTcEC2u+UIafYgCLrdVexp/+ClxrIetVPZ5LtL/g==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.80.6.tgz", + "integrity": "sha512-83eZaH2+B+jP92KuodPqXknzwmiboKAuZY4doRfTEEXAG57pNVNN6cqSRJlwDnmaTBKRffxoncBXbYqHQgulgg==", "dependencies": { "terser": "^5.15.0" }, @@ -41286,17 +40767,17 @@ } }, "node_modules/metro-resolver": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.80.5.tgz", - "integrity": "sha512-haJ/Hveio3zv/Fr4eXVdKzjUeHHDogYok7OpRqPSXGhTXisNXB+sLN7CpcUrCddFRUDLnVaqQOYwhYsFndgUwA==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.80.6.tgz", + "integrity": "sha512-R7trfglG4zY4X9XyM9cvuffAhQ9W1reWoahr1jdEWa6rOI8PyM0qXjcsb8l+fsOQhdSiVlkKcYAmkyrs1S/zrA==", "engines": { "node": ">=18" } }, "node_modules/metro-runtime": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.80.5.tgz", - "integrity": "sha512-L0syTWJUdWzfUmKgkScr6fSBVTh6QDr8eKEkRtn40OBd8LPagrJGySBboWSgbyn9eIb4ayW3Y347HxgXBSAjmg==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.80.6.tgz", + "integrity": "sha512-21GQVd0pp2nACoK0C2PL8mBsEhIFUFFntYrWRlYNHtPQoqDzddrPEIgkyaABGXGued+dZoBlFQl+LASlmmfkvw==", "dependencies": { "@babel/runtime": "^7.0.0" }, @@ -41305,16 +40786,16 @@ } }, "node_modules/metro-source-map": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.80.5.tgz", - "integrity": "sha512-DwSF4l03mKPNqCtyQ6K23I43qzU1BViAXnuH81eYWdHglP+sDlPpY+/7rUahXEo6qXEHXfAJgVoo1sirbXbmsQ==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.80.6.tgz", + "integrity": "sha512-lqDuSLctWy9Qccu4Zl0YB1PzItpsqcKGb1nK0aDY+lzJ26X65OCib2VzHlj+xj7e4PiIKOfsvDCczCBz4cnxdg==", "dependencies": { "@babel/traverse": "^7.20.0", "@babel/types": "^7.20.0", "invariant": "^2.2.4", - "metro-symbolicate": "0.80.5", + "metro-symbolicate": "0.80.6", "nullthrows": "^1.1.1", - "ob1": "0.80.5", + "ob1": "0.80.6", "source-map": "^0.5.6", "vlq": "^1.0.0" }, @@ -41331,12 +40812,12 @@ } }, "node_modules/metro-symbolicate": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.80.5.tgz", - "integrity": "sha512-IsM4mTYvmo9JvIqwEkCZ5+YeDVPST78Q17ZgljfLdHLSpIivOHp9oVoiwQ/YGbLx0xRHRIS/tKiXueWBnj3UWA==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.80.6.tgz", + "integrity": "sha512-SGwKeBi+lK7NmM5+EcW6DyRRa9HmGSvH0LJtlT4XoRMbpxzsLYs0qUEA+olD96pOIP+ta7I8S30nQr2ttqgO8A==", "dependencies": { "invariant": "^2.2.4", - "metro-source-map": "0.80.5", + "metro-source-map": "0.80.6", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "through2": "^2.0.1", @@ -41358,9 +40839,9 @@ } }, "node_modules/metro-transform-plugins": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.80.5.tgz", - "integrity": "sha512-7IdlTqK/k5+qE3RvIU5QdCJUPk4tHWEqgVuYZu8exeW+s6qOJ66hGIJjXY/P7ccucqF+D4nsbAAW5unkoUdS6g==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.80.6.tgz", + "integrity": "sha512-e04tdTC5Fy1vOQrTTXb5biao0t7nR/h+b1IaBTlM5UaHaAJZr658uVOoZhkRxKjbhF2mIwJ/8DdorD2CA15BCg==", "dependencies": { "@babel/core": "^7.20.0", "@babel/generator": "^7.20.0", @@ -41373,21 +40854,21 @@ } }, "node_modules/metro-transform-worker": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.80.5.tgz", - "integrity": "sha512-Q1oM7hfP+RBgAtzRFBDjPhArELUJF8iRCZ8OidqCpYzQJVGuJZ7InSnIf3hn1JyqiUQwv2f1LXBO78i2rAjzyA==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.80.6.tgz", + "integrity": "sha512-jV+VgCLiCj5jQadW/h09qJaqDreL6XcBRY52STCoz2xWn6WWLLMB5nXzQtvFNPmnIOps+Xu8+d5hiPcBNOhYmA==", "dependencies": { "@babel/core": "^7.20.0", "@babel/generator": "^7.20.0", "@babel/parser": "^7.20.0", "@babel/types": "^7.20.0", - "metro": "0.80.5", - "metro-babel-transformer": "0.80.5", - "metro-cache": "0.80.5", - "metro-cache-key": "0.80.5", - "metro-minify-terser": "0.80.5", - "metro-source-map": "0.80.5", - "metro-transform-plugins": "0.80.5", + "metro": "0.80.6", + "metro-babel-transformer": "0.80.6", + "metro-cache": "0.80.6", + "metro-cache-key": "0.80.6", + "metro-minify-terser": "0.80.6", + "metro-source-map": "0.80.6", + "metro-transform-plugins": "0.80.6", "nullthrows": "^1.1.1" }, "engines": { @@ -41481,16 +40962,16 @@ } }, "node_modules/metro/node_modules/hermes-estree": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.18.2.tgz", - "integrity": "sha512-KoLsoWXJ5o81nit1wSyEZnWUGy9cBna9iYMZBR7skKh7okYAYKqQ9/OczwpMHn/cH0hKDyblulGsJ7FknlfVxQ==" + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.19.1.tgz", + "integrity": "sha512-daLGV3Q2MKk8w4evNMKwS8zBE/rcpA800nu1Q5kM08IKijoSnPe9Uo1iIxzPKRkn95IxxsgBMPeYHt3VG4ej2g==" }, "node_modules/metro/node_modules/hermes-parser": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.18.2.tgz", - "integrity": "sha512-1eQfvib+VPpgBZ2zYKQhpuOjw1tH+Emuib6QmjkJWJMhyjM8xnXMvA+76o9LhF0zOAJDZgPfQhg43cyXEyl5Ew==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.19.1.tgz", + "integrity": "sha512-Vp+bXzxYJWrpEuJ/vXxUsLnt0+y4q9zyi4zUlkLqD8FKv4LjIfOvP69R/9Lty3dCyKh0E2BU7Eypqr63/rKT/A==", "dependencies": { - "hermes-estree": "0.18.2" + "hermes-estree": "0.19.1" } }, "node_modules/metro/node_modules/minimatch": { @@ -43446,7 +42927,6 @@ "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -45873,9 +45353,9 @@ } }, "node_modules/ob1": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.80.5.tgz", - "integrity": "sha512-zYDMnnNrFi/1Tqh0vo3PE4p97Tpl9/4MP2k2ECvkbLOZzQuAYZJLTUYVLZb7hJhbhjT+JJxAwBGS8iu5hCSd1w==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.80.6.tgz", + "integrity": "sha512-nlLGZPMQ/kbmkdIb5yvVzep1jKUII2x6ehNsHpgy71jpnJMW7V+KsB3AjYI2Ajb7UqMAMNjlssg6FUodrEMYzg==", "engines": { "node": ">=18" } @@ -49149,9 +48629,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -49977,13 +49457,13 @@ } }, "node_modules/react-router-dom": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.0.tgz", - "integrity": "sha512-z2w+M4tH5wlcLmH3BMMOMdrtrJ9T3oJJNsAlBJbwk+8Syxd5WFJ7J5dxMEW0/GEXD1BBis4uXRrNIz3mORr0ag==", + "version": "6.22.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.1.tgz", + "integrity": "sha512-iwMyyyrbL7zkKY7MRjOVRy+TMnS/OPusaFVxM2P11x9dzSzGmLsebkCvYirGq0DWB9K9hOspHYYtDz33gE5Duw==", "dev": true, "dependencies": { - "@remix-run/router": "1.15.0", - "react-router": "6.22.0" + "@remix-run/router": "1.15.1", + "react-router": "6.22.1" }, "engines": { "node": ">=14.0.0" @@ -49994,12 +49474,12 @@ } }, "node_modules/react-router-dom/node_modules/react-router": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.0.tgz", - "integrity": "sha512-q2yemJeg6gw/YixRlRnVx6IRJWZD6fonnfZhN1JIOhV2iJCPeRNSH3V1ISwHf+JWcESzLC3BOLD1T07tmO5dmg==", + "version": "6.22.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.1.tgz", + "integrity": "sha512-0pdoRGwLtemnJqn1K0XHUbnKiX0S4X8CgvVVmHGOWmofESj31msHo/1YiqcJWK7Wxfq2a4uvvtS01KAQyWK/CQ==", "dev": true, "dependencies": { - "@remix-run/router": "1.15.0" + "@remix-run/router": "1.15.1" }, "engines": { "node": ">=14.0.0" @@ -50356,6 +49836,11 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -50544,13 +50029,14 @@ } }, "node_modules/regexp.prototype.flags": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", - "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "set-function-name": "^2.0.0" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -50795,9 +50281,9 @@ } }, "node_modules/remark-mdx": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.0.0.tgz", - "integrity": "sha512-O7yfjuC6ra3NHPbRVxfflafAj3LTwx3b73aBvkEFU5z4PsD6FD4vrqJAkE5iNGLz71GdjXfgRqm3SQ0h0VuE7g==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.0.1.tgz", + "integrity": "sha512-3Pz3yPQ5Rht2pM5R+0J2MrGoBSrzf+tJG94N+t/ilfdh8YLyyKYtidAYwTveB20BoHAcwIopOUqhcmh2F7hGYA==", "dev": true, "dependencies": { "mdast-util-mdx": "^3.0.0", @@ -51379,9 +50865,23 @@ } }, "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, "node_modules/safe-json-stringify": { "version": "1.2.0", @@ -52347,17 +51847,25 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.3.tgz", + "integrity": "sha512-vfuYK48HXCTFD03G/1/zkIls3Ebr2YNa4qU9gHDZdblHLiqhJrJGkY3+0Nx0JpN9qBhJbVObc1CNciT1bIZJxw==", "dev": true, "dependencies": { - "ip": "^2.0.0", + "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" }, "engines": { - "node": ">= 10.13.0", + "node": ">= 10.0.0", "npm": ">= 3.0.0" } }, @@ -52904,9 +52412,9 @@ } }, "node_modules/spdx-exceptions": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.4.0.tgz", - "integrity": "sha512-hcjppoJ68fhxA/cjbN4T8N6uCUejN8yFw69ttpqtBeCbF3u13n7mb31NB9jKwGTTWWnt9IbRA/mf1FprYS8wfw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", "dev": true }, "node_modules/spdx-expression-parse": { @@ -53345,18 +52853,18 @@ } }, "node_modules/store2": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/store2/-/store2-2.14.2.tgz", - "integrity": "sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==", + "version": "2.14.3", + "resolved": "https://registry.npmjs.org/store2/-/store2-2.14.3.tgz", + "integrity": "sha512-4QcZ+yx7nzEFiV4BMLnr/pRa5HYzNITX2ri0Zh6sT9EyQHbBHacC6YigllUPU9X3D0f/22QCgfokpKs52YRrUg==", "dev": true }, "node_modules/storybook": { - "version": "7.6.14", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-7.6.14.tgz", - "integrity": "sha512-4WMb/Dyzl4QzAd1X1b13cJXwynI7fGbT3qGy+X169hsXn6u73tlRcuPXrTsEO9a+rNBxZiBEBJf5poYxCH2j5Q==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-7.6.16.tgz", + "integrity": "sha512-VSfaYoV/iurMtLE/OcVtKvYe/Skkc+JGQYN0uni2djTlrDbZ1IbG2ig+E2MGOE6KlPmC3wCZhW6CevZyjdFhTQ==", "dev": true, "dependencies": { - "@storybook/cli": "7.6.14" + "@storybook/cli": "7.6.16" }, "bin": { "sb": "index.js", @@ -53452,6 +52960,11 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -53756,33 +53269,27 @@ } }, "node_modules/strip-literal": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", - "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.0.0.tgz", + "integrity": "sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==", "dev": true, "dependencies": { - "acorn": "^8.10.0" + "js-tokens": "^8.0.2" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, - "node_modules/strip-literal/node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-8.0.3.tgz", + "integrity": "sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==", + "dev": true }, "node_modules/stripe": { - "version": "14.16.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-14.16.0.tgz", - "integrity": "sha512-1gOr2LzafWV84cPIO5Md/QPh4XVPLKULVuRpBVOV3Plq3seiHmg/eeOktX+hDl8jpNZuORHYaUJGrNqrABLwdg==", + "version": "14.17.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-14.17.0.tgz", + "integrity": "sha512-iwV5SKoXuRIQFne4twGwiiczOkVW73eE2CKn6ltUKCacDy4SGHBX6kj1/xCV2bzzzQjcVtsh5F1aAbJTmf3tLw==", "dev": true, "dependencies": { "@types/node": ">=8.1.0", @@ -54471,9 +53978,9 @@ } }, "node_modules/terser": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz", - "integrity": "sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==", + "version": "5.27.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.1.tgz", + "integrity": "sha512-29wAr6UU/oQpnTw5HoadwjUZnFQXGdOfj0LjZ4sVxzqwHh/QVkvr7m8y9WoR4iN3FRitVduTc6KdjcW38Npsug==", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -55325,26 +54832,26 @@ } }, "node_modules/turbo": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/turbo/-/turbo-1.12.3.tgz", - "integrity": "sha512-a6q8I0TK9ohACYbkmxzG/JYPuDC4VCvfmXLTlf321qQ4BIAhoyaOj/O2g+zJ6L1vNYnZ82G4LrbMfgLLngbLsg==", + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/turbo/-/turbo-1.12.4.tgz", + "integrity": "sha512-yUJ7elEUSToiGwFZogXpYKJpQ0BvaMbkEuQECIWtkBLcmWzlMOt6bActsIm29oN83mRU0WbzGt4e8H1KHWedhg==", "dev": true, "bin": { "turbo": "bin/turbo" }, "optionalDependencies": { - "turbo-darwin-64": "1.12.3", - "turbo-darwin-arm64": "1.12.3", - "turbo-linux-64": "1.12.3", - "turbo-linux-arm64": "1.12.3", - "turbo-windows-64": "1.12.3", - "turbo-windows-arm64": "1.12.3" + "turbo-darwin-64": "1.12.4", + "turbo-darwin-arm64": "1.12.4", + "turbo-linux-64": "1.12.4", + "turbo-linux-arm64": "1.12.4", + "turbo-windows-64": "1.12.4", + "turbo-windows-arm64": "1.12.4" } }, "node_modules/turbo-darwin-64": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-1.12.3.tgz", - "integrity": "sha512-dDglIaux+A4jOnB9CDH69sujmrnuLJLrKw1t3J+if6ySlFuxSwC++gDq9TVuOZo2+S7lFkGh+x5ytn3wp+jE8Q==", + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-1.12.4.tgz", + "integrity": "sha512-dBwFxhp9isTa9RS/fz2gDVk5wWhKQsPQMozYhjM7TT4jTrnYn0ZJMzr7V3B/M/T8QF65TbniW7w1gtgxQgX5Zg==", "cpu": [ "x64" ], @@ -55355,9 +54862,9 @@ ] }, "node_modules/turbo-darwin-arm64": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-1.12.3.tgz", - "integrity": "sha512-5TqqeujEyHMoVUWGzSzUl5ERSg7HDCdbU3gBs5ziWTpFRpeJ/+Y15kYyZJcMQcubRIH3Y1hL/yA5IhlGdgXOMA==", + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-1.12.4.tgz", + "integrity": "sha512-1Uo5iI6xsJ1j9ObsqxYRsa3W26mEbUe6fnj4rQYV6kDaqYD54oAMJ6hM53q9rB8JvFxwdrUXGp3PwTw9A0qqkA==", "cpu": [ "arm64" ], @@ -55368,9 +54875,9 @@ ] }, "node_modules/turbo-linux-64": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-1.12.3.tgz", - "integrity": "sha512-yUreU+/gq4vlBtcdyfjz7slwz4zM1RG8sSXvyHmAS+QXqSrGkegg4qLl2fRbv/c3EyA/XbfcZuD6tcrXkejr6g==", + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-1.12.4.tgz", + "integrity": "sha512-ONg2aSqKP7LAQOg7ysmU5WpEQp4DGNxSlAiR7um+LKtbmC/UxogbR5+T+Uuq6zGuQ5kJyKjWJ4NhtvUswOqBsA==", "cpu": [ "x64" ], @@ -55381,9 +54888,9 @@ ] }, "node_modules/turbo-linux-arm64": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-1.12.3.tgz", - "integrity": "sha512-XRwAsp2eRSqZmaMVNrmHoKqofeJMuD87zmefZLTRAObh38hIwKgyl2QRsJIbteob5RN77yFbv3lAJ36UIY5h7w==", + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-1.12.4.tgz", + "integrity": "sha512-9FPufkwdgfIKg/9jj87Cdtftw8o36y27/S2vLN7FTR2pp9c0MQiTBOLVYadUr1FlShupddmaMbTkXEhyt9SdrA==", "cpu": [ "arm64" ], @@ -55394,9 +54901,9 @@ ] }, "node_modules/turbo-windows-64": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-1.12.3.tgz", - "integrity": "sha512-CPnRfnUCtmFeShOtUdMCthySjmyHaoTyh9JueiYFvtCNeO3WfDMj63dpOQstQWHdJFYmIrIGfhAclcds9ePQYA==", + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-1.12.4.tgz", + "integrity": "sha512-2mOtxHW5Vjh/5rDVu/aFwsMzI+chs8XcEuJHlY1sYOpEymYTz+u6AXbnzRvwZFMrLKr7J7fQOGl+v96sLKbNdA==", "cpu": [ "x64" ], @@ -55407,9 +54914,9 @@ ] }, "node_modules/turbo-windows-arm64": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-1.12.3.tgz", - "integrity": "sha512-cYA/wlzvp4vlCNHYJ2AjNS3FLXWwUC/5CJompBkTeKFFB6AviE/iLkbIhFikCVSNXZk/3AGanpMUXIkt3bdlwg==", + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-1.12.4.tgz", + "integrity": "sha512-nOY5wae9qnxPOpT1fRuYO0ks6dTwpKMPV6++VkDkamFDLFHUDVM/9kmD2UTeh1yyrKnrZksbb9zmShhmfj1wog==", "cpu": [ "arm64" ], @@ -55505,16 +55012,17 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", - "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.1.tgz", + "integrity": "sha512-tcqKMrTRXjqvHN9S3553NPCaGL0VPgFI92lXszmrE8DMhiDPLBYLlvo8Uu4WZAAX/aGqp/T1sbA4ph8EWjDF9Q==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.7", "for-each": "^0.3.3", + "gopd": "^1.0.1", "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -56328,9 +55836,13 @@ } }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { "uuid": "dist/bin/uuid" } @@ -56541,9 +56053,9 @@ } }, "node_modules/vite-node": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.2.2.tgz", - "integrity": "sha512-1as4rDTgVWJO3n1uHmUYqq7nsFgINQ9u+mRcXpjeOMJUmviqNKjcZB7UfRZrlM7MjYXMKpuWp5oGkjaFLnjawg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.3.0.tgz", + "integrity": "sha512-D/oiDVBw75XMnjAXne/4feCkCEwcbr2SU1bjAhCcfI5Bq3VoOHji8/wCPAfUkDIeohJ5nSZ39fNxM3dNZ6OBOA==", "dev": true, "dependencies": { "cac": "^6.7.14", @@ -56774,9 +56286,9 @@ } }, "node_modules/vite-node/node_modules/rollup": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", - "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -56789,26 +56301,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.10.0", - "@rollup/rollup-android-arm64": "4.10.0", - "@rollup/rollup-darwin-arm64": "4.10.0", - "@rollup/rollup-darwin-x64": "4.10.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", - "@rollup/rollup-linux-arm64-gnu": "4.10.0", - "@rollup/rollup-linux-arm64-musl": "4.10.0", - "@rollup/rollup-linux-riscv64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-musl": "4.10.0", - "@rollup/rollup-win32-arm64-msvc": "4.10.0", - "@rollup/rollup-win32-ia32-msvc": "4.10.0", - "@rollup/rollup-win32-x64-msvc": "4.10.0", + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", "fsevents": "~2.3.2" } }, "node_modules/vite-node/node_modules/vite": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", - "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", "dev": true, "dependencies": { "esbuild": "^0.19.3", @@ -56867,18 +56379,17 @@ "dev": true }, "node_modules/vitest": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.2.2.tgz", - "integrity": "sha512-d5Ouvrnms3GD9USIK36KG8OZ5bEvKEkITFtnGv56HFaSlbItJuYr7hv2Lkn903+AvRAgSixiamozUVfORUekjw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.3.0.tgz", + "integrity": "sha512-V9qb276J1jjSx9xb75T2VoYXdO1UKi+qfflY7V7w93jzX7oA/+RtYE6TcifxksxsZvygSSMwu2Uw6di7yqDMwg==", "dev": true, "dependencies": { - "@vitest/expect": "1.2.2", - "@vitest/runner": "1.2.2", - "@vitest/snapshot": "1.2.2", - "@vitest/spy": "1.2.2", - "@vitest/utils": "1.2.2", + "@vitest/expect": "1.3.0", + "@vitest/runner": "1.3.0", + "@vitest/snapshot": "1.3.0", + "@vitest/spy": "1.3.0", + "@vitest/utils": "1.3.0", "acorn-walk": "^8.3.2", - "cac": "^6.7.14", "chai": "^4.3.10", "debug": "^4.3.4", "execa": "^8.0.1", @@ -56887,11 +56398,11 @@ "pathe": "^1.1.1", "picocolors": "^1.0.0", "std-env": "^3.5.0", - "strip-literal": "^1.3.0", + "strip-literal": "^2.0.0", "tinybench": "^2.5.1", "tinypool": "^0.8.2", "vite": "^5.0.0", - "vite-node": "1.2.2", + "vite-node": "1.3.0", "why-is-node-running": "^2.2.2" }, "bin": { @@ -56906,8 +56417,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "^1.0.0", - "@vitest/ui": "^1.0.0", + "@vitest/browser": "1.3.0", + "@vitest/ui": "1.3.0", "happy-dom": "*", "jsdom": "*" }, @@ -57263,9 +56774,9 @@ } }, "node_modules/vitest/node_modules/rollup": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", - "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -57278,19 +56789,19 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.10.0", - "@rollup/rollup-android-arm64": "4.10.0", - "@rollup/rollup-darwin-arm64": "4.10.0", - "@rollup/rollup-darwin-x64": "4.10.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", - "@rollup/rollup-linux-arm64-gnu": "4.10.0", - "@rollup/rollup-linux-arm64-musl": "4.10.0", - "@rollup/rollup-linux-riscv64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-musl": "4.10.0", - "@rollup/rollup-win32-arm64-msvc": "4.10.0", - "@rollup/rollup-win32-ia32-msvc": "4.10.0", - "@rollup/rollup-win32-x64-msvc": "4.10.0", + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", "fsevents": "~2.3.2" } }, @@ -57307,9 +56818,9 @@ } }, "node_modules/vitest/node_modules/vite": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", - "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", "dev": true, "dependencies": { "esbuild": "^0.19.3", @@ -57449,9 +56960,9 @@ } }, "node_modules/web-streams-polyfill": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.2.tgz", - "integrity": "sha512-3pRGuxRF5gpuZc0W+EpwQRmCD7gRqcDOMt688KmdlDAgAyaB1XlN0zq2njfDNm44XVdIouE7pZ6GzbdyH47uIQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", "engines": { "node": ">= 8" } @@ -57472,9 +56983,9 @@ } }, "node_modules/webpack": { - "version": "5.90.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.1.tgz", - "integrity": "sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==", + "version": "5.90.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.2.tgz", + "integrity": "sha512-ziXu8ABGr0InCMEYFnHrYweinHK2PWrMqnwdHk2oK3rRhv/1B+2FnfwYv5oD+RrknK/Pp/Hmyvu+eAsaMYhzCw==", "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", @@ -58742,7 +58253,7 @@ "dependencies": { "@medplum/core": "*", "@medplum/hl7": "*", - "dcmjs-dimse": "0.1.24", + "dcmjs-dimse": "0.1.25", "node-windows": "1.0.0-beta.8", "ws": "8.16.0" }, @@ -58764,10 +58275,10 @@ "version": "3.0.3", "license": "Apache-2.0", "devDependencies": { - "@mantine/core": "7.5.2", - "@mantine/dropzone": "7.5.2", - "@mantine/hooks": "7.5.2", - "@mantine/notifications": "7.5.2", + "@mantine/core": "7.5.3", + "@mantine/dropzone": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", "@medplum/core": "*", "@medplum/definitions": "*", "@medplum/fhirtypes": "*", @@ -58776,25 +58287,25 @@ "@tabler/icons-react": "2.47.0", "@testing-library/jest-dom": "6.4.2", "@testing-library/react": "14.2.1", - "@types/react": "18.2.55", + "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.22.0", + "react-router-dom": "6.22.1", "rfc6902": "5.1.1", - "vite": "5.1.1" + "vite": "5.1.3" }, "engines": { "node": ">=18.0.0" } }, "packages/app/node_modules/rollup": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", - "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -58807,26 +58318,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.10.0", - "@rollup/rollup-android-arm64": "4.10.0", - "@rollup/rollup-darwin-arm64": "4.10.0", - "@rollup/rollup-darwin-x64": "4.10.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", - "@rollup/rollup-linux-arm64-gnu": "4.10.0", - "@rollup/rollup-linux-arm64-musl": "4.10.0", - "@rollup/rollup-linux-riscv64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-musl": "4.10.0", - "@rollup/rollup-win32-arm64-msvc": "4.10.0", - "@rollup/rollup-win32-ia32-msvc": "4.10.0", - "@rollup/rollup-win32-x64-msvc": "4.10.0", + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", "fsevents": "~2.3.2" } }, "packages/app/node_modules/vite": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", - "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", "dev": true, "dependencies": { "esbuild": "^0.19.3", @@ -58885,7 +58396,7 @@ "dependencies": { "@medplum/core": "*", "form-data": "4.0.0", - "jose": "5.2.1", + "jose": "5.2.2", "node-fetch": "2.7.0", "pdfmake": "0.2.9", "ssh2": "1.15.0", @@ -58911,12 +58422,12 @@ "version": "3.0.3", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.511.0", + "@aws-sdk/types": "3.515.0", "@medplum/core": "*", - "aws-cdk-lib": "2.127.0", - "cdk": "2.127.0", - "cdk-nag": "2.28.30", - "cdk-serverless-clamscan": "2.6.91", + "aws-cdk-lib": "2.128.0", + "cdk": "2.128.0", + "cdk-nag": "2.28.39", + "cdk-serverless-clamscan": "2.6.100", "constructs": "10.3.0" }, "engines": { @@ -58928,19 +58439,19 @@ "version": "3.0.3", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-acm": "3.511.0", - "@aws-sdk/client-cloudformation": "3.511.0", - "@aws-sdk/client-cloudfront": "3.511.0", - "@aws-sdk/client-ecs": "3.511.0", - "@aws-sdk/client-s3": "3.511.0", - "@aws-sdk/client-ssm": "3.511.0", - "@aws-sdk/client-sts": "3.511.0", - "@aws-sdk/types": "3.511.0", + "@aws-sdk/client-acm": "3.515.0", + "@aws-sdk/client-cloudformation": "3.515.0", + "@aws-sdk/client-cloudfront": "3.515.0", + "@aws-sdk/client-ecs": "3.515.0", + "@aws-sdk/client-s3": "3.515.0", + "@aws-sdk/client-ssm": "3.515.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/types": "3.515.0", "@medplum/core": "*", "@medplum/hl7": "*", "aws-sdk-client-mock": "3.0.1", "commander": "12.0.0", - "dotenv": "16.4.2", + "dotenv": "16.4.4", "fast-glob": "3.3.2", "node-fetch": "2.7.0", "tar": "6.2.0" @@ -59006,7 +58517,7 @@ "@docusaurus/theme-mermaid": "3.1.1", "@docusaurus/tsconfig": "3.1.1", "@docusaurus/types": "3.1.1", - "@mdx-js/react": "3.0.0", + "@mdx-js/react": "3.0.1", "@medplum/core": "*", "@medplum/fhirtypes": "*", "@medplum/mock": "*", @@ -59018,7 +58529,7 @@ "react": "18.2.0", "react-dom": "18.2.0", "react-intersection-observer": "9.8.0", - "react-router-dom": "6.22.0", + "react-router-dom": "6.22.1", "typescript": "5.3.3", "url-loader": "4.1.1" }, @@ -59040,10 +58551,10 @@ "version": "3.0.3", "license": "Apache-2.0", "devDependencies": { - "@typescript-eslint/eslint-plugin": "6.21.0", - "@typescript-eslint/parser": "6.21.0", + "@typescript-eslint/eslint-plugin": "7.0.1", + "@typescript-eslint/parser": "7.0.1", "eslint": "8.56.0", - "eslint-plugin-jsdoc": "48.0.6", + "eslint-plugin-jsdoc": "48.1.0", "eslint-plugin-json-files": "4.1.0", "eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-react-refresh": "0.4.5" @@ -59087,10 +58598,10 @@ "devDependencies": { "@medplum/core": "*", "@types/base-64": "1.0.2", - "@types/react": "18.2.55", + "@types/react": "18.2.56", "@types/text-encoding": "0.0.39", - "esbuild": "0.20.0", - "esbuild-node-externals": "1.12.0", + "esbuild": "0.20.1", + "esbuild-node-externals": "1.13.0", "jest": "29.7.0", "jest-expo": "50.0.2", "rimraf": "5.0.5", @@ -59182,30 +58693,30 @@ "devDependencies": { "@graphiql/react": "0.20.3", "@graphiql/toolkit": "0.9.1", - "@mantine/core": "7.5.2", - "@mantine/hooks": "7.5.2", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", "@medplum/core": "*", "@medplum/fhirtypes": "*", "@medplum/react": "*", - "@types/react": "18.2.55", + "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "graphiql": "3.1.1", "graphql": "16.8.1", - "graphql-ws": "5.14.3", + "graphql-ws": "5.15.0", "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "vite": "5.1.1" + "vite": "5.1.3" }, "engines": { "node": ">=18.0.0" } }, "packages/graphiql/node_modules/rollup": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", - "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -59218,26 +58729,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.10.0", - "@rollup/rollup-android-arm64": "4.10.0", - "@rollup/rollup-darwin-arm64": "4.10.0", - "@rollup/rollup-darwin-x64": "4.10.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", - "@rollup/rollup-linux-arm64-gnu": "4.10.0", - "@rollup/rollup-linux-arm64-musl": "4.10.0", - "@rollup/rollup-linux-riscv64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-musl": "4.10.0", - "@rollup/rollup-win32-arm64-msvc": "4.10.0", - "@rollup/rollup-win32-ia32-msvc": "4.10.0", - "@rollup/rollup-win32-x64-msvc": "4.10.0", + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", "fsevents": "~2.3.2" } }, "packages/graphiql/node_modules/vite": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", - "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", "dev": true, "dependencies": { "esbuild": "^0.19.3", @@ -59343,29 +58854,29 @@ "version": "3.0.3", "license": "Apache-2.0", "devDependencies": { - "@mantine/core": "7.5.2", - "@mantine/hooks": "7.5.2", - "@mantine/notifications": "7.5.2", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", "@medplum/core": "*", "@medplum/definitions": "*", "@medplum/fhirtypes": "*", "@medplum/mock": "*", "@medplum/react-hooks": "*", - "@storybook/addon-actions": "7.6.14", - "@storybook/addon-essentials": "7.6.14", - "@storybook/addon-links": "7.6.14", - "@storybook/addon-storysource": "7.6.14", - "@storybook/builder-vite": "7.6.14", - "@storybook/react": "7.6.14", - "@storybook/react-vite": "7.6.14", + "@storybook/addon-actions": "7.6.16", + "@storybook/addon-essentials": "7.6.16", + "@storybook/addon-links": "7.6.16", + "@storybook/addon-storysource": "7.6.16", + "@storybook/builder-vite": "7.6.16", + "@storybook/react": "7.6.16", + "@storybook/react-vite": "7.6.16", "@tabler/icons-react": "2.47.0", "@testing-library/dom": "9.3.4", "@testing-library/jest-dom": "6.4.2", "@testing-library/react": "14.2.1", "@testing-library/user-event": "14.5.2", "@types/jest": "29.5.12", - "@types/node": "20.11.17", - "@types/react": "18.2.55", + "@types/node": "20.11.19", + "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "chromatic": "10.3.1", @@ -59378,7 +58889,7 @@ "rfc6902": "5.1.1", "rimraf": "5.0.5", "sinon": "17.0.1", - "storybook": "7.6.14", + "storybook": "7.6.16", "typescript": "5.3.3", "vite-plugin-turbosnap": "^1.0.3" }, @@ -59422,8 +58933,8 @@ "@testing-library/jest-dom": "6.4.2", "@testing-library/react": "14.2.1", "@types/jest": "29.5.12", - "@types/node": "20.11.17", - "@types/react": "18.2.55", + "@types/node": "20.11.19", + "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "jest": "29.7.0", "jest-each": "29.7.0", @@ -59495,15 +59006,15 @@ "version": "3.0.3", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-cloudwatch-logs": "3.511.0", - "@aws-sdk/client-lambda": "3.511.0", - "@aws-sdk/client-s3": "3.511.0", - "@aws-sdk/client-secrets-manager": "3.511.0", - "@aws-sdk/client-sesv2": "3.511.0", - "@aws-sdk/client-ssm": "3.511.0", + "@aws-sdk/client-cloudwatch-logs": "3.515.0", + "@aws-sdk/client-lambda": "3.516.0", + "@aws-sdk/client-s3": "3.515.0", + "@aws-sdk/client-secrets-manager": "3.515.0", + "@aws-sdk/client-sesv2": "3.515.0", + "@aws-sdk/client-ssm": "3.515.0", "@aws-sdk/cloudfront-signer": "3.496.0", - "@aws-sdk/lib-storage": "3.511.0", - "@aws-sdk/types": "3.511.0", + "@aws-sdk/lib-storage": "3.515.0", + "@aws-sdk/types": "3.515.0", "@medplum/core": "*", "@medplum/definitions": "*", "@medplum/fhir-router": "*", @@ -59516,14 +59027,14 @@ "@smithy/util-stream": "2.1.1", "bcryptjs": "2.4.3", "body-parser": "1.20.2", - "bullmq": "5.1.10", + "bullmq": "5.2.1", "bytes": "3.1.2", "compression": "1.7.4", "cookie-parser": "1.4.6", "cors": "2.8.5", "cron-validator": "1.3.1", "dataloader": "2.2.2", - "dotenv": "16.4.2", + "dotenv": "16.4.4", "express": "4.18.2", "express-rate-limit": "7.1.5", "express-validator": "7.0.1", @@ -59531,7 +59042,7 @@ "hibp": "14.0.3", "http-graceful-shutdown": "^3.1.13", "ioredis": "5.3.2", - "jose": "5.2.1", + "jose": "5.2.2", "jszip": "3.10.1", "node-fetch": "2.7.0", "nodemailer": "6.9.9", @@ -59558,7 +59069,7 @@ "@types/ioredis": "4.28.10", "@types/json-schema": "7.0.15", "@types/mailparser": "3.4.4", - "@types/node": "20.11.17", + "@types/node": "20.11.19", "@types/node-fetch": "2.6.11", "@types/nodemailer": "6.4.14", "@types/pg": "8.11.0", @@ -59582,16 +59093,79 @@ "node": ">=18.0.0" } }, - "packages/server/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" + "packages/server/node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "packages/server/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "packages/server/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "packages/server/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "packages/server/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "packages/server/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" } } } diff --git a/package.json b/package.json index b4e08daeef..2e9dcdfdb5 100644 --- a/package.json +++ b/package.json @@ -37,15 +37,15 @@ "@babel/preset-react": "7.23.3", "@babel/preset-typescript": "7.23.3", "@cyclonedx/cyclonedx-npm": "1.16.1", - "@microsoft/api-documenter": "7.23.23", - "@microsoft/api-extractor": "7.40.1", + "@microsoft/api-documenter": "7.23.24", + "@microsoft/api-extractor": "7.40.2", "@types/jest": "29.5.12", - "@types/node": "20.11.17", + "@types/node": "20.11.19", "babel-jest": "29.7.0", "babel-preset-vite": "1.1.3", "cross-env": "7.0.3", "danger": "11.3.1", - "esbuild": "0.20.0", + "esbuild": "0.20.1", "identity-obj-proxy": "3.0.0", "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", @@ -57,7 +57,7 @@ "source-map-explorer": "2.5.3", "ts-node": "10.9.2", "tslib": "2.6.2", - "turbo": "1.12.3", + "turbo": "1.12.4", "typescript": "5.3.3" }, "packageManager": "npm@9.8.1", @@ -66,7 +66,7 @@ }, "overrides": { "es5-ext": "0.10.53", - "esbuild": "0.20.0", + "esbuild": "0.20.1", "got": "11.8.6", "react": "18.2.0", "react-dom": "18.2.0", diff --git a/packages/agent/package.json b/packages/agent/package.json index 7c6964c597..39429618bf 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -25,7 +25,7 @@ "dependencies": { "@medplum/core": "*", "@medplum/hl7": "*", - "dcmjs-dimse": "0.1.24", + "dcmjs-dimse": "0.1.25", "node-windows": "1.0.0-beta.8", "ws": "8.16.0" }, diff --git a/packages/app/package.json b/packages/app/package.json index f5c1f89d4e..c1dc5ea42e 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -29,10 +29,10 @@ "last 1 Chrome versions" ], "devDependencies": { - "@mantine/core": "7.5.2", - "@mantine/dropzone": "7.5.2", - "@mantine/hooks": "7.5.2", - "@mantine/notifications": "7.5.2", + "@mantine/core": "7.5.3", + "@mantine/dropzone": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", "@medplum/core": "*", "@medplum/definitions": "*", "@medplum/fhirtypes": "*", @@ -41,16 +41,16 @@ "@tabler/icons-react": "2.47.0", "@testing-library/jest-dom": "6.4.2", "@testing-library/react": "14.2.1", - "@types/react": "18.2.55", + "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.22.0", + "react-router-dom": "6.22.1", "rfc6902": "5.1.1", - "vite": "5.1.1" + "vite": "5.1.3" }, "engines": { "node": ">=18.0.0" diff --git a/packages/bot-layer/package.json b/packages/bot-layer/package.json index a96e6e6ac8..a91ce46072 100644 --- a/packages/bot-layer/package.json +++ b/packages/bot-layer/package.json @@ -24,7 +24,7 @@ "dependencies": { "@medplum/core": "*", "form-data": "4.0.0", - "jose": "5.2.1", + "jose": "5.2.2", "node-fetch": "2.7.0", "pdfmake": "0.2.9", "ssh2": "1.15.0", diff --git a/packages/cdk/package.json b/packages/cdk/package.json index 291300e9e0..214f45275c 100644 --- a/packages/cdk/package.json +++ b/packages/cdk/package.json @@ -23,12 +23,12 @@ "test": "jest --runInBand" }, "dependencies": { - "@aws-sdk/types": "3.511.0", + "@aws-sdk/types": "3.515.0", "@medplum/core": "*", - "aws-cdk-lib": "2.127.0", - "cdk": "2.127.0", - "cdk-nag": "2.28.30", - "cdk-serverless-clamscan": "2.6.91", + "aws-cdk-lib": "2.128.0", + "cdk": "2.128.0", + "cdk-nag": "2.28.39", + "cdk-serverless-clamscan": "2.6.100", "constructs": "10.3.0" }, "engines": { diff --git a/packages/cli/package.json b/packages/cli/package.json index fd32f53cd6..af38dee500 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -41,19 +41,19 @@ "test": "jest" }, "dependencies": { - "@aws-sdk/client-acm": "3.511.0", - "@aws-sdk/client-cloudformation": "3.511.0", - "@aws-sdk/client-cloudfront": "3.511.0", - "@aws-sdk/client-ecs": "3.511.0", - "@aws-sdk/client-s3": "3.511.0", - "@aws-sdk/client-ssm": "3.511.0", - "@aws-sdk/client-sts": "3.511.0", - "@aws-sdk/types": "3.511.0", + "@aws-sdk/client-acm": "3.515.0", + "@aws-sdk/client-cloudformation": "3.515.0", + "@aws-sdk/client-cloudfront": "3.515.0", + "@aws-sdk/client-ecs": "3.515.0", + "@aws-sdk/client-s3": "3.515.0", + "@aws-sdk/client-ssm": "3.515.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/types": "3.515.0", "@medplum/core": "*", "@medplum/hl7": "*", "aws-sdk-client-mock": "3.0.1", "commander": "12.0.0", - "dotenv": "16.4.2", + "dotenv": "16.4.4", "fast-glob": "3.3.2", "node-fetch": "2.7.0", "tar": "6.2.0" diff --git a/packages/docs/package.json b/packages/docs/package.json index f1c90646a6..6a11411a0f 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -46,7 +46,7 @@ "@docusaurus/theme-mermaid": "3.1.1", "@docusaurus/tsconfig": "3.1.1", "@docusaurus/types": "3.1.1", - "@mdx-js/react": "3.0.0", + "@mdx-js/react": "3.0.1", "@medplum/core": "*", "@medplum/fhirtypes": "*", "@medplum/mock": "*", @@ -58,7 +58,7 @@ "react": "18.2.0", "react-dom": "18.2.0", "react-intersection-observer": "9.8.0", - "react-router-dom": "6.22.0", + "react-router-dom": "6.22.1", "typescript": "5.3.3", "url-loader": "4.1.1" }, diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index ac71d49fdd..2842693659 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -19,10 +19,10 @@ "author": "Medplum ", "main": "index.cjs", "devDependencies": { - "@typescript-eslint/eslint-plugin": "6.21.0", - "@typescript-eslint/parser": "6.21.0", + "@typescript-eslint/eslint-plugin": "7.0.1", + "@typescript-eslint/parser": "7.0.1", "eslint": "8.56.0", - "eslint-plugin-jsdoc": "48.0.6", + "eslint-plugin-jsdoc": "48.1.0", "eslint-plugin-json-files": "4.1.0", "eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-react-refresh": "0.4.5" diff --git a/packages/expo-polyfills/package.json b/packages/expo-polyfills/package.json index 7635b031bb..c909b3127c 100644 --- a/packages/expo-polyfills/package.json +++ b/packages/expo-polyfills/package.json @@ -49,10 +49,10 @@ "devDependencies": { "@medplum/core": "*", "@types/base-64": "1.0.2", - "@types/react": "18.2.55", + "@types/react": "18.2.56", "@types/text-encoding": "0.0.39", - "esbuild": "0.20.0", - "esbuild-node-externals": "1.12.0", + "esbuild": "0.20.1", + "esbuild-node-externals": "1.13.0", "jest": "29.7.0", "jest-expo": "50.0.2", "rimraf": "5.0.5", diff --git a/packages/graphiql/package.json b/packages/graphiql/package.json index 4729ee18ac..a11645f470 100644 --- a/packages/graphiql/package.json +++ b/packages/graphiql/package.json @@ -25,21 +25,21 @@ "devDependencies": { "@graphiql/react": "0.20.3", "@graphiql/toolkit": "0.9.1", - "@mantine/core": "7.5.2", - "@mantine/hooks": "7.5.2", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", "@medplum/core": "*", "@medplum/fhirtypes": "*", "@medplum/react": "*", - "@types/react": "18.2.55", + "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "graphiql": "3.1.1", "graphql": "16.8.1", - "graphql-ws": "5.14.3", + "graphql-ws": "5.15.0", "postcss": "8.4.35", "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "vite": "5.1.1" + "vite": "5.1.3" }, "engines": { "node": ">=18.0.0" diff --git a/packages/react-hooks/package.json b/packages/react-hooks/package.json index ed837e0595..92c9760539 100644 --- a/packages/react-hooks/package.json +++ b/packages/react-hooks/package.json @@ -65,8 +65,8 @@ "@testing-library/jest-dom": "6.4.2", "@testing-library/react": "14.2.1", "@types/jest": "29.5.12", - "@types/node": "20.11.17", - "@types/react": "18.2.55", + "@types/node": "20.11.19", + "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "jest": "29.7.0", "jest-each": "29.7.0", diff --git a/packages/react/package.json b/packages/react/package.json index 398d22ffca..9bee102774 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -67,29 +67,29 @@ "test": "jest" }, "devDependencies": { - "@mantine/core": "7.5.2", - "@mantine/hooks": "7.5.2", - "@mantine/notifications": "7.5.2", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", "@medplum/core": "*", "@medplum/definitions": "*", "@medplum/fhirtypes": "*", "@medplum/mock": "*", "@medplum/react-hooks": "*", - "@storybook/addon-actions": "7.6.14", - "@storybook/addon-essentials": "7.6.14", - "@storybook/addon-links": "7.6.14", - "@storybook/addon-storysource": "7.6.14", - "@storybook/builder-vite": "7.6.14", - "@storybook/react": "7.6.14", - "@storybook/react-vite": "7.6.14", + "@storybook/addon-actions": "7.6.16", + "@storybook/addon-essentials": "7.6.16", + "@storybook/addon-links": "7.6.16", + "@storybook/addon-storysource": "7.6.16", + "@storybook/builder-vite": "7.6.16", + "@storybook/react": "7.6.16", + "@storybook/react-vite": "7.6.16", "@tabler/icons-react": "2.47.0", "@testing-library/dom": "9.3.4", "@testing-library/jest-dom": "6.4.2", "@testing-library/react": "14.2.1", "@testing-library/user-event": "14.5.2", "@types/jest": "29.5.12", - "@types/node": "20.11.17", - "@types/react": "18.2.55", + "@types/node": "20.11.19", + "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "chromatic": "10.3.1", @@ -102,7 +102,7 @@ "rfc6902": "5.1.1", "rimraf": "5.0.5", "sinon": "17.0.1", - "storybook": "7.6.14", + "storybook": "7.6.16", "typescript": "5.3.3", "vite-plugin-turbosnap": "^1.0.3" }, diff --git a/packages/server/package.json b/packages/server/package.json index 9cd8bdb00d..1d4ae53f17 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -21,15 +21,15 @@ "test": "jest --runInBand" }, "dependencies": { - "@aws-sdk/client-cloudwatch-logs": "3.511.0", - "@aws-sdk/client-lambda": "3.511.0", - "@aws-sdk/client-s3": "3.511.0", - "@aws-sdk/client-secrets-manager": "3.511.0", - "@aws-sdk/client-sesv2": "3.511.0", - "@aws-sdk/client-ssm": "3.511.0", + "@aws-sdk/client-cloudwatch-logs": "3.515.0", + "@aws-sdk/client-lambda": "3.516.0", + "@aws-sdk/client-s3": "3.515.0", + "@aws-sdk/client-secrets-manager": "3.515.0", + "@aws-sdk/client-sesv2": "3.515.0", + "@aws-sdk/client-ssm": "3.515.0", "@aws-sdk/cloudfront-signer": "3.496.0", - "@aws-sdk/lib-storage": "3.511.0", - "@aws-sdk/types": "3.511.0", + "@aws-sdk/lib-storage": "3.515.0", + "@aws-sdk/types": "3.515.0", "@medplum/core": "*", "@medplum/definitions": "*", "@medplum/fhir-router": "*", @@ -42,14 +42,14 @@ "@smithy/util-stream": "2.1.1", "bcryptjs": "2.4.3", "body-parser": "1.20.2", - "bullmq": "5.1.10", + "bullmq": "5.2.1", "bytes": "3.1.2", "compression": "1.7.4", "cookie-parser": "1.4.6", "cors": "2.8.5", "cron-validator": "1.3.1", "dataloader": "2.2.2", - "dotenv": "16.4.2", + "dotenv": "16.4.4", "express": "4.18.2", "express-rate-limit": "7.1.5", "express-validator": "7.0.1", @@ -57,7 +57,7 @@ "hibp": "14.0.3", "http-graceful-shutdown": "^3.1.13", "ioredis": "5.3.2", - "jose": "5.2.1", + "jose": "5.2.2", "jszip": "3.10.1", "node-fetch": "2.7.0", "nodemailer": "6.9.9", @@ -84,7 +84,7 @@ "@types/ioredis": "4.28.10", "@types/json-schema": "7.0.15", "@types/mailparser": "3.4.4", - "@types/node": "20.11.17", + "@types/node": "20.11.19", "@types/node-fetch": "2.6.11", "@types/nodemailer": "6.4.14", "@types/pg": "8.11.0", From 1433b4eb1e82c4470e6de918e2deba3f2833b5ae Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Mon, 19 Feb 2024 10:03:01 -0800 Subject: [PATCH 59/81] FHIR Mapper `for` loops (#3989) * FHIR Mapper for loops * Fixed jsdoc * Tutorial step 12 * Comments on 'for' keyword * Fixed sonar warning --- packages/core/src/fhirmapper/parse.ts | 6 + .../core/src/fhirmapper/transform.test.ts | 98 ++++++ packages/core/src/fhirmapper/transform.ts | 288 ++++++++++++++++-- 3 files changed, 373 insertions(+), 19 deletions(-) diff --git a/packages/core/src/fhirmapper/parse.ts b/packages/core/src/fhirmapper/parse.ts index 87f6019de6..ddc2a39c19 100644 --- a/packages/core/src/fhirmapper/parse.ts +++ b/packages/core/src/fhirmapper/parse.ts @@ -198,6 +198,12 @@ class StructureMapParser { } private parseRuleSources(): StructureMapGroupRuleSource[] { + if (this.parser.hasMore() && this.parser.peek()?.value === 'for') { + // The "for" keyword is optional + // It is not in the official grammar: https://build.fhir.org/mapping.g4 + // But it is used in the examples: https://build.fhir.org/mapping-tutorial.html + this.parser.consume('Symbol', 'for'); + } const sources = [this.parseRuleSource()]; while (this.parser.hasMore() && this.parser.peek()?.value === ',') { this.parser.consume(','); diff --git a/packages/core/src/fhirmapper/transform.test.ts b/packages/core/src/fhirmapper/transform.test.ts index 6590ffcffb..1a4dc3201a 100644 --- a/packages/core/src/fhirmapper/transform.test.ts +++ b/packages/core/src/fhirmapper/transform.test.ts @@ -321,4 +321,102 @@ describe('FHIR Mapper transform', () => { const actual = structureMapTransform(parseMappingLanguage(map), input); expect(actual).toMatchObject(expected); }); + + test('Co-dependency in translation', () => { + // https://build.fhir.org/mapping-tutorial.html#step9 + // Another common translation is where the target mapping for one element depends on the value of another element. + + const map = ` + uses "http://hl7.org/fhir/StructureDefinition/tutorial-left" as source + uses "http://hl7.org/fhir/StructureDefinition/tutorial-right" as target + + group tutorial(source src : TLeft, target tgt : TRight) { + src.i as i where m < 2 -> tgt.j = i; + src.i as i where m >= 2 -> tgt.k = i; + } + `; + + const input1 = [toTypedValue({ i: 'foo', m: 1 })]; + const expected1 = [toTypedValue({ j: 'foo' })]; + const actual1 = structureMapTransform(parseMappingLanguage(map), input1); + expect(actual1).toMatchObject(expected1); + + const input2 = [toTypedValue({ i: 'foo', m: 3 })]; + const expected2 = [toTypedValue({ k: 'foo' })]; + const actual2 = structureMapTransform(parseMappingLanguage(map), input2); + expect(actual2).toMatchObject(expected2); + }); + + test('Reworking Structure #1', () => { + // https://build.fhir.org/mapping-tutorial.html#step11 + // It's now time to start moving away from relatively simple cases to some of the harder ones to manage mappings for. + // The first mixes list management, and converting from a specific structure to a general structure: + + const map = ` + uses "http://hl7.org/fhir/StructureDefinition/tutorial-left" as source + uses "http://hl7.org/fhir/StructureDefinition/tutorial-right" as target + + group tutorial(source src : TLeft, target tgt : TRight) { + src.e as s_e -> tgt.e as t_e then { + for s_e -> t_e.f = s_e, t_e.g = 'g1'; + }; + + src.f as s_f -> tgt.e as t_e first then { + s_f -> t_e.f = s_f, t_e.g = 'g2'; + }; + } + `; + + const input = [toTypedValue({ e: ['foo', 'bar'], f: 'baz' }), toTypedValue({ e: [] })]; + const expected = [ + toTypedValue({ + e: [ + { f: 'foo', g: 'g1' }, + { f: 'bar', g: 'g1' }, + { f: 'baz', g: 'g2' }, + ], + }), + ]; + const actual = structureMapTransform(parseMappingLanguage(map), input); + expect(actual).toMatchObject(expected); + }); + + test('Reworking Structure #2', () => { + // https://build.fhir.org/mapping-tutorial.html#step12 + // The second example for reworking structure moves cardinality around the hierarchy. + // In this case, the source has an optional structure that contains a repeating structure, + // while the target puts the cardinality at the next level up: + + const map = ` + uses "http://hl7.org/fhir/StructureDefinition/tutorial-left" as source + uses "http://hl7.org/fhir/StructureDefinition/tutorial-right" as target + + group tutorial(source src : TLeft, target tgt : TRight) { + // setting up a variable for the parent + src.az1 as s_az1 then { + + // one tgt.az1 for each az3 + s_az1.az3 as s_az3 -> tgt.az1 as t_az1 then { + // value for az2. Note that this refers to a previous context in the source + s_az1.az2 as az2 -> t_az1.az2 = az2; + + // value for az3 + s_az3 -> t_az1.az3 = s_az3; + }; + }; + } + `; + + const input = [toTypedValue({ az1: { az2: 'foo', az3: ['bar', 'baz'] }, f: 'baz' }), toTypedValue({ az1: [] })]; + const expected = [ + toTypedValue({ + az1: [ + { az2: 'foo', az3: 'bar' }, + { az2: 'foo', az3: 'baz' }, + ], + }), + ]; + const actual = structureMapTransform(parseMappingLanguage(map), input); + expect(actual).toMatchObject(expected); + }); }); diff --git a/packages/core/src/fhirmapper/transform.ts b/packages/core/src/fhirmapper/transform.ts index 854978c5be..86b29db4cb 100644 --- a/packages/core/src/fhirmapper/transform.ts +++ b/packages/core/src/fhirmapper/transform.ts @@ -41,12 +41,29 @@ export function structureMapTransform( return evalStructureMap({ root: structureMap, loader }, structureMap, input); } +/** + * Evaluates a FHIR StructureMap. + * + * @param ctx - The transform context. + * @param structureMap - The FHIR StructureMap definition. + * @param input - The input values. + * @returns The transformed values. + * @internal + */ function evalStructureMap(ctx: TransformContext, structureMap: StructureMap, input: TypedValue[]): TypedValue[] { evalImports(ctx, structureMap); hoistGroups(ctx, structureMap); - return evalGroup(ctx, (structureMap.group as StructureMapGroup[])[0], input); + return evalGroup(ctx, structureMap.group[0], input); } +/** + * Evaluates the imports in a FHIR StructureMap. + * For each import statement, the loader function is called to load the imported StructureMap. + * The imported StructureMap is then hoisted into the current context. + * @param ctx - The transform context. + * @param structureMap - The FHIR StructureMap definition. + * @internal + */ function evalImports(ctx: TransformContext, structureMap: StructureMap): void { if (ctx.loader && structureMap.import) { for (const url of structureMap.import) { @@ -58,6 +75,14 @@ function evalImports(ctx: TransformContext, structureMap: StructureMap): void { } } +/** + * Hoists the groups in a FHIR StructureMap into the current context. + * This is necessary to allow groups to reference each other. + * + * @param ctx - The transform context. + * @param structureMap - The FHIR StructureMap definition. + * @internal + */ function hoistGroups(ctx: TransformContext, structureMap: StructureMap): void { if (structureMap.group) { for (const group of structureMap.group) { @@ -66,6 +91,17 @@ function hoistGroups(ctx: TransformContext, structureMap: StructureMap): void { } } +/** + * Evaluates a FHIR StructureMapGroup. + * + * A "group" is similar to a function in a programming language. + * + * @param ctx - The transform context. + * @param group - The FHIR StructureMapGroup definition. + * @param input - The input values. + * @returns The transformed values. + * @internal + */ function evalGroup(ctx: TransformContext, group: StructureMapGroup, input: TypedValue[]): TypedValue[] { const sourceDefinitions: StructureMapGroupInput[] = []; const targetDefinitions: StructureMapGroupInput[] = []; @@ -122,20 +158,66 @@ function evalGroup(ctx: TransformContext, group: StructureMapGroup, input: Typed return outputs; } +/** + * Entry point for evaluating a rule. + * Rule sources are evaluated first, followed by the rule target, child rules, and dependent groups. + * Rule sources are evaluated recursively to handle multiple source statements. + * + * @param ctx - The transform context. + * @param rule - The FHIR Mapping rule definition. + * @internal + */ function evalRule(ctx: TransformContext, rule: StructureMapGroupRule): void { - let haveSource = false; + // https://build.fhir.org/mapping-language.html#7.8.0.8.1 + // If there are multiple source statements, the rule applies for the permutation of the source elements from each source statement. + // E.g. if there are 2 source statements, each with 2 matching elements, the rule applies 4 times, one for each combination. + // Typically, if there is more than one source statement, only one of the elements would repeat. + // If any of the source data elements have no value, then the rule never applies; + // only existing permutations are executed: for multiple source statements, all of them need to match. if (rule.source) { - for (const source of rule.source) { - if (evalSource(ctx, source)) { - haveSource = true; - } - } + evalRuleSourceAt(ctx, rule, 0); } +} - if (!haveSource) { - return; +/** + * Recursively evaluates a rule at a specific source index. + * + * @param ctx - The transform context. + * @param rule - The FHIR Mapping rule definition. + * @param index - The source index to evaluate. + * @internal + */ +function evalRuleSourceAt( + ctx: TransformContext, + rule: StructureMapGroupRule & { source: StructureMapGroupRuleSource[] }, + index: number +): void { + const source = rule.source[index]; + for (const sourceValue of evalSource(ctx, source)) { + if (source.variable) { + setVariable(ctx, source.variable as string, sourceValue); + } + + if (index < rule.source.length - 1) { + // If there are more sources, evaluate the next source + evalRuleSourceAt(ctx, rule, index + 1); + } else { + // Otherwise, evaluate the rule after the sources + evalRuleAfterSources(ctx, rule); + } } +} +/** + * Evaluates a rule after the sources have been evaluated. + * + * This includes the rule targets, child rules, and dependent groups. + * + * @param ctx - The transform context. + * @param rule - The FHIR Mapping rule definition. + * @internal + */ +function evalRuleAfterSources(ctx: TransformContext, rule: StructureMapGroupRule): void { if (rule.target) { for (const target of rule.target) { evalTarget(ctx, target); @@ -153,25 +235,36 @@ function evalRule(ctx: TransformContext, rule: StructureMapGroupRule): void { } } -function evalSource(ctx: TransformContext, source: StructureMapGroupRuleSource): boolean { +/** + * Evaluates a FHIR Mapping source definition. + * + * If the source has a condition, the condition is evaluated. + * If the source has a check, the check is evaluated. + * + * @param ctx - The transform context. + * @param source - The FHIR Mapping source definition. + * @returns The evaluated source values. + * @internal + */ +function evalSource(ctx: TransformContext, source: StructureMapGroupRuleSource): TypedValue[] { const sourceContext = getVariable(ctx, source.context as string) as TypedValue | undefined; if (!sourceContext) { - return false; + return []; } const sourceElement = source.element; if (!sourceElement) { - return true; + return [sourceContext]; } let sourceValue = evalFhirPathTyped(sourceElement, [sourceContext]); if (!sourceValue || sourceValue.length === 0) { - return false; + return []; } if (source.condition) { if (!evalCondition(sourceContext, { [source.variable as string]: sourceValue[0] }, source.condition)) { - return false; + return []; } } @@ -185,17 +278,32 @@ function evalSource(ctx: TransformContext, source: StructureMapGroupRuleSource): sourceValue = evalListMode(source, sourceValue); } - if (source.variable) { - setVariable(ctx, source.variable, unarrayify(sourceValue)); - } - - return true; + return sourceValue; } +/** + * Evaluates a FHIRPath condition for a FHIR Mapping source. + * + * This is used for both the "condition" and "check" properties. + * + * @param input - The input value, typically the rule source. + * @param variables - The variables in scope for the FHIRPath expression. + * @param condition - The FHIRPath condition to evaluate. + * @returns True if the condition is true, false otherwise. + * @internal + */ function evalCondition(input: TypedValue, variables: Record, condition: string): boolean { return toJsBoolean(evalFhirPathTyped(condition, [input], variables)); } +/** + * Evaluates the list mode for a FHIR Mapping source. + * + * @param source - The FHIR Mapping source definition. + * @param sourceValue - The source values. + * @returns The evaluated source values. + * @internal + */ function evalListMode(source: StructureMapGroupRuleSource, sourceValue: TypedValue[]): TypedValue[] { // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check switch (source.listMode) { @@ -216,6 +324,13 @@ function evalListMode(source: StructureMapGroupRuleSource, sourceValue: TypedVal return sourceValue; } +/** + * Evaluates a FHIR Mapping target definition. + * + * @param ctx - The transform context. + * @param target - The FHIR Mapping target definition. + * @internal + */ function evalTarget(ctx: TransformContext, target: StructureMapGroupRuleTarget): void { const targetContext = getVariable(ctx, target.context as string) as TypedValue | undefined; if (!targetContext) { @@ -284,22 +399,69 @@ function evalTarget(ctx: TransformContext, target: StructureMapGroupRuleTarget): } } +/** + * Returns true if the target property is an array field. + * + * @param targetContext - The target context. + * @param element - The element to check (i.e., the property name). + * @returns True if the target property is an array field. + * @internal + */ function isArrayProperty(targetContext: TypedValue, element: string): boolean | undefined { const targetContextTypeDefinition = tryGetDataType(targetContext.type); const targetPropertyTypeDefinition = targetContextTypeDefinition?.elements?.[element]; return targetPropertyTypeDefinition?.isArray; } +/** + * Evaluates the "append" transform. + * + * "Source is element or string - just append them all together" + * + * See: https://build.fhir.org/mapping-language.html#7.8.0.8.2 + * + * @param ctx - The transform context. + * @param target - The FHIR Mapping target definition. + * @returns The evaluated target values. + * @internal + */ function evalAppend(ctx: TransformContext, target: StructureMapGroupRuleTarget): TypedValue[] { const arg1 = resolveParameter(ctx, target.parameter?.[0])?.[0]?.value; const arg2 = resolveParameter(ctx, target.parameter?.[1])?.[0]?.value; return [{ type: 'string', value: (arg1 ?? '').toString() + (arg2 ?? '').toString() }]; } +/** + * Evaluates the "copy" transform. + * + * "Simply copy the source to the target as is (only allowed when the types in source and target match- typically for primitive types). + * In the concrete syntax, this is simply represented as the source variable, e.g. src.a = tgt.b" + * + * See: https://build.fhir.org/mapping-language.html#7.8.0.8.2 + * + * @param ctx - The transform context. + * @param target - The FHIR Mapping target definition. + * @returns The evaluated target values. + * @internal + */ function evalCopy(ctx: TransformContext, target: StructureMapGroupRuleTarget): TypedValue[] { return (target.parameter as StructureMapGroupRuleTargetParameter[]).flatMap((p) => resolveParameter(ctx, p)); } +/** + * Evaluates the "create" transform. + * + * "Use the standard API to create a new instance of data. + * Where structure definitions have been provided, the type parameter must be a string which is a known type of a root element. + * Where they haven't, the application must know the name somehow."" + * + * See: https://build.fhir.org/mapping-language.html#7.8.0.8.2 + * + * @param ctx - The transform context. + * @param target - The FHIR Mapping target definition. + * @returns The evaluated target values. + * @internal + */ function evalCreate(ctx: TransformContext, target: StructureMapGroupRuleTarget): TypedValue[] { const result: Record = {}; if (target.parameter && target.parameter.length > 0) { @@ -308,12 +470,39 @@ function evalCreate(ctx: TransformContext, target: StructureMapGroupRuleTarget): return [toTypedValue(result)]; } +/** + * Evaluates the "evaluate" transform. + * + * "Execute the supplied FHIRPath expression and use the value returned by that." + * + * See: https://build.fhir.org/mapping-language.html#7.8.0.8.2 + * + * @param ctx - The transform context. + * @param target - The FHIR Mapping target definition. + * @returns The evaluated target values. + * @internal + */ function evalEvaluate(ctx: TransformContext, target: StructureMapGroupRuleTarget): TypedValue[] { const typedExpr = resolveParameter(ctx, target.parameter?.[0]); const expr = typedExpr[0].value as string; return evalFhirPathTyped(expr, [], buildFhirPathVariables(ctx) as Record); } +/** + * Evaluates the "translate" transform. + * + * "Use the translate operation. The source is some type of code or coded datatype, + * and the source and map_uri are passed to the translate operation. + * The output determines what value from the translate operation is used for the result of the operation + * (code, system, display, Coding, or CodeableConcept)" + * + * See: https://build.fhir.org/mapping-language.html#7.8.0.8.2 + * + * @param ctx - The transform context. + * @param target - The FHIR Mapping target definition. + * @returns The evaluated target values. + * @internal + */ function evalTranslate(ctx: TransformContext, target: StructureMapGroupRuleTarget): TypedValue[] { const args = (target.parameter as StructureMapGroupRuleTargetParameter[]).flatMap((p) => resolveParameter(ctx, p)); const sourceValue = args[0].value; @@ -326,6 +515,18 @@ function evalTranslate(ctx: TransformContext, target: StructureMapGroupRuleTarge return [toTypedValue(result.match?.[0]?.concept?.code)]; } +/** + * Evaluates the "truncate" transform. + * + * "Source must be some stringy type that has some meaningful length property" + * + * See: https://build.fhir.org/mapping-language.html#7.8.0.8.2 + * + * @param ctx - The transform context. + * @param target - The FHIR Mapping target definition. + * @returns The evaluated target values. + * @internal + */ function evalTruncate(ctx: TransformContext, target: StructureMapGroupRuleTarget): TypedValue[] { const targetValue = resolveParameter(ctx, target.parameter?.[0])?.[0]; const targetLength = resolveParameter(ctx, target.parameter?.[1])?.[0]?.value as number; @@ -335,6 +536,15 @@ function evalTruncate(ctx: TransformContext, target: StructureMapGroupRuleTarget return [targetValue]; } +/** + * Evaluates a rule dependent group. + * + * See: https://hl7.org/fhir/r4/structuremap-definitions.html#StructureMap.group.rule.dependent + * + * @param ctx - The transform context. + * @param dependent - The FHIR Mapping dependent definition. + * @internal + */ function evalDependent(ctx: TransformContext, dependent: StructureMapGroupRuleDependent): void { const dependentGroup = getVariable(ctx, dependent.name as string) as TypedValue | undefined; if (!dependentGroup) { @@ -355,6 +565,18 @@ function evalDependent(ctx: TransformContext, dependent: StructureMapGroupRuleDe evalGroup(newContext, dependentGroup.value as StructureMapGroup, args); } +/** + * Resolves the value of a FHIR Mapping target parameter. + * + * For literal values, the value is returned as-is. + * + * For variables, the value is looked up in the current context. + * + * @param ctx - The transform context. + * @param parameter - The FHIR Mapping target parameter definition. + * @returns The resolved parameter values. + * @internal + */ function resolveParameter( ctx: TransformContext, parameter: StructureMapGroupRuleTargetParameter | undefined @@ -378,6 +600,16 @@ function resolveParameter( return paramValue; } +/** + * Returns a variable value by name. + * + * Recursively searches the parent context if the variable is not found in the current context. + * + * @param ctx - The transform context. + * @param name - The variable name. + * @returns The variable value. + * @internal + */ function getVariable(ctx: TransformContext, name: string): TypedValue[] | TypedValue | undefined { const value = ctx.variables?.[name]; if (value) { @@ -389,6 +621,16 @@ function getVariable(ctx: TransformContext, name: string): TypedValue[] | TypedV return undefined; } +/** + * Builds a collection of FHIRPath variables from the current context. + * + * Recursively searches the parent context to build the complete set of variables. + * + * @param ctx - The transform context. + * @param result - The builder output. + * @returns The result with the FHIRPath variables. + * @internal + */ function buildFhirPathVariables( ctx: TransformContext, result: Record = {} @@ -405,6 +647,14 @@ function buildFhirPathVariables( return result; } +/** + * Sets a variable value in the current context. + * + * @param ctx - The transform context. + * @param name - The variable name. + * @param value - The variable value. + * @internal + */ function setVariable(ctx: TransformContext, name: string, value: TypedValue[] | TypedValue): void { if (!ctx.variables) { ctx.variables = {}; From 157fedf73e0fbed3ce1847701f8779b2f2b38ad8 Mon Sep 17 00:00:00 2001 From: jmalobicky Date: Mon, 19 Feb 2024 14:52:48 -0500 Subject: [PATCH 60/81] Ensure medplum-agent is executable in Dockerfile (#3991) --- packages/agent/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/agent/Dockerfile b/packages/agent/Dockerfile index 4aea49a8cf..081cae6f9d 100644 --- a/packages/agent/Dockerfile +++ b/packages/agent/Dockerfile @@ -9,6 +9,7 @@ ENV MEDPLUM_VERSION ${MEDPLUM_VERSION} RUN adduser -u 5678 --disabled-password --gecos "" app COPY bin/medplum-agent-${MEDPLUM_VERSION}-linux /srv/medplum-agent +RUN chmod +x /srv/medplum-agent WORKDIR /srv From 0c082568e66cff844bd78a2754dcac8e6d0a4f32 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Mon, 19 Feb 2024 12:57:37 -0800 Subject: [PATCH 61/81] Upgrade github actions deps (#3992) * Upgrade github actions deps * Only upload code coverage once --- .github/workflows/build.yml | 9 +++++---- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/coveralls.yml | 2 +- .github/workflows/deploy.yml | 6 +++--- .github/workflows/madge.yml | 6 +++--- .github/workflows/prepare-release.yml | 4 ++-- .github/workflows/prettier-fmt.yml | 6 +++--- .github/workflows/publish.yml | 14 +++++++------- .github/workflows/sonar.yml | 2 +- .github/workflows/staging.yml | 6 +++--- .github/workflows/upgrade-dependencies.yml | 4 ++-- 11 files changed, 31 insertions(+), 30 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3bf7c00e6d..3f2218a3f9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,15 +40,15 @@ jobs: ports: - 5432/tcp steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} registry-url: 'https://registry.npmjs.org' - name: Cache node modules - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-node-modules with: @@ -82,7 +82,8 @@ jobs: POSTGRES_HOST: localhost POSTGRES_PORT: ${{ job.services.postgres.ports[5432] }} - name: Upload code coverage - uses: actions/upload-artifact@v3 + if: ${{ matrix.node-version == 20 && matrix.pg-version == 14 }} + uses: actions/upload-artifact@v4 with: name: medplum-code-coverage path: coverage/lcov.info diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e8294223bd..b17c7a403a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -44,7 +44,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/coveralls.yml b/.github/workflows/coveralls.yml index 5968ea83cf..3b93d7becc 100644 --- a/.github/workflows/coveralls.yml +++ b/.github/workflows/coveralls.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest if: github.event.workflow_run.conclusion == 'success' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: repository: ${{ github.event.workflow_run.head_repository.full_name }} ref: ${{ github.event.workflow_run.head_branch }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 926e5f7248..25e8f21310 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -16,15 +16,15 @@ jobs: TURBO_TEAM: ${{ secrets.TURBO_TEAM }} TURBO_REMOTE_ONLY: ${{ secrets.TURBO_REMOTE_ONLY }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 20 registry-url: 'https://registry.npmjs.org' - name: Cache node modules - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-node-modules with: diff --git a/.github/workflows/madge.yml b/.github/workflows/madge.yml index 71dabc1ba4..917f8ff338 100644 --- a/.github/workflows/madge.yml +++ b/.github/workflows/madge.yml @@ -16,15 +16,15 @@ jobs: outputs: madge_check_errs: ${{ steps.madge.outputs.madge_check_errs }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 20 registry-url: 'https://registry.npmjs.org' - name: Cache node modules - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-node-modules with: diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index f3b554e205..d85dcd7860 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -7,11 +7,11 @@ jobs: name: Prepare release runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 token: ${{ secrets.MEDPLUM_BOT_GITHUB_ACCESS_TOKEN }} - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 20 registry-url: 'https://registry.npmjs.org' diff --git a/.github/workflows/prettier-fmt.yml b/.github/workflows/prettier-fmt.yml index 78b4c61a42..b471ef509c 100644 --- a/.github/workflows/prettier-fmt.yml +++ b/.github/workflows/prettier-fmt.yml @@ -20,15 +20,15 @@ jobs: outputs: prettier_fmt_errs: ${{ steps.fmt.outputs.prettier_fmt_errs }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 20 registry-url: 'https://registry.npmjs.org' - name: Cache node modules - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-node-modules with: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 636f95f3cd..831e07018f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -21,16 +21,16 @@ jobs: pull-requests: write steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 20 registry-url: 'https://registry.npmjs.org' - name: Cache node modules - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-node-modules with: @@ -103,7 +103,7 @@ jobs: contents: write steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install NSIS run: choco install nsis @@ -118,7 +118,7 @@ jobs: java-version: '17' - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 20 registry-url: 'https://registry.npmjs.org' @@ -178,10 +178,10 @@ jobs: contents: write steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 20 registry-url: 'https://registry.npmjs.org' diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 02c665780b..90415c714b 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest if: github.event.workflow_run.conclusion == 'success' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: repository: ${{ github.event.workflow_run.head_repository.full_name }} ref: ${{ github.event.workflow_run.head_branch }} diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index 6cc948ccda..2567455bb4 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -16,15 +16,15 @@ jobs: TURBO_TEAM: ${{ secrets.TURBO_TEAM }} TURBO_REMOTE_ONLY: ${{ secrets.TURBO_REMOTE_ONLY }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 20 registry-url: 'https://registry.npmjs.org' - name: Cache node modules - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-node-modules with: diff --git a/.github/workflows/upgrade-dependencies.yml b/.github/workflows/upgrade-dependencies.yml index 3448ab6b5c..90df5def78 100644 --- a/.github/workflows/upgrade-dependencies.yml +++ b/.github/workflows/upgrade-dependencies.yml @@ -12,11 +12,11 @@ jobs: name: Upgrade dependencies runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 token: ${{ secrets.MEDPLUM_BOT_GITHUB_ACCESS_TOKEN }} - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 20 registry-url: 'https://registry.npmjs.org' From 0ac4b67603460c1f4df0581d7db28b3fd70e9918 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Mon, 19 Feb 2024 13:06:57 -0800 Subject: [PATCH 62/81] Fixes #3975 - _format and _pretty query params (#3993) --- packages/core/src/search/parse.test.ts | 23 ++++++++++++++++++- packages/core/src/search/search.ts | 10 ++++++++ packages/server/src/admin/invite.ts | 2 +- packages/server/src/fhir/job.ts | 2 +- packages/server/src/fhir/operations/README.md | 2 +- .../src/fhir/operations/codesystemimport.ts | 2 +- .../src/fhir/operations/codesystemlookup.ts | 2 +- .../fhir/operations/codesystemvalidatecode.ts | 2 +- .../fhir/operations/conceptmaptranslate.ts | 2 +- .../src/fhir/operations/evaluatemeasure.ts | 2 +- .../src/fhir/operations/getwsbindingtoken.ts | 2 +- .../src/fhir/operations/patienteverything.ts | 2 +- .../fhir/operations/plandefinitionapply.ts | 2 +- .../src/fhir/operations/projectclone.ts | 2 +- .../server/src/fhir/operations/projectinit.ts | 2 +- .../src/fhir/operations/resourcegraph.ts | 2 +- .../structuredefinitionexpandprofile.ts | 2 +- .../fhir/operations/utils/parameters.test.ts | 20 +++++++++------- .../src/fhir/operations/utils/parameters.ts | 5 ++-- packages/server/src/fhir/response.ts | 19 +++++++++++++-- packages/server/src/fhir/routes.test.ts | 10 +++++++- packages/server/src/fhir/routes.ts | 2 +- 22 files changed, 89 insertions(+), 30 deletions(-) diff --git a/packages/core/src/search/parse.test.ts b/packages/core/src/search/parse.test.ts index f36f7cbcb9..3a34d5fa35 100644 --- a/packages/core/src/search/parse.test.ts +++ b/packages/core/src/search/parse.test.ts @@ -2,7 +2,7 @@ import { readJson } from '@medplum/definitions'; import { Bundle, SearchParameter } from '@medplum/fhirtypes'; import { indexSearchParameterBundle } from '../types'; import { indexStructureDefinitionBundle } from '../typeschema/types'; -import { Operator, parseSearchRequest, parseSearchUrl, SearchRequest } from './search'; +import { Operator, SearchRequest, parseSearchRequest, parseSearchUrl } from './search'; describe('Search parser', () => { beforeAll(() => { @@ -653,4 +653,25 @@ describe('Search parser', () => { parseSearchRequest('Patient', { _revinclude: 'Patient:*' }); }).toThrow(); }); + + test('_format', () => { + expect(parseSearchRequest('Patient', { _format: 'json' })).toMatchObject({ + resourceType: 'Patient', + format: 'json', + }); + }); + + test('_pretty=true', () => { + expect(parseSearchRequest('Patient', { _pretty: 'true' })).toMatchObject({ + resourceType: 'Patient', + pretty: true, + }); + }); + + test('_pretty=false', () => { + expect(parseSearchRequest('Patient', { _pretty: 'false' })).toMatchObject({ + resourceType: 'Patient', + pretty: false, + }); + }); }); diff --git a/packages/core/src/search/search.ts b/packages/core/src/search/search.ts index 7d6f16b3e1..3f7be841cb 100644 --- a/packages/core/src/search/search.ts +++ b/packages/core/src/search/search.ts @@ -18,6 +18,8 @@ export interface SearchRequest { include?: IncludeTarget[]; revInclude?: IncludeTarget[]; summary?: 'true' | 'text' | 'data'; + format?: string; + pretty?: boolean; } export interface Filter { @@ -258,6 +260,14 @@ function parseKeyValue(searchRequest: SearchRequest, key: string, value: string) searchRequest.fields = value.split(','); break; + case '_format': + searchRequest.format = value; + break; + + case '_pretty': + searchRequest.pretty = value === 'true'; + break; + default: { const param = globalSchema.types[searchRequest.resourceType]?.searchParams?.[code]; if (param) { diff --git a/packages/server/src/admin/invite.ts b/packages/server/src/admin/invite.ts index 7630123dfb..944775f7f0 100644 --- a/packages/server/src/admin/invite.ts +++ b/packages/server/src/admin/invite.ts @@ -50,7 +50,7 @@ export async function inviteHandler(req: Request, res: Response): Promise } const { membership } = await inviteUser(inviteRequest); - return sendResponse(res, allOk, membership); + return sendResponse(req, res, allOk, membership); } export interface ServerInviteRequest extends InviteRequest { diff --git a/packages/server/src/fhir/job.ts b/packages/server/src/fhir/job.ts index d367100dbd..4ca1f121f1 100644 --- a/packages/server/src/fhir/job.ts +++ b/packages/server/src/fhir/job.ts @@ -24,7 +24,7 @@ jobRouter.get( return; } - await sendResponse(res, allOk, asyncJob); + await sendResponse(req, res, allOk, asyncJob); }) ); diff --git a/packages/server/src/fhir/operations/README.md b/packages/server/src/fhir/operations/README.md index d1e274a22d..30668a836d 100644 --- a/packages/server/src/fhir/operations/README.md +++ b/packages/server/src/fhir/operations/README.md @@ -65,6 +65,6 @@ export async function projectInitHandler(req: Request, res: Response): Promise ({ resource: r, diff --git a/packages/server/src/fhir/operations/structuredefinitionexpandprofile.ts b/packages/server/src/fhir/operations/structuredefinitionexpandprofile.ts index 48021ee927..01e4ef3c8e 100644 --- a/packages/server/src/fhir/operations/structuredefinitionexpandprofile.ts +++ b/packages/server/src/fhir/operations/structuredefinitionexpandprofile.ts @@ -32,7 +32,7 @@ export async function structureDefinitionExpandProfileHandler(req: Request, res: const bundle = bundleResults([profile, ...sds]); - await sendResponse(res, allOk, bundle); + await sendResponse(req, res, allOk, bundle); } async function fetchProfileByUrl(repo: Repository, url: string): Promise { diff --git a/packages/server/src/fhir/operations/utils/parameters.test.ts b/packages/server/src/fhir/operations/utils/parameters.test.ts index 56377b6ef8..a5f7bfbf38 100644 --- a/packages/server/src/fhir/operations/utils/parameters.test.ts +++ b/packages/server/src/fhir/operations/utils/parameters.test.ts @@ -231,6 +231,10 @@ describe('Operation Input Parameters parsing', () => { }); describe('Send Operation output Parameters', () => { + const req = { + query: {}, + } as unknown as Request; + const res = { set: jest.fn(), status: jest.fn(), @@ -243,7 +247,7 @@ describe('Send Operation output Parameters', () => { }); test('Single required parameter', async () => { - await sendOutputParameters(opDef, res, allOk, { singleOut: { value: 20.2, unit: 'kg/m^2' } }); + await sendOutputParameters(req, res, opDef, allOk, { singleOut: { value: 20.2, unit: 'kg/m^2' } }); expect(res.status).toHaveBeenCalledWith(200); expect(res.json).toHaveBeenCalledWith<[Parameters]>({ @@ -253,7 +257,7 @@ describe('Send Operation output Parameters', () => { }); test('Optional output parameter', async () => { - await sendOutputParameters(opDef, res, created, { + await sendOutputParameters(req, res, opDef, created, { singleOut: { value: 20.2, unit: 'kg/m^2' }, multiOut: [{ reference: 'Observation/height' }, { reference: 'Observation/weight' }], }); @@ -286,7 +290,7 @@ describe('Send Operation output Parameters', () => { unit: 'kg/m^2', }, } as Observation; - await sendOutputParameters(resourceReturnOp, res, allOk, obs); + await sendOutputParameters(req, res, resourceReturnOp, allOk, obs); expect(res.status).toHaveBeenCalledWith(200); expect(res.json).toHaveBeenCalledWith(obs); @@ -299,7 +303,7 @@ describe('Send Operation output Parameters', () => { parameter: [{ name: 'return', use: 'out', type: 'Observation', min: 1, max: '1' }], }; const ref = { reference: 'Observation/bmi' } as Reference; - await sendOutputParameters(resourceReturnOp, res, allOk, ref); + await sendOutputParameters(req, res, resourceReturnOp, allOk, ref); expect(res.status).toHaveBeenCalledWith(500); expect(res.json).toHaveBeenCalledWith<[OperationOutcome]>( @@ -324,7 +328,7 @@ describe('Send Operation output Parameters', () => { parameter: [{ name: 'return', use: 'out', type: 'Observation', min: 1, max: '1' }], }; const patient = { resourceType: 'Patient' } as Patient; - await sendOutputParameters(resourceReturnOp, res, allOk, patient); + await sendOutputParameters(req, res, resourceReturnOp, allOk, patient); expect(res.status).toHaveBeenCalledWith(500); expect(res.json).toHaveBeenCalledWith<[OperationOutcome]>( @@ -344,7 +348,7 @@ describe('Send Operation output Parameters', () => { test('Missing required parameter', () => withTestContext(async () => { - await sendOutputParameters(opDef, res, allOk, { incorrectOut: { value: 20.2, unit: 'kg/m^2' } }); + await sendOutputParameters(req, res, opDef, allOk, { incorrectOut: { value: 20.2, unit: 'kg/m^2' } }); expect(res.status).toHaveBeenCalledWith(500); expect(res.json).toHaveBeenCalledWith<[OperationOutcome]>( @@ -363,7 +367,7 @@ describe('Send Operation output Parameters', () => { })); test('Omits extraneous parameters', async () => { - await sendOutputParameters(opDef, res, allOk, { singleOut: { value: 20.2, unit: 'kg/m^2' }, extraOut: 'foo' }); + await sendOutputParameters(req, res, opDef, allOk, { singleOut: { value: 20.2, unit: 'kg/m^2' }, extraOut: 'foo' }); expect(res.status).toHaveBeenCalledWith(200); expect(res.json).toHaveBeenCalledWith<[Parameters]>({ @@ -374,7 +378,7 @@ describe('Send Operation output Parameters', () => { test('Returns error on invalid output', () => withTestContext(async () => { - await sendOutputParameters(opDef, res, allOk, { singleOut: { reference: 'Observation/foo' } }); + await sendOutputParameters(req, res, opDef, allOk, { singleOut: { reference: 'Observation/foo' } }); expect(res.status).toHaveBeenCalledWith(500); expect(res.json).toHaveBeenCalledWith<[OperationOutcome]>( diff --git a/packages/server/src/fhir/operations/utils/parameters.ts b/packages/server/src/fhir/operations/utils/parameters.ts index 22713bf2e1..7090453574 100644 --- a/packages/server/src/fhir/operations/utils/parameters.ts +++ b/packages/server/src/fhir/operations/utils/parameters.ts @@ -106,8 +106,9 @@ function parseParams( } export async function sendOutputParameters( - operation: OperationDefinition, + req: Request, res: Response, + operation: OperationDefinition, outcome: OperationOutcome, output: any ): Promise { @@ -121,7 +122,7 @@ export async function sendOutputParameters( ); } else { // Send Resource as output directly, instead of using Parameters format - await sendResponse(res, outcome, output); + await sendResponse(req, res, outcome, output); } return; } diff --git a/packages/server/src/fhir/response.ts b/packages/server/src/fhir/response.ts index b9012e1d49..2644e38e51 100644 --- a/packages/server/src/fhir/response.ts +++ b/packages/server/src/fhir/response.ts @@ -13,7 +13,12 @@ export function getFullUrl(resourceType: string, id: string): string { return `${getConfig().baseUrl}fhir/R4/${resourceType}/${id}`; } -export async function sendResponse(res: Response, outcome: OperationOutcome, body: Resource): Promise { +export async function sendResponse( + req: Request, + res: Response, + outcome: OperationOutcome, + body: Resource +): Promise { const ctx = getAuthenticatedContext(); if (body.meta?.versionId) { res.set('ETag', `W/"${body.meta.versionId}"`); @@ -24,5 +29,15 @@ export async function sendResponse(res: Response, outcome: OperationOutcome, bod if (isCreated(outcome)) { res.set('Location', getFullUrl(body.resourceType, body.id as string)); } - res.status(getStatus(outcome)).json(await rewriteAttachments(RewriteMode.PRESIGNED_URL, ctx.repo, body)); + + res.status(getStatus(outcome)); + res.set('Content-Type', ContentType.FHIR_JSON); + + const result = await rewriteAttachments(RewriteMode.PRESIGNED_URL, ctx.repo, body); + + if (req.query._pretty === 'true') { + res.send(JSON.stringify(result, undefined, 2)); + } else { + res.json(result); + } } diff --git a/packages/server/src/fhir/routes.test.ts b/packages/server/src/fhir/routes.test.ts index de4d305e10..5e8a74c377 100644 --- a/packages/server/src/fhir/routes.test.ts +++ b/packages/server/src/fhir/routes.test.ts @@ -160,13 +160,21 @@ describe('FHIR Routes', () => { expect(res.status).toBe(400); }); - test('Read resourcex', async () => { + test('Read resource', async () => { const res = await request(app) .get(`/fhir/R4/Patient/${patientId}`) .set('Authorization', 'Bearer ' + accessToken); expect(res.status).toBe(200); }); + test('Read resource _pretty', async () => { + const res = await request(app) + .get(`/fhir/R4/Patient/${patientId}?_pretty=true`) + .set('Authorization', 'Bearer ' + accessToken); + expect(res.status).toBe(200); + expect(res.text).toEqual(JSON.stringify(res.body, undefined, 2)); + }); + test('Read resource invalid UUID', async () => { const res = await request(app) .get(`/fhir/R4/Patient/123`) diff --git a/packages/server/src/fhir/routes.ts b/packages/server/src/fhir/routes.ts index 233e9296f2..e037b23fde 100644 --- a/packages/server/src/fhir/routes.ts +++ b/packages/server/src/fhir/routes.ts @@ -225,7 +225,7 @@ protectedRoutes.use( } sendOutcome(res, result[0]); } else { - await sendResponse(res, result[0], result[1]); + await sendResponse(req, res, result[0], result[1]); } }) ); From e781d5bf8ed80c074fa710fa125aefede233e385 Mon Sep 17 00:00:00 2001 From: Medplum Bot <152649536+medplumbot@users.noreply.github.com> Date: Mon, 19 Feb 2024 15:00:25 -0800 Subject: [PATCH 63/81] Release Version 3.0.4 (#3994) Fixes #3975 - _format and _pretty query params (#3993) Upgrade github actions deps (#3992) Ensure medplum-agent is executable in Dockerfile (#3991) FHIR Mapper `for` loops (#3989) Dependency upgrades (#3990) Document the admin page of the Medplum App (#3968) Added note about required scopes for refresh token (#3988) Add withHelpText prop to ValueSetAutocomplete (#3985) Content update for case study page (#3983) Add scheduled job to upgrade dependencies (#3984) Use same pattern as ResourcePage for default tab (#3972) Websocket demo updates (#3981) Add Agent Dockerfile. Fixes #3912 (#3913) fix-3976 fully propagate traceId to user executed code and into bot-injected medplum client (#3977) More precise clearing of resource.meta in bundle conversion (#3973) Search and paging in ChooseProfileForm (#3962) Apply pattern and fixed values to profile resources (#3918) Add note about admin access policies (#3966) Link sort section to search parameters (#3967) Add maxValues prop to ResourceTypeInput (#3964) Show system and code in ValueSetAutocomplete (#3963) Patient tabs in example provider app (#3947) Dosespot Website Enhancements: part 2 (#3949) feat(subscriptions): add client utils for WebSocket subscriptions (#3890) Don't return early on cache miss (#3948) Remove vercel commands from medplum-provider/vercel.json (#3941) Fixes #3786 - startAsyncRequest and token refresh (#3937) Fixed socket.dev dependency warnings (#3930) Initial medplum-provider package for #3833 (#3939) Add search result parameters to CapabilityStatement (#3936) Added DoseSpot FAQ page (#3933) Revert "Fixes #3786 - startAsyncRequest and token refresh (#3932)" Fixes #3786 - startAsyncRequest and token refresh (#3932) Fixes #3708 - CareTeam.name and Group.name search params (#3934) Fixes #3838 - github actions permissions (#3931) Fix turborepo for forks (#3926) Dependency upgrades (#3925) Fixes #3904 - Added identifiers to Users and Projects (#3924) Fixes #3506 - default bot runtime config setting (#3923) Fix attachment diff display (#3922) Gravatar photos (#3921) Enable linking between Projects (#3909) Enhance request logging (#3915) --- examples/foomedical/package.json | 12 +- examples/medplum-chart-demo/package.json | 10 +- examples/medplum-demo-bots/package.json | 12 +- examples/medplum-fhircast-demo/package.json | 10 +- examples/medplum-hello-world/package.json | 10 +- examples/medplum-live-chat-demo/package.json | 10 +- examples/medplum-nextjs-demo/package.json | 8 +- examples/medplum-provider/package.json | 10 +- .../medplum-react-native-example/package.json | 8 +- examples/medplum-task-demo/package.json | 12 +- .../package.json | 14 +- package-lock.json | 165 +++++++++--------- package.json | 2 +- packages/agent/package.json | 2 +- packages/app/package.json | 2 +- packages/bot-layer/package.json | 2 +- packages/cdk/package.json | 2 +- packages/cli/package.json | 2 +- packages/core/package.json | 2 +- packages/definitions/package.json | 2 +- packages/docs/package.json | 2 +- packages/eslint-config/package.json | 2 +- packages/examples/package.json | 2 +- packages/expo-polyfills/package.json | 2 +- packages/fhir-router/package.json | 2 +- packages/fhirtypes/package.json | 2 +- packages/generator/package.json | 2 +- packages/graphiql/package.json | 2 +- packages/health-gorilla/package.json | 2 +- packages/hl7/package.json | 2 +- packages/mock/package.json | 2 +- packages/react-hooks/package.json | 2 +- packages/react/package.json | 2 +- packages/server/package.json | 2 +- sonar-project.properties | 2 +- 35 files changed, 165 insertions(+), 162 deletions(-) diff --git a/examples/foomedical/package.json b/examples/foomedical/package.json index 01e94bd6d6..a7b613a90d 100644 --- a/examples/foomedical/package.json +++ b/examples/foomedical/package.json @@ -1,6 +1,6 @@ { "name": "foomedical", - "version": "3.0.3", + "version": "3.0.4", "type": "module", "scripts": { "build": "tsc && vite build", @@ -28,11 +28,11 @@ "@mantine/core": "7.5.3", "@mantine/hooks": "7.5.3", "@mantine/notifications": "7.5.3", - "@medplum/core": "3.0.3", - "@medplum/eslint-config": "3.0.3", - "@medplum/fhirtypes": "3.0.3", - "@medplum/mock": "3.0.3", - "@medplum/react": "3.0.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", + "@medplum/react": "3.0.4", "@tabler/icons-react": "2.47.0", "@testing-library/jest-dom": "6.4.2", "@testing-library/react": "14.2.1", diff --git a/examples/medplum-chart-demo/package.json b/examples/medplum-chart-demo/package.json index 1845d5d1ee..963efab1a0 100644 --- a/examples/medplum-chart-demo/package.json +++ b/examples/medplum-chart-demo/package.json @@ -1,6 +1,6 @@ { "name": "medplum-chart-demo", - "version": "3.0.3", + "version": "3.0.4", "private": true, "type": "module", "scripts": { @@ -22,10 +22,10 @@ "@mantine/core": "7.5.3", "@mantine/hooks": "7.5.3", "@mantine/notifications": "7.5.3", - "@medplum/core": "3.0.3", - "@medplum/eslint-config": "3.0.3", - "@medplum/fhirtypes": "3.0.3", - "@medplum/react": "3.0.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", "@tabler/icons-react": "2.47.0", "@types/node": "20.11.19", "@types/react": "18.2.56", diff --git a/examples/medplum-demo-bots/package.json b/examples/medplum-demo-bots/package.json index 7f217f4b97..6a8d5a5a71 100644 --- a/examples/medplum-demo-bots/package.json +++ b/examples/medplum-demo-bots/package.json @@ -1,6 +1,6 @@ { "name": "medplum-demo-bots", - "version": "3.0.3", + "version": "3.0.4", "description": "Medplum Demo Bots", "license": "Apache-2.0", "author": "Medplum ", @@ -25,11 +25,11 @@ ] }, "devDependencies": { - "@medplum/cli": "3.0.3", - "@medplum/core": "3.0.3", - "@medplum/eslint-config": "3.0.3", - "@medplum/fhirtypes": "3.0.3", - "@medplum/mock": "3.0.3", + "@medplum/cli": "3.0.4", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", "@types/node": "20.11.19", "@types/node-fetch": "2.6.11", "@types/ssh2-sftp-client": "9.0.3", diff --git a/examples/medplum-fhircast-demo/package.json b/examples/medplum-fhircast-demo/package.json index 33284d63db..73662c1d45 100644 --- a/examples/medplum-fhircast-demo/package.json +++ b/examples/medplum-fhircast-demo/package.json @@ -1,6 +1,6 @@ { "name": "medplum-fhircast-demo", - "version": "3.0.3", + "version": "3.0.4", "private": true, "type": "module", "scripts": { @@ -18,10 +18,10 @@ "@mantine/core": "7.5.3", "@mantine/hooks": "7.5.3", "@mantine/notifications": "7.5.3", - "@medplum/core": "3.0.3", - "@medplum/eslint-config": "3.0.3", - "@medplum/fhirtypes": "3.0.3", - "@medplum/react": "3.0.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", "@tabler/icons-react": "2.47.0", "@types/react": "18.2.56", "@types/react-dom": "18.2.19", diff --git a/examples/medplum-hello-world/package.json b/examples/medplum-hello-world/package.json index 1a37b4e1a7..ffffb31275 100644 --- a/examples/medplum-hello-world/package.json +++ b/examples/medplum-hello-world/package.json @@ -1,6 +1,6 @@ { "name": "medplum-hello-world", - "version": "3.0.3", + "version": "3.0.4", "private": true, "type": "module", "scripts": { @@ -22,10 +22,10 @@ "@mantine/core": "7.5.3", "@mantine/hooks": "7.5.3", "@mantine/notifications": "7.5.3", - "@medplum/core": "3.0.3", - "@medplum/eslint-config": "3.0.3", - "@medplum/fhirtypes": "3.0.3", - "@medplum/react": "3.0.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", "@tabler/icons-react": "2.47.0", "@types/node": "20.11.19", "@types/react": "18.2.56", diff --git a/examples/medplum-live-chat-demo/package.json b/examples/medplum-live-chat-demo/package.json index 6d010bb88a..ce0df630af 100644 --- a/examples/medplum-live-chat-demo/package.json +++ b/examples/medplum-live-chat-demo/package.json @@ -1,6 +1,6 @@ { "name": "medplum-live-chat-demo", - "version": "3.0.3", + "version": "3.0.4", "private": true, "type": "module", "scripts": { @@ -22,10 +22,10 @@ "@mantine/core": "7.5.3", "@mantine/hooks": "7.5.3", "@mantine/notifications": "7.5.3", - "@medplum/core": "3.0.3", - "@medplum/eslint-config": "3.0.3", - "@medplum/fhirtypes": "3.0.3", - "@medplum/react": "3.0.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", "@tabler/icons-react": "2.47.0", "@types/node": "20.11.19", "@types/react": "18.2.56", diff --git a/examples/medplum-nextjs-demo/package.json b/examples/medplum-nextjs-demo/package.json index 9b0486bbd0..e70623fb19 100644 --- a/examples/medplum-nextjs-demo/package.json +++ b/examples/medplum-nextjs-demo/package.json @@ -1,6 +1,6 @@ { "name": "medplum-nextjs-demo", - "version": "3.0.3", + "version": "3.0.4", "private": true, "type": "module", "scripts": { @@ -13,15 +13,15 @@ "@mantine/core": "7.5.3", "@mantine/hooks": "7.5.3", "@mantine/notifications": "7.5.3", - "@medplum/core": "3.0.3", - "@medplum/react": "3.0.3", + "@medplum/core": "3.0.4", + "@medplum/react": "3.0.4", "next": "14.1.0", "react": "18.2.0", "react-dom": "18.2.0", "rfc6902": "5.1.1" }, "devDependencies": { - "@medplum/fhirtypes": "3.0.3", + "@medplum/fhirtypes": "3.0.4", "@types/node": "20.11.19", "@types/react": "18.2.56", "@types/react-dom": "18.2.19", diff --git a/examples/medplum-provider/package.json b/examples/medplum-provider/package.json index 38f5b38181..3ab551a9f4 100644 --- a/examples/medplum-provider/package.json +++ b/examples/medplum-provider/package.json @@ -1,6 +1,6 @@ { "name": "medplum-provider", - "version": "3.0.3", + "version": "3.0.4", "private": true, "type": "module", "scripts": { @@ -22,10 +22,10 @@ "@mantine/core": "7.5.3", "@mantine/hooks": "7.5.3", "@mantine/notifications": "7.5.3", - "@medplum/core": "3.0.3", - "@medplum/eslint-config": "3.0.3", - "@medplum/fhirtypes": "3.0.3", - "@medplum/react": "3.0.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", "@tabler/icons-react": "2.47.0", "@types/node": "20.11.19", "@types/react": "18.2.56", diff --git a/examples/medplum-react-native-example/package.json b/examples/medplum-react-native-example/package.json index 9f20951c00..37b91c30f5 100644 --- a/examples/medplum-react-native-example/package.json +++ b/examples/medplum-react-native-example/package.json @@ -1,6 +1,6 @@ { "name": "medplum-react-native-example", - "version": "3.0.3", + "version": "3.0.4", "main": "src/main.ts", "scripts": { "android": "expo start --android", @@ -20,9 +20,9 @@ }, "dependencies": { "@expo/webpack-config": "19.0.1", - "@medplum/core": "3.0.3", - "@medplum/expo-polyfills": "3.0.3", - "@medplum/react-hooks": "3.0.3", + "@medplum/core": "3.0.4", + "@medplum/expo-polyfills": "3.0.4", + "@medplum/react-hooks": "3.0.4", "expo": "50.0.7", "expo-status-bar": "1.11.1", "react": "18.2.0", diff --git a/examples/medplum-task-demo/package.json b/examples/medplum-task-demo/package.json index 535771d6a0..be69fa3334 100644 --- a/examples/medplum-task-demo/package.json +++ b/examples/medplum-task-demo/package.json @@ -1,6 +1,6 @@ { "name": "medplum-task-demo", - "version": "3.0.3", + "version": "3.0.4", "private": true, "type": "module", "scripts": { @@ -25,11 +25,11 @@ "@mantine/core": "7.5.3", "@mantine/hooks": "7.5.3", "@mantine/notifications": "7.5.3", - "@medplum/core": "3.0.3", - "@medplum/definitions": "3.0.3", - "@medplum/eslint-config": "3.0.3", - "@medplum/fhirtypes": "3.0.3", - "@medplum/react": "3.0.3", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", "@tabler/icons-react": "2.47.0", "@types/node": "20.11.19", "@types/react": "18.2.56", diff --git a/examples/medplum-websocket-subscriptions-demo/package.json b/examples/medplum-websocket-subscriptions-demo/package.json index fe2c093f3c..318a4c8d4f 100644 --- a/examples/medplum-websocket-subscriptions-demo/package.json +++ b/examples/medplum-websocket-subscriptions-demo/package.json @@ -1,6 +1,6 @@ { "name": "medplum-websocket-subscriptions-demo", - "version": "3.0.3", + "version": "3.0.4", "private": true, "type": "module", "scripts": { @@ -23,12 +23,12 @@ "@mantine/core": "7.5.3", "@mantine/hooks": "7.5.3", "@mantine/notifications": "7.5.3", - "@medplum/core": "3.0.3", - "@medplum/eslint-config": "3.0.3", - "@medplum/fhir-router": "3.0.3", - "@medplum/fhirtypes": "3.0.3", - "@medplum/mock": "3.0.3", - "@medplum/react": "3.0.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhir-router": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", + "@medplum/react": "3.0.4", "@tabler/icons-react": "2.47.0", "@types/node": "20.11.19", "@types/react": "18.2.56", diff --git a/package-lock.json b/package-lock.json index 2437b37aad..1a74abe9a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "root", - "version": "3.0.3", + "version": "3.0.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "root", - "version": "3.0.3", + "version": "3.0.4", "workspaces": [ "packages/*", "examples/*" @@ -45,7 +45,7 @@ } }, "examples/foomedical": { - "version": "3.0.3", + "version": "3.0.4", "devDependencies": { "@babel/core": "7.23.9", "@babel/preset-env": "7.23.9", @@ -54,11 +54,11 @@ "@mantine/core": "7.5.3", "@mantine/hooks": "7.5.3", "@mantine/notifications": "7.5.3", - "@medplum/core": "3.0.3", - "@medplum/eslint-config": "3.0.3", - "@medplum/fhirtypes": "3.0.3", - "@medplum/mock": "3.0.3", - "@medplum/react": "3.0.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", + "@medplum/react": "3.0.4", "@tabler/icons-react": "2.47.0", "@testing-library/jest-dom": "6.4.2", "@testing-library/react": "14.2.1", @@ -172,15 +172,15 @@ } }, "examples/medplum-chart-demo": { - "version": "3.0.3", + "version": "3.0.4", "devDependencies": { "@mantine/core": "7.5.3", "@mantine/hooks": "7.5.3", "@mantine/notifications": "7.5.3", - "@medplum/core": "3.0.3", - "@medplum/eslint-config": "3.0.3", - "@medplum/fhirtypes": "3.0.3", - "@medplum/react": "3.0.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", "@tabler/icons-react": "2.47.0", "@types/node": "20.11.19", "@types/react": "18.2.56", @@ -283,14 +283,14 @@ } }, "examples/medplum-demo-bots": { - "version": "3.0.3", + "version": "3.0.4", "license": "Apache-2.0", "devDependencies": { - "@medplum/cli": "3.0.3", - "@medplum/core": "3.0.3", - "@medplum/eslint-config": "3.0.3", - "@medplum/fhirtypes": "3.0.3", - "@medplum/mock": "3.0.3", + "@medplum/cli": "3.0.4", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", "@types/node": "20.11.19", "@types/node-fetch": "2.6.11", "@types/ssh2-sftp-client": "9.0.3", @@ -308,15 +308,15 @@ } }, "examples/medplum-fhircast-demo": { - "version": "3.0.3", + "version": "3.0.4", "devDependencies": { "@mantine/core": "7.5.3", "@mantine/hooks": "7.5.3", "@mantine/notifications": "7.5.3", - "@medplum/core": "3.0.3", - "@medplum/eslint-config": "3.0.3", - "@medplum/fhirtypes": "3.0.3", - "@medplum/react": "3.0.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", "@tabler/icons-react": "2.47.0", "@types/react": "18.2.56", "@types/react-dom": "18.2.19", @@ -418,15 +418,15 @@ } }, "examples/medplum-hello-world": { - "version": "3.0.3", + "version": "3.0.4", "devDependencies": { "@mantine/core": "7.5.3", "@mantine/hooks": "7.5.3", "@mantine/notifications": "7.5.3", - "@medplum/core": "3.0.3", - "@medplum/eslint-config": "3.0.3", - "@medplum/fhirtypes": "3.0.3", - "@medplum/react": "3.0.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", "@tabler/icons-react": "2.47.0", "@types/node": "20.11.19", "@types/react": "18.2.56", @@ -529,15 +529,15 @@ } }, "examples/medplum-live-chat-demo": { - "version": "3.0.3", + "version": "3.0.4", "devDependencies": { "@mantine/core": "7.5.3", "@mantine/hooks": "7.5.3", "@mantine/notifications": "7.5.3", - "@medplum/core": "3.0.3", - "@medplum/eslint-config": "3.0.3", - "@medplum/fhirtypes": "3.0.3", - "@medplum/react": "3.0.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", "@tabler/icons-react": "2.47.0", "@types/node": "20.11.19", "@types/react": "18.2.56", @@ -640,20 +640,20 @@ } }, "examples/medplum-nextjs-demo": { - "version": "3.0.3", + "version": "3.0.4", "dependencies": { "@mantine/core": "7.5.3", "@mantine/hooks": "7.5.3", "@mantine/notifications": "7.5.3", - "@medplum/core": "3.0.3", - "@medplum/react": "3.0.3", + "@medplum/core": "3.0.4", + "@medplum/react": "3.0.4", "next": "14.1.0", "react": "18.2.0", "react-dom": "18.2.0", "rfc6902": "5.1.1" }, "devDependencies": { - "@medplum/fhirtypes": "3.0.3", + "@medplum/fhirtypes": "3.0.4", "@types/node": "20.11.19", "@types/react": "18.2.56", "@types/react-dom": "18.2.19", @@ -665,15 +665,15 @@ } }, "examples/medplum-provider": { - "version": "3.0.3", + "version": "3.0.4", "devDependencies": { "@mantine/core": "7.5.3", "@mantine/hooks": "7.5.3", "@mantine/notifications": "7.5.3", - "@medplum/core": "3.0.3", - "@medplum/eslint-config": "3.0.3", - "@medplum/fhirtypes": "3.0.3", - "@medplum/react": "3.0.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", "@tabler/icons-react": "2.47.0", "@types/node": "20.11.19", "@types/react": "18.2.56", @@ -776,12 +776,12 @@ } }, "examples/medplum-react-native-example": { - "version": "3.0.3", + "version": "3.0.4", "dependencies": { "@expo/webpack-config": "19.0.1", - "@medplum/core": "3.0.3", - "@medplum/expo-polyfills": "3.0.3", - "@medplum/react-hooks": "3.0.3", + "@medplum/core": "3.0.4", + "@medplum/expo-polyfills": "3.0.4", + "@medplum/react-hooks": "3.0.4", "expo": "50.0.7", "expo-status-bar": "1.11.1", "react": "18.2.0", @@ -795,16 +795,16 @@ } }, "examples/medplum-task-demo": { - "version": "3.0.3", + "version": "3.0.4", "devDependencies": { "@mantine/core": "7.5.3", "@mantine/hooks": "7.5.3", "@mantine/notifications": "7.5.3", - "@medplum/core": "3.0.3", - "@medplum/definitions": "3.0.3", - "@medplum/eslint-config": "3.0.3", - "@medplum/fhirtypes": "3.0.3", - "@medplum/react": "3.0.3", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", "@tabler/icons-react": "2.47.0", "@types/node": "20.11.19", "@types/react": "18.2.56", @@ -907,18 +907,18 @@ } }, "examples/medplum-websocket-subscriptions-demo": { - "version": "3.0.3", + "version": "3.0.4", "devDependencies": { "@emotion/react": "11.11.3", "@mantine/core": "7.5.3", "@mantine/hooks": "7.5.3", "@mantine/notifications": "7.5.3", - "@medplum/core": "3.0.3", - "@medplum/eslint-config": "3.0.3", - "@medplum/fhir-router": "3.0.3", - "@medplum/fhirtypes": "3.0.3", - "@medplum/mock": "3.0.3", - "@medplum/react": "3.0.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhir-router": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", + "@medplum/react": "3.0.4", "@tabler/icons-react": "2.47.0", "@types/node": "20.11.19", "@types/react": "18.2.56", @@ -32538,6 +32538,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -32551,6 +32552,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -42927,6 +42929,7 @@ "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -58248,7 +58251,7 @@ }, "packages/agent": { "name": "@medplum/agent", - "version": "3.0.3", + "version": "3.0.4", "license": "Apache-2.0", "dependencies": { "@medplum/core": "*", @@ -58272,7 +58275,7 @@ }, "packages/app": { "name": "@medplum/app", - "version": "3.0.3", + "version": "3.0.4", "license": "Apache-2.0", "devDependencies": { "@mantine/core": "7.5.3", @@ -58391,7 +58394,7 @@ }, "packages/bot-layer": { "name": "@medplum/bot-layer", - "version": "3.0.3", + "version": "3.0.4", "license": "Apache-2.0", "dependencies": { "@medplum/core": "*", @@ -58419,7 +58422,7 @@ }, "packages/cdk": { "name": "@medplum/cdk", - "version": "3.0.3", + "version": "3.0.4", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.515.0", @@ -58436,7 +58439,7 @@ }, "packages/cli": { "name": "@medplum/cli", - "version": "3.0.3", + "version": "3.0.4", "license": "Apache-2.0", "dependencies": { "@aws-sdk/client-acm": "3.515.0", @@ -58479,7 +58482,7 @@ }, "packages/core": { "name": "@medplum/core", - "version": "3.0.3", + "version": "3.0.4", "license": "Apache-2.0", "devDependencies": { "@medplum/definitions": "*", @@ -58500,7 +58503,7 @@ }, "packages/definitions": { "name": "@medplum/definitions", - "version": "3.0.3", + "version": "3.0.4", "license": "Apache-2.0", "engines": { "node": ">=18.0.0" @@ -58508,7 +58511,7 @@ }, "packages/docs": { "name": "@medplum/docs", - "version": "3.0.3", + "version": "3.0.4", "license": "Apache-2.0", "devDependencies": { "@docusaurus/core": "3.1.1", @@ -58548,7 +58551,7 @@ }, "packages/eslint-config": { "name": "@medplum/eslint-config", - "version": "3.0.3", + "version": "3.0.4", "license": "Apache-2.0", "devDependencies": { "@typescript-eslint/eslint-plugin": "7.0.1", @@ -58573,7 +58576,7 @@ }, "packages/examples": { "name": "@medplum/examples", - "version": "3.0.3", + "version": "3.0.4", "license": "Apache-2.0", "devDependencies": { "@jest/globals": "29.7.0", @@ -58588,7 +58591,7 @@ }, "packages/expo-polyfills": { "name": "@medplum/expo-polyfills", - "version": "3.0.3", + "version": "3.0.4", "license": "Apache-2.0", "dependencies": { "base-64": "1.0.0", @@ -58619,7 +58622,7 @@ }, "packages/fhir-router": { "name": "@medplum/fhir-router", - "version": "3.0.3", + "version": "3.0.4", "license": "Apache-2.0", "dependencies": { "@medplum/core": "*", @@ -58635,7 +58638,7 @@ }, "packages/fhirtypes": { "name": "@medplum/fhirtypes", - "version": "3.0.3", + "version": "3.0.4", "license": "Apache-2.0", "engines": { "node": ">=18.0.0" @@ -58643,7 +58646,7 @@ }, "packages/generator": { "name": "@medplum/generator", - "version": "3.0.3", + "version": "3.0.4", "license": "Apache-2.0", "devDependencies": { "@medplum/core": "*", @@ -58688,7 +58691,7 @@ }, "packages/graphiql": { "name": "@medplum/graphiql", - "version": "3.0.3", + "version": "3.0.4", "license": "Apache-2.0", "devDependencies": { "@graphiql/react": "0.20.3", @@ -58802,7 +58805,7 @@ }, "packages/health-gorilla": { "name": "@medplum/health-gorilla", - "version": "3.0.3", + "version": "3.0.4", "license": "Apache-2.0", "dependencies": { "@medplum/core": "*", @@ -58817,7 +58820,7 @@ }, "packages/hl7": { "name": "@medplum/hl7", - "version": "3.0.3", + "version": "3.0.4", "license": "Apache-2.0", "dependencies": { "@medplum/core": "*" @@ -58831,7 +58834,7 @@ }, "packages/mock": { "name": "@medplum/mock", - "version": "3.0.3", + "version": "3.0.4", "license": "Apache-2.0", "dependencies": { "@medplum/core": "*", @@ -58851,7 +58854,7 @@ }, "packages/react": { "name": "@medplum/react", - "version": "3.0.3", + "version": "3.0.4", "license": "Apache-2.0", "devDependencies": { "@mantine/core": "7.5.3", @@ -58922,7 +58925,7 @@ }, "packages/react-hooks": { "name": "@medplum/react-hooks", - "version": "3.0.3", + "version": "3.0.4", "license": "Apache-2.0", "devDependencies": { "@medplum/core": "*", @@ -59003,7 +59006,7 @@ }, "packages/server": { "name": "@medplum/server", - "version": "3.0.3", + "version": "3.0.4", "license": "Apache-2.0", "dependencies": { "@aws-sdk/client-cloudwatch-logs": "3.515.0", diff --git a/package.json b/package.json index 2e9dcdfdb5..1cee9f4727 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "3.0.3", + "version": "3.0.4", "private": true, "workspaces": [ "packages/*", diff --git a/packages/agent/package.json b/packages/agent/package.json index 39429618bf..64b92b9770 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/agent", - "version": "3.0.3", + "version": "3.0.4", "description": "Medplum Agent", "homepage": "https://www.medplum.com/", "bugs": { diff --git a/packages/app/package.json b/packages/app/package.json index c1dc5ea42e..46bc86c10f 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/app", - "version": "3.0.3", + "version": "3.0.4", "description": "Medplum App", "homepage": "https://www.medplum.com/", "bugs": { diff --git a/packages/bot-layer/package.json b/packages/bot-layer/package.json index a91ce46072..eaaef8db69 100644 --- a/packages/bot-layer/package.json +++ b/packages/bot-layer/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/bot-layer", - "version": "3.0.3", + "version": "3.0.4", "description": "Medplum Bot Lambda Layer", "keywords": [ "medplum", diff --git a/packages/cdk/package.json b/packages/cdk/package.json index 214f45275c..69fd468f59 100644 --- a/packages/cdk/package.json +++ b/packages/cdk/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/cdk", - "version": "3.0.3", + "version": "3.0.4", "description": "Medplum CDK Infra as Code", "homepage": "https://www.medplum.com/", "bugs": { diff --git a/packages/cli/package.json b/packages/cli/package.json index af38dee500..9af8776654 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/cli", - "version": "3.0.3", + "version": "3.0.4", "description": "Medplum Command Line Interface", "keywords": [ "medplum", diff --git a/packages/core/package.json b/packages/core/package.json index 763e9e0ae1..cfe132e2d1 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/core", - "version": "3.0.3", + "version": "3.0.4", "description": "Medplum TS/JS Library", "keywords": [ "medplum", diff --git a/packages/definitions/package.json b/packages/definitions/package.json index 76a661a0b7..185fc748dd 100644 --- a/packages/definitions/package.json +++ b/packages/definitions/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/definitions", - "version": "3.0.3", + "version": "3.0.4", "description": "Medplum Data Definitions", "keywords": [ "medplum", diff --git a/packages/docs/package.json b/packages/docs/package.json index 6a11411a0f..d4b74042e2 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/docs", - "version": "3.0.3", + "version": "3.0.4", "description": "Medplum Docs", "homepage": "https://www.medplum.com/", "bugs": { diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 2842693659..7a4ed40c3d 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/eslint-config", - "version": "3.0.3", + "version": "3.0.4", "description": "Shared ESLint configuration for Medplum projects", "keywords": [ "eslint", diff --git a/packages/examples/package.json b/packages/examples/package.json index eeba3bc98a..0c27987cb0 100644 --- a/packages/examples/package.json +++ b/packages/examples/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/examples", - "version": "3.0.3", + "version": "3.0.4", "description": "Medplum Code Examples", "homepage": "https://www.medplum.com/", "bugs": { diff --git a/packages/expo-polyfills/package.json b/packages/expo-polyfills/package.json index c909b3127c..282f9cc9c7 100644 --- a/packages/expo-polyfills/package.json +++ b/packages/expo-polyfills/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/expo-polyfills", - "version": "3.0.3", + "version": "3.0.4", "description": "A module for polyfilling the minimum necessary web APIs for using the Medplum client on React Native", "keywords": [ "react-native", diff --git a/packages/fhir-router/package.json b/packages/fhir-router/package.json index 3141e0ddc2..89ca31fd22 100644 --- a/packages/fhir-router/package.json +++ b/packages/fhir-router/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/fhir-router", - "version": "3.0.3", + "version": "3.0.4", "description": "Medplum FHIR Router", "keywords": [ "medplum", diff --git a/packages/fhirtypes/package.json b/packages/fhirtypes/package.json index 8f98e70545..baefb32c3c 100644 --- a/packages/fhirtypes/package.json +++ b/packages/fhirtypes/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/fhirtypes", - "version": "3.0.3", + "version": "3.0.4", "description": "Medplum FHIR Type Definitions", "keywords": [ "medplum", diff --git a/packages/generator/package.json b/packages/generator/package.json index f5fa166f2f..a47f3427f1 100644 --- a/packages/generator/package.json +++ b/packages/generator/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/generator", - "version": "3.0.3", + "version": "3.0.4", "description": "Medplum Code Generator", "homepage": "https://www.medplum.com/", "repository": { diff --git a/packages/graphiql/package.json b/packages/graphiql/package.json index a11645f470..58f6743518 100644 --- a/packages/graphiql/package.json +++ b/packages/graphiql/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/graphiql", - "version": "3.0.3", + "version": "3.0.4", "description": "Medplum GraphiQL", "homepage": "https://www.medplum.com/", "bugs": { diff --git a/packages/health-gorilla/package.json b/packages/health-gorilla/package.json index c08d9fd6a6..9d62ec3594 100644 --- a/packages/health-gorilla/package.json +++ b/packages/health-gorilla/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/health-gorilla", - "version": "3.0.3", + "version": "3.0.4", "description": "Medplum Health Gorilla SDK", "homepage": "https://www.medplum.com/", "bugs": { diff --git a/packages/hl7/package.json b/packages/hl7/package.json index d8e4d2ac6c..e69bf8bb48 100644 --- a/packages/hl7/package.json +++ b/packages/hl7/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/hl7", - "version": "3.0.3", + "version": "3.0.4", "description": "Medplum HL7 Utilities", "keywords": [ "medplum", diff --git a/packages/mock/package.json b/packages/mock/package.json index e5fa2939dd..c472cc2350 100644 --- a/packages/mock/package.json +++ b/packages/mock/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/mock", - "version": "3.0.3", + "version": "3.0.4", "description": "Medplum Mock Client", "keywords": [ "medplum", diff --git a/packages/react-hooks/package.json b/packages/react-hooks/package.json index 92c9760539..ea4f19bd5a 100644 --- a/packages/react-hooks/package.json +++ b/packages/react-hooks/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/react-hooks", - "version": "3.0.3", + "version": "3.0.4", "description": "Medplum React Hooks Library", "keywords": [ "medplum", diff --git a/packages/react/package.json b/packages/react/package.json index 9bee102774..f32a6507b8 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/react", - "version": "3.0.3", + "version": "3.0.4", "description": "Medplum React Component Library", "keywords": [ "medplum", diff --git a/packages/server/package.json b/packages/server/package.json index 1d4ae53f17..058bb3bf5a 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/server", - "version": "3.0.3", + "version": "3.0.4", "description": "Medplum Server", "homepage": "https://www.medplum.com/", "bugs": { diff --git a/sonar-project.properties b/sonar-project.properties index a7ebbe09b5..6831a363b9 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,7 +1,7 @@ sonar.organization=medplum sonar.projectKey=medplum_medplum sonar.projectName=Medplum -sonar.projectVersion=3.0.3 +sonar.projectVersion=3.0.4 sonar.sources=packages sonar.sourceEncoding=UTF-8 sonar.exclusions=**/node_modules/**,\ From 0d4741d0a589a5001f92a0d5cab1bb2a1b1d39df Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Mon, 19 Feb 2024 17:23:50 -0800 Subject: [PATCH 64/81] Escape characters in cicd-deploy.sh slack message (#3996) --- scripts/cicd-deploy.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/cicd-deploy.sh b/scripts/cicd-deploy.sh index 1992497349..673ffc9e06 100755 --- a/scripts/cicd-deploy.sh +++ b/scripts/cicd-deploy.sh @@ -6,6 +6,9 @@ # Inspects files changed in the most recent commit # and deploys the appropriate service +# Echo commands +set -x + COMMIT_MESSAGE=$(git log -1 --pretty=%B) echo "$COMMIT_MESSAGE" @@ -77,15 +80,17 @@ fi # Send a slack message # +ESCAPED_COMMIT_MESSAGE=$(echo "$COMMIT_MESSAGE" | sed 's/"/\\"/g') + read -r -d '' PAYLOAD <<- EOM { - "text": "Deploying ${COMMIT_MESSAGE}", + "text": "Deploying ${ESCAPED_COMMIT_MESSAGE}", "blocks": [ { "type": "section", "text": { "type": "mrkdwn", - "text": "Deploying ${COMMIT_MESSAGE}\\n\\n* Deploy app: ${DEPLOY_APP}\\n* Deploy graphiql: ${DEPLOY_GRAPHIQL}\\n* Deploy server: ${DEPLOY_SERVER}" + "text": "Deploying ${ESCAPED_COMMIT_MESSAGE}\\n\\n* Deploy app: ${DEPLOY_APP}\\n* Deploy graphiql: ${DEPLOY_GRAPHIQL}\\n* Deploy server: ${DEPLOY_SERVER}" } } ] From 1ec6fa86ae15bd0b409d319d4cce0d72d9aff497 Mon Sep 17 00:00:00 2001 From: Nate Merrill Date: Tue, 20 Feb 2024 12:48:25 -0700 Subject: [PATCH 65/81] Respect Bot.runAsUser in $execute endpoint (#3919) --- packages/server/src/fhir/operations/execute.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/server/src/fhir/operations/execute.ts b/packages/server/src/fhir/operations/execute.ts index 713995aa38..277772e43e 100644 --- a/packages/server/src/fhir/operations/execute.ts +++ b/packages/server/src/fhir/operations/execute.ts @@ -38,7 +38,7 @@ import { generateAccessToken } from '../../oauth/keys'; import { recordHistogramValue } from '../../otel/otel'; import { AuditEventOutcome, logAuditEvent } from '../../util/auditevent'; import { MockConsole } from '../../util/console'; -import { createAuditEventEntities } from '../../workers/utils'; +import { createAuditEventEntities, findProjectMembership } from '../../workers/utils'; import { sendOutcome } from '../outcomes'; import { getSystemRepo } from '../repo'; import { getBinaryStorage } from '../storage'; @@ -85,12 +85,23 @@ export const executeHandler = asyncWrap(async (req: Request, res: Response) => { const systemRepo = getSystemRepo(); const bot = await systemRepo.readResource('Bot', userBot.id as string); + // Find the project membership + // If the bot is configured to run as the user, then use the current user's membership + // Otherwise, use the bot's project membership + const project = bot.meta?.project as string; + let runAs: ProjectMembership | undefined; + if (bot.runAsUser) { + runAs = ctx.membership; + } else { + runAs = (await findProjectMembership(project, createReference(bot))) ?? ctx.membership; + } + // Execute the bot // If the request is HTTP POST, then the body is the input // If the request is HTTP GET, then the query string is the input const result = await executeBot({ bot, - runAs: ctx.membership, + runAs, input: req.method === 'POST' ? req.body : req.query, contentType: req.header('content-type') as string, }); From ca550f9e557bfbe08f60edacd86e1abdff301484 Mon Sep 17 00:00:00 2001 From: Derrick Farris Date: Tue, 20 Feb 2024 13:36:53 -0800 Subject: [PATCH 66/81] feat(subscriptions): cleanup WS Subscription resources, add feature flag (#3978) * refactor(server/subscriptions): use Redis sets * fix(subscriptions): mark WS sub as inactive on d/c * cleanup: `isMember` -> `subActive` in tests * fix(subscriptions): remove subs after d/c * fix(mock): fix `Redis.srem()` sig * test: make sure $get-ws-binding-token returns error if Subscription is deleted * test: make sure going through repo for $get-ws-binding-token * feat(subscriptions): add feature flag for WS subs * fix(subscriptions): catch if project doesn't exist * fix(subscriptions-test): add feature flag to WS sub tests * feat(subscriptions): start logging if `AccessPolicy` not satisfied * cleanup: typo * cleanup: more typos * cleanup: return error from repo --- .../dist/fhir/r4/valuesets-medplum.json | 5 + packages/fhirtypes/dist/Project.d.ts | 2 +- packages/server/src/__mocks__/ioredis.ts | 113 +++++++++++++-- packages/server/src/fhir/accesspolicy.ts | 2 +- .../fhir/operations/getwsbindingtoken.test.ts | 130 +++++++++++++++--- .../src/fhir/operations/getwsbindingtoken.ts | 17 ++- packages/server/src/fhir/repo.ts | 15 +- .../src/subscriptions/websockets.test.ts | 51 ++++++- .../server/src/subscriptions/websockets.ts | 46 +++++-- .../server/src/workers/subscription.test.ts | 100 +++++++++++--- packages/server/src/workers/subscription.ts | 93 +++++++++++-- 11 files changed, 471 insertions(+), 103 deletions(-) diff --git a/packages/definitions/dist/fhir/r4/valuesets-medplum.json b/packages/definitions/dist/fhir/r4/valuesets-medplum.json index 0f511500a9..9609c2c3b9 100644 --- a/packages/definitions/dist/fhir/r4/valuesets-medplum.json +++ b/packages/definitions/dist/fhir/r4/valuesets-medplum.json @@ -148,6 +148,11 @@ "code": "graphql-introspection", "display": "GraphQL Introspection", "definition": "GraphQL Introspection" + }, + { + "code": "websocket-subscriptions", + "display": "WebSocket Subscriptions", + "definition": "WebSocket Subscriptions" } ] } diff --git a/packages/fhirtypes/dist/Project.d.ts b/packages/fhirtypes/dist/Project.d.ts index a52d82e35d..4c2a3a2394 100644 --- a/packages/fhirtypes/dist/Project.d.ts +++ b/packages/fhirtypes/dist/Project.d.ts @@ -86,7 +86,7 @@ export interface Project { /** * A list of optional features that are enabled for the project. */ - features?: ('bots' | 'cron' | 'email' | 'google-auth-required' | 'graphql-introspection')[]; + features?: ('bots' | 'cron' | 'email' | 'google-auth-required' | 'graphql-introspection' | 'websocket-subscriptions')[]; /** * The default access policy for patients using open registration. diff --git a/packages/server/src/__mocks__/ioredis.ts b/packages/server/src/__mocks__/ioredis.ts index 665c1980fe..b1f455ec90 100644 --- a/packages/server/src/__mocks__/ioredis.ts +++ b/packages/server/src/__mocks__/ioredis.ts @@ -2,6 +2,44 @@ const values = new Map(); const sets = new Map>(); const subscribers = new Map>(); +class ReplyError extends Error {} + +type Command = { + command: 'srem' | 'del'; + args: string[]; +}; + +type Result = number | null; + +class MultiClient { + private redis: Redis; + private commandQueue: Command[]; + + constructor(redis: Redis) { + this.redis = redis; + this.commandQueue = []; + } + + srem(setKey: string, members: string[]): this { + this.commandQueue.push({ command: 'srem', args: [setKey, ...members] }); + return this; + } + + del(setKey: string | string[]): this { + this.commandQueue.push({ command: 'del', args: [...setKey] }); + return this; + } + + async exec(): Promise { + const results = [] as Result[]; + for (const cmd of this.commandQueue) { + results.push((await this.redis[cmd.command](cmd.args[0], cmd.args.slice(1))) ?? null); + } + this.commandQueue = []; + return results; + } +} + class Redis { private callback?: (...args: any[]) => void; @@ -18,12 +56,18 @@ class Redis { return 'PONG'; } - async get(key: string): Promise { - return values.get(key); + async get(key: string): Promise { + return values.get(key) ?? null; } - async mget(...keys: string[]): Promise<(string | undefined)[]> { - return keys.map((key) => values.get(key)); + async mget(...keys: string[] | string[][]): Promise<(string | null)[]> { + let normalizedKeys: string[]; + if (keys.length === 1 && Array.isArray(keys[0])) { + normalizedKeys = keys[0]; + } else { + normalizedKeys = keys as string[]; + } + return normalizedKeys.map((key) => values.get(key) ?? null); } async set(key: string, value: string, ...args: (string | number)[]): Promise { @@ -98,12 +142,16 @@ class Redis { } async sadd(setKey: string, ...members: string[]): Promise { + const existingValue = sets.get(setKey); let keySet: Set; - if (!sets.has(setKey)) { + if (existingValue) { + if (!(existingValue instanceof Set)) { + throw new ReplyError('WRONGTYPE Operation against a key holding the wrong kind of value'); + } + keySet = existingValue; + } else { keySet = new Set(); sets.set(setKey, keySet); - } else { - keySet = sets.get(setKey) as Set; } let added = 0; for (const member of members) { @@ -116,12 +164,53 @@ class Redis { return added; } + async srem(setKey: string, member: string[]): Promise; + async srem(setKey: string, ...members: string[] | string[][]): Promise { + const keySet = sets.get(setKey); + if (!keySet) { + return 0; + } + if (!(keySet instanceof Set)) { + throw new ReplyError('WRONGTYPE Operation against a key holding the wrong kind of value'); + } + let normalizedMembers: string[]; + if (Array.isArray(members[0])) { + normalizedMembers = members[0]; + } else { + normalizedMembers = members as string[]; + } + let removed = 0; + for (const member of normalizedMembers) { + if (keySet.has(member)) { + keySet.delete(member); + removed += 1; + } + } + return removed; + } + async smembers(setKey: string): Promise { - const set = sets.get(setKey); - if (!set) { + const keySet = sets.get(setKey); + if (!keySet) { return []; } - return Array.from(set.keys()); + if (!(keySet instanceof Set)) { + throw new ReplyError('WRONGTYPE Operation against a key holding the wrong kind of value'); + } + return Array.from(keySet.keys()); + } + + async smismember(setKey: string, ...members: string[]): Promise { + const keySet = sets.get(setKey); + if (!keySet) { + return new Array(members.length).fill(0); + } + if (!(keySet instanceof Set)) { + throw new ReplyError('WRONGTYPE Operation against a key holding the wrong kind of value'); + } + return members.map((member) => { + return keySet.has(member) ? 1 : 0; + }); } async scard(setKey: string): Promise { @@ -131,6 +220,10 @@ class Redis { } return set.size; } + + multi(): MultiClient { + return new MultiClient(this); + } } export default Redis; diff --git a/packages/server/src/fhir/accesspolicy.ts b/packages/server/src/fhir/accesspolicy.ts index 61f1ec070f..d6696982bf 100644 --- a/packages/server/src/fhir/accesspolicy.ts +++ b/packages/server/src/fhir/accesspolicy.ts @@ -89,7 +89,7 @@ export async function getAccessPolicyForLogin( * @param membership - The user project membership. * @returns The parameterized compound access policy. */ -async function buildAccessPolicy(membership: ProjectMembership): Promise { +export async function buildAccessPolicy(membership: ProjectMembership): Promise { let access: ProjectMembershipAccess[] = []; if (membership.accessPolicy) { diff --git a/packages/server/src/fhir/operations/getwsbindingtoken.test.ts b/packages/server/src/fhir/operations/getwsbindingtoken.test.ts index a33c2a608d..b88c7f8205 100644 --- a/packages/server/src/fhir/operations/getwsbindingtoken.test.ts +++ b/packages/server/src/fhir/operations/getwsbindingtoken.test.ts @@ -1,10 +1,11 @@ import { ContentType } from '@medplum/core'; -import { Parameters, Subscription } from '@medplum/fhirtypes'; +import { OperationOutcome, Parameters, Subscription } from '@medplum/fhirtypes'; import express from 'express'; import request from 'supertest'; import { initApp, shutdownApp } from '../../app'; import { loadTestConfig } from '../../config'; -import { initTestAuth } from '../../test.setup'; +import { verifyJwt } from '../../oauth/keys'; +import { initTestAuth, withTestContext } from '../../test.setup'; const app = express(); let accessToken: string; @@ -20,8 +21,102 @@ describe('Get WebSocket binding token', () => { await shutdownApp(); }); - test('Basic', async () => { - // Create Subscription + test('Basic', () => + withTestContext(async () => { + // Create Subscription + const res1 = await request(app) + .post(`/fhir/R4/Subscription`) + .set('Authorization', 'Bearer ' + accessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send({ + resourceType: 'Subscription', + reason: 'test', + status: 'active', + criteria: 'Patient', + channel: { + type: 'websocket', + }, + } satisfies Subscription); + const createdSub = res1.body as Subscription; + expect(res1.status).toBe(201); + expect(createdSub).toBeDefined(); + expect(createdSub.id).toBeDefined(); + + // Start the export + const res2 = await request(app) + .get(`/fhir/R4/Subscription/${createdSub.id}/$get-ws-binding-token`) + .set('Authorization', 'Bearer ' + accessToken); + expect(res2.status).toBe(200); + expect(res2.body).toBeDefined(); + + const params = res2.body as Parameters; + expect(params.resourceType).toEqual('Parameters'); + expect(params.parameter?.length).toEqual(3); + expect(params.parameter?.[0]).toBeDefined(); + expect(params.parameter?.[0]?.name).toEqual('token'); + + const token = params.parameter?.[0]?.valueString as string; + expect(token).toBeDefined(); + + const { payload } = await verifyJwt(token); + expect(payload?.sub).toBeDefined(); + expect(payload?.exp).toBeDefined(); + expect(payload?.aud).toBeDefined(); + expect(payload?.username).toBeDefined(); + expect(payload?.subscription_id).toBeDefined(); + + expect(params.parameter?.[1]).toBeDefined(); + expect(params.parameter?.[1]?.name).toEqual('expiration'); + expect(params.parameter?.[1]?.valueDateTime).toBeDefined(); + expect(new Date(params.parameter?.[1]?.valueDateTime as string).getTime()).toBeGreaterThanOrEqual(Date.now()); + expect(params.parameter?.[2]).toBeDefined(); + expect(params.parameter?.[2]?.name).toEqual('websocket-url'); + expect(params.parameter?.[2]?.valueUrl).toBeDefined(); + })); + + test('should return OperationOutcome error if Subscription no longer exists', () => + withTestContext(async () => { + // Create subscription to watch patient + const res1 = await request(app) + .post(`/fhir/R4/Subscription`) + .set('Authorization', 'Bearer ' + accessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send({ + resourceType: 'Subscription', + reason: 'test', + status: 'active', + criteria: 'Patient', + channel: { + type: 'websocket', + }, + } satisfies Subscription); + const createdSub = res1.body as Subscription; + expect(res1.status).toBe(201); + expect(createdSub).toBeDefined(); + expect(createdSub.id).toBeDefined(); + + const res2 = await request(app) + .delete(`/fhir/R4/Subscription/${createdSub.id as string}`) + .set('Authorization', 'Bearer ' + accessToken); + + expect(res2.body).toMatchObject({ + resourceType: 'OperationOutcome', + issue: [{ severity: 'information', code: 'informational' }], + }); + + // Call $get-ws-binding-token + const res3 = await request(app) + .get(`/fhir/R4/Subscription/${createdSub.id}/$get-ws-binding-token`) + .set('Authorization', 'Bearer ' + accessToken); + + expect(res3.body).toMatchObject({ + resourceType: 'OperationOutcome', + issue: [{ severity: 'error', code: 'invalid' }], + }); + })); + + test('should return OperationOutcome error if user does not have access to this Subscription', async () => { + // Create subscription to watch patient const res1 = await request(app) .post(`/fhir/R4/Subscription`) .set('Authorization', 'Bearer ' + accessToken) @@ -40,25 +135,16 @@ describe('Get WebSocket binding token', () => { expect(createdSub).toBeDefined(); expect(createdSub.id).toBeDefined(); - // Start the export + const anotherUserToken = await initTestAuth(); + + // Call $get-ws-binding-token const res2 = await request(app) .get(`/fhir/R4/Subscription/${createdSub.id}/$get-ws-binding-token`) - .set('Authorization', 'Bearer ' + accessToken); - expect(res2.status).toBe(200); - expect(res2.body).toBeDefined(); - - const params = res2.body as Parameters; - expect(params.resourceType).toEqual('Parameters'); - expect(params.parameter?.length).toEqual(3); - expect(params.parameter?.[0]).toBeDefined(); - expect(params.parameter?.[0].name).toEqual('token'); - expect(params.parameter?.[0].valueString).toBeDefined(); - expect(params.parameter?.[1]).toBeDefined(); - expect(params.parameter?.[1].name).toEqual('expiration'); - expect(params.parameter?.[1].valueDateTime).toBeDefined(); - expect(new Date(params.parameter?.[1].valueDateTime as string).getTime()).toBeGreaterThanOrEqual(Date.now()); - expect(params.parameter?.[2]).toBeDefined(); - expect(params.parameter?.[2].name).toEqual('websocket-url'); - expect(params.parameter?.[2].valueUrl).toBeDefined(); + .set('Authorization', 'Bearer ' + anotherUserToken); + + expect(res2.body).toMatchObject({ + resourceType: 'OperationOutcome', + issue: [{ severity: 'error', code: 'invalid' }], + }); }); }); diff --git a/packages/server/src/fhir/operations/getwsbindingtoken.ts b/packages/server/src/fhir/operations/getwsbindingtoken.ts index a2da54083a..58f895ae2d 100644 --- a/packages/server/src/fhir/operations/getwsbindingtoken.ts +++ b/packages/server/src/fhir/operations/getwsbindingtoken.ts @@ -1,10 +1,9 @@ -import { allOk, badRequest, resolveId } from '@medplum/core'; -import { Parameters } from '@medplum/fhirtypes'; +import { allOk, badRequest, normalizeErrorString, resolveId } from '@medplum/core'; +import { Parameters, Subscription } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { getConfig } from '../../config'; import { getAuthenticatedContext } from '../../context'; import { generateAccessToken } from '../../oauth/keys'; -import { getRedis } from '../../redis'; import { sendOutcome } from '../outcomes'; import { sendResponse } from '../response'; @@ -28,21 +27,21 @@ export type AdditionalWsBindingClaims = { * @param res - The HTTP response. */ export async function getWsBindingTokenHandler(req: Request, res: Response): Promise { - const { login, profile } = getAuthenticatedContext(); + const { login, profile, repo } = getAuthenticatedContext(); const { baseUrl } = getConfig(); - const redis = getRedis(); const clientId = login.client && resolveId(login.client); const userId = resolveId(login.user); if (!userId) { - await sendOutcome(res, badRequest('Login missing user')); + sendOutcome(res, badRequest('Login missing user')); return; } const subscriptionId = req.params.id; - const subExists = await redis.exists(`Subscription/${subscriptionId}`); - if (!subExists) { - await sendOutcome(res, badRequest('Content could not be parsed')); + try { + await repo.readResource('Subscription', subscriptionId); + } catch (err: unknown) { + sendOutcome(res, badRequest(`Error reading subscription: ${normalizeErrorString(err)}`)); return; } diff --git a/packages/server/src/fhir/repo.ts b/packages/server/src/fhir/repo.ts index f1605c5105..f14cbfd85f 100644 --- a/packages/server/src/fhir/repo.ts +++ b/packages/server/src/fhir/repo.ts @@ -47,7 +47,6 @@ import { ResourceType, SearchParameter, StructureDefinition, - Subscription, } from '@medplum/fhirtypes'; import { randomUUID } from 'crypto'; import { Pool, PoolClient } from 'pg'; @@ -541,19 +540,7 @@ export class Repository extends BaseRepository implements FhirRepository (sub.id as string) === (result.id as string) - ); - if (existingIdx !== -1) { - currentWsSubscriptions[existingIdx] = result; - } else { - currentWsSubscriptions.push(result); - } - await redis.set(`medplum:subscriptions:r4:project:${project}`, JSON.stringify(currentWsSubscriptions)); + await redis.sadd(`medplum:subscriptions:r4:project:${project}:active`, `Subscription/${result.id}`); } } diff --git a/packages/server/src/subscriptions/websockets.test.ts b/packages/server/src/subscriptions/websockets.test.ts index 1ced8d4cfe..d9e7a5e3e9 100644 --- a/packages/server/src/subscriptions/websockets.test.ts +++ b/packages/server/src/subscriptions/websockets.test.ts @@ -1,4 +1,4 @@ -import { getReferenceString, sleep } from '@medplum/core'; +import { OperationOutcomeError, getReferenceString, sleep } from '@medplum/core'; import { Bundle, BundleEntry, @@ -17,6 +17,7 @@ import { initApp, shutdownApp } from '../app'; import { registerNew } from '../auth/register'; import { MedplumServerConfig, loadTestConfig } from '../config'; import { Repository } from '../fhir/repo'; +import { getRedis } from '../redis'; import { withTestContext } from '../test.setup'; import { execSubscriptionJob, getSubscriptionQueue } from '../workers/subscription'; @@ -30,6 +31,7 @@ describe('WebSockets Subscriptions', () => { let project: Project; let repo: Repository; let accessToken: string; + let patientSubscription: Subscription; beforeAll(async () => { app = express(); @@ -58,6 +60,9 @@ describe('WebSockets Subscriptions', () => { }, }); + // TODO: Remove this when the websocket-subscriptions feature flag is removed + project = await withTestContext(() => repo.updateResource({ ...project, features: ['websocket-subscriptions'] })); + await new Promise((resolve) => { server.listen(0, 'localhost', 511, resolve); }); @@ -80,7 +85,7 @@ describe('WebSockets Subscriptions', () => { expect(version1.id).toBeDefined(); // Create subscription to watch patient - const subscription = await repo.createResource({ + patientSubscription = await repo.createResource({ resourceType: 'Subscription', reason: 'test', status: 'active', @@ -89,11 +94,11 @@ describe('WebSockets Subscriptions', () => { type: 'websocket', }, }); - expect(subscription).toBeDefined(); + expect(patientSubscription).toBeDefined(); // Call $get-ws-binding-token const res = await request(server) - .get(`/fhir/R4/Subscription/${subscription.id}/$get-ws-binding-token`) + .get(`/fhir/R4/Subscription/${patientSubscription.id}/$get-ws-binding-token`) .set('Authorization', 'Bearer ' + accessToken); expect(res.body).toBeDefined(); @@ -103,11 +108,13 @@ describe('WebSockets Subscriptions', () => { expect(body.parameter?.[0]?.name).toEqual('token'); expect(body.parameter?.[0]?.valueString).toBeDefined(); + const token = body.parameter?.[0]?.valueString as string; + let version2: Patient; await request(server) .ws('/ws/subscriptions-r4') .set('Authorization', 'Bearer ' + accessToken) - .sendJson({ type: 'bind-with-token', payload: { token: body.parameter?.[0]?.valueString as string } }) + .sendJson({ type: 'bind-with-token', payload: { token } }) // Add a new patient for this project .exec(async () => { const queue = getSubscriptionQueue() as any; @@ -133,6 +140,19 @@ describe('WebSockets Subscriptions', () => { // Clear the queue queue.add.mockClear(); + + let subActive = false; + while (!subActive) { + await sleep(0); + subActive = + ( + await getRedis().smismember( + `medplum:subscriptions:r4:project:${project.id}:active`, + `Subscription/${patientSubscription?.id as string}` + ) + )[0] === 1; + } + expect(subActive).toEqual(true); }) .expectJson((msg: Bundle): boolean => { if (!msg.entry?.[1]) { @@ -155,6 +175,27 @@ describe('WebSockets Subscriptions', () => { .expectClosed(); })); + test('Subscription removed from active and deleted after WebSocket closed', () => + withTestContext(async () => { + let subActive = true; + while (subActive) { + await sleep(0); + subActive = + ( + await getRedis().smismember( + `medplum:subscriptions:r4:project:${project.id}:active`, + `Subscription/${patientSubscription?.id as string}` + ) + )[0] === 1; + } + expect(subActive).toEqual(false); + + // Check Patient subscription is NOT still in the cache + await expect(repo.readResource('Subscription', patientSubscription?.id as string)).rejects.toThrow( + OperationOutcomeError + ); + })); + test('Should reject if given an invalid binding token', () => withTestContext(async () => { const version1 = await repo.createResource({ diff --git a/packages/server/src/subscriptions/websockets.ts b/packages/server/src/subscriptions/websockets.ts index 679f77811b..0404fc55b6 100644 --- a/packages/server/src/subscriptions/websockets.ts +++ b/packages/server/src/subscriptions/websockets.ts @@ -1,9 +1,11 @@ import { badRequest, createReference } from '@medplum/core'; -import { Bundle, Resource } from '@medplum/fhirtypes'; +import { Bundle, Resource, Subscription } from '@medplum/fhirtypes'; import { Redis } from 'ioredis'; import { JWTPayload } from 'jose'; import crypto from 'node:crypto'; import ws from 'ws'; +import { AdditionalWsBindingClaims } from '../fhir/operations/getwsbindingtoken'; +import { CacheEntry } from '../fhir/repo'; import { getFullUrl } from '../fhir/response'; import { heartbeat } from '../heartbeat'; import { globalLogger } from '../logger'; @@ -24,10 +26,19 @@ export async function handleR4SubscriptionConnection(socket: ws.WebSocket): Prom const redis = getRedis(); const subscriptionIds = [] as string[]; let redisSubscriber: Redis; - let onDisconnect: (() => void) | undefined; + let onDisconnect: (() => Promise) | undefined; let heartbeatHandler: (() => void) | undefined; - const onBind = async (tokenPayload: JWTPayload): Promise => { + const onBind = async (tokenPayload: JWTPayload & Partial): Promise => { + const subscriptionId = tokenPayload?.subscription_id; + if (!subscriptionId) { + socket.send( + JSON.stringify(badRequest('Token claims missing subscription_id. Make sure you are sending the correct token.')) + ); + socket.terminate(); + return; + } + if (!redisSubscriber) { // Create a redis client for this connection. // According to Redis documentation: http://redis.io/commands/subscribe @@ -40,16 +51,18 @@ export async function handleR4SubscriptionConnection(socket: ws.WebSocket): Prom socket.send(message, { binary: false }); }); - onDisconnect = () => redisSubscriber.disconnect(); + onDisconnect = async (): Promise => { + redisSubscriber.disconnect(); + const cacheEntryStr = (await redis.get(`Subscription/${subscriptionId}`)) as string | null; + if (!cacheEntryStr) { + globalLogger.error('[WS] Failed to retrieve subscription cache entry on WebSocket disconnect.'); + return; + } + const cacheEntry = JSON.parse(cacheEntryStr) as CacheEntry; + await markInMemorySubscriptionsInactive(cacheEntry.projectId, subscriptionIds); + }; } - const subscriptionId = tokenPayload?.subscription_id as string | undefined; - if (!subscriptionId) { - socket.send( - JSON.stringify(badRequest('Token claims missing subscription_id. Make sure you are sending the correct token.')) - ); - return; - } if (!subscriptionIds.includes(subscriptionId)) { subscriptionIds.push(subscriptionId); } @@ -92,7 +105,7 @@ export async function handleR4SubscriptionConnection(socket: ws.WebSocket): Prom socket.on('close', async () => { if (onDisconnect) { - onDisconnect(); + onDisconnect().catch(console.error); } if (heartbeatHandler) { heartbeat.removeEventListener('heartbeat', heartbeatHandler); @@ -160,3 +173,12 @@ export function createSubEventNotification { + const refStrs = []; + for (const subscriptionId of subscriptionIds) { + refStrs.push(`Subscription/${subscriptionId}`); + } + const redis = getRedis(); + await redis.multi().srem(`medplum:subscriptions:r4:project:${projectId}:active`, refStrs).del(refStrs).exec(); +} diff --git a/packages/server/src/workers/subscription.test.ts b/packages/server/src/workers/subscription.test.ts index 917ed3800e..dfdffdbfa1 100644 --- a/packages/server/src/workers/subscription.test.ts +++ b/packages/server/src/workers/subscription.test.ts @@ -1,5 +1,5 @@ import { InvokeCommand, LambdaClient } from '@aws-sdk/client-lambda'; -import { ContentType, Operator, createReference, getReferenceString, stringify } from '@medplum/core'; +import { ContentType, LogLevel, Operator, createReference, getReferenceString, stringify } from '@medplum/core'; import { AuditEvent, Bot, Observation, Patient, Project, ProjectMembership, Subscription } from '@medplum/fhirtypes'; import { AwsClientStub, mockClient } from 'aws-sdk-client-mock'; import { Job } from 'bullmq'; @@ -9,6 +9,7 @@ import { initAppServices, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; import { getDatabasePool } from '../database'; import { Repository, getSystemRepo } from '../fhir/repo'; +import { globalLogger } from '../logger'; import { getRedis } from '../redis'; import { createTestProject, withTestContext } from '../test.setup'; import { AuditEventOutcome } from '../util/auditevent'; @@ -47,22 +48,17 @@ describe('Subscription Worker', () => { await initAppServices(config); // Create one simple project with no advanced features enabled - const testProject = await withTestContext(() => - systemRepo.createResource({ - resourceType: 'Project', + const { project, client } = await withTestContext(() => + createTestProject({ name: 'Test Project', - owner: { - reference: 'User/' + randomUUID(), - }, + features: ['websocket-subscriptions'], }) ); repo = new Repository({ extendedMode: true, - projects: [testProject.id as string], - author: { - reference: 'ClientApplication/' + randomUUID(), - }, + projects: [project.id as string], + author: createReference(client), }); // Create another project, this one with bots enabled @@ -1114,7 +1110,7 @@ describe('Subscription Worker', () => { test('AuditEvent has Subscription account details', () => withTestContext(async () => { - const project = randomUUID(); + const project = (await createTestProject()).project.id as string; const account = { reference: 'Organization/' + randomUUID(), }; @@ -1166,15 +1162,15 @@ describe('Subscription Worker', () => { }); expect(bundle.entry?.length).toEqual(1); - const auditEvent = bundle.entry?.[0].resource as AuditEvent; + const auditEvent = bundle.entry?.[0]?.resource as AuditEvent; expect(auditEvent.meta?.account).toBeDefined(); expect(auditEvent.meta?.account?.reference).toEqual(account.reference); expect(auditEvent.entity).toHaveLength(2); })); - test('Audit Event outcome from custom codes', () => + test('AuditEvent outcome from custom codes', () => withTestContext(async () => { - const project = randomUUID(); + const project = (await createTestProject()).project.id as string; const account = { reference: 'Organization/' + randomUUID(), }; @@ -1333,7 +1329,7 @@ describe('Subscription Worker', () => { expect(queue.add).not.toHaveBeenCalled(); })); - test('WebSocket Subscription', () => + test('WebSocket Subscription -- Enabled', () => withTestContext(async () => { const subscription = await repo.createResource({ resourceType: 'Subscription', @@ -1396,4 +1392,76 @@ describe('Subscription Worker', () => { await deferredPromise; })); + + test('WebSocket Subscription -- Feature Flag Not Enabled', () => + withTestContext(async () => { + globalLogger.level = LogLevel.WARN; + const originalConsoleLog = console.log; + console.log = jest.fn(); + + const noWsSubProject = await systemRepo.createResource({ + resourceType: 'Project', + name: 'Test Project', + owner: { + reference: 'User/' + randomUUID(), + }, + }); + + const noWsSubRepo = new Repository({ + extendedMode: true, + projects: [noWsSubProject.id as string], + author: { + reference: 'ClientApplication/' + randomUUID(), + }, + }); + + const subscription = await noWsSubRepo.createResource({ + resourceType: 'Subscription', + reason: 'test', + status: 'active', + criteria: 'Patient', + channel: { + type: 'websocket', + }, + }); + expect(subscription).toBeDefined(); + expect(subscription.id).toBeDefined(); + + // Subscribe to the topic + const subscriber = getRedis().duplicate(); + await subscriber.subscribe(subscription.id as string); + + let resolve: () => void; + let reject: (error: Error) => void; + + const deferredPromise = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + + subscriber.on('message', () => { + reject(new Error('Should not have been called')); + }); + + const queue = getSubscriptionQueue() as any; + queue.add.mockClear(); + + const patient = await noWsSubRepo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family: 'Smith' }], + }); + expect(patient).toBeDefined(); + expect(queue.add).not.toHaveBeenCalled(); + + // Give some time for the callback to get called (it shouldn't) + setTimeout(() => { + resolve(); + }, 150); + + await deferredPromise; + expect(console.log).toHaveBeenLastCalledWith(expect.stringMatching(/WebSocket Subscriptions/)); + + console.log = originalConsoleLog; + globalLogger.level = LogLevel.NONE; + })); }); diff --git a/packages/server/src/workers/subscription.ts b/packages/server/src/workers/subscription.ts index e322acff4b..ab03388ab4 100644 --- a/packages/server/src/workers/subscription.ts +++ b/packages/server/src/workers/subscription.ts @@ -1,33 +1,37 @@ import { + AccessPolicyInteraction, ContentType, createReference, getExtension, getExtensionValue, + getReferenceString, isGone, matchesSearchRequest, normalizeOperationOutcome, OperationOutcomeError, Operator, parseSearchUrl, + satisfiedAccessPolicy, serverError, stringify, } from '@medplum/core'; -import { Bot, ProjectMembership, Reference, Resource, Subscription } from '@medplum/fhirtypes'; +import { Bot, Project, ProjectMembership, Reference, Resource, Subscription } from '@medplum/fhirtypes'; import { Job, Queue, QueueBaseOptions, Worker } from 'bullmq'; import { createHmac } from 'crypto'; import fetch, { HeadersInit } from 'node-fetch'; import { URL } from 'url'; import { MedplumServerConfig } from '../config'; import { getRequestContext, RequestContext, requestContextStore } from '../context'; +import { buildAccessPolicy } from '../fhir/accesspolicy'; import { executeBot } from '../fhir/operations/execute'; import { getSystemRepo, Repository } from '../fhir/repo'; import { globalLogger } from '../logger'; import { getRedis } from '../redis'; import { createSubEventNotification } from '../subscriptions/websockets'; +import { parseTraceparent } from '../traceparent'; import { AuditEventOutcome } from '../util/auditevent'; import { BackgroundJobContext, BackgroundJobInteraction } from './context'; import { createAuditEvent, findProjectMembership, isFhirCriteriaMet, isJobSuccessful } from './utils'; -import { parseTraceparent } from '../traceparent'; /** * The upper limit on the number of times a job can be retried. @@ -126,6 +130,43 @@ export function getSubscriptionQueue(): Queue | undefined { return queue; } +/** + * Checks if this resource should create a notification for this `Subscription` based on the access policy that should be applied for this `Subscription`. + * The `AccessPolicy` of author's `ProjectMembership` for this resource's `Project` is used when evaluating whether the `AccessPolicy` is satisfied. + * + * Currently we log if the `AccessPolicy` is not satisfied only. + * + * TODO: Actually prevent notifications for `Subscriptions` where the `AccessPolicy` is not satisfied. + * + * @param resource - The resource to evaluate against the `AccessPolicy`. + * @param project - The project containing the resource. + * @param subscription - The `Subscription` to get the `AccessPolicy` for. + */ +async function checkAccessPolicy(resource: Resource, project: Project, subscription: Subscription): Promise { + // Check access policy + const subAuthor = subscription.meta?.author; + if (subAuthor) { + const membership = await findProjectMembership(project.id as string, subAuthor); + if (membership) { + const accessPolicy = await buildAccessPolicy(membership); + const satisfied = !!satisfiedAccessPolicy(resource, AccessPolicyInteraction.READ, accessPolicy); + if (!satisfied) { + globalLogger.warn( + `[Subscription Access Policy]: Access Policy not satisfied for '${getReferenceString(resource)}'`, + { author: subAuthor, project, accessPolicy } + ); + } + } else { + globalLogger.warn( + `[Subscription Access Policy]: No membership for author '${getReferenceString(subAuthor)}' in project '${getReferenceString(project)}'` + ); + } + } else { + // Log it if there is no author for this Subscription (this is not good) + globalLogger.warn(`[Subscription Access Policy]: No author for subscription '${getReferenceString(subscription)}'`); + } +} + /** * Adds all subscription jobs for a given resource. * @@ -147,10 +188,28 @@ export async function addSubscriptionJobs(resource: Resource, context: Backgroun // Never send subscriptions for audit events return; } + + const systemRepo = getSystemRepo(); + let project: Project | undefined; + try { + const projectId = resource.meta?.project; + if (projectId) { + project = await systemRepo.readResource('Project', projectId); + } + } catch (_err: unknown) { + project = undefined; + } + const requestTime = new Date().toISOString(); - const subscriptions = await getSubscriptions(resource); + if (!project) { + ctx.logger.debug('Did not evaluate subscriptions for resource without project'); + globalLogger.warn(`[Subscription Access Policy]: No project for resource '${getReferenceString(resource)}'`); + return; + } + const subscriptions = await getSubscriptions(resource, project); ctx.logger.debug(`Evaluate ${subscriptions.length} subscription(s)`); for (const subscription of subscriptions) { + await checkAccessPolicy(resource, project, subscription); const criteria = await matchesCriteria(resource, subscription, context); if (criteria) { await addSubscriptionJobData({ @@ -266,13 +325,11 @@ async function addSubscriptionJobData(job: SubscriptionJobData): Promise { /** * Loads the list of all subscriptions in this repository. * @param resource - The resource that was created or updated. + * @param project - The project that contains this resource. * @returns The list of all subscriptions in this repository. */ -async function getSubscriptions(resource: Resource): Promise { - const project = resource.meta?.project; - if (!project) { - return []; - } +async function getSubscriptions(resource: Resource, project: Project): Promise { + const projectId = project.id as string; const systemRepo = getSystemRepo(); const subscriptions = await systemRepo.searchResources({ resourceType: 'Subscription', @@ -281,7 +338,7 @@ async function getSubscriptions(resource: Resource): Promise { { code: '_project', operator: Operator.EQUALS, - value: project, + value: projectId, }, { code: 'status', @@ -290,10 +347,20 @@ async function getSubscriptions(resource: Resource): Promise { }, ], }); - const inMemorySubscriptionsStr = await getRedis().get(`medplum:subscriptions:r4:project:${project}`); - if (inMemorySubscriptionsStr) { - const inMemorySubscriptions = JSON.parse(inMemorySubscriptionsStr) as Subscription[]; - subscriptions.push(...inMemorySubscriptions); + const redisOnlySubRefStrs = await getRedis().smembers(`medplum:subscriptions:r4:project:${projectId}:active`); + if (redisOnlySubRefStrs.length) { + const redisOnlySubStrs = await getRedis().mget(redisOnlySubRefStrs); + if (project.features?.includes('websocket-subscriptions')) { + const subArrStr = '[' + redisOnlySubStrs.filter(Boolean).join(',') + ']'; + const inMemorySubs = JSON.parse(subArrStr) as { resource: Subscription; projectId: string }[]; + for (const { resource } of inMemorySubs) { + subscriptions.push(resource); + } + } else { + globalLogger.warn( + `[WebSocket Subscriptions]: subscription for resource '${getReferenceString(resource)}' might have been fired but WebSocket subscriptions are not enabled for project '${project.name ?? getReferenceString(project)}'` + ); + } } return subscriptions; } From 77afde3e48691f3ea6e1e04322902675fa2d14ea Mon Sep 17 00:00:00 2001 From: Matt Willer Date: Tue, 20 Feb 2024 14:56:16 -0800 Subject: [PATCH 67/81] Implement ValueSet expand operation over Coding table (#3862) * WIP: ValueSet expand * Expand code system * Implement new ValueSet expand operation logic * Add LOINC stub CodeSystem for tests * Handle is-not-a filter case * Deterministic ValueSet selection and duplicate handling * Fix CodeSystem search * Remove status check * Add project filter to rebuild ValueSets * Reindex CodeSystems after ValueSet rebuild * Fix clobbering * Fix implicit parent property * Batch deletion of coding properties * Disambiguate CodeSystems * Fix subsumption query * Add feature flag * Fix merge conflict * Add test case * Add tests * Update test case * Refactor to satisfy Sonar * Increase test coverage * Update test case * Include ancestor codes in is-a filter * Cache CodeSystem resources during expansion * Optimiize inclusion DB query * Optimize hierarchy filter query * Fix filtering of explicitly listed codings * Remove arbitrary ordering * Unify CodeSystem resolution logic * Simplify expansion loop * Minor simplification * Fix merge conflicts --- .../dist/fhir/r4/profiles-medplum.json | 2 +- .../dist/fhir/r4/valuesets-medplum.json | 58 +++ packages/fhirtypes/dist/Project.d.ts | 2 +- packages/server/src/admin/super.ts | 5 +- packages/server/src/fhir/lookups/coding.ts | 18 +- .../fhir/operations/codesystemimport.test.ts | 24 -- .../src/fhir/operations/codesystemimport.ts | 17 +- .../fhir/operations/codesystemlookup.test.ts | 2 +- .../src/fhir/operations/codesystemlookup.ts | 16 +- .../operations/codesystemvalidatecode.test.ts | 2 +- .../fhir/operations/codesystemvalidatecode.ts | 39 +- .../server/src/fhir/operations/expand.test.ts | 390 ++++++++++++++---- packages/server/src/fhir/operations/expand.ts | 284 +++++++++++-- .../src/fhir/operations/utils/parameters.ts | 4 + packages/server/src/seeds/valuesets.ts | 15 +- packages/server/src/test.setup.ts | 2 +- 16 files changed, 686 insertions(+), 194 deletions(-) diff --git a/packages/definitions/dist/fhir/r4/profiles-medplum.json b/packages/definitions/dist/fhir/r4/profiles-medplum.json index cb3dc1cc2d..d1ed01186d 100644 --- a/packages/definitions/dist/fhir/r4/profiles-medplum.json +++ b/packages/definitions/dist/fhir/r4/profiles-medplum.json @@ -220,7 +220,7 @@ }, "binding" : { "strength" : "required", - "valueSet" : "https://medplum.com/fhir/ValueSet/project-feature|4.0.1" + "valueSet" : "https://medplum.com/fhir/ValueSet/project-feature" } }, { diff --git a/packages/definitions/dist/fhir/r4/valuesets-medplum.json b/packages/definitions/dist/fhir/r4/valuesets-medplum.json index 9609c2c3b9..9d1d11426d 100644 --- a/packages/definitions/dist/fhir/r4/valuesets-medplum.json +++ b/packages/definitions/dist/fhir/r4/valuesets-medplum.json @@ -149,6 +149,11 @@ "display": "GraphQL Introspection", "definition": "GraphQL Introspection" }, + { + "code": "terminology", + "display": "Terminology Service", + "definition": "Updated implementation of Terminology Service functionality" + }, { "code": "websocket-subscriptions", "display": "WebSocket Subscriptions", @@ -701,6 +706,59 @@ ] } } + }, + { + "fullUrl": "https://medplum.com/fhir/CodeSystem/loinc-stub", + "resource": { + "resourceType": "CodeSystem", + "url": "http://loinc.org", + "version": "0.0.0", + "status": "active", + "hierarchyMeaning": "is-a", + "content": "example", + "concept": [ + { + "code": "LA28865-6", + "display": "Longitudinal care-coordination focused care team" + }, + { + "code": "86645-9", + "display": "Pregnancy intention in the next year - Reported" + }, + { + "code": "82810-3", + "display": "Pregnancy status" + }, + { + "code": "76690-7", + "display": "Sexual orientation" + }, + { + "code": "77606-2", + "display": "Weight-for-length Per age and sex" + }, + { + "code": "8480-6", + "display": "Systolic blood pressure" + }, + { + "code": "8462-4", + "display": "Diastolic blood pressure" + }, + { + "code": "8867-4", + "display": "Heart rate" + }, + { + "code": "2708-6", + "display": "Oxygen saturation in Arterial blood" + }, + { + "code": "3151-8", + "display": "Inhaled oxygen flow rate" + } + ] + } } ] } diff --git a/packages/fhirtypes/dist/Project.d.ts b/packages/fhirtypes/dist/Project.d.ts index 4c2a3a2394..6b3e2fd52e 100644 --- a/packages/fhirtypes/dist/Project.d.ts +++ b/packages/fhirtypes/dist/Project.d.ts @@ -86,7 +86,7 @@ export interface Project { /** * A list of optional features that are enabled for the project. */ - features?: ('bots' | 'cron' | 'email' | 'google-auth-required' | 'graphql-introspection' | 'websocket-subscriptions')[]; + features?: ('bots' | 'cron' | 'email' | 'google-auth-required' | 'graphql-introspection' | 'terminology' | 'websocket-subscriptions')[]; /** * The default access policy for patients using open registration. diff --git a/packages/server/src/admin/super.ts b/packages/server/src/admin/super.ts index 4d53e35ee9..707fde50f9 100644 --- a/packages/server/src/admin/super.ts +++ b/packages/server/src/admin/super.ts @@ -37,7 +37,10 @@ superAdminRouter.post( requireSuperAdmin(); requireAsync(req); - await sendAsyncResponse(req, res, () => rebuildR4ValueSets()); + await sendAsyncResponse(req, res, async () => { + await rebuildR4ValueSets(); + await getSystemRepo().reindexResourceType('CodeSystem'); + }); }) ); diff --git a/packages/server/src/fhir/lookups/coding.ts b/packages/server/src/fhir/lookups/coding.ts index 3d537f258d..0a26f552bc 100644 --- a/packages/server/src/fhir/lookups/coding.ts +++ b/packages/server/src/fhir/lookups/coding.ts @@ -27,7 +27,7 @@ export class CodingTable extends LookupTable { } async indexResource(client: PoolClient, resource: Resource): Promise { - if (resource.resourceType === 'CodeSystem' && resource.content === 'complete') { + if (resource.resourceType === 'CodeSystem' && (resource.content === 'complete' || resource.content === 'example')) { await this.deleteValuesForResource(client, resource); const elements = this.getCodeSystemElements(resource); @@ -47,13 +47,15 @@ export class CodingTable extends LookupTable { .execute(client); await new DeleteQuery('CodeSystem_Property').where('system', '=', resource.id).execute(client); if (deletedCodes.length) { - await new DeleteQuery('Coding_Property') - .where( - 'coding', - 'IN', - deletedCodes.map((c) => c.id) - ) - .execute(client); + for (let i = 0; i < deletedCodes.length; i += 500) { + await new DeleteQuery('Coding_Property') + .where( + 'coding', + 'IN', + deletedCodes.slice(i, i + 500).map((c) => c.id) + ) + .execute(client); + } } } diff --git a/packages/server/src/fhir/operations/codesystemimport.test.ts b/packages/server/src/fhir/operations/codesystemimport.test.ts index 744b2937e2..fc8c2837c9 100644 --- a/packages/server/src/fhir/operations/codesystemimport.test.ts +++ b/packages/server/src/fhir/operations/codesystemimport.test.ts @@ -175,30 +175,6 @@ describe('CodeSystem $import', () => { expect(res.body.issue[0].code).toEqual('invalid'); }); - test('Returns error on ambiguous code system', async () => { - // Upload another copy of the CodeSystem - const res = await request(app) - .post(`/fhir/R4/CodeSystem`) - .set('Authorization', 'Bearer ' + accessToken) - .set('Content-Type', ContentType.FHIR_JSON) - .send(snomedJSON); - expect(res.status).toEqual(201); - - const res2 = await request(app) - .post(`/fhir/R4/CodeSystem/$import`) - .set('Authorization', 'Bearer ' + accessToken) - .set('Content-Type', ContentType.FHIR_JSON) - .send({ - resourceType: 'Parameters', - parameter: [ - { name: 'system', valueUri: snomed.url }, - { name: 'concept', valueCoding: { code: '1', display: 'Aspirin' } }, - ], - }); - expect(res2.status).toEqual(400); - expect(res2.body.issue[0].code).toEqual('invalid'); - }); - test('Returns error on unknown code for property', async () => { const res2 = await request(app) .post(`/fhir/R4/CodeSystem/$import`) diff --git a/packages/server/src/fhir/operations/codesystemimport.ts b/packages/server/src/fhir/operations/codesystemimport.ts index ec4f99970b..c56bc2a8b1 100644 --- a/packages/server/src/fhir/operations/codesystemimport.ts +++ b/packages/server/src/fhir/operations/codesystemimport.ts @@ -1,4 +1,4 @@ -import { OperationOutcomeError, Operator, allOk, badRequest, normalizeOperationOutcome } from '@medplum/core'; +import { OperationOutcomeError, allOk, badRequest, normalizeOperationOutcome } from '@medplum/core'; import { CodeSystem, Coding, OperationDefinition } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { PoolClient } from 'pg'; @@ -6,6 +6,7 @@ import { requireSuperAdmin } from '../../admin/super'; import { sendOutcome } from '../outcomes'; import { InsertQuery, SelectQuery } from '../sql'; import { parseInputParameters, sendOutputParameters } from './utils/parameters'; +import { findCodeSystem } from './expand'; const operation: OperationDefinition = { resourceType: 'OperationDefinition', @@ -61,18 +62,7 @@ export async function codeSystemImportHandler(req: Request, res: Response): Prom const ctx = requireSuperAdmin(); const params = parseInputParameters(operation, req); - const codeSystems = await ctx.repo.searchResources({ - resourceType: 'CodeSystem', - filters: [{ code: 'url', operator: Operator.EQUALS, value: params.system }], - }); - if (codeSystems.length === 0) { - sendOutcome(res, badRequest('No CodeSystem found with URL ' + params.system)); - return; - } else if (codeSystems.length > 1) { - sendOutcome(res, badRequest('Ambiguous code system URI: ' + params.system)); - return; - } - const codeSystem = codeSystems[0]; + const codeSystem = await findCodeSystem(params.system); try { await ctx.repo.withTransaction(async (db) => { @@ -159,6 +149,7 @@ async function processProperties( } export const parentProperty = 'http://hl7.org/fhir/concept-properties#parent'; +export const childProperty = 'http://hl7.org/fhir/concept-properties#child'; async function resolveProperty(codeSystem: CodeSystem, code: string, db: PoolClient): Promise<[number, boolean]> { let prop = codeSystem.property?.find((p) => p.code === code); diff --git a/packages/server/src/fhir/operations/codesystemlookup.test.ts b/packages/server/src/fhir/operations/codesystemlookup.test.ts index 0c9b81c2d0..558aa5cab2 100644 --- a/packages/server/src/fhir/operations/codesystemlookup.test.ts +++ b/packages/server/src/fhir/operations/codesystemlookup.test.ts @@ -169,7 +169,7 @@ describe('CodeSystem lookup', () => { expect(res.status).toEqual(400); expect(res.body).toMatchObject({ resourceType: 'OperationOutcome', - issue: [{ severity: 'error', code: 'invalid', details: { text: 'CodeSystem not found' } }], + issue: [{ severity: 'error', code: 'invalid', details: { text: `Code system ${codeSystem.url} not found` } }], }); }); }); diff --git a/packages/server/src/fhir/operations/codesystemlookup.ts b/packages/server/src/fhir/operations/codesystemlookup.ts index fcb8766175..bbd40368b6 100644 --- a/packages/server/src/fhir/operations/codesystemlookup.ts +++ b/packages/server/src/fhir/operations/codesystemlookup.ts @@ -1,12 +1,12 @@ -import { Operator, TypedValue, allOk, badRequest, notFound } from '@medplum/core'; -import { CodeSystem, Coding } from '@medplum/fhirtypes'; +import { TypedValue, allOk, badRequest, notFound } from '@medplum/core'; +import { Coding } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; -import { getAuthenticatedContext } from '../../context'; import { getDatabasePool } from '../../database'; import { sendOutcome } from '../outcomes'; import { Column, Condition, SelectQuery } from '../sql'; import { getOperationDefinition } from './definitions'; import { parseInputParameters, sendOutputParameters } from './utils/parameters'; +import { findCodeSystem } from './expand'; const operation = getOperationDefinition('CodeSystem', 'lookup'); @@ -18,7 +18,6 @@ type CodeSystemLookupParameters = { }; export async function codeSystemLookupHandler(req: Request, res: Response): Promise { - const ctx = getAuthenticatedContext(); const params = parseInputParameters(operation, req); let coding: Coding; @@ -31,14 +30,7 @@ export async function codeSystemLookupHandler(req: Request, res: Response): Prom return; } - const codeSystem = await ctx.repo.searchOne({ - resourceType: 'CodeSystem', - filters: [{ code: 'url', operator: Operator.EQUALS, value: coding.system as string }], - }); - if (!codeSystem) { - sendOutcome(res, badRequest('CodeSystem not found')); - return; - } + const codeSystem = await findCodeSystem(coding.system as string); const lookup = new SelectQuery('Coding'); const codeSystemTable = lookup.getNextJoinAlias(); diff --git a/packages/server/src/fhir/operations/codesystemvalidatecode.test.ts b/packages/server/src/fhir/operations/codesystemvalidatecode.test.ts index b65b420e27..d3df957ffc 100644 --- a/packages/server/src/fhir/operations/codesystemvalidatecode.test.ts +++ b/packages/server/src/fhir/operations/codesystemvalidatecode.test.ts @@ -146,7 +146,7 @@ describe('CodeSystem validate-code', () => { expect(res.status).toEqual(400); expect(res.body).toMatchObject({ resourceType: 'OperationOutcome', - issue: [{ severity: 'error', code: 'invalid', details: { text: 'CodeSystem not found' } }], + issue: [{ severity: 'error', code: 'invalid', details: { text: `Code system ${codeSystem.url} not found` } }], }); }); }); diff --git a/packages/server/src/fhir/operations/codesystemvalidatecode.ts b/packages/server/src/fhir/operations/codesystemvalidatecode.ts index bec701a44a..345667ea2c 100644 --- a/packages/server/src/fhir/operations/codesystemvalidatecode.ts +++ b/packages/server/src/fhir/operations/codesystemvalidatecode.ts @@ -1,12 +1,12 @@ -import { Operator, allOk, badRequest } from '@medplum/core'; +import { allOk, badRequest } from '@medplum/core'; import { CodeSystem, Coding } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; -import { getAuthenticatedContext } from '../../context'; import { getDatabasePool } from '../../database'; import { sendOutcome } from '../outcomes'; import { Column, Condition, SelectQuery } from '../sql'; import { getOperationDefinition } from './definitions'; import { parseInputParameters, sendOutputParameters } from './utils/parameters'; +import { findCodeSystem } from './expand'; const operation = getOperationDefinition('CodeSystem', 'validate-code'); @@ -26,7 +26,6 @@ type CodeSystemValidateCodeParameters = { * @param res - The HTTP response. */ export async function codeSystemValidateCodeHandler(req: Request, res: Response): Promise { - const ctx = getAuthenticatedContext(); const params = parseInputParameters(operation, req); let coding: Coding; @@ -39,15 +38,20 @@ export async function codeSystemValidateCodeHandler(req: Request, res: Response) return; } - const codeSystem = await ctx.repo.searchOne({ - resourceType: 'CodeSystem', - filters: [{ code: 'url', operator: Operator.EQUALS, value: coding.system as string }], - }); - if (!codeSystem) { - sendOutcome(res, badRequest('CodeSystem not found')); - return; + const codeSystem = await findCodeSystem(coding.system as string); + const result = await validateCode(codeSystem, coding.code as string); + + const output: Record = Object.create(null); + if (result) { + output.result = true; + output.display = result.display; + } else { + output.result = false; } + await sendOutputParameters(req, res, operation, allOk, output); +} +export async function validateCode(codeSystem: CodeSystem, code: string): Promise { const query = new SelectQuery('Coding'); const codeSystemTable = query.getNextJoinAlias(); query.innerJoin( @@ -55,16 +59,13 @@ export async function codeSystemValidateCodeHandler(req: Request, res: Response) codeSystemTable, new Condition(new Column('Coding', 'system'), '=', new Column(codeSystemTable, 'id')) ); - query.column('display').where(new Column(codeSystemTable, 'id'), '=', codeSystem.id).where('code', '=', coding.code); + query + .column('id') + .column('display') + .where(new Column(codeSystemTable, 'id'), '=', codeSystem.id) + .where('code', '=', code); const db = getDatabasePool(); const result = await query.execute(db); - const output: Record = Object.create(null); - if (result.length) { - output.result = true; - output.display = result[0].display; - } else { - output.result = false; - } - await sendOutputParameters(req, res, operation, allOk, output); + return result.length ? { id: result[0].id, system: codeSystem.url, code, display: result[0].display } : undefined; } diff --git a/packages/server/src/fhir/operations/expand.test.ts b/packages/server/src/fhir/operations/expand.test.ts index 7cefb80817..36600308dc 100644 --- a/packages/server/src/fhir/operations/expand.test.ts +++ b/packages/server/src/fhir/operations/expand.test.ts @@ -1,5 +1,12 @@ -import { ContentType, LOINC, SNOMED } from '@medplum/core'; -import { OperationOutcome, ValueSet, ValueSetExpansionContains } from '@medplum/fhirtypes'; +import { ContentType, SNOMED, LOINC } from '@medplum/core'; +import { + CodeSystem, + OperationOutcome, + Project, + ValueSet, + ValueSetExpansion, + ValueSetExpansionContains, +} from '@medplum/fhirtypes'; import { randomUUID } from 'crypto'; import express from 'express'; import request from 'supertest'; @@ -8,7 +15,7 @@ import { loadTestConfig } from '../../config'; import { initTestAuth, withTestContext } from '../../test.setup'; import { getSystemRepo } from '../repo'; -describe('Expand', () => { +describe.each>([{ features: [] }, { features: ['terminology'] }])('Expand with %j', (projectProps) => { const app = express(); const systemRepo = getSystemRepo(); let accessToken: string; @@ -16,14 +23,14 @@ describe('Expand', () => { beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); - accessToken = await initTestAuth(); + accessToken = await initTestAuth(projectProps); }); afterAll(async () => { await shutdownApp(); }); - test('No system', async () => { + test('No ValueSet URL', async () => { const res = await request(app) .get(`/fhir/R4/ValueSet/$expand`) .set('Authorization', 'Bearer ' + accessToken); @@ -39,7 +46,7 @@ describe('Expand', () => { expect((res.body as OperationOutcome).issue?.[0].details?.text).toContain('ValueSet not found'); }); - test('No systems', async () => { + test('No logical definition', async () => { const url = 'https://example.com/ValueSet/' + randomUUID(); await withTestContext(() => systemRepo.createResource({ @@ -52,7 +59,9 @@ describe('Expand', () => { .get(`/fhir/R4/ValueSet/$expand?url=${encodeURIComponent(url)}`) .set('Authorization', 'Bearer ' + accessToken); expect(res.status).toBe(400); - expect((res.body as OperationOutcome).issue?.[0].details?.text).toContain('No systems found'); + expect((res.body as OperationOutcome).issue?.[0].details?.text).toMatch( + /(^Missing ValueSet definition$)|(^No systems found$)/ + ); }); test('No filter', async () => { @@ -81,12 +90,12 @@ describe('Expand', () => { .get( `/fhir/R4/ValueSet/$expand?url=${encodeURIComponent( 'http://hl7.org/fhir/ValueSet/observation-codes' - )}&filter=left` + )}&filter=rate` ) .set('Authorization', 'Bearer ' + accessToken); expect(res.status).toBe(200); expect(res.body.expansion.contains[0].system).toBe(LOINC); - expect(res.body.expansion.contains[0].display).toMatch(/left/i); + expect(res.body.expansion.contains[0].display).toMatch(/rate/i); }); test('Success with count and offset', async () => { @@ -94,36 +103,13 @@ describe('Expand', () => { .get( `/fhir/R4/ValueSet/$expand?url=${encodeURIComponent( 'http://hl7.org/fhir/ValueSet/observation-codes' - )}&filter=left&offset=1&count=1` + )}&filter=blood&offset=1&count=1` ) .set('Authorization', 'Bearer ' + accessToken); expect(res.status).toBe(200); - expect(res.body.expansion.offset).toBe(1); expect(res.body.expansion.contains.length).toBe(1); expect(res.body.expansion.contains[0].system).toBe(LOINC); - expect(res.body.expansion.contains[0].display).toMatch(/left/i); - }); - - test('Resource types', async () => { - const valueSet = 'http://hl7.org/fhir/ValueSet/resource-types|4.0.1'; - const res = await request(app) - .get(`/fhir/R4/ValueSet/$expand?url=${encodeURIComponent(valueSet)}&filter=Patient`) - .set('Authorization', 'Bearer ' + accessToken); - expect(res.status).toBe(200); - expect(res.body).toMatchObject({ - resourceType: 'ValueSet', - url: 'http://hl7.org/fhir/ValueSet/resource-types', - expansion: { - offset: 0, - contains: [ - { - system: 'http://hl7.org/fhir/resource-types', - code: 'Patient', - display: 'Patient', - }, - ], - }, - }); + expect(res.body.expansion.contains[0].display).toMatch(/blood/i); }); test('No duplicates', async () => { @@ -136,7 +122,6 @@ describe('Expand', () => { resourceType: 'ValueSet', url: 'http://hl7.org/fhir/ValueSet/subscription-status', expansion: { - offset: 0, contains: [ { system: 'http://hl7.org/fhir/subscription-status', @@ -149,29 +134,6 @@ describe('Expand', () => { expect(res.body.expansion.contains.length).toBe(1); }); - test('External system', async () => { - const valueSet = 'http://hl7.org/fhir/ValueSet/servicerequest-category'; - const filter = 'imaging'; - const res = await request(app) - .get(`/fhir/R4/ValueSet/$expand?url=${encodeURIComponent(valueSet)}&filter=${encodeURIComponent(filter)}`) - .set('Authorization', 'Bearer ' + accessToken); - expect(res.status).toBe(200); - expect(res.body).toMatchObject({ - resourceType: 'ValueSet', - url: valueSet, - expansion: { - offset: 0, - contains: [ - { - system: SNOMED, - code: '363679005', - display: 'Imaging', - }, - ], - }, - }); - }); - test('Marital status', async () => { // This is a good test, because it covers a bunch of edge cases. // Marital status is the combination of two code systems: http://hl7.org/fhir/v3/MaritalStatus and http://hl7.org/fhir/v3/NullFlavor @@ -187,7 +149,6 @@ describe('Expand', () => { resourceType: 'ValueSet', url: valueSet, expansion: { - offset: 0, contains: [ { system: 'http://terminology.hl7.org/CodeSystem/v3-MaritalStatus', @@ -209,19 +170,19 @@ describe('Expand', () => { .get( `/fhir/R4/ValueSet/$expand?url=${encodeURIComponent( 'http://hl7.org/fhir/ValueSet/observation-codes' - )}&filter=${encodeURIComponent('(left)')}` + )}&filter=${encodeURIComponent('intention - reported')}` ) .set('Authorization', 'Bearer ' + accessToken); expect(res.status).toBe(200); expect(res.body.expansion.contains[0].system).toBe(LOINC); - expect(res.body.expansion.contains[0].display).toMatch(/left/i); + expect(res.body.expansion.contains[0].display).toMatch(/pregnancy intention/i); }); test('Handle empty string after punctuation', async () => { const res = await request(app) .get( `/fhir/R4/ValueSet/$expand?url=${encodeURIComponent( - 'http://hl7.org/fhir/ValueSet/observation-codes' + 'http://hl7.org/fhir/ValueSet/care-plan-activity-kind' )}&filter=${encodeURIComponent('[')}` ) .set('Authorization', 'Bearer ' + accessToken); @@ -230,25 +191,23 @@ describe('Expand', () => { test('No null `display` field', async () => { const res = await request(app) - .get(`/fhir/R4/ValueSet/$expand?url=${encodeURIComponent('http://hl7.org/fhir/ValueSet/observation-codes')}`) + .get( + `/fhir/R4/ValueSet/$expand?url=${encodeURIComponent('http://hl7.org/fhir/ValueSet/care-plan-activity-kind')}` + ) .set('Authorization', 'Bearer ' + accessToken); expect(res.status).toBe(200); const body = res.body as ValueSet; expect(body).toBeDefined(); - let foundNullDisplay = false; const contains = body.expansion?.contains; expect(contains).toBeDefined(); expect(contains?.length).toBeGreaterThan(0); for (const code of contains as ValueSetExpansionContains[]) { - if (code.display && code.display === null) { - foundNullDisplay = true; - break; + if (code.display === null) { + fail(`Found null display value for coding ${code.system}|${code.code}`); } } - - expect(foundNullDisplay).toBe(false); }); test('User uploaded ValueSet', async () => { @@ -259,26 +218,23 @@ describe('Expand', () => { .send({ resourceType: 'ValueSet', status: 'active', - url: 'https://example.com/fhir/ValueSet/fruits', + url: 'https://example.com/fhir/ValueSet/clinical-resources' + randomUUID(), expansion: { timestamp: '2023-09-13T23:24:00.000Z', }, compose: { include: [ { - system: 'http://example.com/fruits', + system: 'http://hl7.org/fhir/resource-types', concept: [ { - code: 'apple', - display: 'Apple', + code: 'Patient', }, { - code: 'banana', - display: 'Banana', + code: 'Practitioner', }, { - code: 'cherry', - display: 'Cherry', + code: 'Observation', }, ], }, @@ -286,14 +242,286 @@ describe('Expand', () => { }, }); expect(res1.status).toBe(201); + const url = res1.body.url; + + const res2 = await request(app) + .get(`/fhir/R4/ValueSet/$expand?url=${encodeURIComponent(url)}`) + .set('Authorization', 'Bearer ' + accessToken); + expect(res2.status).toBe(200); + expect(res2.body.expansion.contains).toContainEqual({ + system: 'http://hl7.org/fhir/resource-types', + code: 'Patient', + display: 'Patient', + }); + expect(res2.body.expansion.contains).toContainEqual({ + system: 'http://hl7.org/fhir/resource-types', + code: 'Practitioner', + display: 'Practitioner', + }); + expect(res2.body.expansion.contains).toContainEqual({ + system: 'http://hl7.org/fhir/resource-types', + code: 'Observation', + display: 'Observation', + }); + }); + + test('CodeSystem resolution', async () => { + const codeSystem: CodeSystem = { + resourceType: 'CodeSystem', + status: 'active', + url: 'http://example.com/CodeSystem/foo' + randomUUID(), + version: '1', + content: 'not-present', + }; + const superAdminAccessToken = await initTestAuth({ superAdmin: true }); + + // First version of code system + const res1 = await request(app) + .post('/fhir/R4/CodeSystem') + .set('Authorization', 'Bearer ' + accessToken) + .send(codeSystem); + expect(res1.status).toEqual(201); + const res2 = await request(app) + .post(`/fhir/R4/CodeSystem/$import`) + .set('Authorization', 'Bearer ' + superAdminAccessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send({ + resourceType: 'Parameters', + parameter: [ + { name: 'system', valueUri: codeSystem.url }, + { name: 'concept', valueCoding: { code: 'foo', display: 'Foo' } }, + { name: 'concept', valueCoding: { code: 'bar', display: 'Bar' } }, + ], + }); + expect(res2.status).toEqual(200); + + // Second version of code system + codeSystem.version = '2'; + const res3 = await request(app) + .post('/fhir/R4/CodeSystem') + .set('Authorization', 'Bearer ' + accessToken) + .send(codeSystem); + expect(res3.status).toEqual(201); + const res4 = await request(app) + .post(`/fhir/R4/CodeSystem/$import`) + .set('Authorization', 'Bearer ' + superAdminAccessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send({ + resourceType: 'Parameters', + parameter: [ + { name: 'system', valueUri: codeSystem.url }, + { name: 'concept', valueCoding: { code: 'baz', display: 'Baz' } }, + { name: 'concept', valueCoding: { code: 'quux', display: 'Quux' } }, + ], + }); + expect(res4.status).toEqual(200); + + // ValueSet containing all of target CodeSystem + const res5 = await request(app) + .post('/fhir/R4/ValueSet') + .set('Authorization', 'Bearer ' + accessToken) + .send({ + resourceType: 'ValueSet', + status: 'active', + url: 'http://example.com/ValueSet/bar' + randomUUID(), + compose: { + include: [{ system: codeSystem.url }], + }, + }); + expect(res5.status).toEqual(201); + const valueSet = res5.body as ValueSet; + + const res6 = await request(app) + .get(`/fhir/R4/ValueSet/$expand?url=${encodeURIComponent(valueSet.url as string)}`) + .set('Authorization', 'Bearer ' + accessToken); + expect(res6.status).toEqual(200); + }); +}); + +describe('Updated implementation', () => { + const app = express(); + let accessToken: string; + + beforeAll(async () => { + const config = await loadTestConfig(); + await initApp(app, config); + accessToken = await initTestAuth({ features: ['terminology'] }); + }); + + afterAll(async () => { + await shutdownApp(); + }); + + test('Returns error for recursive definition', async () => { + const valueSet: ValueSet = { + resourceType: 'ValueSet', + status: 'active', + url: 'https://example.com/fhir/ValueSet/recursive' + randomUUID(), + compose: { + include: [{ valueSet: ['http://example.com/ValueSet/recursive'] }], + }, + }; + const res1 = await request(app) + .post(`/fhir/R4/ValueSet`) + .set('Authorization', 'Bearer ' + accessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send(valueSet); + expect(res1.status).toBe(201); const res2 = await request(app) + .get(`/fhir/R4/ValueSet/$expand?url=${encodeURIComponent(valueSet.url as string)}`) + .set('Authorization', 'Bearer ' + accessToken); + expect(res2.status).toEqual(400); + }); + + test('Subsumption', async () => { + const res = await request(app) .get( - `/fhir/R4/ValueSet/$expand?url=${encodeURIComponent('https://example.com/fhir/ValueSet/fruits')}&filter=apple` + `/fhir/R4/ValueSet/$expand?url=${encodeURIComponent('http://hl7.org/fhir/ValueSet/relatedperson-relationshiptype')}&count=100` ) .set('Authorization', 'Bearer ' + accessToken); - expect(res2.status).toBe(200); - expect(res2.body.expansion.contains[0].system).toBe('http://example.com/fruits'); - expect(res2.body.expansion.contains[0].display).toMatch(/apple/i); + expect(res.status).toEqual(200); + const expansion = res.body.expansion as ValueSetExpansion; + + expect( + expansion.contains?.find( + (c) => c.system === 'http://terminology.hl7.org/CodeSystem/v3-RoleCode' && c.code === 'FRND' + )?.display + ).toEqual('unrelated friend'); + }); + + test('Returns error when CodeSystem not found', async () => { + const res1 = await request(app) + .post(`/fhir/R4/ValueSet`) + .set('Authorization', 'Bearer ' + accessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send({ + resourceType: 'ValueSet', + status: 'active', + url: 'https://example.com/csdne' + randomUUID(), + expansion: { + timestamp: '2023-09-13T23:24:00.000Z', + }, + compose: { + include: [ + { + system: 'http://example.com/the-codesystem-does-not-exist', + concept: [ + { + code: '0', + }, + ], + }, + ], + }, + }); + expect(res1.status).toBe(201); + const url = res1.body.url; + + const res2 = await request(app) + .get(`/fhir/R4/ValueSet/$expand?url=${encodeURIComponent(url)}`) + .set('Authorization', 'Bearer ' + accessToken); + expect(res2.status).toBe(400); + expect(res2.body.issue[0].details.text).toEqual( + 'Code system http://example.com/the-codesystem-does-not-exist not found' + ); + }); + + test('Prefers current Project version of common CodeSystem', async () => { + const res1 = await request(app) + .post(`/fhir/R4/CodeSystem`) + .set('Authorization', 'Bearer ' + accessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send({ + resourceType: 'CodeSystem', + status: 'active', + url: SNOMED, + content: 'complete', + concept: [{ code: '314159265', display: 'Test SNOMED override' }], + }); + expect(res1.status).toEqual(201); + + const res2 = await request(app) + .post(`/fhir/R4/ValueSet`) + .set('Authorization', 'Bearer ' + accessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send({ + resourceType: 'ValueSet', + status: 'active', + url: 'https://example.com/snomed-all' + randomUUID(), + compose: { + include: [ + { + system: SNOMED, + }, + ], + }, + }); + expect(res2.status).toBe(201); + + const res3 = await request(app) + .get(`/fhir/R4/ValueSet/$expand?url=${encodeURIComponent(res2.body.url)}`) + .set('Authorization', 'Bearer ' + accessToken); + expect(res3.status).toBe(200); + const coding = res3.body.expansion.contains[0]; + expect(coding.system).toBe(SNOMED); + expect(coding.code).toBe('314159265'); + expect(coding.display).toEqual('Test SNOMED override'); + }); + + test('Returns error when property filter is invalid for CodeSystem', async () => { + const res1 = await request(app) + .post(`/fhir/R4/CodeSystem`) + .set('Authorization', 'Bearer ' + accessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send({ + resourceType: 'CodeSystem', + status: 'active', + url: 'http://example.com/custom-code-system', + content: 'complete', + hierarchyMeaning: 'grouped-by', + concept: [{ code: 'A', concept: [{ code: 'B' }] }], + }); + expect(res1.status).toEqual(201); + + const res2 = await request(app) + .post(`/fhir/R4/ValueSet`) + .set('Authorization', 'Bearer ' + accessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send({ + resourceType: 'ValueSet', + status: 'active', + url: 'https://example.com/invalid-hierarchy' + randomUUID(), + compose: { + include: [ + { + system: 'http://example.com/custom-code-system', + filter: [{ property: 'concept', op: 'is-a', value: 'A' }], + }, + ], + }, + }); + expect(res2.status).toBe(201); + + const res3 = await request(app) + .get(`/fhir/R4/ValueSet/$expand?url=${encodeURIComponent(res2.body.url)}`) + .set('Authorization', 'Bearer ' + accessToken); + expect(res3.status).toBe(400); + expect(res3.body.issue[0].details.text).toMatch(/invalid filter/i); + }); + + test('Includes ancestor code in is-a filter', async () => { + const res = await request(app) + .get(`/fhir/R4/ValueSet/$expand?url=${encodeURIComponent('http://hl7.org/fhir/ValueSet/care-team-category')}`) + .set('Authorization', 'Bearer ' + accessToken); + expect(res.status).toEqual(200); + const expansion = res.body.expansion as ValueSetExpansion; + + expect(expansion.contains).toHaveLength(1); + expect(expansion.contains?.[0]).toEqual({ + system: LOINC, + code: 'LA28865-6', + display: expect.stringMatching(/care team/i), + }); }); }); diff --git a/packages/server/src/fhir/operations/expand.ts b/packages/server/src/fhir/operations/expand.ts index 71f36b834b..3d19a8dcc1 100644 --- a/packages/server/src/fhir/operations/expand.ts +++ b/packages/server/src/fhir/operations/expand.ts @@ -1,11 +1,23 @@ -import { badRequest, Operator as SearchOperator } from '@medplum/core'; -import { ValueSet, ValueSetComposeInclude } from '@medplum/fhirtypes'; +import { badRequest, OperationOutcomeError, Operator, Operator as SearchOperator } from '@medplum/core'; +import { + CodeSystem, + Coding, + ValueSet, + ValueSetComposeInclude, + ValueSetComposeIncludeFilter, + ValueSetExpansionContains, +} from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { asyncWrap } from '../../async'; -import { getDatabasePool } from '../../database'; import { sendOutcome } from '../outcomes'; import { getSystemRepo } from '../repo'; -import { Condition, Conjunction, Disjunction, Expression, SelectQuery } from '../sql'; +import { Column, Condition, Conjunction, SelectQuery, Expression, Disjunction } from '../sql'; +import { getAuthenticatedContext } from '../../context'; +import { parentProperty } from './codesystemimport'; +import { clamp } from './utils/parameters'; +import { validateCode } from './codesystemvalidatecode'; +import { getDatabasePool } from '../../database'; +import { r4ProjectId } from '../../seed'; // Implements FHIR "Value Set Expansion" // https://www.hl7.org/fhir/operation-valueset-expand.html @@ -14,7 +26,7 @@ import { Condition, Conjunction, Disjunction, Expression, SelectQuery } from '.. // 1) The "url" parameter to identify the value set // 2) The "filter" parameter for text search // 3) Optional offset for pagination (default is zero for beginning) -// 4) Optional count for pagination (default is 10, can be 1-20) +// 4) Optional count for pagination (default is 10, can be 1-1000) export const expandOperator = asyncWrap(async (req: Request, res: Response) => { let url = req.query.url as string | undefined; @@ -34,20 +46,12 @@ export const expandOperator = asyncWrap(async (req: Request, res: Response) => { url = url.substring(0, pipeIndex); } - // First, get the ValueSet resource - const valueSet = await getValueSetByUrl(url); + let valueSet = await getValueSetByUrl(url); if (!valueSet) { sendOutcome(res, badRequest('ValueSet not found')); return; } - // Build a collection of all systems to include - const systemExpressions = buildValueSetSystems(valueSet); - if (systemExpressions.length === 0) { - sendOutcome(res, badRequest('No systems found')); - return; - } - let offset = 0; if (req.query.offset) { offset = Math.max(0, parseInt(req.query.offset as string, 10)); @@ -55,7 +59,40 @@ export const expandOperator = asyncWrap(async (req: Request, res: Response) => { let count = 10; if (req.query.count) { - count = Math.max(1, Math.min(20, parseInt(req.query.count as string, 10))); + count = clamp(parseInt(req.query.count as string, 10), 1, 1000); + } + + if (shouldUseLegacyTable()) { + const elements = await queryValueSetElements(valueSet, offset, count, filter); + res.status(200).json({ + resourceType: 'ValueSet', + url, + expansion: { + offset, + contains: elements, + }, + } as ValueSet); + } else { + valueSet = await expandValueSet(valueSet, offset, count, filter); + res.status(200).json(valueSet); + } +}); + +function shouldUseLegacyTable(): boolean { + const ctx = getAuthenticatedContext(); + return !ctx.project.features?.includes('terminology'); +} + +async function queryValueSetElements( + valueSet: ValueSet, + offset: number, + count: number, + filter?: string +): Promise { + // Build a collection of all systems to include + const systemExpressions = buildValueSetSystems(valueSet); + if (systemExpressions.length === 0) { + throw new OperationOutcomeError(badRequest('No systems found')); } const client = getDatabasePool(); @@ -81,17 +118,10 @@ export const expandOperator = asyncWrap(async (req: Request, res: Response) => { system: row.system, code: row.code, display: row.display ?? undefined, // if display is NULL, we want to filter it out before sending this to the client - })); + })) as ValueSetExpansionContains[]; - res.status(200).json({ - resourceType: 'ValueSet', - url, - expansion: { - offset, - contains: elements, - }, - } as ValueSet); -}); + return elements; +} function filterToTsvectorQuery(filter: string | undefined): string | undefined { if (!filter) { @@ -132,15 +162,215 @@ function processInclude(systemExpressions: Expression[], include: ValueSetCompos return; } - const systemExpression = new Condition('system', '=', include.system as string); + const systemExpression = new Condition('system', '=', include.system); if (include.concept) { const codeExpressions: Expression[] = []; for (const concept of include.concept) { - codeExpressions.push(new Condition('code', '=', concept.code as string)); + codeExpressions.push(new Condition('code', '=', concept.code)); } systemExpressions.push(new Conjunction([systemExpression, new Disjunction(codeExpressions)])); } else { systemExpressions.push(systemExpression); } } + +const MAX_EXPANSION_SIZE = 1001; + +export async function expandValueSet( + valueSet: ValueSet, + offset: number, + count: number, + filter?: string +): Promise { + const expansion = valueSet.expansion; + if (expansion?.contains?.length && !expansion.parameter && expansion.total === expansion.contains.length) { + // Full expansion is already available, use that + return valueSet; + } + + // Compute expansion + const expandedSet = [] as ValueSetExpansionContains[]; + await computeExpansion(valueSet, expandedSet, offset, count, filter); + if (expandedSet.length >= MAX_EXPANSION_SIZE) { + valueSet.expansion = { + total: 1001, + timestamp: new Date().toISOString(), + contains: expandedSet.slice(0, 1000), + }; + } else { + valueSet.expansion = { + total: expandedSet.length, + timestamp: new Date().toISOString(), + contains: expandedSet.slice(0, count), + }; + } + return valueSet; +} + +async function computeExpansion( + valueSet: ValueSet, + expansion: ValueSetExpansionContains[], + offset: number, + count: number, + filter?: string +): Promise { + if (!valueSet.compose?.include.length) { + throw new OperationOutcomeError(badRequest('Missing ValueSet definition', 'ValueSet.compose.include')); + } + + const codeSystemCache: Record = Object.create(null); + for (const include of valueSet.compose.include) { + if (!include.system) { + throw new OperationOutcomeError( + badRequest('Missing system URL for ValueSet include', 'ValueSet.compose.include.system') + ); + } + + const codeSystem = codeSystemCache[include.system] ?? (await findCodeSystem(include.system)); + codeSystemCache[include.system] = codeSystem; + if (include.concept) { + const concepts = await Promise.all(include.concept.flatMap((c) => validateCode(codeSystem, c.code))); + for (const c of concepts) { + if (c && (!filter || c.display?.includes(filter))) { + c.id = undefined; + expansion.push(c); + } + } + } else { + await includeInExpansion(include, expansion, codeSystem, offset, count, filter); + } + + if (expansion.length > count) { + // Return partial expansion + return; + } + } +} + +export async function findCodeSystem(url: string): Promise { + const { repo, logger } = getAuthenticatedContext(); + const codeSystems = await repo.searchResources({ + resourceType: 'CodeSystem', + filters: [{ code: 'url', operator: Operator.EQUALS, value: url }], + sortRules: [ + // Select highest version (by lexical sort -- no version is assumed to be "current") + { code: 'version', descending: true }, + // Break ties by selecting more recently-updated resource (lexically -- no date is assumed to be current) + { code: 'date', descending: true }, + ], + }); + + if (!codeSystems.length) { + throw new OperationOutcomeError(badRequest(`Code system ${url} not found`, 'ValueSet.compose.include.system')); + } else if (codeSystems.length === 1) { + return codeSystems[0]; + } else { + codeSystems.sort((a: CodeSystem, b: CodeSystem) => { + // Select the non-base FHIR versions of resources before the base FHIR ones + // This is kind of a kludge, but is required to break ties because some CodeSystems (including SNOMED) + // don't have a version and the base spec version doesn't include a date (and so is always considered current) + if (a.meta?.project === r4ProjectId) { + return 1; + } else if (b.meta?.project === r4ProjectId) { + return -1; + } + return 0; + }); + logger.warn('Possibly ambiguous CodeSystem', { url, codeSystems: codeSystems.map((cs) => cs.id) }); + return codeSystems[0]; + } +} + +async function includeInExpansion( + include: ValueSetComposeInclude, + expansion: ValueSetExpansionContains[], + codeSystem: CodeSystem, + offset: number, + count: number, + filter?: string +): Promise { + const ctx = getAuthenticatedContext(); + + let query = new SelectQuery('Coding') + .column('code') + .column('display') + .where('system', '=', codeSystem.id) + .limit(count + 1) + .offset(offset); + if (filter) { + query.where('display', '!=', null).where('display', 'TSVECTOR_ENGLISH', filterToTsvectorQuery(filter)); + } + if (include.filter?.length) { + for (const condition of include.filter) { + switch (condition.op) { + case 'is-a': + { + const coding = await validateCode(codeSystem, condition.value); + if (!coding) { + ctx.logger.warn('Invalid parent code in ValueSet', { codeSystem: codeSystem.id, code: condition.value }); + return; // Invalid parent code, don't make DB query with incorrect filters + } + if (!filter || coding.display?.includes(filter)) { + expansion.push({ ...coding, id: undefined }); + } + query = addParentCondition(condition, query, codeSystem, coding); + } + break; + default: + ctx.logger.warn('Unknown filter type in ValueSet', { filter: condition }); + return; // Unknown filter type, don't make DB query with incorrect filters + } + } + } + + const results = await query.execute(ctx.repo.getDatabaseClient()); + const system = codeSystem.url; + for (const { code, display } of results) { + expansion.push({ system, code, display }); + } +} + +function addParentCondition( + condition: ValueSetComposeIncludeFilter, + query: SelectQuery, + codeSystem: CodeSystem, + parent: Coding +): SelectQuery { + if (codeSystem.hierarchyMeaning !== 'is-a') { + throw new OperationOutcomeError( + badRequest( + `Invalid filter: CodeSystem ${codeSystem.url} does not have an is-a hierarchy`, + 'ValueSet.compose.include.filter' + ) + ); + } + let property = codeSystem.property?.find((p) => p.uri === parentProperty); + if (!property) { + // Implicit parent property for hierarchical CodeSystems + property = { code: codeSystem.hierarchyMeaning ?? 'parent', uri: parentProperty, type: 'code' }; + } + + const propertyTable = query.getNextJoinAlias(); + query.leftJoin( + 'Coding_Property', + propertyTable, + new Conjunction([ + new Condition(new Column('Coding', 'id'), '=', new Column(propertyTable, 'coding')), + new Condition(new Column(propertyTable, 'target'), '=', parent.id), + ]) + ); + + const csPropertyTable = query.getNextJoinAlias(); + query.innerJoin( + 'CodeSystem_Property', + csPropertyTable, + new Conjunction([ + new Condition(new Column(propertyTable, 'property'), '=', new Column(csPropertyTable, 'id')), + new Condition(new Column(csPropertyTable, 'code'), '=', property.code), + ]) + ); + query.where(new Column(propertyTable, 'coding'), condition.op === 'is-not-a' ? '=' : '!=', null); + + return query; +} diff --git a/packages/server/src/fhir/operations/utils/parameters.ts b/packages/server/src/fhir/operations/utils/parameters.ts index 7090453574..05d00c1bc4 100644 --- a/packages/server/src/fhir/operations/utils/parameters.ts +++ b/packages/server/src/fhir/operations/utils/parameters.ts @@ -212,3 +212,7 @@ function makeParameter(param: OperationDefinitionParameter, value: any): Paramet } return undefined; } + +export function clamp(n: number, min: number, max: number): number { + return Math.max(min, Math.min(max, n)); +} diff --git a/packages/server/src/seeds/valuesets.ts b/packages/server/src/seeds/valuesets.ts index cd30f66a3a..f80ab2d464 100644 --- a/packages/server/src/seeds/valuesets.ts +++ b/packages/server/src/seeds/valuesets.ts @@ -9,12 +9,12 @@ import { r4ProjectId } from '../seed'; */ export async function rebuildR4ValueSets(): Promise { const systemRepo = getSystemRepo(); - const files = ['valuesets.json', 'v2-tables.json', 'v3-codesystems.json', 'valuesets-medplum.json']; + const files = ['v2-tables.json', 'v3-codesystems.json', 'valuesets.json', 'valuesets-medplum.json']; for (const file of files) { const bundle = readJson('fhir/r4/' + file) as Bundle; for (const entry of bundle.entry as BundleEntry[]) { const resource = entry.resource as CodeSystem | ValueSet; - await deleteExisting(systemRepo, resource); + await deleteExisting(systemRepo, resource, r4ProjectId); await systemRepo.createResource({ ...resource, meta: { ...resource.meta, project: r4ProjectId }, @@ -23,10 +23,17 @@ export async function rebuildR4ValueSets(): Promise { } } -async function deleteExisting(systemRepo: Repository, resource: CodeSystem | ValueSet): Promise { +async function deleteExisting( + systemRepo: Repository, + resource: CodeSystem | ValueSet, + projectId: string +): Promise { const bundle = await systemRepo.search({ resourceType: resource.resourceType, - filters: [{ code: 'url', operator: Operator.EQUALS, value: resource.url as string }], + filters: [ + { code: 'url', operator: Operator.EQUALS, value: resource.url as string }, + { code: '_project', operator: Operator.EQUALS, value: projectId }, + ], }); if (bundle.entry && bundle.entry.length > 0) { for (const entry of bundle.entry) { diff --git a/packages/server/src/test.setup.ts b/packages/server/src/test.setup.ts index 70738df9be..897881ef46 100644 --- a/packages/server/src/test.setup.ts +++ b/packages/server/src/test.setup.ts @@ -212,7 +212,7 @@ export function waitFor(fn: () => Promise): Promise { } export async function waitForAsyncJob(contentLocation: string, app: Express, accessToken: string): Promise { - for (let i = 0; i < 10; i++) { + for (let i = 0; i < 30; i++) { const res = await request(app) .get(new URL(contentLocation).pathname) .set('Authorization', 'Bearer ' + accessToken); From 43ecc11e8bc043d066a6da7c2c66d2a1e5c32eba Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Tue, 20 Feb 2024 15:49:13 -0800 Subject: [PATCH 68/81] Revert "feat(subscriptions): cleanup WS Subscription resources, add feature flag (#3978)" This reverts commit ca550f9e557bfbe08f60edacd86e1abdff301484. --- packages/server/src/__mocks__/ioredis.ts | 113 ++------------- packages/server/src/fhir/accesspolicy.ts | 2 +- .../fhir/operations/getwsbindingtoken.test.ts | 130 +++--------------- .../src/fhir/operations/getwsbindingtoken.ts | 17 +-- packages/server/src/fhir/repo.ts | 15 +- .../src/subscriptions/websockets.test.ts | 51 +------ .../server/src/subscriptions/websockets.ts | 46 ++----- .../server/src/workers/subscription.test.ts | 100 +++----------- packages/server/src/workers/subscription.ts | 93 ++----------- 9 files changed, 102 insertions(+), 465 deletions(-) diff --git a/packages/server/src/__mocks__/ioredis.ts b/packages/server/src/__mocks__/ioredis.ts index b1f455ec90..665c1980fe 100644 --- a/packages/server/src/__mocks__/ioredis.ts +++ b/packages/server/src/__mocks__/ioredis.ts @@ -2,44 +2,6 @@ const values = new Map(); const sets = new Map>(); const subscribers = new Map>(); -class ReplyError extends Error {} - -type Command = { - command: 'srem' | 'del'; - args: string[]; -}; - -type Result = number | null; - -class MultiClient { - private redis: Redis; - private commandQueue: Command[]; - - constructor(redis: Redis) { - this.redis = redis; - this.commandQueue = []; - } - - srem(setKey: string, members: string[]): this { - this.commandQueue.push({ command: 'srem', args: [setKey, ...members] }); - return this; - } - - del(setKey: string | string[]): this { - this.commandQueue.push({ command: 'del', args: [...setKey] }); - return this; - } - - async exec(): Promise { - const results = [] as Result[]; - for (const cmd of this.commandQueue) { - results.push((await this.redis[cmd.command](cmd.args[0], cmd.args.slice(1))) ?? null); - } - this.commandQueue = []; - return results; - } -} - class Redis { private callback?: (...args: any[]) => void; @@ -56,18 +18,12 @@ class Redis { return 'PONG'; } - async get(key: string): Promise { - return values.get(key) ?? null; + async get(key: string): Promise { + return values.get(key); } - async mget(...keys: string[] | string[][]): Promise<(string | null)[]> { - let normalizedKeys: string[]; - if (keys.length === 1 && Array.isArray(keys[0])) { - normalizedKeys = keys[0]; - } else { - normalizedKeys = keys as string[]; - } - return normalizedKeys.map((key) => values.get(key) ?? null); + async mget(...keys: string[]): Promise<(string | undefined)[]> { + return keys.map((key) => values.get(key)); } async set(key: string, value: string, ...args: (string | number)[]): Promise { @@ -142,16 +98,12 @@ class Redis { } async sadd(setKey: string, ...members: string[]): Promise { - const existingValue = sets.get(setKey); let keySet: Set; - if (existingValue) { - if (!(existingValue instanceof Set)) { - throw new ReplyError('WRONGTYPE Operation against a key holding the wrong kind of value'); - } - keySet = existingValue; - } else { + if (!sets.has(setKey)) { keySet = new Set(); sets.set(setKey, keySet); + } else { + keySet = sets.get(setKey) as Set; } let added = 0; for (const member of members) { @@ -164,53 +116,12 @@ class Redis { return added; } - async srem(setKey: string, member: string[]): Promise; - async srem(setKey: string, ...members: string[] | string[][]): Promise { - const keySet = sets.get(setKey); - if (!keySet) { - return 0; - } - if (!(keySet instanceof Set)) { - throw new ReplyError('WRONGTYPE Operation against a key holding the wrong kind of value'); - } - let normalizedMembers: string[]; - if (Array.isArray(members[0])) { - normalizedMembers = members[0]; - } else { - normalizedMembers = members as string[]; - } - let removed = 0; - for (const member of normalizedMembers) { - if (keySet.has(member)) { - keySet.delete(member); - removed += 1; - } - } - return removed; - } - async smembers(setKey: string): Promise { - const keySet = sets.get(setKey); - if (!keySet) { + const set = sets.get(setKey); + if (!set) { return []; } - if (!(keySet instanceof Set)) { - throw new ReplyError('WRONGTYPE Operation against a key holding the wrong kind of value'); - } - return Array.from(keySet.keys()); - } - - async smismember(setKey: string, ...members: string[]): Promise { - const keySet = sets.get(setKey); - if (!keySet) { - return new Array(members.length).fill(0); - } - if (!(keySet instanceof Set)) { - throw new ReplyError('WRONGTYPE Operation against a key holding the wrong kind of value'); - } - return members.map((member) => { - return keySet.has(member) ? 1 : 0; - }); + return Array.from(set.keys()); } async scard(setKey: string): Promise { @@ -220,10 +131,6 @@ class Redis { } return set.size; } - - multi(): MultiClient { - return new MultiClient(this); - } } export default Redis; diff --git a/packages/server/src/fhir/accesspolicy.ts b/packages/server/src/fhir/accesspolicy.ts index d6696982bf..61f1ec070f 100644 --- a/packages/server/src/fhir/accesspolicy.ts +++ b/packages/server/src/fhir/accesspolicy.ts @@ -89,7 +89,7 @@ export async function getAccessPolicyForLogin( * @param membership - The user project membership. * @returns The parameterized compound access policy. */ -export async function buildAccessPolicy(membership: ProjectMembership): Promise { +async function buildAccessPolicy(membership: ProjectMembership): Promise { let access: ProjectMembershipAccess[] = []; if (membership.accessPolicy) { diff --git a/packages/server/src/fhir/operations/getwsbindingtoken.test.ts b/packages/server/src/fhir/operations/getwsbindingtoken.test.ts index b88c7f8205..a33c2a608d 100644 --- a/packages/server/src/fhir/operations/getwsbindingtoken.test.ts +++ b/packages/server/src/fhir/operations/getwsbindingtoken.test.ts @@ -1,11 +1,10 @@ import { ContentType } from '@medplum/core'; -import { OperationOutcome, Parameters, Subscription } from '@medplum/fhirtypes'; +import { Parameters, Subscription } from '@medplum/fhirtypes'; import express from 'express'; import request from 'supertest'; import { initApp, shutdownApp } from '../../app'; import { loadTestConfig } from '../../config'; -import { verifyJwt } from '../../oauth/keys'; -import { initTestAuth, withTestContext } from '../../test.setup'; +import { initTestAuth } from '../../test.setup'; const app = express(); let accessToken: string; @@ -21,102 +20,8 @@ describe('Get WebSocket binding token', () => { await shutdownApp(); }); - test('Basic', () => - withTestContext(async () => { - // Create Subscription - const res1 = await request(app) - .post(`/fhir/R4/Subscription`) - .set('Authorization', 'Bearer ' + accessToken) - .set('Content-Type', ContentType.FHIR_JSON) - .send({ - resourceType: 'Subscription', - reason: 'test', - status: 'active', - criteria: 'Patient', - channel: { - type: 'websocket', - }, - } satisfies Subscription); - const createdSub = res1.body as Subscription; - expect(res1.status).toBe(201); - expect(createdSub).toBeDefined(); - expect(createdSub.id).toBeDefined(); - - // Start the export - const res2 = await request(app) - .get(`/fhir/R4/Subscription/${createdSub.id}/$get-ws-binding-token`) - .set('Authorization', 'Bearer ' + accessToken); - expect(res2.status).toBe(200); - expect(res2.body).toBeDefined(); - - const params = res2.body as Parameters; - expect(params.resourceType).toEqual('Parameters'); - expect(params.parameter?.length).toEqual(3); - expect(params.parameter?.[0]).toBeDefined(); - expect(params.parameter?.[0]?.name).toEqual('token'); - - const token = params.parameter?.[0]?.valueString as string; - expect(token).toBeDefined(); - - const { payload } = await verifyJwt(token); - expect(payload?.sub).toBeDefined(); - expect(payload?.exp).toBeDefined(); - expect(payload?.aud).toBeDefined(); - expect(payload?.username).toBeDefined(); - expect(payload?.subscription_id).toBeDefined(); - - expect(params.parameter?.[1]).toBeDefined(); - expect(params.parameter?.[1]?.name).toEqual('expiration'); - expect(params.parameter?.[1]?.valueDateTime).toBeDefined(); - expect(new Date(params.parameter?.[1]?.valueDateTime as string).getTime()).toBeGreaterThanOrEqual(Date.now()); - expect(params.parameter?.[2]).toBeDefined(); - expect(params.parameter?.[2]?.name).toEqual('websocket-url'); - expect(params.parameter?.[2]?.valueUrl).toBeDefined(); - })); - - test('should return OperationOutcome error if Subscription no longer exists', () => - withTestContext(async () => { - // Create subscription to watch patient - const res1 = await request(app) - .post(`/fhir/R4/Subscription`) - .set('Authorization', 'Bearer ' + accessToken) - .set('Content-Type', ContentType.FHIR_JSON) - .send({ - resourceType: 'Subscription', - reason: 'test', - status: 'active', - criteria: 'Patient', - channel: { - type: 'websocket', - }, - } satisfies Subscription); - const createdSub = res1.body as Subscription; - expect(res1.status).toBe(201); - expect(createdSub).toBeDefined(); - expect(createdSub.id).toBeDefined(); - - const res2 = await request(app) - .delete(`/fhir/R4/Subscription/${createdSub.id as string}`) - .set('Authorization', 'Bearer ' + accessToken); - - expect(res2.body).toMatchObject({ - resourceType: 'OperationOutcome', - issue: [{ severity: 'information', code: 'informational' }], - }); - - // Call $get-ws-binding-token - const res3 = await request(app) - .get(`/fhir/R4/Subscription/${createdSub.id}/$get-ws-binding-token`) - .set('Authorization', 'Bearer ' + accessToken); - - expect(res3.body).toMatchObject({ - resourceType: 'OperationOutcome', - issue: [{ severity: 'error', code: 'invalid' }], - }); - })); - - test('should return OperationOutcome error if user does not have access to this Subscription', async () => { - // Create subscription to watch patient + test('Basic', async () => { + // Create Subscription const res1 = await request(app) .post(`/fhir/R4/Subscription`) .set('Authorization', 'Bearer ' + accessToken) @@ -135,16 +40,25 @@ describe('Get WebSocket binding token', () => { expect(createdSub).toBeDefined(); expect(createdSub.id).toBeDefined(); - const anotherUserToken = await initTestAuth(); - - // Call $get-ws-binding-token + // Start the export const res2 = await request(app) .get(`/fhir/R4/Subscription/${createdSub.id}/$get-ws-binding-token`) - .set('Authorization', 'Bearer ' + anotherUserToken); - - expect(res2.body).toMatchObject({ - resourceType: 'OperationOutcome', - issue: [{ severity: 'error', code: 'invalid' }], - }); + .set('Authorization', 'Bearer ' + accessToken); + expect(res2.status).toBe(200); + expect(res2.body).toBeDefined(); + + const params = res2.body as Parameters; + expect(params.resourceType).toEqual('Parameters'); + expect(params.parameter?.length).toEqual(3); + expect(params.parameter?.[0]).toBeDefined(); + expect(params.parameter?.[0].name).toEqual('token'); + expect(params.parameter?.[0].valueString).toBeDefined(); + expect(params.parameter?.[1]).toBeDefined(); + expect(params.parameter?.[1].name).toEqual('expiration'); + expect(params.parameter?.[1].valueDateTime).toBeDefined(); + expect(new Date(params.parameter?.[1].valueDateTime as string).getTime()).toBeGreaterThanOrEqual(Date.now()); + expect(params.parameter?.[2]).toBeDefined(); + expect(params.parameter?.[2].name).toEqual('websocket-url'); + expect(params.parameter?.[2].valueUrl).toBeDefined(); }); }); diff --git a/packages/server/src/fhir/operations/getwsbindingtoken.ts b/packages/server/src/fhir/operations/getwsbindingtoken.ts index 58f895ae2d..a2da54083a 100644 --- a/packages/server/src/fhir/operations/getwsbindingtoken.ts +++ b/packages/server/src/fhir/operations/getwsbindingtoken.ts @@ -1,9 +1,10 @@ -import { allOk, badRequest, normalizeErrorString, resolveId } from '@medplum/core'; -import { Parameters, Subscription } from '@medplum/fhirtypes'; +import { allOk, badRequest, resolveId } from '@medplum/core'; +import { Parameters } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { getConfig } from '../../config'; import { getAuthenticatedContext } from '../../context'; import { generateAccessToken } from '../../oauth/keys'; +import { getRedis } from '../../redis'; import { sendOutcome } from '../outcomes'; import { sendResponse } from '../response'; @@ -27,21 +28,21 @@ export type AdditionalWsBindingClaims = { * @param res - The HTTP response. */ export async function getWsBindingTokenHandler(req: Request, res: Response): Promise { - const { login, profile, repo } = getAuthenticatedContext(); + const { login, profile } = getAuthenticatedContext(); const { baseUrl } = getConfig(); + const redis = getRedis(); const clientId = login.client && resolveId(login.client); const userId = resolveId(login.user); if (!userId) { - sendOutcome(res, badRequest('Login missing user')); + await sendOutcome(res, badRequest('Login missing user')); return; } const subscriptionId = req.params.id; - try { - await repo.readResource('Subscription', subscriptionId); - } catch (err: unknown) { - sendOutcome(res, badRequest(`Error reading subscription: ${normalizeErrorString(err)}`)); + const subExists = await redis.exists(`Subscription/${subscriptionId}`); + if (!subExists) { + await sendOutcome(res, badRequest('Content could not be parsed')); return; } diff --git a/packages/server/src/fhir/repo.ts b/packages/server/src/fhir/repo.ts index f14cbfd85f..f1605c5105 100644 --- a/packages/server/src/fhir/repo.ts +++ b/packages/server/src/fhir/repo.ts @@ -47,6 +47,7 @@ import { ResourceType, SearchParameter, StructureDefinition, + Subscription, } from '@medplum/fhirtypes'; import { randomUUID } from 'crypto'; import { Pool, PoolClient } from 'pg'; @@ -540,7 +541,19 @@ export class Repository extends BaseRepository implements FhirRepository (sub.id as string) === (result.id as string) + ); + if (existingIdx !== -1) { + currentWsSubscriptions[existingIdx] = result; + } else { + currentWsSubscriptions.push(result); + } + await redis.set(`medplum:subscriptions:r4:project:${project}`, JSON.stringify(currentWsSubscriptions)); } } diff --git a/packages/server/src/subscriptions/websockets.test.ts b/packages/server/src/subscriptions/websockets.test.ts index d9e7a5e3e9..1ced8d4cfe 100644 --- a/packages/server/src/subscriptions/websockets.test.ts +++ b/packages/server/src/subscriptions/websockets.test.ts @@ -1,4 +1,4 @@ -import { OperationOutcomeError, getReferenceString, sleep } from '@medplum/core'; +import { getReferenceString, sleep } from '@medplum/core'; import { Bundle, BundleEntry, @@ -17,7 +17,6 @@ import { initApp, shutdownApp } from '../app'; import { registerNew } from '../auth/register'; import { MedplumServerConfig, loadTestConfig } from '../config'; import { Repository } from '../fhir/repo'; -import { getRedis } from '../redis'; import { withTestContext } from '../test.setup'; import { execSubscriptionJob, getSubscriptionQueue } from '../workers/subscription'; @@ -31,7 +30,6 @@ describe('WebSockets Subscriptions', () => { let project: Project; let repo: Repository; let accessToken: string; - let patientSubscription: Subscription; beforeAll(async () => { app = express(); @@ -60,9 +58,6 @@ describe('WebSockets Subscriptions', () => { }, }); - // TODO: Remove this when the websocket-subscriptions feature flag is removed - project = await withTestContext(() => repo.updateResource({ ...project, features: ['websocket-subscriptions'] })); - await new Promise((resolve) => { server.listen(0, 'localhost', 511, resolve); }); @@ -85,7 +80,7 @@ describe('WebSockets Subscriptions', () => { expect(version1.id).toBeDefined(); // Create subscription to watch patient - patientSubscription = await repo.createResource({ + const subscription = await repo.createResource({ resourceType: 'Subscription', reason: 'test', status: 'active', @@ -94,11 +89,11 @@ describe('WebSockets Subscriptions', () => { type: 'websocket', }, }); - expect(patientSubscription).toBeDefined(); + expect(subscription).toBeDefined(); // Call $get-ws-binding-token const res = await request(server) - .get(`/fhir/R4/Subscription/${patientSubscription.id}/$get-ws-binding-token`) + .get(`/fhir/R4/Subscription/${subscription.id}/$get-ws-binding-token`) .set('Authorization', 'Bearer ' + accessToken); expect(res.body).toBeDefined(); @@ -108,13 +103,11 @@ describe('WebSockets Subscriptions', () => { expect(body.parameter?.[0]?.name).toEqual('token'); expect(body.parameter?.[0]?.valueString).toBeDefined(); - const token = body.parameter?.[0]?.valueString as string; - let version2: Patient; await request(server) .ws('/ws/subscriptions-r4') .set('Authorization', 'Bearer ' + accessToken) - .sendJson({ type: 'bind-with-token', payload: { token } }) + .sendJson({ type: 'bind-with-token', payload: { token: body.parameter?.[0]?.valueString as string } }) // Add a new patient for this project .exec(async () => { const queue = getSubscriptionQueue() as any; @@ -140,19 +133,6 @@ describe('WebSockets Subscriptions', () => { // Clear the queue queue.add.mockClear(); - - let subActive = false; - while (!subActive) { - await sleep(0); - subActive = - ( - await getRedis().smismember( - `medplum:subscriptions:r4:project:${project.id}:active`, - `Subscription/${patientSubscription?.id as string}` - ) - )[0] === 1; - } - expect(subActive).toEqual(true); }) .expectJson((msg: Bundle): boolean => { if (!msg.entry?.[1]) { @@ -175,27 +155,6 @@ describe('WebSockets Subscriptions', () => { .expectClosed(); })); - test('Subscription removed from active and deleted after WebSocket closed', () => - withTestContext(async () => { - let subActive = true; - while (subActive) { - await sleep(0); - subActive = - ( - await getRedis().smismember( - `medplum:subscriptions:r4:project:${project.id}:active`, - `Subscription/${patientSubscription?.id as string}` - ) - )[0] === 1; - } - expect(subActive).toEqual(false); - - // Check Patient subscription is NOT still in the cache - await expect(repo.readResource('Subscription', patientSubscription?.id as string)).rejects.toThrow( - OperationOutcomeError - ); - })); - test('Should reject if given an invalid binding token', () => withTestContext(async () => { const version1 = await repo.createResource({ diff --git a/packages/server/src/subscriptions/websockets.ts b/packages/server/src/subscriptions/websockets.ts index 0404fc55b6..679f77811b 100644 --- a/packages/server/src/subscriptions/websockets.ts +++ b/packages/server/src/subscriptions/websockets.ts @@ -1,11 +1,9 @@ import { badRequest, createReference } from '@medplum/core'; -import { Bundle, Resource, Subscription } from '@medplum/fhirtypes'; +import { Bundle, Resource } from '@medplum/fhirtypes'; import { Redis } from 'ioredis'; import { JWTPayload } from 'jose'; import crypto from 'node:crypto'; import ws from 'ws'; -import { AdditionalWsBindingClaims } from '../fhir/operations/getwsbindingtoken'; -import { CacheEntry } from '../fhir/repo'; import { getFullUrl } from '../fhir/response'; import { heartbeat } from '../heartbeat'; import { globalLogger } from '../logger'; @@ -26,19 +24,10 @@ export async function handleR4SubscriptionConnection(socket: ws.WebSocket): Prom const redis = getRedis(); const subscriptionIds = [] as string[]; let redisSubscriber: Redis; - let onDisconnect: (() => Promise) | undefined; + let onDisconnect: (() => void) | undefined; let heartbeatHandler: (() => void) | undefined; - const onBind = async (tokenPayload: JWTPayload & Partial): Promise => { - const subscriptionId = tokenPayload?.subscription_id; - if (!subscriptionId) { - socket.send( - JSON.stringify(badRequest('Token claims missing subscription_id. Make sure you are sending the correct token.')) - ); - socket.terminate(); - return; - } - + const onBind = async (tokenPayload: JWTPayload): Promise => { if (!redisSubscriber) { // Create a redis client for this connection. // According to Redis documentation: http://redis.io/commands/subscribe @@ -51,18 +40,16 @@ export async function handleR4SubscriptionConnection(socket: ws.WebSocket): Prom socket.send(message, { binary: false }); }); - onDisconnect = async (): Promise => { - redisSubscriber.disconnect(); - const cacheEntryStr = (await redis.get(`Subscription/${subscriptionId}`)) as string | null; - if (!cacheEntryStr) { - globalLogger.error('[WS] Failed to retrieve subscription cache entry on WebSocket disconnect.'); - return; - } - const cacheEntry = JSON.parse(cacheEntryStr) as CacheEntry; - await markInMemorySubscriptionsInactive(cacheEntry.projectId, subscriptionIds); - }; + onDisconnect = () => redisSubscriber.disconnect(); } + const subscriptionId = tokenPayload?.subscription_id as string | undefined; + if (!subscriptionId) { + socket.send( + JSON.stringify(badRequest('Token claims missing subscription_id. Make sure you are sending the correct token.')) + ); + return; + } if (!subscriptionIds.includes(subscriptionId)) { subscriptionIds.push(subscriptionId); } @@ -105,7 +92,7 @@ export async function handleR4SubscriptionConnection(socket: ws.WebSocket): Prom socket.on('close', async () => { if (onDisconnect) { - onDisconnect().catch(console.error); + onDisconnect(); } if (heartbeatHandler) { heartbeat.removeEventListener('heartbeat', heartbeatHandler); @@ -173,12 +160,3 @@ export function createSubEventNotification { - const refStrs = []; - for (const subscriptionId of subscriptionIds) { - refStrs.push(`Subscription/${subscriptionId}`); - } - const redis = getRedis(); - await redis.multi().srem(`medplum:subscriptions:r4:project:${projectId}:active`, refStrs).del(refStrs).exec(); -} diff --git a/packages/server/src/workers/subscription.test.ts b/packages/server/src/workers/subscription.test.ts index dfdffdbfa1..917ed3800e 100644 --- a/packages/server/src/workers/subscription.test.ts +++ b/packages/server/src/workers/subscription.test.ts @@ -1,5 +1,5 @@ import { InvokeCommand, LambdaClient } from '@aws-sdk/client-lambda'; -import { ContentType, LogLevel, Operator, createReference, getReferenceString, stringify } from '@medplum/core'; +import { ContentType, Operator, createReference, getReferenceString, stringify } from '@medplum/core'; import { AuditEvent, Bot, Observation, Patient, Project, ProjectMembership, Subscription } from '@medplum/fhirtypes'; import { AwsClientStub, mockClient } from 'aws-sdk-client-mock'; import { Job } from 'bullmq'; @@ -9,7 +9,6 @@ import { initAppServices, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; import { getDatabasePool } from '../database'; import { Repository, getSystemRepo } from '../fhir/repo'; -import { globalLogger } from '../logger'; import { getRedis } from '../redis'; import { createTestProject, withTestContext } from '../test.setup'; import { AuditEventOutcome } from '../util/auditevent'; @@ -48,17 +47,22 @@ describe('Subscription Worker', () => { await initAppServices(config); // Create one simple project with no advanced features enabled - const { project, client } = await withTestContext(() => - createTestProject({ + const testProject = await withTestContext(() => + systemRepo.createResource({ + resourceType: 'Project', name: 'Test Project', - features: ['websocket-subscriptions'], + owner: { + reference: 'User/' + randomUUID(), + }, }) ); repo = new Repository({ extendedMode: true, - projects: [project.id as string], - author: createReference(client), + projects: [testProject.id as string], + author: { + reference: 'ClientApplication/' + randomUUID(), + }, }); // Create another project, this one with bots enabled @@ -1110,7 +1114,7 @@ describe('Subscription Worker', () => { test('AuditEvent has Subscription account details', () => withTestContext(async () => { - const project = (await createTestProject()).project.id as string; + const project = randomUUID(); const account = { reference: 'Organization/' + randomUUID(), }; @@ -1162,15 +1166,15 @@ describe('Subscription Worker', () => { }); expect(bundle.entry?.length).toEqual(1); - const auditEvent = bundle.entry?.[0]?.resource as AuditEvent; + const auditEvent = bundle.entry?.[0].resource as AuditEvent; expect(auditEvent.meta?.account).toBeDefined(); expect(auditEvent.meta?.account?.reference).toEqual(account.reference); expect(auditEvent.entity).toHaveLength(2); })); - test('AuditEvent outcome from custom codes', () => + test('Audit Event outcome from custom codes', () => withTestContext(async () => { - const project = (await createTestProject()).project.id as string; + const project = randomUUID(); const account = { reference: 'Organization/' + randomUUID(), }; @@ -1329,7 +1333,7 @@ describe('Subscription Worker', () => { expect(queue.add).not.toHaveBeenCalled(); })); - test('WebSocket Subscription -- Enabled', () => + test('WebSocket Subscription', () => withTestContext(async () => { const subscription = await repo.createResource({ resourceType: 'Subscription', @@ -1392,76 +1396,4 @@ describe('Subscription Worker', () => { await deferredPromise; })); - - test('WebSocket Subscription -- Feature Flag Not Enabled', () => - withTestContext(async () => { - globalLogger.level = LogLevel.WARN; - const originalConsoleLog = console.log; - console.log = jest.fn(); - - const noWsSubProject = await systemRepo.createResource({ - resourceType: 'Project', - name: 'Test Project', - owner: { - reference: 'User/' + randomUUID(), - }, - }); - - const noWsSubRepo = new Repository({ - extendedMode: true, - projects: [noWsSubProject.id as string], - author: { - reference: 'ClientApplication/' + randomUUID(), - }, - }); - - const subscription = await noWsSubRepo.createResource({ - resourceType: 'Subscription', - reason: 'test', - status: 'active', - criteria: 'Patient', - channel: { - type: 'websocket', - }, - }); - expect(subscription).toBeDefined(); - expect(subscription.id).toBeDefined(); - - // Subscribe to the topic - const subscriber = getRedis().duplicate(); - await subscriber.subscribe(subscription.id as string); - - let resolve: () => void; - let reject: (error: Error) => void; - - const deferredPromise = new Promise((_resolve, _reject) => { - resolve = _resolve; - reject = _reject; - }); - - subscriber.on('message', () => { - reject(new Error('Should not have been called')); - }); - - const queue = getSubscriptionQueue() as any; - queue.add.mockClear(); - - const patient = await noWsSubRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice'], family: 'Smith' }], - }); - expect(patient).toBeDefined(); - expect(queue.add).not.toHaveBeenCalled(); - - // Give some time for the callback to get called (it shouldn't) - setTimeout(() => { - resolve(); - }, 150); - - await deferredPromise; - expect(console.log).toHaveBeenLastCalledWith(expect.stringMatching(/WebSocket Subscriptions/)); - - console.log = originalConsoleLog; - globalLogger.level = LogLevel.NONE; - })); }); diff --git a/packages/server/src/workers/subscription.ts b/packages/server/src/workers/subscription.ts index ab03388ab4..e322acff4b 100644 --- a/packages/server/src/workers/subscription.ts +++ b/packages/server/src/workers/subscription.ts @@ -1,37 +1,33 @@ import { - AccessPolicyInteraction, ContentType, createReference, getExtension, getExtensionValue, - getReferenceString, isGone, matchesSearchRequest, normalizeOperationOutcome, OperationOutcomeError, Operator, parseSearchUrl, - satisfiedAccessPolicy, serverError, stringify, } from '@medplum/core'; -import { Bot, Project, ProjectMembership, Reference, Resource, Subscription } from '@medplum/fhirtypes'; +import { Bot, ProjectMembership, Reference, Resource, Subscription } from '@medplum/fhirtypes'; import { Job, Queue, QueueBaseOptions, Worker } from 'bullmq'; import { createHmac } from 'crypto'; import fetch, { HeadersInit } from 'node-fetch'; import { URL } from 'url'; import { MedplumServerConfig } from '../config'; import { getRequestContext, RequestContext, requestContextStore } from '../context'; -import { buildAccessPolicy } from '../fhir/accesspolicy'; import { executeBot } from '../fhir/operations/execute'; import { getSystemRepo, Repository } from '../fhir/repo'; import { globalLogger } from '../logger'; import { getRedis } from '../redis'; import { createSubEventNotification } from '../subscriptions/websockets'; -import { parseTraceparent } from '../traceparent'; import { AuditEventOutcome } from '../util/auditevent'; import { BackgroundJobContext, BackgroundJobInteraction } from './context'; import { createAuditEvent, findProjectMembership, isFhirCriteriaMet, isJobSuccessful } from './utils'; +import { parseTraceparent } from '../traceparent'; /** * The upper limit on the number of times a job can be retried. @@ -130,43 +126,6 @@ export function getSubscriptionQueue(): Queue | undefined { return queue; } -/** - * Checks if this resource should create a notification for this `Subscription` based on the access policy that should be applied for this `Subscription`. - * The `AccessPolicy` of author's `ProjectMembership` for this resource's `Project` is used when evaluating whether the `AccessPolicy` is satisfied. - * - * Currently we log if the `AccessPolicy` is not satisfied only. - * - * TODO: Actually prevent notifications for `Subscriptions` where the `AccessPolicy` is not satisfied. - * - * @param resource - The resource to evaluate against the `AccessPolicy`. - * @param project - The project containing the resource. - * @param subscription - The `Subscription` to get the `AccessPolicy` for. - */ -async function checkAccessPolicy(resource: Resource, project: Project, subscription: Subscription): Promise { - // Check access policy - const subAuthor = subscription.meta?.author; - if (subAuthor) { - const membership = await findProjectMembership(project.id as string, subAuthor); - if (membership) { - const accessPolicy = await buildAccessPolicy(membership); - const satisfied = !!satisfiedAccessPolicy(resource, AccessPolicyInteraction.READ, accessPolicy); - if (!satisfied) { - globalLogger.warn( - `[Subscription Access Policy]: Access Policy not satisfied for '${getReferenceString(resource)}'`, - { author: subAuthor, project, accessPolicy } - ); - } - } else { - globalLogger.warn( - `[Subscription Access Policy]: No membership for author '${getReferenceString(subAuthor)}' in project '${getReferenceString(project)}'` - ); - } - } else { - // Log it if there is no author for this Subscription (this is not good) - globalLogger.warn(`[Subscription Access Policy]: No author for subscription '${getReferenceString(subscription)}'`); - } -} - /** * Adds all subscription jobs for a given resource. * @@ -188,28 +147,10 @@ export async function addSubscriptionJobs(resource: Resource, context: Backgroun // Never send subscriptions for audit events return; } - - const systemRepo = getSystemRepo(); - let project: Project | undefined; - try { - const projectId = resource.meta?.project; - if (projectId) { - project = await systemRepo.readResource('Project', projectId); - } - } catch (_err: unknown) { - project = undefined; - } - const requestTime = new Date().toISOString(); - if (!project) { - ctx.logger.debug('Did not evaluate subscriptions for resource without project'); - globalLogger.warn(`[Subscription Access Policy]: No project for resource '${getReferenceString(resource)}'`); - return; - } - const subscriptions = await getSubscriptions(resource, project); + const subscriptions = await getSubscriptions(resource); ctx.logger.debug(`Evaluate ${subscriptions.length} subscription(s)`); for (const subscription of subscriptions) { - await checkAccessPolicy(resource, project, subscription); const criteria = await matchesCriteria(resource, subscription, context); if (criteria) { await addSubscriptionJobData({ @@ -325,11 +266,13 @@ async function addSubscriptionJobData(job: SubscriptionJobData): Promise { /** * Loads the list of all subscriptions in this repository. * @param resource - The resource that was created or updated. - * @param project - The project that contains this resource. * @returns The list of all subscriptions in this repository. */ -async function getSubscriptions(resource: Resource, project: Project): Promise { - const projectId = project.id as string; +async function getSubscriptions(resource: Resource): Promise { + const project = resource.meta?.project; + if (!project) { + return []; + } const systemRepo = getSystemRepo(); const subscriptions = await systemRepo.searchResources({ resourceType: 'Subscription', @@ -338,7 +281,7 @@ async function getSubscriptions(resource: Resource, project: Project): Promise Date: Tue, 20 Feb 2024 15:32:38 -0800 Subject: [PATCH 69/81] Add index for coding property relation (#3997) --- packages/server/src/migrations/schema/index.ts | 1 + packages/server/src/migrations/schema/v61.ts | 12 ++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 packages/server/src/migrations/schema/v61.ts diff --git a/packages/server/src/migrations/schema/index.ts b/packages/server/src/migrations/schema/index.ts index 8caa6227cd..b3539fbcf4 100644 --- a/packages/server/src/migrations/schema/index.ts +++ b/packages/server/src/migrations/schema/index.ts @@ -59,3 +59,4 @@ export * as v57 from './v57'; export * as v58 from './v58'; export * as v59 from './v59'; export * as v60 from './v60'; +export * as v61 from './v61'; diff --git a/packages/server/src/migrations/schema/v61.ts b/packages/server/src/migrations/schema/v61.ts new file mode 100644 index 0000000000..5db22d699f --- /dev/null +++ b/packages/server/src/migrations/schema/v61.ts @@ -0,0 +1,12 @@ +/* + * Generated by @medplum/generator + * Do not edit manually. + */ + +import { PoolClient } from 'pg'; + +export async function run(client: PoolClient): Promise { + await client.query( + 'CREATE INDEX CONCURRENTLY IF NOT EXISTS "Coding_Property_coding_idx" ON "Coding_Property" (coding)' + ); +} From 41417dc8ef25bab1f029b61167991ce7557fd1f9 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Thu, 22 Feb 2024 09:14:04 -0800 Subject: [PATCH 70/81] Sub-patient Search and Resource pages (#4006) --- examples/medplum-provider/src/App.tsx | 5 +- .../src/pages/patient/PatientPage.tsx | 28 +++++--- .../src/pages/patient/PatientSearchPage.tsx | 69 +++++++++++++++++++ 3 files changed, 93 insertions(+), 9 deletions(-) create mode 100644 examples/medplum-provider/src/pages/patient/PatientSearchPage.tsx diff --git a/examples/medplum-provider/src/App.tsx b/examples/medplum-provider/src/App.tsx index 9e6792cdca..a42d792ade 100644 --- a/examples/medplum-provider/src/App.tsx +++ b/examples/medplum-provider/src/App.tsx @@ -11,6 +11,7 @@ import { EncounterTab } from './pages/patient/EncounterTab'; import { LabsTab } from './pages/patient/LabsTab'; import { MedsTab } from './pages/patient/MedsTab'; import { PatientPage } from './pages/patient/PatientPage'; +import { PatientSearchPage } from './pages/patient/PatientSearchPage'; import { TasksTab } from './pages/patient/TasksTab'; import { TimelineTab } from './pages/patient/TimelineTab'; @@ -45,7 +46,9 @@ export function App(): JSX.Element | null { } /> } /> } /> - } /> + } /> + } /> + } /> } /> } /> diff --git a/examples/medplum-provider/src/pages/patient/PatientPage.tsx b/examples/medplum-provider/src/pages/patient/PatientPage.tsx index dbfb7e072c..c60a2f095b 100644 --- a/examples/medplum-provider/src/pages/patient/PatientPage.tsx +++ b/examples/medplum-provider/src/pages/patient/PatientPage.tsx @@ -6,14 +6,22 @@ import { Outlet, useNavigate } from 'react-router-dom'; import { usePatient } from '../../hooks/usePatient'; import classes from './PatientPage.module.css'; -const tabs = ['Timeline', 'Edit', 'Encounter', 'Tasks', 'Meds', 'Labs']; +const tabs = [ + { id: 'timeline', url: '', label: 'Timeline' }, + { id: 'edit', url: 'edit', label: 'Edit' }, + { id: 'encounter', url: 'encounter', label: 'Encounter' }, + { id: 'tasks', url: 'Task?patient=%patient.id', label: 'Tasks' }, + { id: 'meds', url: 'MedicationRequest?patient=%patient.id', label: 'Meds' }, + { id: 'labs', url: 'ServiceRequest?patient=%patient.id', label: 'Labs' }, + { id: 'devices', url: 'Device?patient=%patient.id', label: 'Devices' }, +]; export function PatientPage(): JSX.Element { const navigate = useNavigate(); const patient = usePatient(); const [currentTab, setCurrentTab] = useState(() => { - const tab = window.location.pathname.split('/').pop(); - return tab && tabs.map((t) => t.toLowerCase()).includes(tab) ? tab : tabs[0].toLowerCase(); + const tabId = window.location.pathname.split('/')[3] ?? ''; + return tabId && tabs.find((t) => t.id === tabId) ? tabId : tabs[0].id; }); if (!patient) { @@ -26,10 +34,14 @@ export function PatientPage(): JSX.Element { */ function onTabChange(newTabName: string | null): void { if (!newTabName) { - newTabName = tabs[0].toLowerCase(); + newTabName = tabs[0].id; + } + + const tab = tabs.find((t) => t.id === newTabName); + if (tab) { + setCurrentTab(tab.id); + navigate(`/Patient/${patient?.id}/${tab.url.replace('%patient.id', patient?.id as string)}`); } - setCurrentTab(newTabName); - navigate(`/Patient/${patient?.id}/${newTabName}`); } return ( @@ -44,8 +56,8 @@ export function PatientPage(): JSX.Element { {tabs.map((t) => ( - - {t} + + {t.label} ))} diff --git a/examples/medplum-provider/src/pages/patient/PatientSearchPage.tsx b/examples/medplum-provider/src/pages/patient/PatientSearchPage.tsx new file mode 100644 index 0000000000..682ec2ec63 --- /dev/null +++ b/examples/medplum-provider/src/pages/patient/PatientSearchPage.tsx @@ -0,0 +1,69 @@ +import { Paper } from '@mantine/core'; +import { DEFAULT_SEARCH_COUNT, formatSearchQuery, parseSearchDefinition, SearchRequest } from '@medplum/core'; +import { Patient } from '@medplum/fhirtypes'; +import { Loading, MemoizedSearchControl, useMedplum } from '@medplum/react'; +import { useEffect, useState } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { usePatient } from '../../hooks/usePatient'; + +export function PatientSearchPage(): JSX.Element { + const medplum = useMedplum(); + const patient = usePatient(); + const navigate = useNavigate(); + const location = useLocation(); + const [search, setSearch] = useState(); + + useEffect(() => { + if (!patient) { + return; + } + + const parsedSearch = parseSearchDefinition(location.pathname + location.search); + const populatedSearch = addSearchValues(patient, parsedSearch); + + if ( + location.pathname === `/Patient/${patient.id}/${populatedSearch.resourceType}` && + location.search === formatSearchQuery(populatedSearch) + ) { + setSearch(populatedSearch); + } else { + navigate(`/Patient/${patient.id}/${populatedSearch.resourceType}${formatSearchQuery(populatedSearch)}`); + } + }, [medplum, patient, navigate, location]); + + if (!patient || !search?.resourceType || !search.fields || search.fields.length === 0) { + return ; + } + + return ( + + navigate(`/Patient/${patient.id}/${e.resource.resourceType}/${e.resource.id}`)} + onAuxClick={(e) => window.open(`/Patient/${patient.id}/${e.resource.resourceType}/${e.resource.id}`, '_blank')} + onChange={(e) => { + navigate(`/Patient/${patient.id}/${search.resourceType}${formatSearchQuery(e.definition)}`); + }} + /> + + ); +} + +function addSearchValues(patient: Patient, search: SearchRequest): SearchRequest { + const resourceType = search.resourceType; + const fields = search.fields ?? ['_id', '_lastUpdated']; + const filters = search.filters ?? []; + const sortRules = search.sortRules; + const offset = search.offset ?? 0; + const count = search.count ?? DEFAULT_SEARCH_COUNT; + return { + ...search, + resourceType, + fields, + filters, + sortRules, + offset, + count, + }; +} From bb597de1c2015f88b2f88ab4fe3c064b484304f2 Mon Sep 17 00:00:00 2001 From: Derrick Farris Date: Thu, 22 Feb 2024 09:21:58 -0800 Subject: [PATCH 71/81] feat(subscriptions): cleanup WS `Subscription`s, log access policy satisfied (#4003) * refactor(server/subscriptions): use Redis sets * fix(subscriptions): mark WS sub as inactive on d/c * cleanup: `isMember` -> `subActive` in tests * fix(subscriptions): remove subs after d/c * fix(mock): fix `Redis.srem()` sig * test: make sure $get-ws-binding-token returns error if Subscription is deleted * test: make sure going through repo for $get-ws-binding-token * feat(subscriptions): add feature flag for WS subs * fix(subscriptions): catch if project doesn't exist * fix(subscriptions-test): add feature flag to WS sub tests * feat(subscriptions): start logging if `AccessPolicy` not satisfied * cleanup: typo * cleanup: more typos * cleanup: return error from repo * fix(subscription): don't throw when access policy check fails * test: add reference to PRs related to test * cleanup: remove stray console.log * cleanup(subscription): log references instead of full `Subscription` * cleanup(subscription): log if project not found --- packages/server/src/__mocks__/ioredis.ts | 113 ++++++++- packages/server/src/fhir/accesspolicy.ts | 2 +- .../fhir/operations/getwsbindingtoken.test.ts | 130 +++++++++-- .../src/fhir/operations/getwsbindingtoken.ts | 17 +- packages/server/src/fhir/repo.ts | 29 +-- .../src/subscriptions/websockets.test.ts | 51 +++- .../server/src/subscriptions/websockets.ts | 46 +++- .../server/src/workers/subscription.test.ts | 219 ++++++++++++++++-- packages/server/src/workers/subscription.ts | 117 ++++++++-- 9 files changed, 609 insertions(+), 115 deletions(-) diff --git a/packages/server/src/__mocks__/ioredis.ts b/packages/server/src/__mocks__/ioredis.ts index 665c1980fe..b1f455ec90 100644 --- a/packages/server/src/__mocks__/ioredis.ts +++ b/packages/server/src/__mocks__/ioredis.ts @@ -2,6 +2,44 @@ const values = new Map(); const sets = new Map>(); const subscribers = new Map>(); +class ReplyError extends Error {} + +type Command = { + command: 'srem' | 'del'; + args: string[]; +}; + +type Result = number | null; + +class MultiClient { + private redis: Redis; + private commandQueue: Command[]; + + constructor(redis: Redis) { + this.redis = redis; + this.commandQueue = []; + } + + srem(setKey: string, members: string[]): this { + this.commandQueue.push({ command: 'srem', args: [setKey, ...members] }); + return this; + } + + del(setKey: string | string[]): this { + this.commandQueue.push({ command: 'del', args: [...setKey] }); + return this; + } + + async exec(): Promise { + const results = [] as Result[]; + for (const cmd of this.commandQueue) { + results.push((await this.redis[cmd.command](cmd.args[0], cmd.args.slice(1))) ?? null); + } + this.commandQueue = []; + return results; + } +} + class Redis { private callback?: (...args: any[]) => void; @@ -18,12 +56,18 @@ class Redis { return 'PONG'; } - async get(key: string): Promise { - return values.get(key); + async get(key: string): Promise { + return values.get(key) ?? null; } - async mget(...keys: string[]): Promise<(string | undefined)[]> { - return keys.map((key) => values.get(key)); + async mget(...keys: string[] | string[][]): Promise<(string | null)[]> { + let normalizedKeys: string[]; + if (keys.length === 1 && Array.isArray(keys[0])) { + normalizedKeys = keys[0]; + } else { + normalizedKeys = keys as string[]; + } + return normalizedKeys.map((key) => values.get(key) ?? null); } async set(key: string, value: string, ...args: (string | number)[]): Promise { @@ -98,12 +142,16 @@ class Redis { } async sadd(setKey: string, ...members: string[]): Promise { + const existingValue = sets.get(setKey); let keySet: Set; - if (!sets.has(setKey)) { + if (existingValue) { + if (!(existingValue instanceof Set)) { + throw new ReplyError('WRONGTYPE Operation against a key holding the wrong kind of value'); + } + keySet = existingValue; + } else { keySet = new Set(); sets.set(setKey, keySet); - } else { - keySet = sets.get(setKey) as Set; } let added = 0; for (const member of members) { @@ -116,12 +164,53 @@ class Redis { return added; } + async srem(setKey: string, member: string[]): Promise; + async srem(setKey: string, ...members: string[] | string[][]): Promise { + const keySet = sets.get(setKey); + if (!keySet) { + return 0; + } + if (!(keySet instanceof Set)) { + throw new ReplyError('WRONGTYPE Operation against a key holding the wrong kind of value'); + } + let normalizedMembers: string[]; + if (Array.isArray(members[0])) { + normalizedMembers = members[0]; + } else { + normalizedMembers = members as string[]; + } + let removed = 0; + for (const member of normalizedMembers) { + if (keySet.has(member)) { + keySet.delete(member); + removed += 1; + } + } + return removed; + } + async smembers(setKey: string): Promise { - const set = sets.get(setKey); - if (!set) { + const keySet = sets.get(setKey); + if (!keySet) { return []; } - return Array.from(set.keys()); + if (!(keySet instanceof Set)) { + throw new ReplyError('WRONGTYPE Operation against a key holding the wrong kind of value'); + } + return Array.from(keySet.keys()); + } + + async smismember(setKey: string, ...members: string[]): Promise { + const keySet = sets.get(setKey); + if (!keySet) { + return new Array(members.length).fill(0); + } + if (!(keySet instanceof Set)) { + throw new ReplyError('WRONGTYPE Operation against a key holding the wrong kind of value'); + } + return members.map((member) => { + return keySet.has(member) ? 1 : 0; + }); } async scard(setKey: string): Promise { @@ -131,6 +220,10 @@ class Redis { } return set.size; } + + multi(): MultiClient { + return new MultiClient(this); + } } export default Redis; diff --git a/packages/server/src/fhir/accesspolicy.ts b/packages/server/src/fhir/accesspolicy.ts index 61f1ec070f..d6696982bf 100644 --- a/packages/server/src/fhir/accesspolicy.ts +++ b/packages/server/src/fhir/accesspolicy.ts @@ -89,7 +89,7 @@ export async function getAccessPolicyForLogin( * @param membership - The user project membership. * @returns The parameterized compound access policy. */ -async function buildAccessPolicy(membership: ProjectMembership): Promise { +export async function buildAccessPolicy(membership: ProjectMembership): Promise { let access: ProjectMembershipAccess[] = []; if (membership.accessPolicy) { diff --git a/packages/server/src/fhir/operations/getwsbindingtoken.test.ts b/packages/server/src/fhir/operations/getwsbindingtoken.test.ts index a33c2a608d..b88c7f8205 100644 --- a/packages/server/src/fhir/operations/getwsbindingtoken.test.ts +++ b/packages/server/src/fhir/operations/getwsbindingtoken.test.ts @@ -1,10 +1,11 @@ import { ContentType } from '@medplum/core'; -import { Parameters, Subscription } from '@medplum/fhirtypes'; +import { OperationOutcome, Parameters, Subscription } from '@medplum/fhirtypes'; import express from 'express'; import request from 'supertest'; import { initApp, shutdownApp } from '../../app'; import { loadTestConfig } from '../../config'; -import { initTestAuth } from '../../test.setup'; +import { verifyJwt } from '../../oauth/keys'; +import { initTestAuth, withTestContext } from '../../test.setup'; const app = express(); let accessToken: string; @@ -20,8 +21,102 @@ describe('Get WebSocket binding token', () => { await shutdownApp(); }); - test('Basic', async () => { - // Create Subscription + test('Basic', () => + withTestContext(async () => { + // Create Subscription + const res1 = await request(app) + .post(`/fhir/R4/Subscription`) + .set('Authorization', 'Bearer ' + accessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send({ + resourceType: 'Subscription', + reason: 'test', + status: 'active', + criteria: 'Patient', + channel: { + type: 'websocket', + }, + } satisfies Subscription); + const createdSub = res1.body as Subscription; + expect(res1.status).toBe(201); + expect(createdSub).toBeDefined(); + expect(createdSub.id).toBeDefined(); + + // Start the export + const res2 = await request(app) + .get(`/fhir/R4/Subscription/${createdSub.id}/$get-ws-binding-token`) + .set('Authorization', 'Bearer ' + accessToken); + expect(res2.status).toBe(200); + expect(res2.body).toBeDefined(); + + const params = res2.body as Parameters; + expect(params.resourceType).toEqual('Parameters'); + expect(params.parameter?.length).toEqual(3); + expect(params.parameter?.[0]).toBeDefined(); + expect(params.parameter?.[0]?.name).toEqual('token'); + + const token = params.parameter?.[0]?.valueString as string; + expect(token).toBeDefined(); + + const { payload } = await verifyJwt(token); + expect(payload?.sub).toBeDefined(); + expect(payload?.exp).toBeDefined(); + expect(payload?.aud).toBeDefined(); + expect(payload?.username).toBeDefined(); + expect(payload?.subscription_id).toBeDefined(); + + expect(params.parameter?.[1]).toBeDefined(); + expect(params.parameter?.[1]?.name).toEqual('expiration'); + expect(params.parameter?.[1]?.valueDateTime).toBeDefined(); + expect(new Date(params.parameter?.[1]?.valueDateTime as string).getTime()).toBeGreaterThanOrEqual(Date.now()); + expect(params.parameter?.[2]).toBeDefined(); + expect(params.parameter?.[2]?.name).toEqual('websocket-url'); + expect(params.parameter?.[2]?.valueUrl).toBeDefined(); + })); + + test('should return OperationOutcome error if Subscription no longer exists', () => + withTestContext(async () => { + // Create subscription to watch patient + const res1 = await request(app) + .post(`/fhir/R4/Subscription`) + .set('Authorization', 'Bearer ' + accessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send({ + resourceType: 'Subscription', + reason: 'test', + status: 'active', + criteria: 'Patient', + channel: { + type: 'websocket', + }, + } satisfies Subscription); + const createdSub = res1.body as Subscription; + expect(res1.status).toBe(201); + expect(createdSub).toBeDefined(); + expect(createdSub.id).toBeDefined(); + + const res2 = await request(app) + .delete(`/fhir/R4/Subscription/${createdSub.id as string}`) + .set('Authorization', 'Bearer ' + accessToken); + + expect(res2.body).toMatchObject({ + resourceType: 'OperationOutcome', + issue: [{ severity: 'information', code: 'informational' }], + }); + + // Call $get-ws-binding-token + const res3 = await request(app) + .get(`/fhir/R4/Subscription/${createdSub.id}/$get-ws-binding-token`) + .set('Authorization', 'Bearer ' + accessToken); + + expect(res3.body).toMatchObject({ + resourceType: 'OperationOutcome', + issue: [{ severity: 'error', code: 'invalid' }], + }); + })); + + test('should return OperationOutcome error if user does not have access to this Subscription', async () => { + // Create subscription to watch patient const res1 = await request(app) .post(`/fhir/R4/Subscription`) .set('Authorization', 'Bearer ' + accessToken) @@ -40,25 +135,16 @@ describe('Get WebSocket binding token', () => { expect(createdSub).toBeDefined(); expect(createdSub.id).toBeDefined(); - // Start the export + const anotherUserToken = await initTestAuth(); + + // Call $get-ws-binding-token const res2 = await request(app) .get(`/fhir/R4/Subscription/${createdSub.id}/$get-ws-binding-token`) - .set('Authorization', 'Bearer ' + accessToken); - expect(res2.status).toBe(200); - expect(res2.body).toBeDefined(); - - const params = res2.body as Parameters; - expect(params.resourceType).toEqual('Parameters'); - expect(params.parameter?.length).toEqual(3); - expect(params.parameter?.[0]).toBeDefined(); - expect(params.parameter?.[0].name).toEqual('token'); - expect(params.parameter?.[0].valueString).toBeDefined(); - expect(params.parameter?.[1]).toBeDefined(); - expect(params.parameter?.[1].name).toEqual('expiration'); - expect(params.parameter?.[1].valueDateTime).toBeDefined(); - expect(new Date(params.parameter?.[1].valueDateTime as string).getTime()).toBeGreaterThanOrEqual(Date.now()); - expect(params.parameter?.[2]).toBeDefined(); - expect(params.parameter?.[2].name).toEqual('websocket-url'); - expect(params.parameter?.[2].valueUrl).toBeDefined(); + .set('Authorization', 'Bearer ' + anotherUserToken); + + expect(res2.body).toMatchObject({ + resourceType: 'OperationOutcome', + issue: [{ severity: 'error', code: 'invalid' }], + }); }); }); diff --git a/packages/server/src/fhir/operations/getwsbindingtoken.ts b/packages/server/src/fhir/operations/getwsbindingtoken.ts index a2da54083a..58f895ae2d 100644 --- a/packages/server/src/fhir/operations/getwsbindingtoken.ts +++ b/packages/server/src/fhir/operations/getwsbindingtoken.ts @@ -1,10 +1,9 @@ -import { allOk, badRequest, resolveId } from '@medplum/core'; -import { Parameters } from '@medplum/fhirtypes'; +import { allOk, badRequest, normalizeErrorString, resolveId } from '@medplum/core'; +import { Parameters, Subscription } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { getConfig } from '../../config'; import { getAuthenticatedContext } from '../../context'; import { generateAccessToken } from '../../oauth/keys'; -import { getRedis } from '../../redis'; import { sendOutcome } from '../outcomes'; import { sendResponse } from '../response'; @@ -28,21 +27,21 @@ export type AdditionalWsBindingClaims = { * @param res - The HTTP response. */ export async function getWsBindingTokenHandler(req: Request, res: Response): Promise { - const { login, profile } = getAuthenticatedContext(); + const { login, profile, repo } = getAuthenticatedContext(); const { baseUrl } = getConfig(); - const redis = getRedis(); const clientId = login.client && resolveId(login.client); const userId = resolveId(login.user); if (!userId) { - await sendOutcome(res, badRequest('Login missing user')); + sendOutcome(res, badRequest('Login missing user')); return; } const subscriptionId = req.params.id; - const subExists = await redis.exists(`Subscription/${subscriptionId}`); - if (!subExists) { - await sendOutcome(res, badRequest('Content could not be parsed')); + try { + await repo.readResource('Subscription', subscriptionId); + } catch (err: unknown) { + sendOutcome(res, badRequest(`Error reading subscription: ${normalizeErrorString(err)}`)); return; } diff --git a/packages/server/src/fhir/repo.ts b/packages/server/src/fhir/repo.ts index f1605c5105..f7a02f10f0 100644 --- a/packages/server/src/fhir/repo.ts +++ b/packages/server/src/fhir/repo.ts @@ -47,7 +47,6 @@ import { ResourceType, SearchParameter, StructureDefinition, - Subscription, } from '@medplum/fhirtypes'; import { randomUUID } from 'crypto'; import { Pool, PoolClient } from 'pg'; @@ -201,18 +200,16 @@ export class Repository extends BaseRepository implements FhirRepository(resource: T): Promise { + const resourceWithId = { + ...resource, + id: randomUUID(), + }; try { - const result = await this.updateResourceImpl( - { - ...resource, - id: randomUUID(), - }, - true - ); + const result = await this.updateResourceImpl(resourceWithId, true); this.logEvent(CreateInteraction, AuditEventOutcome.Success, undefined, result); return result; } catch (err) { - this.logEvent(CreateInteraction, AuditEventOutcome.MinorFailure, err, resource); + this.logEvent(CreateInteraction, AuditEventOutcome.MinorFailure, err, resourceWithId); throw err; } } @@ -541,19 +538,7 @@ export class Repository extends BaseRepository implements FhirRepository (sub.id as string) === (result.id as string) - ); - if (existingIdx !== -1) { - currentWsSubscriptions[existingIdx] = result; - } else { - currentWsSubscriptions.push(result); - } - await redis.set(`medplum:subscriptions:r4:project:${project}`, JSON.stringify(currentWsSubscriptions)); + await redis.sadd(`medplum:subscriptions:r4:project:${project}:active`, `Subscription/${result.id}`); } } diff --git a/packages/server/src/subscriptions/websockets.test.ts b/packages/server/src/subscriptions/websockets.test.ts index 1ced8d4cfe..d9e7a5e3e9 100644 --- a/packages/server/src/subscriptions/websockets.test.ts +++ b/packages/server/src/subscriptions/websockets.test.ts @@ -1,4 +1,4 @@ -import { getReferenceString, sleep } from '@medplum/core'; +import { OperationOutcomeError, getReferenceString, sleep } from '@medplum/core'; import { Bundle, BundleEntry, @@ -17,6 +17,7 @@ import { initApp, shutdownApp } from '../app'; import { registerNew } from '../auth/register'; import { MedplumServerConfig, loadTestConfig } from '../config'; import { Repository } from '../fhir/repo'; +import { getRedis } from '../redis'; import { withTestContext } from '../test.setup'; import { execSubscriptionJob, getSubscriptionQueue } from '../workers/subscription'; @@ -30,6 +31,7 @@ describe('WebSockets Subscriptions', () => { let project: Project; let repo: Repository; let accessToken: string; + let patientSubscription: Subscription; beforeAll(async () => { app = express(); @@ -58,6 +60,9 @@ describe('WebSockets Subscriptions', () => { }, }); + // TODO: Remove this when the websocket-subscriptions feature flag is removed + project = await withTestContext(() => repo.updateResource({ ...project, features: ['websocket-subscriptions'] })); + await new Promise((resolve) => { server.listen(0, 'localhost', 511, resolve); }); @@ -80,7 +85,7 @@ describe('WebSockets Subscriptions', () => { expect(version1.id).toBeDefined(); // Create subscription to watch patient - const subscription = await repo.createResource({ + patientSubscription = await repo.createResource({ resourceType: 'Subscription', reason: 'test', status: 'active', @@ -89,11 +94,11 @@ describe('WebSockets Subscriptions', () => { type: 'websocket', }, }); - expect(subscription).toBeDefined(); + expect(patientSubscription).toBeDefined(); // Call $get-ws-binding-token const res = await request(server) - .get(`/fhir/R4/Subscription/${subscription.id}/$get-ws-binding-token`) + .get(`/fhir/R4/Subscription/${patientSubscription.id}/$get-ws-binding-token`) .set('Authorization', 'Bearer ' + accessToken); expect(res.body).toBeDefined(); @@ -103,11 +108,13 @@ describe('WebSockets Subscriptions', () => { expect(body.parameter?.[0]?.name).toEqual('token'); expect(body.parameter?.[0]?.valueString).toBeDefined(); + const token = body.parameter?.[0]?.valueString as string; + let version2: Patient; await request(server) .ws('/ws/subscriptions-r4') .set('Authorization', 'Bearer ' + accessToken) - .sendJson({ type: 'bind-with-token', payload: { token: body.parameter?.[0]?.valueString as string } }) + .sendJson({ type: 'bind-with-token', payload: { token } }) // Add a new patient for this project .exec(async () => { const queue = getSubscriptionQueue() as any; @@ -133,6 +140,19 @@ describe('WebSockets Subscriptions', () => { // Clear the queue queue.add.mockClear(); + + let subActive = false; + while (!subActive) { + await sleep(0); + subActive = + ( + await getRedis().smismember( + `medplum:subscriptions:r4:project:${project.id}:active`, + `Subscription/${patientSubscription?.id as string}` + ) + )[0] === 1; + } + expect(subActive).toEqual(true); }) .expectJson((msg: Bundle): boolean => { if (!msg.entry?.[1]) { @@ -155,6 +175,27 @@ describe('WebSockets Subscriptions', () => { .expectClosed(); })); + test('Subscription removed from active and deleted after WebSocket closed', () => + withTestContext(async () => { + let subActive = true; + while (subActive) { + await sleep(0); + subActive = + ( + await getRedis().smismember( + `medplum:subscriptions:r4:project:${project.id}:active`, + `Subscription/${patientSubscription?.id as string}` + ) + )[0] === 1; + } + expect(subActive).toEqual(false); + + // Check Patient subscription is NOT still in the cache + await expect(repo.readResource('Subscription', patientSubscription?.id as string)).rejects.toThrow( + OperationOutcomeError + ); + })); + test('Should reject if given an invalid binding token', () => withTestContext(async () => { const version1 = await repo.createResource({ diff --git a/packages/server/src/subscriptions/websockets.ts b/packages/server/src/subscriptions/websockets.ts index 679f77811b..0404fc55b6 100644 --- a/packages/server/src/subscriptions/websockets.ts +++ b/packages/server/src/subscriptions/websockets.ts @@ -1,9 +1,11 @@ import { badRequest, createReference } from '@medplum/core'; -import { Bundle, Resource } from '@medplum/fhirtypes'; +import { Bundle, Resource, Subscription } from '@medplum/fhirtypes'; import { Redis } from 'ioredis'; import { JWTPayload } from 'jose'; import crypto from 'node:crypto'; import ws from 'ws'; +import { AdditionalWsBindingClaims } from '../fhir/operations/getwsbindingtoken'; +import { CacheEntry } from '../fhir/repo'; import { getFullUrl } from '../fhir/response'; import { heartbeat } from '../heartbeat'; import { globalLogger } from '../logger'; @@ -24,10 +26,19 @@ export async function handleR4SubscriptionConnection(socket: ws.WebSocket): Prom const redis = getRedis(); const subscriptionIds = [] as string[]; let redisSubscriber: Redis; - let onDisconnect: (() => void) | undefined; + let onDisconnect: (() => Promise) | undefined; let heartbeatHandler: (() => void) | undefined; - const onBind = async (tokenPayload: JWTPayload): Promise => { + const onBind = async (tokenPayload: JWTPayload & Partial): Promise => { + const subscriptionId = tokenPayload?.subscription_id; + if (!subscriptionId) { + socket.send( + JSON.stringify(badRequest('Token claims missing subscription_id. Make sure you are sending the correct token.')) + ); + socket.terminate(); + return; + } + if (!redisSubscriber) { // Create a redis client for this connection. // According to Redis documentation: http://redis.io/commands/subscribe @@ -40,16 +51,18 @@ export async function handleR4SubscriptionConnection(socket: ws.WebSocket): Prom socket.send(message, { binary: false }); }); - onDisconnect = () => redisSubscriber.disconnect(); + onDisconnect = async (): Promise => { + redisSubscriber.disconnect(); + const cacheEntryStr = (await redis.get(`Subscription/${subscriptionId}`)) as string | null; + if (!cacheEntryStr) { + globalLogger.error('[WS] Failed to retrieve subscription cache entry on WebSocket disconnect.'); + return; + } + const cacheEntry = JSON.parse(cacheEntryStr) as CacheEntry; + await markInMemorySubscriptionsInactive(cacheEntry.projectId, subscriptionIds); + }; } - const subscriptionId = tokenPayload?.subscription_id as string | undefined; - if (!subscriptionId) { - socket.send( - JSON.stringify(badRequest('Token claims missing subscription_id. Make sure you are sending the correct token.')) - ); - return; - } if (!subscriptionIds.includes(subscriptionId)) { subscriptionIds.push(subscriptionId); } @@ -92,7 +105,7 @@ export async function handleR4SubscriptionConnection(socket: ws.WebSocket): Prom socket.on('close', async () => { if (onDisconnect) { - onDisconnect(); + onDisconnect().catch(console.error); } if (heartbeatHandler) { heartbeat.removeEventListener('heartbeat', heartbeatHandler); @@ -160,3 +173,12 @@ export function createSubEventNotification { + const refStrs = []; + for (const subscriptionId of subscriptionIds) { + refStrs.push(`Subscription/${subscriptionId}`); + } + const redis = getRedis(); + await redis.multi().srem(`medplum:subscriptions:r4:project:${projectId}:active`, refStrs).del(refStrs).exec(); +} diff --git a/packages/server/src/workers/subscription.test.ts b/packages/server/src/workers/subscription.test.ts index 917ed3800e..cb87ac6f14 100644 --- a/packages/server/src/workers/subscription.test.ts +++ b/packages/server/src/workers/subscription.test.ts @@ -1,6 +1,15 @@ import { InvokeCommand, LambdaClient } from '@aws-sdk/client-lambda'; -import { ContentType, Operator, createReference, getReferenceString, stringify } from '@medplum/core'; -import { AuditEvent, Bot, Observation, Patient, Project, ProjectMembership, Subscription } from '@medplum/fhirtypes'; +import { ContentType, LogLevel, Operator, createReference, getReferenceString, stringify } from '@medplum/core'; +import { + AccessPolicy, + AuditEvent, + Bot, + Observation, + Patient, + Project, + ProjectMembership, + Subscription, +} from '@medplum/fhirtypes'; import { AwsClientStub, mockClient } from 'aws-sdk-client-mock'; import { Job } from 'bullmq'; import { createHmac, randomUUID } from 'crypto'; @@ -9,6 +18,7 @@ import { initAppServices, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; import { getDatabasePool } from '../database'; import { Repository, getSystemRepo } from '../fhir/repo'; +import { globalLogger } from '../logger'; import { getRedis } from '../redis'; import { createTestProject, withTestContext } from '../test.setup'; import { AuditEventOutcome } from '../util/auditevent'; @@ -47,22 +57,17 @@ describe('Subscription Worker', () => { await initAppServices(config); // Create one simple project with no advanced features enabled - const testProject = await withTestContext(() => - systemRepo.createResource({ - resourceType: 'Project', + const { project, client } = await withTestContext(() => + createTestProject({ name: 'Test Project', - owner: { - reference: 'User/' + randomUUID(), - }, + features: [], }) ); repo = new Repository({ extendedMode: true, - projects: [testProject.id as string], - author: { - reference: 'ClientApplication/' + randomUUID(), - }, + projects: [project.id as string], + author: createReference(client), }); // Create another project, this one with bots enabled @@ -1114,7 +1119,7 @@ describe('Subscription Worker', () => { test('AuditEvent has Subscription account details', () => withTestContext(async () => { - const project = randomUUID(); + const project = (await createTestProject()).project.id as string; const account = { reference: 'Organization/' + randomUUID(), }; @@ -1166,15 +1171,15 @@ describe('Subscription Worker', () => { }); expect(bundle.entry?.length).toEqual(1); - const auditEvent = bundle.entry?.[0].resource as AuditEvent; + const auditEvent = bundle.entry?.[0]?.resource as AuditEvent; expect(auditEvent.meta?.account).toBeDefined(); expect(auditEvent.meta?.account?.reference).toEqual(account.reference); expect(auditEvent.entity).toHaveLength(2); })); - test('Audit Event outcome from custom codes', () => + test('AuditEvent outcome from custom codes', () => withTestContext(async () => { - const project = randomUUID(); + const project = (await createTestProject()).project.id as string; const account = { reference: 'Organization/' + randomUUID(), }; @@ -1333,9 +1338,109 @@ describe('Subscription Worker', () => { expect(queue.add).not.toHaveBeenCalled(); })); - test('WebSocket Subscription', () => + test('Subscription -- AccessPolicy check throws (regression in #3978, see #4003)', () => withTestContext(async () => { - const subscription = await repo.createResource({ + globalLogger.level = LogLevel.WARN; + const originalConsoleLog = console.log; + console.log = jest.fn(); + + const url = 'https://example.com/subscription'; + + const accessPolicy = await repo.createResource({ + resourceType: 'AccessPolicy', + resource: [{ resourceType: 'Patient', readonly: false }], + }); + + const { project, client } = await createTestProject( + { + name: 'AccessPolicy Throw Project', + owner: { + reference: 'User/' + randomUUID(), + }, + features: [], + }, + { accessPolicy: createReference(accessPolicy) } + ); + + const apTestRepo = new Repository({ + extendedMode: true, + projects: [project.id as string], + author: { + reference: getReferenceString(client), + }, + }); + + const subscription = await apTestRepo.createResource({ + resourceType: 'Subscription', + reason: 'test', + status: 'active', + criteria: 'Patient', + channel: { + type: 'rest-hook', + endpoint: url, + }, + }); + expect(subscription).toBeDefined(); + expect(subscription.id).toBeDefined(); + + // Create the patient + const patient = await apTestRepo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family: 'Smith' }], + }); + expect(patient).toBeDefined(); + + // Clear the queue + const queue = getSubscriptionQueue() as any; + queue.add.mockClear(); + + // Clear the queue + queue.add.mockClear(); + + await systemRepo.deleteResource('AccessPolicy', accessPolicy.id as string); + + // Update the patient + const patient2 = await apTestRepo.updateResource({ ...patient, name: [{ given: ['Bob'], family: 'Smith' }] }); + + expect(queue.add).toHaveBeenCalled(); + (fetch as unknown as jest.Mock).mockImplementation(() => ({ status: 200 })); + + const job = { id: 1, data: queue.add.mock.calls[0][1] } as unknown as Job; + await execSubscriptionJob(job); + expect(fetch).toHaveBeenCalledWith( + url, + expect.objectContaining({ + method: 'POST', + body: stringify(patient2), + }) + ); + + expect(console.log).toHaveBeenCalledTimes(2); + + globalLogger.level = LogLevel.NONE; + console.log = originalConsoleLog; + })); + + test('WebSocket Subscription -- Enabled', () => + withTestContext(async () => { + const wsSubProject = await systemRepo.createResource({ + resourceType: 'Project', + name: 'WebSocket Subs Project', + owner: { + reference: 'User/' + randomUUID(), + }, + features: ['websocket-subscriptions'], + }); + + const wsSubRepo = new Repository({ + extendedMode: true, + projects: [wsSubProject.id as string], + author: { + reference: 'ClientApplication/' + randomUUID(), + }, + }); + + const subscription = await wsSubRepo.createResource({ resourceType: 'Subscription', reason: 'test', status: 'active', @@ -1365,7 +1470,7 @@ describe('Subscription Worker', () => { const queue = getSubscriptionQueue() as any; queue.add.mockClear(); - const patient = await repo.createResource({ + const patient = await wsSubRepo.createResource({ resourceType: 'Patient', name: [{ given: ['Alice'], family: 'Smith' }], }); @@ -1381,7 +1486,7 @@ describe('Subscription Worker', () => { queue.add.mockClear(); // Update the patient - await repo.updateResource({ ...patient, active: true }); + await wsSubRepo.updateResource({ ...patient, active: true }); // Update should also trigger the subscription expect(queue.add).toHaveBeenCalled(); @@ -1390,10 +1495,82 @@ describe('Subscription Worker', () => { queue.add.mockClear(); // Delete the patient - await repo.deleteResource('Patient', patient.id as string); + await wsSubRepo.deleteResource('Patient', patient.id as string); expect(queue.add).toHaveBeenCalled(); await deferredPromise; })); + + test('WebSocket Subscription -- Feature Flag Not Enabled', () => + withTestContext(async () => { + globalLogger.level = LogLevel.WARN; + const originalConsoleLog = console.log; + console.log = jest.fn(); + + const noWsSubProject = await systemRepo.createResource({ + resourceType: 'Project', + name: 'No WebSocket Subs Project', + owner: { + reference: 'User/' + randomUUID(), + }, + }); + + const noWsSubRepo = new Repository({ + extendedMode: true, + projects: [noWsSubProject.id as string], + author: { + reference: 'ClientApplication/' + randomUUID(), + }, + }); + + const subscription = await noWsSubRepo.createResource({ + resourceType: 'Subscription', + reason: 'test', + status: 'active', + criteria: 'Patient', + channel: { + type: 'websocket', + }, + }); + expect(subscription).toBeDefined(); + expect(subscription.id).toBeDefined(); + + // Subscribe to the topic + const subscriber = getRedis().duplicate(); + await subscriber.subscribe(subscription.id as string); + + let resolve: () => void; + let reject: (error: Error) => void; + + const deferredPromise = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + + subscriber.on('message', () => { + reject(new Error('Should not have been called')); + }); + + const queue = getSubscriptionQueue() as any; + queue.add.mockClear(); + + const patient = await noWsSubRepo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family: 'Smith' }], + }); + expect(patient).toBeDefined(); + expect(queue.add).not.toHaveBeenCalled(); + + // Give some time for the callback to get called (it shouldn't) + setTimeout(() => { + resolve(); + }, 150); + + await deferredPromise; + expect(console.log).toHaveBeenLastCalledWith(expect.stringMatching(/WebSocket Subscriptions/)); + + console.log = originalConsoleLog; + globalLogger.level = LogLevel.NONE; + })); }); diff --git a/packages/server/src/workers/subscription.ts b/packages/server/src/workers/subscription.ts index e322acff4b..847ccd4b6c 100644 --- a/packages/server/src/workers/subscription.ts +++ b/packages/server/src/workers/subscription.ts @@ -1,33 +1,37 @@ import { + AccessPolicyInteraction, ContentType, createReference, getExtension, getExtensionValue, + getReferenceString, isGone, matchesSearchRequest, normalizeOperationOutcome, OperationOutcomeError, Operator, parseSearchUrl, + satisfiedAccessPolicy, serverError, stringify, } from '@medplum/core'; -import { Bot, ProjectMembership, Reference, Resource, Subscription } from '@medplum/fhirtypes'; +import { Bot, Project, ProjectMembership, Reference, Resource, Subscription } from '@medplum/fhirtypes'; import { Job, Queue, QueueBaseOptions, Worker } from 'bullmq'; import { createHmac } from 'crypto'; import fetch, { HeadersInit } from 'node-fetch'; import { URL } from 'url'; import { MedplumServerConfig } from '../config'; import { getRequestContext, RequestContext, requestContextStore } from '../context'; +import { buildAccessPolicy } from '../fhir/accesspolicy'; import { executeBot } from '../fhir/operations/execute'; import { getSystemRepo, Repository } from '../fhir/repo'; import { globalLogger } from '../logger'; import { getRedis } from '../redis'; import { createSubEventNotification } from '../subscriptions/websockets'; +import { parseTraceparent } from '../traceparent'; import { AuditEventOutcome } from '../util/auditevent'; import { BackgroundJobContext, BackgroundJobInteraction } from './context'; import { createAuditEvent, findProjectMembership, isFhirCriteriaMet, isJobSuccessful } from './utils'; -import { parseTraceparent } from '../traceparent'; /** * The upper limit on the number of times a job can be retried. @@ -126,6 +130,61 @@ export function getSubscriptionQueue(): Queue | undefined { return queue; } +/** + * Checks if this resource should create a notification for this `Subscription` based on the access policy that should be applied for this `Subscription`. + * The `AccessPolicy` of author's `ProjectMembership` for this resource's `Project` is used when evaluating whether the `AccessPolicy` is satisfied. + * + * Currently we log if the `AccessPolicy` is not satisfied only. + * + * TODO: Actually prevent notifications for `Subscriptions` where the `AccessPolicy` is not satisfied. + * + * @param resource - The resource to evaluate against the `AccessPolicy`. + * @param project - The project containing the resource. + * @param subscription - The `Subscription` to get the `AccessPolicy` for. + */ +async function checkAccessPolicy(resource: Resource, project: Project, subscription: Subscription): Promise { + try { + // Check access policy + const subAuthor = subscription.meta?.author; + if (subAuthor) { + const membership = await findProjectMembership(project.id as string, subAuthor); + if (membership) { + const accessPolicy = await buildAccessPolicy(membership); + const satisfied = satisfiedAccessPolicy(resource, AccessPolicyInteraction.READ, accessPolicy); + if (!satisfied) { + const resourceReference = getReferenceString(resource); + const subReference = getReferenceString(subscription); + const projectReference = getReferenceString(project); + globalLogger.warn( + `[Subscription Access Policy]: Access Policy not satisfied on '${resourceReference}' for '${subReference}'`, + { subscription: subReference, project: projectReference, accessPolicy } + ); + } + } else { + const projectReference = getReferenceString(project); + const authorReference = getReferenceString(subAuthor); + const subReference = getReferenceString(subscription); + globalLogger.warn( + `[Subscription Access Policy]: No membership for subscription author '${authorReference}' in project '${projectReference}'`, + { subscription: subReference } + ); + } + } else { + // Log it if there is no author for this Subscription (this is not good) + globalLogger.warn( + `[Subscription Access Policy]: No author for subscription '${getReferenceString(subscription)}'` + ); + } + } catch (err: unknown) { + const resourceReference = getReferenceString(resource); + const subReference = getReferenceString(subscription); + globalLogger.warn( + `[Subscription Access Policy]: Error occurred while checking access policy for resource '${resourceReference}' against '${subReference}'`, + { error: err, subscription: subReference } + ); + } +} + /** * Adds all subscription jobs for a given resource. * @@ -147,12 +206,36 @@ export async function addSubscriptionJobs(resource: Resource, context: Backgroun // Never send subscriptions for audit events return; } + + const systemRepo = getSystemRepo(); + let project: Project | undefined; + try { + const projectId = resource.meta?.project; + if (projectId) { + project = await systemRepo.readResource('Project', projectId); + } + } catch (err: unknown) { + const resourceReference = getReferenceString(resource); + globalLogger.error(`[Subscription]: No project found for '${resourceReference}' -- something is very wrong.`, { + error: err, + resource: resourceReference, + }); + return; + } + if (!project) { + ctx.logger.debug('Did not evaluate subscriptions for resource without project'); + globalLogger.warn(`[Subscription Access Policy]: No project for resource '${getReferenceString(resource)}'`); + return; + } + const requestTime = new Date().toISOString(); - const subscriptions = await getSubscriptions(resource); + const subscriptions = await getSubscriptions(resource, project); ctx.logger.debug(`Evaluate ${subscriptions.length} subscription(s)`); + for (const subscription of subscriptions) { const criteria = await matchesCriteria(resource, subscription, context); if (criteria) { + await checkAccessPolicy(resource, project, subscription); await addSubscriptionJobData({ subscriptionId: subscription.id as string, resourceType: resource.resourceType, @@ -266,13 +349,11 @@ async function addSubscriptionJobData(job: SubscriptionJobData): Promise { /** * Loads the list of all subscriptions in this repository. * @param resource - The resource that was created or updated. + * @param project - The project that contains this resource. * @returns The list of all subscriptions in this repository. */ -async function getSubscriptions(resource: Resource): Promise { - const project = resource.meta?.project; - if (!project) { - return []; - } +async function getSubscriptions(resource: Resource, project: Project): Promise { + const projectId = project.id as string; const systemRepo = getSystemRepo(); const subscriptions = await systemRepo.searchResources({ resourceType: 'Subscription', @@ -281,7 +362,7 @@ async function getSubscriptions(resource: Resource): Promise { { code: '_project', operator: Operator.EQUALS, - value: project, + value: projectId, }, { code: 'status', @@ -290,10 +371,20 @@ async function getSubscriptions(resource: Resource): Promise { }, ], }); - const inMemorySubscriptionsStr = await getRedis().get(`medplum:subscriptions:r4:project:${project}`); - if (inMemorySubscriptionsStr) { - const inMemorySubscriptions = JSON.parse(inMemorySubscriptionsStr) as Subscription[]; - subscriptions.push(...inMemorySubscriptions); + const redisOnlySubRefStrs = await getRedis().smembers(`medplum:subscriptions:r4:project:${projectId}:active`); + if (redisOnlySubRefStrs.length) { + const redisOnlySubStrs = await getRedis().mget(redisOnlySubRefStrs); + if (project.features?.includes('websocket-subscriptions')) { + const subArrStr = '[' + redisOnlySubStrs.filter(Boolean).join(',') + ']'; + const inMemorySubs = JSON.parse(subArrStr) as { resource: Subscription; projectId: string }[]; + for (const { resource } of inMemorySubs) { + subscriptions.push(resource); + } + } else { + globalLogger.warn( + `[WebSocket Subscriptions]: subscription for resource '${getReferenceString(resource)}' might have been fired but WebSocket subscriptions are not enabled for project '${project.name ?? getReferenceString(project)}'` + ); + } } return subscriptions; } From 901876f89a39f18e72216fc2126ae97bba0e48cd Mon Sep 17 00:00:00 2001 From: Matt Willer Date: Thu, 22 Feb 2024 09:49:52 -0800 Subject: [PATCH 72/81] Implement recursive subsumption in ValueSet expansion (#4005) * Implement recursive subsumption in ValueSet expansion * Cleanup --- .../server/src/fhir/operations/expand.test.ts | 17 +++++++ packages/server/src/fhir/operations/expand.ts | 51 +++++++++---------- packages/server/src/fhir/sql.ts | 39 +++++++++++++- 3 files changed, 80 insertions(+), 27 deletions(-) diff --git a/packages/server/src/fhir/operations/expand.test.ts b/packages/server/src/fhir/operations/expand.test.ts index 36600308dc..2428bb73b6 100644 --- a/packages/server/src/fhir/operations/expand.test.ts +++ b/packages/server/src/fhir/operations/expand.test.ts @@ -524,4 +524,21 @@ describe('Updated implementation', () => { display: expect.stringMatching(/care team/i), }); }); + + test('Recursive subsumption', async () => { + const res = await request(app) + .get( + `/fhir/R4/ValueSet/$expand?url=${encodeURIComponent('http://hl7.org/fhir/ValueSet/relatedperson-relationshiptype')}&count=200` + ) + .set('Authorization', 'Bearer ' + accessToken); + expect(res.status).toEqual(200); + const expansion = res.body.expansion as ValueSetExpansion; + + expect( + expansion.contains?.filter((c) => c.system === 'http://terminology.hl7.org/CodeSystem/v2-0131') + ).toHaveLength(12); + expect( + expansion.contains?.filter((c) => c.system === 'http://terminology.hl7.org/CodeSystem/v3-RoleCode') + ).toHaveLength(110); + }); }); diff --git a/packages/server/src/fhir/operations/expand.ts b/packages/server/src/fhir/operations/expand.ts index 3d19a8dcc1..5dba838b1f 100644 --- a/packages/server/src/fhir/operations/expand.ts +++ b/packages/server/src/fhir/operations/expand.ts @@ -1,17 +1,10 @@ import { badRequest, OperationOutcomeError, Operator, Operator as SearchOperator } from '@medplum/core'; -import { - CodeSystem, - Coding, - ValueSet, - ValueSetComposeInclude, - ValueSetComposeIncludeFilter, - ValueSetExpansionContains, -} from '@medplum/fhirtypes'; +import { CodeSystem, Coding, ValueSet, ValueSetComposeInclude, ValueSetExpansionContains } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { asyncWrap } from '../../async'; import { sendOutcome } from '../outcomes'; import { getSystemRepo } from '../repo'; -import { Column, Condition, Conjunction, SelectQuery, Expression, Disjunction } from '../sql'; +import { Column, Condition, Conjunction, SelectQuery, Expression, Disjunction, Union } from '../sql'; import { getAuthenticatedContext } from '../../context'; import { parentProperty } from './codesystemimport'; import { clamp } from './utils/parameters'; @@ -293,6 +286,7 @@ async function includeInExpansion( const ctx = getAuthenticatedContext(); let query = new SelectQuery('Coding') + .column('id') .column('code') .column('display') .where('system', '=', codeSystem.id) @@ -311,10 +305,7 @@ async function includeInExpansion( ctx.logger.warn('Invalid parent code in ValueSet', { codeSystem: codeSystem.id, code: condition.value }); return; // Invalid parent code, don't make DB query with incorrect filters } - if (!filter || coding.display?.includes(filter)) { - expansion.push({ ...coding, id: undefined }); - } - query = addParentCondition(condition, query, codeSystem, coding); + query = addParentCondition(query, codeSystem, coding); } break; default: @@ -331,12 +322,7 @@ async function includeInExpansion( } } -function addParentCondition( - condition: ValueSetComposeIncludeFilter, - query: SelectQuery, - codeSystem: CodeSystem, - parent: Coding -): SelectQuery { +function addParentCondition(query: SelectQuery, codeSystem: CodeSystem, parent: Coding): SelectQuery { if (codeSystem.hierarchyMeaning !== 'is-a') { throw new OperationOutcomeError( badRequest( @@ -351,14 +337,13 @@ function addParentCondition( property = { code: codeSystem.hierarchyMeaning ?? 'parent', uri: parentProperty, type: 'code' }; } + const base = new SelectQuery('Coding').column('id').column('code').column('display').where('id', '=', parent.id); + const propertyTable = query.getNextJoinAlias(); - query.leftJoin( + query.innerJoin( 'Coding_Property', propertyTable, - new Conjunction([ - new Condition(new Column('Coding', 'id'), '=', new Column(propertyTable, 'coding')), - new Condition(new Column(propertyTable, 'target'), '=', parent.id), - ]) + new Condition(new Column('Coding', 'id'), '=', new Column(propertyTable, 'coding')) ); const csPropertyTable = query.getNextJoinAlias(); @@ -370,7 +355,21 @@ function addParentCondition( new Condition(new Column(csPropertyTable, 'code'), '=', property.code), ]) ); - query.where(new Column(propertyTable, 'coding'), condition.op === 'is-not-a' ? '=' : '!=', null); - return query; + const recursiveCTE = 'cte_descendants'; + const recursiveTable = query.getNextJoinAlias(); + query.innerJoin( + recursiveCTE, + recursiveTable, + new Condition(new Column(propertyTable, 'target'), '=', new Column(recursiveTable, 'id')) + ); + const offset = query.offset_; + query.offset(0); + + return new SelectQuery('cte_descendants') + .column('code') + .column('display') + .withRecursive('cte_descendants', new Union(base, query)) + .limit(query.limit_) + .offset(offset); } diff --git a/packages/server/src/fhir/sql.ts b/packages/server/src/fhir/sql.ts index 738b35dbcb..9852505b45 100644 --- a/packages/server/src/fhir/sql.ts +++ b/packages/server/src/fhir/sql.ts @@ -239,6 +239,21 @@ export class Exists implements Expression { } } +export class Union implements Expression { + constructor( + readonly left: SelectQuery, + readonly right: SelectQuery + ) {} + + buildSql(sql: SqlBuilder): void { + sql.append('('); + this.left.buildSql(sql); + sql.append(') UNION ('); + this.right.buildSql(sql); + sql.append(')'); + } +} + export class Join { constructor( readonly joinType: 'LEFT JOIN' | 'INNER JOIN', @@ -386,12 +401,19 @@ export abstract class BaseQuery { } } -export class SelectQuery extends BaseQuery { +interface CTE { + name: string; + expr: Expression; + recursive?: boolean; +} + +export class SelectQuery extends BaseQuery implements Expression { readonly distinctOns: Column[]; readonly columns: Column[]; readonly joins: Join[]; readonly groupBys: GroupBy[]; readonly orderBys: OrderBy[]; + with?: CTE; limit_: number; offset_: number; joinCount = 0; @@ -407,6 +429,11 @@ export class SelectQuery extends BaseQuery { this.offset_ = 0; } + withRecursive(name: string, expr: Expression): this { + this.with = { name, expr: expr, recursive: true }; + return this; + } + distinctOn(column: Column | string): this { this.distinctOns.push(getColumn(column, this.tableName)); return this; @@ -461,6 +488,16 @@ export class SelectQuery extends BaseQuery { if (this.explain) { sql.append('EXPLAIN '); } + if (this.with) { + sql.append('WITH '); + if (this.with.recursive) { + sql.append('RECURSIVE '); + } + sql.appendIdentifier(this.with.name); + sql.append(' AS ('); + this.with.expr.buildSql(sql); + sql.append(') '); + } sql.append('SELECT '); this.buildDistinctOn(sql); this.buildColumns(sql); From 5e54ae739a87afa66fe6a255ee52d0989e8efe37 Mon Sep 17 00:00:00 2001 From: Matt Willer Date: Thu, 22 Feb 2024 10:47:05 -0800 Subject: [PATCH 73/81] Fix version check in update-server CLI (#4001) --- packages/cli/src/aws/update-server.test.ts | 2 +- packages/cli/src/aws/update-server.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/aws/update-server.test.ts b/packages/cli/src/aws/update-server.test.ts index dcc01f48c8..1e9c35f721 100644 --- a/packages/cli/src/aws/update-server.test.ts +++ b/packages/cli/src/aws/update-server.test.ts @@ -99,7 +99,7 @@ describe('update-server command', () => { medplum = { startAsyncRequest: jest.fn(), - get: jest.fn().mockResolvedValue(`{"version":"2.4.17-b27a9f"}`), + get: jest.fn().mockResolvedValue({ version: '2.4.17-b27a9f' }), } as unknown as MedplumClient; (createMedplumClient as unknown as jest.Mock).mockResolvedValue(medplum); }); diff --git a/packages/cli/src/aws/update-server.ts b/packages/cli/src/aws/update-server.ts index f65e128264..91070ab83f 100644 --- a/packages/cli/src/aws/update-server.ts +++ b/packages/cli/src/aws/update-server.ts @@ -20,7 +20,7 @@ export async function updateServerCommand(tag: string, options: any): Promise -1) { From 2f5b8da000fd75cc6d3e392fa043434a7320606c Mon Sep 17 00:00:00 2001 From: jmalobicky Date: Thu, 22 Feb 2024 14:04:37 -0500 Subject: [PATCH 74/81] Agent Docker image ping utility (#4008) * Add `ping` utility to use Agent tools from App * Add LOG_LEVEL setting via env var in Dockerfile * Update the Agent README to includes notes on building and running docker image --- packages/agent/Dockerfile | 8 +++++++- packages/agent/README.md | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/packages/agent/Dockerfile b/packages/agent/Dockerfile index 081cae6f9d..e442330d9a 100644 --- a/packages/agent/Dockerfile +++ b/packages/agent/Dockerfile @@ -5,6 +5,12 @@ ARG MEDPLUM_VERSION ENV GIT_SHA ${GIT_SHA} ENV MEDPLUM_VERSION ${MEDPLUM_VERSION} +ENV MEDPLUM_LOG_LEVEL=${MEDPLUM_LOG_LEVEL:-"INFO"} + +RUN apt-get update && \ + apt-get install -y iputils-ping && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* RUN adduser -u 5678 --disabled-password --gecos "" app @@ -15,4 +21,4 @@ WORKDIR /srv USER app -CMD ./medplum-agent $MEDPLUM_BASE_URL $MEDPLUM_CLIENT_ID $MEDPLUM_CLIENT_SECRET $MEDPLUM_AGENT_ID +CMD ./medplum-agent $MEDPLUM_BASE_URL $MEDPLUM_CLIENT_ID $MEDPLUM_CLIENT_SECRET $MEDPLUM_AGENT_ID $MEDPLUM_LOG_LEVEL diff --git a/packages/agent/README.md b/packages/agent/README.md index db88dc1c92..ffc04a427c 100644 --- a/packages/agent/README.md +++ b/packages/agent/README.md @@ -41,3 +41,27 @@ References: - [JSign](https://ebourg.github.io/jsign/) - [Shawl](https://github.com/mtkennerly/shawl) - [NSIS](https://nsis.sourceforge.io/) + +## Docker Image + +Build and run the docker image + +```bash +docker build -t medplum-agent:latest \ + --build-arg GIT_SHA=$(git log -1 --format=format:%H) \ + --build-arg MEDPLUM_VERSION=3.0.3 . +``` + +```bash +docker run --rm \ + -e MEDPLUM_BASE_URL="" \ + -e MEDPLUM_CLIENT_ID="" \ + -e MEDPLUM_CLIENT_SECRET="" \ + -e MEDPLUM_AGENT_ID="" \ + medplum-agent:latest +``` + +Optionally set the `MEDPLUM_LOG_LEVEL` environment variable +```bash + -e MEDPLUM_LOG_LEVEL="DEBUG" +``` From ff039c6df839a84f34fe33c23f7a2649dbe5e405 Mon Sep 17 00:00:00 2001 From: Derrick Farris Date: Thu, 22 Feb 2024 15:18:32 -0800 Subject: [PATCH 75/81] chore(deps): pin all monorepo deps to package version (#4017) * chore(deps): pin all monorepo deps to package version * chore(deps): update `package-lock.json` --- package-lock.json | 110 +++++++++++++-------------- packages/agent/package.json | 8 +- packages/app/package.json | 10 +-- packages/bot-layer/package.json | 4 +- packages/cdk/package.json | 2 +- packages/cli/package.json | 8 +- packages/core/package.json | 4 +- packages/docs/package.json | 6 +- packages/examples/package.json | 6 +- packages/expo-polyfills/package.json | 4 +- packages/fhir-router/package.json | 6 +- packages/generator/package.json | 6 +- packages/graphiql/package.json | 6 +- packages/health-gorilla/package.json | 2 +- packages/mock/package.json | 8 +- packages/react-hooks/package.json | 10 +-- packages/react/package.json | 12 +-- packages/server/package.json | 8 +- scripts/prepare-release.sh | 1 + 19 files changed, 111 insertions(+), 110 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1a74abe9a9..25d61b7380 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58254,15 +58254,15 @@ "version": "3.0.4", "license": "Apache-2.0", "dependencies": { - "@medplum/core": "*", - "@medplum/hl7": "*", + "@medplum/core": "3.0.4", + "@medplum/hl7": "3.0.4", "dcmjs-dimse": "0.1.25", "node-windows": "1.0.0-beta.8", "ws": "8.16.0" }, "devDependencies": { - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", "@types/async-eventemitter": "0.2.4", "@types/node-windows": "0.1.6", "@types/ws": "8.5.10", @@ -58282,11 +58282,11 @@ "@mantine/dropzone": "7.5.3", "@mantine/hooks": "7.5.3", "@mantine/notifications": "7.5.3", - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", - "@medplum/react": "*", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", + "@medplum/react": "3.0.4", "@tabler/icons-react": "2.47.0", "@testing-library/jest-dom": "6.4.2", "@testing-library/react": "14.2.1", @@ -58397,7 +58397,7 @@ "version": "3.0.4", "license": "Apache-2.0", "dependencies": { - "@medplum/core": "*", + "@medplum/core": "3.0.4", "form-data": "4.0.0", "jose": "5.2.2", "node-fetch": "2.7.0", @@ -58412,7 +58412,7 @@ "node": ">=18.0.0" }, "peerDependencies": { - "@medplum/core": "*", + "@medplum/core": "3.0.4", "form-data": "^4.0.0", "node-fetch": "^2.7.0", "pdfmake": "^0.2.7", @@ -58426,7 +58426,7 @@ "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.515.0", - "@medplum/core": "*", + "@medplum/core": "3.0.4", "aws-cdk-lib": "2.128.0", "cdk": "2.128.0", "cdk-nag": "2.28.39", @@ -58450,8 +58450,8 @@ "@aws-sdk/client-ssm": "3.515.0", "@aws-sdk/client-sts": "3.515.0", "@aws-sdk/types": "3.515.0", - "@medplum/core": "*", - "@medplum/hl7": "*", + "@medplum/core": "3.0.4", + "@medplum/hl7": "3.0.4", "aws-sdk-client-mock": "3.0.1", "commander": "12.0.0", "dotenv": "16.4.4", @@ -58463,8 +58463,8 @@ "medplum": "dist/cjs/index.cjs" }, "devDependencies": { - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", "@types/node-fetch": "2.6.11", "@types/tar": "6.1.11" }, @@ -58485,8 +58485,8 @@ "version": "3.0.4", "license": "Apache-2.0", "devDependencies": { - "@medplum/definitions": "*", - "@medplum/fhirtypes": "*", + "@medplum/definitions": "3.0.4", + "@medplum/fhirtypes": "3.0.4", "jest-websocket-mock": "2.5.0" }, "engines": { @@ -58521,9 +58521,9 @@ "@docusaurus/tsconfig": "3.1.1", "@docusaurus/types": "3.1.1", "@mdx-js/react": "3.0.1", - "@medplum/core": "*", - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", + "@medplum/core": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", "@svgr/webpack": "8.1.0", "clsx": "2.1.0", "file-loader": "6.2.0", @@ -58580,9 +58580,9 @@ "license": "Apache-2.0", "devDependencies": { "@jest/globals": "29.7.0", - "@medplum/core": "*", - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", + "@medplum/core": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", "jest": "29.7.0" }, "engines": { @@ -58599,7 +58599,7 @@ "text-encoding": "0.7.0" }, "devDependencies": { - "@medplum/core": "*", + "@medplum/core": "3.0.4", "@types/base-64": "1.0.2", "@types/react": "18.2.56", "@types/text-encoding": "0.0.39", @@ -58611,7 +58611,7 @@ "ts-jest": "29.1.2" }, "peerDependencies": { - "@medplum/core": "*", + "@medplum/core": "3.0.4", "expo": "*", "expo-crypto": "^12.6.0", "expo-secure-store": "^12.3.1", @@ -58625,9 +58625,9 @@ "version": "3.0.4", "license": "Apache-2.0", "dependencies": { - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhirtypes": "*", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhirtypes": "3.0.4", "dataloader": "2.2.2", "graphql": "16.8.1", "rfc6902": "5.1.1" @@ -58649,9 +58649,9 @@ "version": "3.0.4", "license": "Apache-2.0", "devDependencies": { - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhirtypes": "*", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhirtypes": "3.0.4", "@types/json-schema": "7.0.15", "@types/pg": "8.11.0", "@types/unzipper": "0.10.9", @@ -58698,9 +58698,9 @@ "@graphiql/toolkit": "0.9.1", "@mantine/core": "7.5.3", "@mantine/hooks": "7.5.3", - "@medplum/core": "*", - "@medplum/fhirtypes": "*", - "@medplum/react": "*", + "@medplum/core": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "graphiql": "3.1.1", @@ -58808,7 +58808,7 @@ "version": "3.0.4", "license": "Apache-2.0", "dependencies": { - "@medplum/core": "*", + "@medplum/core": "3.0.4", "@medplum/fhirtypes": "*" }, "devDependencies": { @@ -58837,10 +58837,10 @@ "version": "3.0.4", "license": "Apache-2.0", "dependencies": { - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhir-router": "*", - "@medplum/fhirtypes": "*", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhir-router": "3.0.4", + "@medplum/fhirtypes": "3.0.4", "dataloader": "2.2.2", "jest-websocket-mock": "2.5.0", "rfc6902": "5.1.1" @@ -58860,11 +58860,11 @@ "@mantine/core": "7.5.3", "@mantine/hooks": "7.5.3", "@mantine/notifications": "7.5.3", - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", - "@medplum/react-hooks": "*", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", + "@medplum/react-hooks": "3.0.4", "@storybook/addon-actions": "7.6.16", "@storybook/addon-essentials": "7.6.16", "@storybook/addon-links": "7.6.16", @@ -58903,7 +58903,7 @@ "@mantine/core": "^7.0.0", "@mantine/hooks": "^7.0.0", "@mantine/notifications": "^7.0.0", - "@medplum/core": "*", + "@medplum/core": "3.0.4", "react": "^17.0.2 || ^18.0.0", "react-dom": "^17.0.2 || ^18.0.0", "rfc6902": "^5.0.1" @@ -58928,10 +58928,10 @@ "version": "3.0.4", "license": "Apache-2.0", "devDependencies": { - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", "@testing-library/dom": "9.3.4", "@testing-library/jest-dom": "6.4.2", "@testing-library/react": "14.2.1", @@ -58951,7 +58951,7 @@ "node": ">=18.0.0" }, "peerDependencies": { - "@medplum/core": "*", + "@medplum/core": "3.0.4", "react": "^17.0.2 || ^18.0.0", "react-dom": "^17.0.2 || ^18.0.0" } @@ -59018,9 +59018,9 @@ "@aws-sdk/cloudfront-signer": "3.496.0", "@aws-sdk/lib-storage": "3.515.0", "@aws-sdk/types": "3.515.0", - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhir-router": "*", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhir-router": "3.0.4", "@opentelemetry/auto-instrumentations-node": "0.41.1", "@opentelemetry/exporter-metrics-otlp-proto": "0.48.0", "@opentelemetry/exporter-trace-otlp-proto": "0.48.0", @@ -59060,7 +59060,7 @@ }, "devDependencies": { "@jest/test-sequencer": "29.7.0", - "@medplum/fhirtypes": "*", + "@medplum/fhirtypes": "3.0.4", "@types/bcryptjs": "2.4.6", "@types/body-parser": "1.19.5", "@types/bytes": "3.1.4", diff --git a/packages/agent/package.json b/packages/agent/package.json index 64b92b9770..d786d70c35 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -23,15 +23,15 @@ "test": "jest" }, "dependencies": { - "@medplum/core": "*", - "@medplum/hl7": "*", + "@medplum/core": "3.0.4", + "@medplum/hl7": "3.0.4", "dcmjs-dimse": "0.1.25", "node-windows": "1.0.0-beta.8", "ws": "8.16.0" }, "devDependencies": { - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", "@types/async-eventemitter": "0.2.4", "@types/node-windows": "0.1.6", "@types/ws": "8.5.10", diff --git a/packages/app/package.json b/packages/app/package.json index 46bc86c10f..f5f6b5bb3e 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -33,11 +33,11 @@ "@mantine/dropzone": "7.5.3", "@mantine/hooks": "7.5.3", "@mantine/notifications": "7.5.3", - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", - "@medplum/react": "*", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", + "@medplum/react": "3.0.4", "@tabler/icons-react": "2.47.0", "@testing-library/jest-dom": "6.4.2", "@testing-library/react": "14.2.1", diff --git a/packages/bot-layer/package.json b/packages/bot-layer/package.json index eaaef8db69..6b3b0efe3a 100644 --- a/packages/bot-layer/package.json +++ b/packages/bot-layer/package.json @@ -22,7 +22,7 @@ "author": "Medplum ", "type": "module", "dependencies": { - "@medplum/core": "*", + "@medplum/core": "3.0.4", "form-data": "4.0.0", "jose": "5.2.2", "node-fetch": "2.7.0", @@ -34,7 +34,7 @@ "@types/node-fetch": "2.6.11" }, "peerDependencies": { - "@medplum/core": "*", + "@medplum/core": "3.0.4", "form-data": "^4.0.0", "node-fetch": "^2.7.0", "pdfmake": "^0.2.7", diff --git a/packages/cdk/package.json b/packages/cdk/package.json index 69fd468f59..c0ca259228 100644 --- a/packages/cdk/package.json +++ b/packages/cdk/package.json @@ -24,7 +24,7 @@ }, "dependencies": { "@aws-sdk/types": "3.515.0", - "@medplum/core": "*", + "@medplum/core": "3.0.4", "aws-cdk-lib": "2.128.0", "cdk": "2.128.0", "cdk-nag": "2.28.39", diff --git a/packages/cli/package.json b/packages/cli/package.json index 9af8776654..b4d4eefa88 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -49,8 +49,8 @@ "@aws-sdk/client-ssm": "3.515.0", "@aws-sdk/client-sts": "3.515.0", "@aws-sdk/types": "3.515.0", - "@medplum/core": "*", - "@medplum/hl7": "*", + "@medplum/core": "3.0.4", + "@medplum/hl7": "3.0.4", "aws-sdk-client-mock": "3.0.1", "commander": "12.0.0", "dotenv": "16.4.4", @@ -59,8 +59,8 @@ "tar": "6.2.0" }, "devDependencies": { - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", "@types/node-fetch": "2.6.11", "@types/tar": "6.1.11" }, diff --git a/packages/core/package.json b/packages/core/package.json index cfe132e2d1..f07563f1f1 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -55,8 +55,8 @@ "test": "jest" }, "devDependencies": { - "@medplum/definitions": "*", - "@medplum/fhirtypes": "*", + "@medplum/definitions": "3.0.4", + "@medplum/fhirtypes": "3.0.4", "jest-websocket-mock": "2.5.0" }, "peerDependencies": { diff --git a/packages/docs/package.json b/packages/docs/package.json index d4b74042e2..d8aeb627ae 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -47,9 +47,9 @@ "@docusaurus/tsconfig": "3.1.1", "@docusaurus/types": "3.1.1", "@mdx-js/react": "3.0.1", - "@medplum/core": "*", - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", + "@medplum/core": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", "@svgr/webpack": "8.1.0", "clsx": "2.1.0", "file-loader": "6.2.0", diff --git a/packages/examples/package.json b/packages/examples/package.json index 0c27987cb0..316c54b74d 100644 --- a/packages/examples/package.json +++ b/packages/examples/package.json @@ -19,9 +19,9 @@ }, "devDependencies": { "@jest/globals": "29.7.0", - "@medplum/core": "*", - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", + "@medplum/core": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", "jest": "29.7.0" }, "engines": { diff --git a/packages/expo-polyfills/package.json b/packages/expo-polyfills/package.json index 282f9cc9c7..6b8e17b528 100644 --- a/packages/expo-polyfills/package.json +++ b/packages/expo-polyfills/package.json @@ -47,7 +47,7 @@ "text-encoding": "0.7.0" }, "devDependencies": { - "@medplum/core": "*", + "@medplum/core": "3.0.4", "@types/base-64": "1.0.2", "@types/react": "18.2.56", "@types/text-encoding": "0.0.39", @@ -59,7 +59,7 @@ "ts-jest": "29.1.2" }, "peerDependencies": { - "@medplum/core": "*", + "@medplum/core": "3.0.4", "expo": "*", "expo-crypto": "^12.6.0", "expo-secure-store": "^12.3.1", diff --git a/packages/fhir-router/package.json b/packages/fhir-router/package.json index 89ca31fd22..767be19be0 100644 --- a/packages/fhir-router/package.json +++ b/packages/fhir-router/package.json @@ -53,9 +53,9 @@ "test": "jest" }, "dependencies": { - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhirtypes": "*", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhirtypes": "3.0.4", "dataloader": "2.2.2", "graphql": "16.8.1", "rfc6902": "5.1.1" diff --git a/packages/generator/package.json b/packages/generator/package.json index a47f3427f1..ef40366dd1 100644 --- a/packages/generator/package.json +++ b/packages/generator/package.json @@ -21,9 +21,9 @@ "test": "jest" }, "devDependencies": { - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhirtypes": "*", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhirtypes": "3.0.4", "@types/json-schema": "7.0.15", "@types/pg": "8.11.0", "@types/unzipper": "0.10.9", diff --git a/packages/graphiql/package.json b/packages/graphiql/package.json index 58f6743518..adfdeab027 100644 --- a/packages/graphiql/package.json +++ b/packages/graphiql/package.json @@ -27,9 +27,9 @@ "@graphiql/toolkit": "0.9.1", "@mantine/core": "7.5.3", "@mantine/hooks": "7.5.3", - "@medplum/core": "*", - "@medplum/fhirtypes": "*", - "@medplum/react": "*", + "@medplum/core": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", "@types/react": "18.2.56", "@types/react-dom": "18.2.19", "graphiql": "3.1.1", diff --git a/packages/health-gorilla/package.json b/packages/health-gorilla/package.json index 9d62ec3594..61d9d6027d 100644 --- a/packages/health-gorilla/package.json +++ b/packages/health-gorilla/package.json @@ -39,7 +39,7 @@ "test": "jest" }, "dependencies": { - "@medplum/core": "*", + "@medplum/core": "3.0.4", "@medplum/fhirtypes": "*" }, "devDependencies": { diff --git a/packages/mock/package.json b/packages/mock/package.json index c472cc2350..7418bfbad4 100644 --- a/packages/mock/package.json +++ b/packages/mock/package.json @@ -53,10 +53,10 @@ "test": "jest" }, "dependencies": { - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhir-router": "*", - "@medplum/fhirtypes": "*", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhir-router": "3.0.4", + "@medplum/fhirtypes": "3.0.4", "dataloader": "2.2.2", "jest-websocket-mock": "2.5.0", "rfc6902": "5.1.1" diff --git a/packages/react-hooks/package.json b/packages/react-hooks/package.json index ea4f19bd5a..7a56b7067d 100644 --- a/packages/react-hooks/package.json +++ b/packages/react-hooks/package.json @@ -57,10 +57,10 @@ "test": "jest" }, "devDependencies": { - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", "@testing-library/dom": "9.3.4", "@testing-library/jest-dom": "6.4.2", "@testing-library/react": "14.2.1", @@ -77,7 +77,7 @@ "typescript": "5.3.3" }, "peerDependencies": { - "@medplum/core": "*", + "@medplum/core": "3.0.4", "react": "^17.0.2 || ^18.0.0", "react-dom": "^17.0.2 || ^18.0.0" }, diff --git a/packages/react/package.json b/packages/react/package.json index f32a6507b8..5df90198ef 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -70,11 +70,11 @@ "@mantine/core": "7.5.3", "@mantine/hooks": "7.5.3", "@mantine/notifications": "7.5.3", - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", - "@medplum/react-hooks": "*", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", + "@medplum/react-hooks": "3.0.4", "@storybook/addon-actions": "7.6.16", "@storybook/addon-essentials": "7.6.16", "@storybook/addon-links": "7.6.16", @@ -110,7 +110,7 @@ "@mantine/core": "^7.0.0", "@mantine/hooks": "^7.0.0", "@mantine/notifications": "^7.0.0", - "@medplum/core": "*", + "@medplum/core": "3.0.4", "react": "^17.0.2 || ^18.0.0", "react-dom": "^17.0.2 || ^18.0.0", "rfc6902": "^5.0.1" diff --git a/packages/server/package.json b/packages/server/package.json index 058bb3bf5a..987ae7f9d2 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -30,9 +30,9 @@ "@aws-sdk/cloudfront-signer": "3.496.0", "@aws-sdk/lib-storage": "3.515.0", "@aws-sdk/types": "3.515.0", - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhir-router": "*", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhir-router": "3.0.4", "@opentelemetry/auto-instrumentations-node": "0.41.1", "@opentelemetry/exporter-metrics-otlp-proto": "0.48.0", "@opentelemetry/exporter-trace-otlp-proto": "0.48.0", @@ -72,7 +72,7 @@ }, "devDependencies": { "@jest/test-sequencer": "29.7.0", - "@medplum/fhirtypes": "*", + "@medplum/fhirtypes": "3.0.4", "@types/bcryptjs": "2.4.6", "@types/body-parser": "1.19.5", "@types/bytes": "3.1.4", diff --git a/scripts/prepare-release.sh b/scripts/prepare-release.sh index bc33c6868e..f65f25f050 100755 --- a/scripts/prepare-release.sh +++ b/scripts/prepare-release.sh @@ -42,6 +42,7 @@ sed -i'' -E -e "s/\"version\": \"[^\"]+\"/\"version\": \"$NEW_VERSION\"/g" packa # Set version in all examples/\*/package.json files find examples -name 'package.json' -print0 | xargs -0 sed -i'' -E -e "s/(\"@medplum\/[^\"]+\"): \"[^\"]+\"/\1: \"$NEW_VERSION\"/g" +find packages -name 'package.json' -print0 | xargs -0 sed -i'' -E -e "s/(\"@medplum\/[^\"]+\"): \"[^\"]+\"/\1: \"$NEW_VERSION\"/g" # Run `npm version $version --workspaces` npm version "$NEW_VERSION" --workspaces From 66802659d36c1adaa69c1283cba4a6e7b3452c68 Mon Sep 17 00:00:00 2001 From: Reshma Khilnani Date: Thu, 22 Feb 2024 15:57:48 -0800 Subject: [PATCH 76/81] Adding Flexpa case study content (#4014) Co-authored-by: Reshma Khilnani --- .../docs/blog/2024-02-22-flexpa-case-study.md | 51 ++++++++++++++++++ packages/docs/src/pages/case-studies.tsx | 10 ++-- packages/docs/static/img/blog/flexpa-logo.png | Bin 0 -> 3848 bytes packages/docs/static/img/blog/flexpa.png | Bin 0 -> 132764 bytes 4 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 packages/docs/blog/2024-02-22-flexpa-case-study.md create mode 100644 packages/docs/static/img/blog/flexpa-logo.png create mode 100644 packages/docs/static/img/blog/flexpa.png diff --git a/packages/docs/blog/2024-02-22-flexpa-case-study.md b/packages/docs/blog/2024-02-22-flexpa-case-study.md new file mode 100644 index 0000000000..9bd4c260e5 --- /dev/null +++ b/packages/docs/blog/2024-02-22-flexpa-case-study.md @@ -0,0 +1,51 @@ +--- +slug: flexpa-case-study +title: Flexpa - sync health history to apps +authors: + name: Joshua Kelly + title: Flexpa CTO + url: https://github.com/jdjkelly + image_url: https://github.com/jdjkelly.png +tags: [billing, fhir-datastore, self-host] +--- + +# Flexpa - sync health history to apps + +Claims data is a uniquely rich source of financial and clinical data important to many healthcare workflows. The EDI 837 Health Care Claim transaction is one of the oldest forms of electronic data exchange, stemming from being defined as a required data transmission specification by HIPAA. + +Today, we are showcasing [Flexpa](https://www.flexpa.com/) which connects applications to claims data via direct patient consent and a modern FHIR API powered by Medplum. + + + +## How does it work? +Flexpa aggregates and standardizes Patient Access APIs created by payers as required by CMS-9115-F. First, patients authenticate and consent to a data-sharing request from an application. + +Then, Flexpa extracts, transforms, and loads payer responses into a normalized FHIR dataset. Flexpa stores data in a temporary FHIR server cache during the period for which a patient has granted access. + +Finally, applications receive a patient-specific authorization response which can be used to retrieve data from a FHIR API provided by Flexpa – powered by Medplum. + +![Flexpa](/img/blog/flexpa.png) + +## What problems does Flexpa solve? + +Payer FHIR servers offer an extremely variable API experience and implementing against 200+ of them is painful. Using Medplum as a data cache for their own FHIR API allows for a uniform developer experience on top of the underlying network access. Flexpa allows developers to use claims data to deliver risk factor adjustment scoring to value-based care providers, help patients navigate care, join clinical trials, negotiate bills, and more. + +## How does Flexpa use Medplum? + +Flexpa takes advantage of several important features of Medplum’s FHIR implementation: + +- [Self-hosting](/docs/self-hosting) +- [Multi-tenant through Projects](/docs/auth/user-management-guide#background-user-model) +- [Update as Create](/docs/sdk/core.medplumclient.createresourceifnoneexist) +- Client assigned IDs +- [Batch](/docs/fhir-datastore/fhir-batch-requests) transactions +- [Medplum App](/docs/app) +- FHIR Operations such as [$validate](/docs/api/fhir/operations/validate-a-resource), [$everything](/docs/api/fhir/operations/patient-everything) and [$expunge](/docs/fhir-datastore/deleting-data#expunge-operation) + +Medplum’s open source implementation provides Flexpa with the ability to contribute back to the project when improvements or changes are required. Additionally, Medplum’s technology choices and stack align perfectly with Flexpa’s making working with Medplum easy for Flexpa’s development team. + +## Related Resources + +- [Flexpa](https://www.flexpa.com/) website +- [Flexpa Blog](https://www.flexpa.com/blog) +- [CMS FHIR](/docs/compliance/cms-fhir) compliance documentation diff --git a/packages/docs/src/pages/case-studies.tsx b/packages/docs/src/pages/case-studies.tsx index 210a7b589b..4e0a1df04a 100644 --- a/packages/docs/src/pages/case-studies.tsx +++ b/packages/docs/src/pages/case-studies.tsx @@ -134,11 +134,11 @@ export default function CaseStudiesPage(): JSX.Element { youtubeUrl="https://youtu.be/sy3YKRFyPII" /> diff --git a/packages/docs/static/img/blog/flexpa-logo.png b/packages/docs/static/img/blog/flexpa-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..05d5399f84bc9305a5cabd87ffed49e7042c2a09 GIT binary patch literal 3848 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 KMNUMnLSTX_GEs;C literal 0 HcmV?d00001 diff --git a/packages/docs/static/img/blog/flexpa.png b/packages/docs/static/img/blog/flexpa.png new file mode 100644 index 0000000000000000000000000000000000000000..afba9e593efa5edec9d51f02d9aecd9ca749bc82 GIT binary patch literal 132764 zcmZ^}1z4L)w+70tQ9)XyIJCGFQoKb%Def-C(&A2`I0>|+cyagQPS8Lpgcf&7aDo&k zID`O6kemIVbN>C`d(O@Ce9uh2nYGp|nRjN*d=c83N~HJc?-LObk*X-m>k<*&3?m}C zVSM)v;mz1_;tUZHiHw7soVJRb+!JjNH(Lj18zLg*h-4#TW4->zIi?A5uk1hFRle7D zk5%`E^1Zycr*k5rA-uWEK^hWMt>QjC+(}A;d&g8}?WpzC9x=E$|>G4}4iPTgs zTLp!Xo5pvL?WsQEM66CwiN1!b*=tbLV^Cc0O`?d;FQRjM2Sd)E5cyJTJ?kg_$h|sT zvJquNYxs@r(_gP!wL^uenpH!`seGaHoC2Yw?tDbNj%L;DH;FKVaduRPLf=CDS-Lb? z;o7%8-nnU+J3rqK4%d4M`y^%=Zi5?YVDS{XDoz(G>|eJfdDm^Wroizkjca6C^IB&7 zs9nu9#`AX=b){o^LLBXE*1JVl8!fWLccS3|3J#CrA3Dih$o8co<-Y0)6+Jz=A21P@ z+8g@vre}Ee`TWP{;iON=DAwh_@L?M~-#sL@Y<~UQp#!YR*yaBaK(%(ehu1|+g|n9% zl7~T2h^h7GuBxsD+Q6BW>sQ~XUQVaqh|y)y0}Opt`;vB}R^ik0r#CFU!kN%@R-H*= zR`-Qx9|en1IDCB;_#oWsR{q!0J7P6Hd&vN3(X$oJnXwhscb@QxrJ*aQa(~0yww0Xizu4_F0(>m_ruop^P|?D&#w=;J}*I_p8lE3($q0i}r0R*sKcagVM#C20(qzlswZzPWDSB%VDWc_6f`swlA)pC@Rq zLc)IMptCp|o))OgKGcHdGS1pRiDuIz6&$hnc zWCzR&FVh~(-*;?TxoQ^I4;y&FBu+%u{NSXs1CnQF)K{16K3Laru7YR?E5x9{bki#T>F+-N~(dmguM1s41-pqJm&QJJk7zyw!TF zm3miL2u=6wT;bvK7pys}cSGwF>mLx+9Yn2QFq|e=3$M0ZmcCPww81 ze?rC{9Pu2d-aNwbmN)I*#wQBxJ1L(G-ZIVHg?(gtD?UR`|MSs%imbnAe%ZWdnh&Wc zdC*D(`NMSn^~v3Pd8{JxEP}Gn9tCvKy<-I`KcR@}deX1VoA4?8A&(GcdTi3u^*#w} zVcpC^OT)w#jN)5WkLHtl<%w*o$MeUejVwSHJk zp?a74i^~_eFXQh4UXKf zF%lq1jDL}T8;%;2N^ec7Mw-$iAN@M|e)LWHR1tsC{tUwmXN~F&=V8@h&f(0AOB0bQ z9g}#I?%)ST|r z!mT+cH|G^p-z|30t4#|>a{21g`EngciaR5He^WBpKDg`-1BoWX3E4E=zG#+stLORE zvJZ)loMb0$Ctav136caK{Wbb?beE!2v?d*femwf}YyDRvdKTe*!MybIw7VLH8I0n7 zBfo8GXkUI6{c5O~TbAb#UN+%b@9+a_XZ$KV%KDOew zHJLD(uVptC;uq#OGRNi!jD|X{Ith0d&uwJzkEJ+d zy8Ku?tz&ZBbWC+Ha`5SAZuF_AnS(YR(8|&-3%(Y~_wUjm79~!R`y^M@DW}w8)oc}S z6}&na|CmirQGmtIdrh3nsD!bkO4CQlA@)9#fk?UEgp-}5UCxIhbmQ2>m~6r3S69Z% zf$l|xKXt#>Q}~9xDtB&7Kc0rV?z>VnNi;G0JUG)o<2}n%XR@XGAc)Q){?)l;71R4D zHa5pK2b+VJzCQLi95|grO-lFpe|bnvrQ603oHX>f+ZjtkniwGc0}#a zA-}4B34hl5^6+)X>y)thHClJyEP72)X~0(H@2TgFH8V9{?_xXlIyjbxt;TBetcV6Nh zOKsJZLWH7+Yl6L1i$wXE)~-5zx(FZ2vQD<{`FFvPq=dLS{-PkWKg|ZKEj>SC0CYiC zpD|w?C`YtJ($a*Fw%DUp=2eJw@0CTAN$Rz~XZt=me+i%dXG3-L& z5LKs8l^D&Xl{xmhtMzlzaf^#+{Y)zxjX=wVB%(06u0}6l$yA8aZe4C}1 zQ(gMGhEs;U3DWZiDymq9kA(Nag4 z=6;$|daCrn8EZ-Yaefn;a#Ng!fqIlb@Ivhn(8#gPk&ux2p!Kn{SvY`7z!@asKQ^bZ zq>3$~8OR#o9e_XE;D;aXl!B%&Bx=|pDtO(dB8HSlsSzm)#`{0OO5Y9r-eFg%t&Aq1 z)|Jo9vEGw~2Ah0lykgbGl_}NxW|Z(^_p0tW8>jZlK{KB=M7xJp^1I3W^0OW64%GP& zsogYYZ#CPamH8#p)rD-)4CG>9UK`R}^mcH(>YnN7RnJA0ZRRgnoeQWb3g)vPK09Wz zYN1y@{(ci-(qL$UtarMo9{h3BTYT!}gv+Abk{*m^H+dfd2Y4+-U%tbel07G%U}}&u z_6qkdZD20TwOE>toBchTHa{~)H8+9Sodj>ytS;{BVb> zYn|i_y}&lx=ngLEsgzVVDmKA#$s{5B>c7+V?B3YL=AwZN!~ZGHj0}Kw?267D(bdbIN@>w$R(!3&>fM#v%+Z zl5hHYN?uABR8(z4R2xv>F@nAaC8AKdzLO@a*D8vOhY0qyN`xCC5_g^C4ftHp)l z_3&wvfSW0<0LRig2sy(-P{fzyl0YGuoXezh)a+&%NEtK*ZoHhk;NL~IeVYIDm!MHc z^7Z|&P_3eAdNwdo7ctMBUv(CmmeNjmka#a zJOvH?0bBXa*u5ZR6oxj&Dz+LLM7)IhU829j9EffaYJU+Pdcs3UJ`zHShzTVH;gSD# z<9}LjhJCyFKlK~Ne|MDClT%S4lzLVkHa4yw?A<)a^jcjAtfn0FjXjMu)FrIkTm;@( zyII-@_`1CRn}kTpSAtM=vGIKO#Mi~y^@D`3H0!^5ND%6OzXq^A`BxWDCuvq=4ecj# zZXPyI!~~uTJZA;ofAZvsl!vvggs!~ef0GmbNweB}dcKzc0DOFW1bl=A+&t_6f-hgb z1Uwf42nq2MdhmbnbM<`Z%kTPu?Z24(4!ydT%Y{S_noDim!~u<>)(R@ z{rj(R+W0#BBgyr{f2&1M5b$>kKv3X0;NQFnq*8ysmC$zZwQ+tU@8Ci(4?zY{?D=!4 zfA#-gE&oXT4@%>IC|^Do{!hyPX!-wA8ho(vkaKe(i1Y;hT6E)dih1YNZ$50I!HmC&3m197*uOn4(GK~(T<$OKZyNQW{1 z5W;xO0hvSSmxFXlu}5?56>vstVXyH2h3>#LzpZ zT!y`ypEvxDsV_oVgw4cotJyHpx*L?=T$ZGhR$N2fa;Y6{T)-kwvkrMlAV<`dx|b zPgx*13!3j|A(b0+Y(f1o-c?*o-FiDDjg#UYAfq2JJ(BIE9a2*h4!><0xFN*G{dRj;zwc_Xm3 z<=ePXs3r5Jp3s5T)jCwm5G`_NR<+uDPYry6b8a((%iFmiVJ^h?g_6FxcG8gZmQ3ux zz$912%pnz1^s5ZDN(Bo@v8I z1u4cV?n+<(bZZib>ycQ4G85KRy@~dw+?a*9mek zMg2;CS*y`}-A$PWzLHpdaqtWS4|U!FZl8MY;ZAU19!{NILV2;r3q3u+pJL20AmrpF z76zS$1iabP+bi>Q^jiqK2tVz2VnI+>tv9?}%x##xZp%e2=wk3a;dBcGe+6YxT#UQt;Ve*3Z34 zOapWc0*w$5693Br9tQSZBIdsqd;j)n0oDm ze&9EY_E>W0s8JPPwn;7wS85ZfCULBzH>(15Er71zDYS42Doag}XLS|p$2Y5NfVQ&% zyw`PdVg`%|jQ@5oz8d0p>F!^rC*vqb4e$aLRsh)(19QQQ@C#J*5;%Ef$88tM=zVgH41*?#5T_m+ex8%^|{0`yIcppmE#8%tcxy z%8Knpn|KVIouzTRhj?jc0_OE&3W_`~atSsg%)q=hDMPY03NM*glau%KrBJGzGL2?n z9RQ7+mTos&izNK)3pu7;81AiTbQQMs0%WnV(+G7JLTM@yo1bt)B-cD4X~7#6qj#3J zdw0kt@n8~^W;e@bMatw}88|jE7A>l|0r8gxExlnvkz=KK z?3G_oTBu%?Tih&}LK-3I+qZtdC{7@s#T=Eas!8NS0{=WMnLy5_jb9uCi?`h*1pXe- zsuktI^0#rOPESmz<>@ILSlC{24uS0Lin?lbe`Z<9+Z_pXCObA-nqjX%jciT9B*zyM zFMUDq{nbYxs&r_;0&1XVcGXG(KdKMhZtiJ3QkY$UC0b8J3YIIpR zg-<{?{UH6_708tU6MVS~I~2Rv>$&syK7k4xaE(Yb!{zv=Z|5TlhLzwPG#Gyf26g=G zWg%r`>9%K2Oc|`CX9!eBmk;r$PqY3F%t>8sXKqcM#<=5FtU( zTp}mBk2){&+CJ3|`jIbgo|%pM-~%DUh%x~odOtIDd-GwXJfvWaqSbY{fKSfmPC|f0 zvRTWP&S}^A8agk3oQd-C;Ol<%RxCro0dl%_%w2LM(8$_L=6q{)FVf9ze6v2*=nNG5 z!xaz2ZOcJ?7l7HooTei$=+2Hs?{rDQDyEElmgL9@U-rI}{I>K!+!E^0DZ6ZLDi42* zg98PspQi6Bn2WbfK9xTD)yrkprb*8!mSo=D;66^#{afht{q1YjXYOtF_#y2V;}Jim z(#!Yo3H$m-1By7uX?+i!{+&~OaKBF|8Rk=L*(uvv$E%i^S`C9Pnh>ov*J&JLXBD5e zZZ|bmWjeUHWPHLAEc8XUP|jRR%LxML49zEE4<_h54W6UI$TFF`nWBqV)rgbMidyXV zQegl@1&_oE1{IJ44PXGBNf74lO|jLUO-LzlP0fRV^Y0FtBJLGq4e;s3v*8)wpZZc3 zdCakHc-#vH0NM(33g5?+==G-LpZj#DHXUJFu3Mlc`@Lc6aQQa4Z$1H^`grJbQ_BQc z@>kBr$~=qtSP+yfsz>f>Q#8{rEOMbh0@em1NxB6`7SF-}*$h(cqi!st$O%GxK!lEp zg@H?{yIl;9oh9dc+?g3uz*aLM9RM1G>Kj{H@I90T@j|YOJ3Pyc*G(G;;m!h)!pmFgi+b0EZ`)U6bS;z(9?=g_Um+C@qZIFIA z6~~-!k=k;yjcbNA$wCGCcp$6CFvR#hMVHyv-I+0f*sxtkP-D!+sm{^i`rdZ4eT8yU zv4tj6(CS4&f#6o!v7j<1NIMe8Uv(^9NLWw_enbRMwIq;6D*)w-03iCrGd`yN31+)h zuXQ!ScU<3N<-D|tqTnM|i-sOc#8OyoCRJ2Df5N^gOjJScQX2~SrFT>ZmuU5us;j~q z>vY9txSs_b?<{nF81wCYepe4ka(7_;jjEO#Wx&ogpQ;MtbizJt6w5W2rzpn-% z*C}x{SPFA46rQNYB50U4DVbuk=O8d<;>kAnN}Nn-WUQ=|R-0L%l#NVM%w`4o(VCvi zSjGWzVm++I`5RS#SC}|ES_07t--u1@7nhK@>cPH>&3|Fg8w*w`ejz{nJZRGYu)9DY zV4F9Ay#EjSsMjW!RO$rB4Z&c;4j}!Hfg!LOzN}+@5Wz5nmDi7H(g}2U`Jzy~%P8o0 zyo8HxVtUL5Tz8}oKC&)>af73&87AW^W(M+Z&BfgpdQS!+@Y+w`AV~T~D*`Eg*_w%ir|WNZ>9tJwxT@WsAhIbO%pj3QO4 zuN>2#QzV)9ja|RS!@{gCxQgzOd4O-ACmdJ7Blo$4)!lKc^YJX~Kgpr;n zcZa^0)dt|8b2|ROIe?b|8>~x}Si)%!F9cXDgdX_87wyKmQghBYq;;Td(p%c|h@)d5 z=y0tUNn4Mluj}+7WCarJG)rd(E}`3X=!u;UD5QgutXytK7caXHDdPv_#GpCa^A}^c z>sZ3dJa+=un~INlB<=TD7)Ddcec`UE!_#pZ!w2=BHmzv43t*Vd&b&YZVg|NVL)p%* zoYL0VL!=ef*|4kGICO=iNK-*2hcSE4dUETrTp92@@f5+9Dk%(4ZY(mW0CdBV0W-e%tm~MMV#kfBMP#Xj7S*5wNv&G`B}m za|y4#fIkLyr80Y(sV4-mEE;-Yvtt0W_Ud&8paeO*XRFzz-+~FyU>5;}^pkzJUcu-b zD0v*&>#g;omsLRi2?3W+)0;5x!>5_Jc{kL@jAIw@+DpNs{k>5-!}Hksw5btG;cU3B0l7x)Jcca)x^ zg6iP`^aX#fp{Ljcc&TLxKZM{X>%%s9YJ~ucqg-1%@cZ;p3*r1lb)Oxb)B-%_`L6Y_ zNDoiHEeGDcIOkGcoR9^vhCKz7nn*N=yUbbVUTi*BR}$g|P8x=3V|8A6F~C13kxlFn z((USP1ZBv<4#Z`4ssXlBU*#44Mrx~>Rr+8Wz2=lf?m6?+`*y{g8z>0K%wp#vxd1y8 z&%W9QUiuy>jLozi%xU`9>6j8Hf`1Bz*u9 zvLA*bS3P%L5Cop<(a~5%Cp&+O?AJTlxm-Y&3S^eeS;R^x93hx#a=~(Kxp}8?P}B@> zF>vWHbU+_&v)NSUEJZGTWV6a6Bm+c6PQe|K{$K(~6%jV5Tk2;sr&kch@mf1@%{;pYMd1y&*#o4}E$Sc*}-?bR49lz%&dCNRIT z+F8CNK}~C72QP8l!k<}YZJ~uh;a+Q5rB;cW-4)Va5oTr=t4M$H6?=4e3wSiD&9p)-mp!-PP7^#^2BZhB_8{;uT(CHc$IxT3R@ zaVJBcdA13U0>+^v^Sd9DW}TcYreue_X$bjRs7+@9>^=dke=g*rHm`g=F+!Dyk;I z`SclFgQ?*YOw?CSlA?Dj0K#G+Q%aPd8i(Uw0)SPZx$g~|*7U_zW^hHdCYt4zIh3rHpNcq{w|8WDxIsuHi8&WL(M zc4wn;7KwgXpV-0>_1EZk%j`dQ<&Ylwl@9wF6^Ctr@6X<6mQ1BOY+m-&4CX}U7PoV@ z;MQW>M~q-Yq)`JBx^9mR(HzjKcYM|~uC;?+=xER1G_QA}RRxM<>g!!2hswjNbUp2L zsREFu6}u-UId2-8OWRla;KFJH=_Zr;{jWEnI*l&TWFgraISe$n`|C(GN))UojCS&C z`C6(dcji24odu@!>PJ0IGZ)vNe5e`#&M62~*6{Q`d6#-=Y(lRf`B+K>UOEGxtodRP z6^u>bdN2v6N{!CAtcYlvdgh;wJh3bMFg{f&bPN^d#$cU%e;YoXjN5HDYGq~poiTS;{3h&?#rQ5cjmdv2mFORO6WZc`RzZIlR-Yblb+eq>`b=O`OuDO-9z znKQPvF0gy&T@0 z^fzN;`tl4-qXLK1T%4tVtlXo%Vnbf{88L_FeBlgl*#iI2oE0Do4IGvF({oNUOf(>Ey_%Z7<)n$hi8ee=mmr?{$LFup@{Ykon_WeFkMOB@QyF2jtH$Q$m zt-+T_rhlp9HYfI+YjDy7{7r++>2q!}8QUJvX0+RUO6<&{>BR2LC&Tv(W16UvnV6y3 zA$L)92`>EG(nuJOxQlb#Te*dU-eb^3@?CUO8Px;l9|=1>0@CX~t1VcQ9eyhzg;mE)(u#ww5p~G?s=A(UviY%R}@?+Z|WFXK1xHMZGJTFSJ2b$ z;fiWaMmIJUwF?PM>cR(2qy~d%5?A;h2dX1@5)eM9)Pv9Wt_q3uyvBgvS$S4}>5N%A zn*+{yE$-V!s^_4hS@|bhnCop{Hfs<|nj?QRfkM}EmW;xTdWw~ShrHjEVgQ=qWPH^m_|}dDS%@8*yy>ATmGdo^)-S=$7j`0G5|&?AWfB% zuanEv)HSvDppHA^L_eIw7_nGwLiOTf$i4LJAAs)(pgxE$Q zU_=2@v}|iR{uGRFN&lu-^K7ZeMQ4q7oGEHzyRW4Ks9h{)KEz?+`Maq7>rH$vKm_$H z^;E(kv`6r2%jnm@*CE5JR<>sYhP<%8tk$YQ8WW9&oP35wyr!s9^M{Ti!a6~_Z~Ry+ zNvJxDP?FPw?TSgJ=FnqP>tV%?Pqg3XP8{@W?Q)h4a5b(WwMW?xj*d$ApAlik4mF^t z2j+Y>;Js!O&}@qDkb--8*5|ziFa6e}^GJXs;`t~`z2H;-?&_1t$w4^if~x&^={HOU zVSr*SdaU$8ZA#~(ABLp;C}mnYmImmzQ6*oe-Id{LO1U|1{L&-D26`+ukk$Od-tfzx z8pE=VYy)V@JLa02?+4f(k=pw?e;%dFvkkT?K>mC{Z0k)_@=;gJwJ|tzbgE)@hG_Qv zu6A^v?*1hXysgx2 zfNOO6!dOylJ5NY0(YcB#5ch{)A4}Lv_IrI+=+qG&P+t{qN$;aJH4^%bx=4P!IR8q0 zq6*MT2^$V{<;oJyDE-XDvF1VVE;? zD&cCPq|`6j{T)59*qF5>mMqpbt4bQC(OG({`#~{>q2qUf+QvEn((XRxmw~{XkZ8c3 zxHIDOTRPJwP~W?K4kIu1uPectFkzN%_=y$C)3*&LNN+ct#}5csWJjGmz(x*j5kcz2 zh)>L3vm(QlW<7dyb2fuk*2k`)v@}I9tuTkj)Mck>q4eVgzft`r7NT$jgV>z6b8PX> zkRFra;E1RbgWUW|t+BV)tf`DkXJu4*dIbxHKQ{&@8SmTODnEHtX(t+8{`6AaG=*0* z*|n*$WiQs*6HHR^#CVt9Q$1E<5&PEQNsS3xzxsJ`yw{M?vKQFzV+sOG!8NFE29E8C z0-YzVMEUkon^Q_Qnt<4BNBhS%KP59yXi;h+?@4H!@e_8(uj+`=mK0d}J;5gWg{cn( z+B36(U9Vo7-8U~kMEBa$R2->`Dy>|k-61)NKKP;$7NQ4!B3wP2aLvcAR88z1S*fht771L#)FR_gh{%Z!;wXiwcy z9t186-vgqBX<>jgx?jHrm&EMRDSoqL%?l){q=_+G zl)?OEqt25)FhCqAH=vG;6MBpEys)&|=Px3RN{A)J zC(F_5em;&zH#$uhUCy>|{ut56^W7+MHLLFq@1U)c z!#eLjwPu?kZOU_e$9+(}xH{;RcW0kLZWLuYADKF} zZNb=`x2wbIE|joDFXUHh5aGn*c-fdmVGPC}m}zT;0$XNM|IYIT6&aj!f30rp%QyZ` zW&(<~`2l^GSF5aof9D_$guIHjL^1@gw%?iebc4+P{6K1RvqZn`J|ouA%0ayWKvhXE zjdk>lyH_xxw`uTVC(R!6W~jiJlE#0dKEk|~=dqKIx;niMDqFC$l#`YnU6GdUUyvQq zljqLkts=tNn8#L<-{WvpdJyU;DYDvd&i`lL)O+_V=mLjmtaF|2!&Hw=)v;AHkr>=> ze7^N8RTm)b2Qt6T&N+#KHMW#H+T2XpoDJuM+{PWo0<~Nw29^NsQ--()#3!&#|O=w6<=;Z9i7)!p(M2{Z2^caA}H09R{ z@;G?!eHg?#hDqm}K0Du^ap{VF-o%jlwI?#Z*!7%>IAr`#QsGVGDsO%zm#N5er`YTj zxff}HhW5=NDTJ723dmPhyF`Zx_3H<9OMqm{(r!K)xj|xA9%5t0tbrWIAbTbW-RE z(s4%Tmu*7ho!YG_5VAW>Ro2X*7o#A*=Ltz;$)zSa2C8bU`D0r=GaGBJFVaR$ejJU} za5TCASUj?jV%2x}kV}~m7D-80`p5|EfLLe+IGwelhh{I|*#1X7HB(|Tsbn3$i`1;< zOgMe$Y?7rbLy+XpUsjIpnZYa5cg%Jp-yJ=Hg+TwxSX~))y}QyxmwS?TE@F6f!9^`)gB} zArv?urr=&Zx}FNkNr4)7_YGdTvDyBd*-TWQ>eeh_h`O1r7%S%Q<-WqG{g<|5pMENn z%Zat!p5k3-qXU&SApPQ%*Np_311EX%h0v#S1DSza#&vMQ$v9J^QYpA`sCKSuO1bFx z*NjPx_RyYYNR?5f{dkr-y(B8aU*YK6loIgc*Ag;ej07Qlk|+lkmPCPV-_#z7c%jA% zfe6D~%}@)TH{*Qc?{SEXu49sI!e`@V@j26FevVQ8<`v{_eC%wU{kZ18_>S^+4-^^v zI1T^k)HtmBrR?m*vpI21M+43wMP?79G=7!6bo-{RN)1d&glkLHG;p*B*!H{6=`HR3lgHXft-}eppocV zuwOg%l7tAd=Qnb&#CaQbo^B!~=W!lg*_9jnrex>G#L=_w)|ze7DX7H@o2MQq`dme; z!n5v|k9pZ}J%0v$*o(z?6Uk@$10@>nEzVCvL|ppVb!588H~e}na(bt{`uv4rS7-FOSN5rA6a}Q3Yn*3U6^FNth?!xvei4xijnbtT}_9NFT5QMNz@oEW?%5CRqs^GbdP-+wC+tqsG%Q2~P zS~~uSxSzjiK4Yp1^81eKJ24RBh$+lj%}#2i*>j3bOYOs^ruF4Mb){Fy7}>2GpI$wA zc=zw(ZOh%~Zith<>9)2FLj+oAvZz2zA9su_VUzB!(P2uDJmXk|%lmL%0ZF>hfjHl{ ztnlsMK=tJ_YPZ<=CP>@p6dLDA_&Tc?y6>VNrhC!sn7%f?zFKiwAqEePY+^v%yR!KF zYNSp1ev|WThHLvf_m;CM{CHNx%g2nd$q5Sy&A_zWwbs2UB;wRgWR%ESGSEkPaU&>5 znXE)iv+v*}Q+^I-&iaz&HYA`jDU?FC)bK9@CHIAvMhCtK%PTucce0ovZzoNXT=YCw zQB>}66&o=Tu;4#5{BK1G#V+Fwt=6rMlOPbz5tkcu?UB)UkJXRvC0!GvnA?J7kV#xt z^y?d?U0QtMhrh$f`C%NJ!UY#0T9K(u?Jv6?-sbp{zwL>HDdyyq`M?^P|sQ+cqf5iQ33^f6FvrZ6{2vH-mT;H>(Dn_w=RRP1C!!HVpibBed49JY2 zWcfE^zdV%8717Pr_AXm{I6w^FraeCD_UOF$uyGqruE~`PuA@Q5 zsPqfni&|3A9Lmm(d9nL`{Or^JkAUfiPkv>J3$|aXu*4>r^v^ECY-P1_RF3zWqA-Mg`1O_k+`7$B!MtKb*iQa+uDf}*}>Dh)=^ ze4{$KiuLVVGQJ`^=lB0FU(cA|+C2+?Ia{Ak^-SEO#qN8i7FpwcW#$rVEtULDo=v&1 z7SHacwYX4<*PyT_ubnAdvzO2Dooc^3cx%0bd~0<`DE?1xGTb%pm}a*~>Ee+cQkglu zuwzpuOQvEq$}ee2c%2}>?eCcsmqg3UleFhddnL!^SV7kP?uV4}jm+S|;-VW7|2Na# zhku3(+25sf=@hMXy;m9;?i)>lmbtDBiC@lZTM*|WrUep?UHYGnXeWK(2tJHcu~5&b zd`?_)-r8A8{C^si=T?KYGIPp@nu~qfb=QG$FtgF-`-aP~QhPmPL477iXi?s0tH^_o znkRzau}}Dhaw#%ryNv$v%0DLknWOY^qJkZ?_Kw{fTZ`Zf(yJ~8Yfe0?i<#3NtsvkQ zs6Ls>U+(A|I4J_A7IF@Ivd(G$e=}0}cnivIQ9qX&oYJeBTyPXayY6wwz4m@Z7Ng_b zz>`04J=d$cPDGey+M}t$6mqY>UeqYC^ZpyE{^7d&?p9y(z<^m`lYMmx&1j9smJw+g zpBviiv(AmXlS<&WRz*M|9aitLDv83n@dQ_}MqR`bq^=Uw9_TzzNlEE&b%C|=Vg?`n zdb|tlCfpVM6hjniWFkIaG(Fk#YJf!kdhm}0{*9D(|H=)wVQPz0?9VsCxo)n)zuW?XRzgD0JUzF-Udj5M5`^9k|n@i_lPOqLHV@lzN<&F@C$#Mfjne#uv>pZ@&6rJ;QEK$ezX-Ks#N{!edB4HK3J183i|fQ zQZs1rOkNoIeUE^r(OQcVrxvre>VeNDc+eZe3>UjYuN;>g$+73S>Gm9p zYut2+k_>JPwGsu;w9ED~#I1v_)*DlsTeAi3g#@$Ne_5Q*+L{xQfxEGBTGZTGyT?b=E3Q0Z5mo zB9`kT7OhzD8ryhc&Ix;g3|d$wajNpo__jR9|3djw&0ESOAM&*yZ#sv>Z#$+#NSmV=B)$y=n3E2X-TK+y~t2(@JxoXZnJBM~zg(+v{Tbnp_bjbO6S=T3GW_v3l;TnUnQEplmT3uQI zxUEaGCBZ7_w=8k=*YvBOv@&6g9^vWVN?Z!}K$E#G7Fm16pmB_=jes}w2? zTy_8%m}_hPWS%H6N95_5M+* zzPuv|MrHBHbYzRw+UF+C>n*EGt;WbO?R3S;^z?|G9Z~WL=JPSZQb#zxej0tG#Q4q~ z`@&Xp0nxD$JD#fZ>F9LN<|t}UcA1t)&6=w8`lDN$bet7{4s#mjwKy}43Wr5bhUYzl zN3}y0B$dOx^0*e!ckd5Z&@q>izzyf2po`w1hGfN_ zPRA9VlB|Tl%juv6+>5jM593u$iy47en?b;~%RRRyrjInMaq<>klLn{Uys-_6_x^^+ zw;=hCA20_594fIHOyL;Go4+NA9ugiO1YI3__H(2b2JCFnC=P=BM@M@ZUVNWKOMQY^S3^my`bwTW=W_W&d>z10pF6(p@6m z9ny_}bhmVubPnCk04k-@-QC^N3_WxU4EYXv{l7f-`<3Hh&iL(IYwdj+DXq9YM0ebx zW^-L|4Q7KLEL)I0r{(AiMWH^^aCwFCw2rY|BlUT+%?y1KF8;@jkoPUqr!&XDBkbLY{rc2GUKX`)t~*b zNAw{i7LZ*Ta0>|dYz9Y;vJ7rJ^{cRdWjKD#`h3BfhD)8#T2GkqbpVH@!v9anjLPGz z3N4#OQn&k6{L^0iQ8A8jsoW%I9z@Y+k(gI8^9|0Sb#ad6^0w&Hmgtc+Ns`m1FX(>F zN{-6!1TBo6&Cxgj>X9u+#kA|!%%BVA{kjUjve=wR{>C77-juG0p>vGqGYsPXfV<)V zn=df{{;|`C@zVks;RDs{87v2+5bAq!f|}Lb#60P{Ao5#SL4<|&+pVPuo^N?Ar|p8y zJwzlo;o>Vow0-Q-l;1WMcEcer@r0OOt|!{)Ve=}Sx0SMOhuP6O-q!7T*YT;}$$NQu z?e}Kn;}+QsU$&m}k-BWV2xLfQseQovl~5;oz#hlVCY`|Y*1Z4m{InFlfw>39&O93L zim{m~?E39ie8VO2eOJZ9a796Uz{3`g<3s!Dy~uJvE;IxY=V`xW+~r1mRzu(u==nb2 zxC&01)n{=88_X8Rd5s^(Zs)BQYxdC6ao?f@pr37&dxgNHzgI*MNqJ9s{r>rn(X+*+ zTAkEl2jQy)_VgX0d98){vmuh4?NekGFY&uF&>gd2aqDHysofTsAmDB$FW3J{CVy-CV6TvZE1W^qmZ2 z;(fEFF`k+FWW^M-GL~brLbW7n0W1vNU`il1E+c?erOe<&&|b8?xt%kAUD9_W3_s4d z5$1r3&giK1`4RMNPftfj{ZOAEsJpU3^I=;V3z>Mql?p7j3x6}6-abpmr%wT~H(Mm@ zW2AqD?u6p8vbw3Nt{g{xHwid$p3!q?>eD1{3QVGL?QT2oCsyiEwuzp<{o6VkgFXbh zG7h1JxvpI7jM$5N?xbohZ6^&)bv!P2WIn_Ro>aOXfF4&tv?9g1Uw)k*Ry;%Uo~z_; zU(z1E!z8@7UIZSpooR=k#}wbumk_Mcdj9r@v)0F}Hgk^XM6H<2l3nw8^@JrsQC==6`=2 z`6PED7nsq=hP8dy$DNe@?P@|Yk+@uOC6nt?5^adL@%+;p(fp6WJ~K+Zb=toi-#y-T zxAzQ>N*EB}uyfK~Q#$ND4dp$^-Cd-20H1CGp6B}qB-0}~lU^|~AXp@q_4iKiK@`3H zpm41$0@?0$9`ud$OUy=Emv`yo^VU5i2Hl3QPI^s68>Yx=%TAIHcUO%IB|c_7W)>tk zPg)WueVyJ#z~LoW;9V#uA)a*yR%Ud1>}W&2k>63ac=sr=Uq_>|GU zkF@tWOTq2NYaG~NBnih^$Jt{yw|A4IJ|;Zz;m0vv$%&6E31W-p+EF8nyRWM}&~*1k zi8?$4VhnJBapS1RCC-`>0d{ohtv=c(uW7ldsnuaU>ys}C4}2Gjeb?6i6r!rk6CdEz zC6f5~?^lL)PEg>*VW3#u+irC4YJobVTlm~fWOgM704<_^VW_V>Z}=8nr?$JhV7peu zu|L$uv+m7>2?~ROu5qM;gWfS|(KI;tEJt>FbPTG0d#8+_NR=oO-oI;dEf@n!(|(+7 z!{g}F4V4Nwsh6<;Js}2s9Xq?e3#}*kM9n1z7=$SgGV!{>qn;$ZTbgH2*HQ`CBWak#pay4=YbUyHUht)wD0Y zAUyKBFNd>Lb;LyR4hUr1mu@eG9VWbdwG8z=d)kOpiF&N6-x=(bbSCR`@3#t+x`*m_ zJ?}9rD5W)}n-WLfBi^GYdnZwxS-!?3O|m$aX}yih>$&YV&%x8(eciec*BdCu5lJ@h zb7%Zq=;Ru-jbtElBy%-3W`#|D?SAA3Yjtg%#H^XS@Kz|O7lpWLKI6->um*Tx-D}-z zZbsXVv7Ud+s$*kHDo%;*>-d4NSG5rbk0yvf@Lau-GL($*Gd<8rT#%ukl`-}T`7MxM zd(}eu4>W9+)x)r*U>^TG-vn%-*e&9Fk-z3`XxxBB0NO^xR>ul<=Uc5*5CPd%~>Be*TKrDVjl^F z;_VJ3r|@|Ysqv#cj{M}&;*&8v>ki=j{lbP&Jf-mB=QqSl(E6ooqzd;v3=cA+6M`|Kaa+k6C#3>LoXzsbySs@i6`G1?iB z(b@UVmPowSt8w?$*zl9EfbD_uR>c1#a2s z9@`_^Wt+RgZSj&V{fEyI<9&N8Eq0{lg_9K65IXJ}6qc0bhr>9V&(6AffdvFWZ}>P3 zta`kKgj%)@Efet}?xbSdjL*}j10vV#ddMaka7Sm~7Am)NhePk&D|~Wj(HC-|T?UVH z8VHcYp`{)2&RsF&NBH9r=8q}5K6y-YW=Xx&(@L&&SGja$lnT8C?#de-wwfpEop z+(&hMj}?lqPCyRBqz&D9+zWga_ky`BR;!2P*{?3E?gb=Y~8K! zS}5se$o))xP%@jk4s-$D@(`%5&n+u+Kr(9qJ~C@ngt#4P1iFY9)#>V*Ltg&6VIiR{ zM!--2iYJ9lWw={$$-ww{6dAoBI?>Ym-fray8n_kyZ&fMdVVT3od>=5-CwL_6dpdK(uqDtCW)ov!KGzp^gSw0fM$hk zzn9{tdEO)`>d8JI~zl}q0RuW6|D6>b?|hOh)4q!W8xo&A}V zz}94DW-k5wS>}P40?CKPJIK84a$hnk-}Cf!nWH>Eyp2F+OnwH`IH6H4n$bqhiT4di z0_8a8$;vezXo7pAhbT%Mrnm0;8>e8+P~6L9_FPC!F9xD#wIS2pfg44T(_>E`L#Fc1Dw+P7`^ygA-N*bmweQ;?av>bYw^zFs8)brO;T5+|rjI{r;H zYxP~XV~4hCiZFp{=jR^@US|;$jGD8HR*x$dgAOX^i>%r^!D3)$K5)LF^ zi}6Z@x!ad8mZOMudix0E9u_OQa-A=`KVXXrO9ehQ_$@aqD#7?CkMP}Ak{^-`cuRt* z9puk5m|Y&6PqtczoRzx!{C{L)_KF$BWHvG0GnX{%se$vGiu*r^(v;sCzm5-$2i2m} z0&dr*46yy2=tVYR0^;1?k>k-0jYdZ1T_UP1p6@Z046Ojo9ypth1__em&KKA097QdU zU*#f_wJRCihnlzBJ2HlC&P{Ji23d#M3?2)EdN9Xekjw^E zk5tuN(w!YlvyFGTwUNS$r%I0wAN4Qtm{wO#qhLc5j ziuE{h@8`wnu1bepj?L93hP6TNsEgqg&qdYIIjj}nh`99)z&w&B zk54vq0Wl)>HC$!jKz)eHN=&xEa#~e&uc3|0|VJF{P@+P68xtw zxfaiJP1ys|{D@dMyg^>2DfFO)w?HFr0rb{|G+?@=P1S>~rW{IY^iIwM0_pt%Qxv!NZ=lOV{YL(}Dh(0ez z=ky;4kEeyd#(K{LfNIqygvV z-5e*~k-{!XB1e_;Id%py=O1!xY2B5AKbNfcT%JoU54>M>oJJpoB`?*9+8sRMWwXg33Z914gUI%dl%($8u%in04`qC|4VdDKFxUzM#2qDc zT`qr9(Gt3PR~weee*K;GY(sqjJn;3FP;lYrAOhJyB?zNZb;_Sc$lB-miZ!zyU`H|4j3uXO1WMyQlusTaQp0ia&g{;2XB`E8xBz~JO%-@p!J4*LlqaBGzL4-B@uv%e;~n`i{>LTb0YH7(99*qP;E(2aLY%{02{kSc=Zjh>o?x$- zz%KO#Lb+kLgg>z%g}(iwpJ3uax?l4LNNo(vSHd>Rc|1+6FK-c8+14EpBMIZtoOr`q zs#ae2;VV4!BF6V(3c59J$zKevYLW?lJG;j_VM%85-{s7!lELs&4AYSCz{XbA#Puol3OZ}&z@!0|o34B%V+Vg=#ks<|NN19IESwC}Tv z=6D+#^0#i|@tn^8qD;YPg5kxHJeM%|B-iMIdONIh#0wzeKn_j9m{8C86cumrwP_m4 zs4oaw3)6k4VK~@29Yrci)(+ChBNIyE!j_0q*}c26CFQhfHy?=)y}z>av4EX$yBUfl zH2Ob$RAoA4Cy_~Veqz8mJ@ds@VUwwI(gfT5yf67u`j!`|s8~gGoG=cx)w^FT0>if0 z;v<*}-}iE4{{Q)070S+vHwtlS_-L-1^UM>871oInYbhObLg$B))p3|jkGqprF)sR- z>sF8YE@E#9{`lor#{3d}uh6^oL;!AQr>gH`@J|; zVO5gooF5q=rXCzCVZ9CI5|Cu>)&t+v!iT$>Z>^jda2VE%F?#xK@P4^>**L{Hm&sg| z{<1vNE|eSbfA5`+y7Nw7r(h(~WW497YFb(evhLXCU6 zPG9Sy*ca`WzA}fZX@VkdX4HJPl7zTX`nS*QETR#g?gX;rhy5o1-(UD^iNYab$?gn! z>NRp_gY*46&PfblYC1dLTtLjV-9!r2tf+(hGX7>%?<6($pZ}j9xaNX0H+0w3P7=(8 zIaYXx5aZ9?uI($KPI+VCL#b4@zWnZI0_J=6(Wb(M4}TK?5d9Q=%SBDm^Fy!Ox#7G!3c`Yf8cLj0{ZGi>xA&B9X(s0jnao;Hek4vj3f8;w z52pU>BH-a#!UD$|JbpR0qgZ2it$fUmD;@Xzd87$4X?>8hsJ#(eM!xTsUe}>DZ1b-B&=l0X(!Xw_64KOJrj(*7Fd;Y-RR3SpyM@j8YWpnOy z2X?gU9hIzvNBc_z6k|<7(lzj>#g=xG4SvSWGl2|G1{ZbGFDvbctM)%SMz^HZwyyrz z`%+8H(w}v*;WTi~t&CKUOCIkfrPkiIayc+}{}3~iZYSX94XSG&YG(B)(ByfpcJ87$NK^}m>)PWf?s-oxP!_|(o4yvQ{9VDesN z@bRJRux9`?2nBG{CW${bpa!$cy|Gr6RA6lX@0k0kJNY%i%g3_~V3nr^(yZn0h3%_F z=bIlg7p&@j%QV?>`AskM6k9YA?^``!dg}fX@mM-DVf!)ACRp{my$B`1>W*mX%0BOi ztEsYLF#y?xe#g>L?r4d0@YVp{0y`M-30+g}DaLaAi2p&S^x>Nb)uG#B9(Q7w!oLIs z7qn0D^|CU545H^6>1-J8#z(FoZD2vee-&`~aagTjRbPt(6%o>;#@O7Z~wTB0N<8ZbuXHQ__}GZK}{JbYED&@t3&IaVyU0tM-_m z&}n`v;ZJP>bP}&|>Ol#u8=fc4OEmhUT6y`Zr@y<=9+v=EsgI>GWi($#(U`pR^DT+B zPjx{3>MDYbY$sOp|otVhGhTPXcT7`Wn`Ya;KSVp%v|IU2%nx z7taV}dq}Nq`^{E5$$h#*!5=thpVxd9r^_uh!{PCBvm$kiB;{CCIX{ittFt^@t@6ho zzBZKN1^N3()2`_90kLLZS4O9GZ_eQkcaX7ho%MormA(|IRD%({8-oru=Y8z26Z#Kcjm!l zB8a5>qiUOL0xM-~hG3k*$DCNZ5QKk<6Gdq;u)5|exi`Fyi#B=8diC2h36cq^-9SEv zt(rQTI6YaihN3dP21`OYQxW{rg*4Gg7Oc;<$&A!NwVR{NhaZ4bBVqj`QHh!{bPC(G zskL}=9*Mh$lY<&ku~^TvyY+~y$^LJLaZCrwN2_O-sgKni%3S&mtyaH_q$ZBGI`53! zn4ECIv1a@-glOSsjy-idYI$;%OqYt|;I&mQ@wUkA9i30ESjw+c8yj{GXrkE{LRXl+ zh(AvB(<#OohBhRBXJ%xi?%HSDGOk^^IKHvdsYUa+(Bn8(t1>Moao+AogP^aU2K&@6ZUN8Vp=eL*X2Til)uN;7^IuYC?) zYv#v*c(4fe1GV>Sdd&|E5c^ZVR85E*j`q|cKfAms{Q3#rpk?-=K?qB8zxNDt+0L56 z_J;;>nC6;*9V3cF%w~|}FV{H(g~GXOwQyL$L)&*;a^1LC_9eU`eK)wIwI5Zx@{Wd2 zHAmXJqyC`lNikI>Vvgqsn&5;b(WDJX{N(gzGgQ7V&E0zc0EFMg>uTcX2l6@^>aqp+ ze?#51FkI{4wU^80u^TK2Tb?-|AWh!2B4tD%;3l`m>ht_B>&L=aovRJ27uNn6;ttqI z*u<@^SM83_xz}b;^urvK9j@h%<()6&v6bTePa)Geiv%@sHLt$;a^?9oH!Fd|qnepW zdXhYzouND7o7Lnr7K1F!QteD0hn3GI(SjE?8&P;8Gf@@_)6H`uGfQ)fGf)l4rTk`5 zEjN9f_5JT@XA!`%e1g)g!d*Tu&}CCo{8LIQw)kaEXn68Wxs`q!0$Z^`Z4H6E^5 z?+f(tD>pT0_*uD`iRrn)OaQBnlIdKcroy4iLm5P=AtA@_}l6quZDlQdIh1prhvO6f~15u~DCD0WR$^AbGY)0hIY{-zL4j7P;8zeZo zkU006>}NoQHUs27`dPxy>u&R_p2g31po(JD2|-5Tld5=mM3Txt!~bb36;WXYH^7&! z{N(sJ<)&dqEd7}C@F7W!tT~!)GFBFN7nxWTD6{wqP2c(p0hq);whMXGt?%K8^n`(CUgv_{-mc)C z?X!s5v-O_o4~XPR=z(D`d9^{f<#+BnVJ!QQ(ZLtp(jVUp@x_E(vJqzwH zt%m$O3-LzM3+rruZ-b~D>Oq7{+?|5U7>7cdg6!%gsEnpz=fqMlH@I+M6w^bWx8#%=3Z<0OB<7ucxn3-xjo-HjgAp{^3k!DEYpu zV^~kS+iH3|e1XUK26W-Izv4Fd4X|jlSw#;_iW5Dbg%{o&q@IzZ&q+}h*_o|%I9|Hi zQ1NBS@jM~iUvcEG@mZOL#Mu0E^Uim09)9W^yR7ocW%{p3^M6KxcHE_m=lgew7)p5f zLn&V|bYG4yMLW0aE~&G>ll&|2fW}lwV>v^eyA}KjSsYY!<#@i!BR7!WP4QuQhX{R; z!c3bdt8&B2S!}JxT(#Ro+f5NOE%<`0xKzD-@V}5@Lh|Dqg$f&hI`PscNu*rOxhdr&Y^~uVr1{ z?IygU{kdYrEcz=!>c4&jHXa5%RS~e&?6w1)4V{_bqZYnh4|jvI??pQ!>BYq5kGi@p z^JV)**$&k**m9EJ-%xjE>tg6OvQ-o~40+iTX!>T**y$N|cIT(jl&juhEu?69J9hXv zXSsq6_J2$97Sv4e)028$=y>m(IqygK#4E;o^-^Cr5`YWi{SGeo8B}}~1#WP5Vi1T#nx!05O5$oZ6Sq+Hz?r>H) zzVLcr#n)Ee>%y7Ozo5BO+#+1py}C9RCj+H%qFyJc?i zWg3UI@>ocC;@e+ocBg-EATwD~SgMBP8pAFsZ>C?c7JZ=M?(vWL=4(&%KHStqlDY>v z&QHtqQPs$Qo2<^Kn9iaH`gbCac}B9t^`px4IbNo^wVzd+561I zzGvQeikT{p%b}sht~QW5&2}7EG1;%SfSj+7%v{jk6yhnc${vzI79I!=?%aN7S{3PC zI`Ykz->ZGO=FGJVAlu3&cb5G7QacTU19!qor&aek*mlgH`WYbEE_~l*1{%1jZVii? zA9ExK#jpkw-}&x$QkP$Y@!7t=^gdAc(w@HICBO3$xX4JJp+r>NZb&dpHsW!$YCec! zEzr9#b7#~l2lB`R_xMjQZmHry%%kxdcgY>fn`_@hReYW#G%8L%iF|qt1-UriEI)5_ zOS;0{mE7>xKR362_c<(USc(o#Pyu6EYyQSxbx}~p)+*K*NiT?1xvj6I6!q})c&cHs zy+GLlJnzr!C!KnKdqhqK*aD|i{(8QTAe{MUTISKZr1CmQrl^)JNp5lT4>!q;Ln5>I zRC!fgDJ2fc5#rT|LhqXF?@9CjV&TyMQl;r_mCBQhz`;VY{rA?rG%0JSsTA9`-}Sj& z#_}$Z*XE(*CaZ|>=L9@Kt~Cd6l=-^W#`zPUJ8xH-T#tdJm*<~eh*TjGBsQz{8d`dv z2bPrdt23&smP-(K>%p&*X#;OsCw-ly=OXZuyB1V|Z6NU6?bx8PF>`6Q;JQnaDDt%b zEX(TuRCA$cBjf+bF>WyUPdSi?eY^Y(u!<+L1 z3>LuJCuwB<*PeDr2VU+3_?=(Wnr({d@$0mDIX6G15l^S42Z&S_Y{M87z(K z!EUNgf=*W3`12&$&tA9A1ElHC+kNc&UmtEB6Ud>$t_UXj;X z7vjv_gV$aXt-|!<4JePyYdy&AMQl34+OcBT8`@fNwmfEA(In9E$KC8nFn5A0mGhFi zsDyV^-l+AcWSva95Ibe%5gs@Cnl0NKPVZ`Lb4t6jT$zF;`i2>`5AuH z@MMsDlEX}8@gP$DE0s15kO*|W+@`L zCsg9U%;rx+g|a9LNDyLw6{tjN1{Q)5GLgw?HOT@GrMg35;weeL?0vhYWWg5n$CsbJ zflLI52;WV`*F)hv^1F}CC(Vb`MY*DnUBg@M^9I>B_sI97;{lf2z?uoUXoraa{Hww2 z>HG;{D@P)i-oIEvXHN_aKfWBvDi5-$3X$}t%oD;Nxv#e?qnDSc=eAJvWo#0utvW2f zckYcekpxRH`CryOMk+A*?3F@VVTog-@Uw73^6?Jm&8n+s^F{SUVYWl7v(t*qt|-zI zRIJ0E<1);!-+DP-gUmH#nj&UQN71<*0vd$MXj!3q!r8moe-ZW-titWZM^`mI#(CFk zUh5Uzg^+`rkawy#kreJAO>yKMPD(SP_QAo+0@IP~WV4H3su+Kv5Gbf{FbE2`;@lst z>7ZTBN$#e_SjlTYPn}ps6{z14bo1MJkvUg{H)1?N?pUbcqbqF0ryMUl~YndBuRY^0vua{G>=SFBL= zpubL5dVmk{Kep}>4R&Nw76rmJ;Yc(j1~sC}b5=y^V)aCkS^CH%iRDxY;1!|5BFTc9 zr8ngmy8j!(|Nf64sKWP{c)yl&eAJQBATe#_S*&^EfDt-HV>EZaM^eH-6Q<&KpgyCX zdx7i2nXf#3&mPKt^#~8^5G(}dpOAY&;DrUowu)&Y|K!mvRyv#A-OZQFBC-h4Fb~FS zle4h%B__?t%TcO2xe3cf?awl^hc zFWo0>lO-y|a;N|wW8~{~yAQj8xDM=br+>jdOE$QY`BEjLmg~3o_OoL*t9fr%Z_#{^ z(UOr)P1Fsqfa`IS4cumLswll3w{N?_~DX_w8?~G9InptZNwON8)f4S6B3h|xZ zfr!4d5}VI@N2N5&U+WQloWuHFx(V4#MQYKU{pZ<6nCp^vLjm``0*BDrWK-~M@k<4{ z>~JTo3k0bxX&F8qXf2mHir2!@rT96B~A#TVb*JAqMn0Ie#BZEw2J1q%2AakH1d=&ae0V z%9+iI$10|Skfhgd6YH>AnJV?)vBL)yDx??MLs(($SF5?pbn66GtM-=MCJa2U<8o#8 zbFXWQ3#~-&7q5Lv3=54B;O+_GT65AxXS&gF4991+pw3T!Ri9lk##*Uk;#oiMxRCU0 z-XAV9xBhqZh^+?evle;_UOSy$t#2$)3Hh7Koz2JA(Pgi;#dkxQ6HFTr^C`D15TOS@ zW4W~6n)mEqd@jQp$;t4c*GeQij{lm^2YKvQ>c}+OyuWanex1$qM-`~H|LQw_+q-k@ ztYl>Y>m(t9AZXbD695+i+H8kPK^0ZJx^d_}d{Fbcbk{1X^Zt{7FGA`*>b#%)e7EQ^ z&M`zm@YnK2Kf$|PSPdDEPh1?z+oy;qg{rb1Fm&LQxv!5%3cKopk@(jEH7baEkex)R1X8 z2dmt$LHtGA|LzY5o{!wn^>J)}_TdIOUF0l~Hf)5vk+IC6!2uht^etJy!}&E)K#mMO z!)t2Hg$bOyOsC!qfw;eNkVL!E1~975M$ zK-@SSstg@XcV*cz81BlFYEH`6+5JpyHU7;Tv6ks96^LdaO!zBw@^H%-ifoSWe7dDl zfB%`#4nO2m@Lx=eEgx&W?%t^FPTB|8!QGHs-{V<$e^)A+E*6p?fiB1D)rI;ZI8HI@ zBX`sS!hd9gg9UW9dSBXUoRvBH*$Uk4<>wTv5Oy5@)|-y9xGA11fgT70Swj1lxj)nb zQ~&PqS^TTHxdyodbK{2R#^<4kGvrDXX5-;%T zP0>p`!mLwGQ>K@$g`U1LFL?GcN&}hD2WsT-`e=j@A|1^jkt95Se1wj?6kOFewAs^q z&~Nu`f@Z}RjeA+G3b*VScVpGGRvK#?Z^utJ1KlKYum~7fGehdRx?KFW^{{X`k5V^TEIFY*UX|aZFZcew)8={HB=4h8PVL<8;=DE=F3}6qP8n0zvg z2dS!mkZICkZW8V=4S??cOAhycf`FH^<)E9yWR#PjW(W24v-+v|)?Mz%N9edgKnraI zP56tFc!qfG4B=iKZ%?uKjE{%$L@3E1ox4P9C-p9SCaYE4`*uSH+BECwmPiWzP1}UA zoqOH-4f4fOtpY}u#%uu9>UZrbZ#86!{Yr7AEOI%La0uT~(*Rj$tP%fF7rRuI4y$*y z-iPS-$#~GoUAg6YJ9$Pe<)=$L75|ImV`sIea6M>g5%3`}wFi~8mF;DMK%UkAJbFgK z=uYjv_7eJ`oGRn4;9hwek%R91l|!wHK}Y1Zgx?iLR`IT)1dhyQCQ%;H2t6tWpW=D0 zMI9a$a91O8I$i#@ zUoP)TzguJhIVCURzhsuX68Nu1DWVcvvBW&vy=Sobe=@w8;s)f*b}FUmmN*Gv{X=|S(x+DYMe4Qp>Y-u!xbV%& zRS|ES^)@Z{Ojn@U=aZGDG>YGM#Fx~C3MUh+MMLq)WEqa#`aV6($4Xbayi5Nw1TH+& z;jj@uv#q>$u`^xD~ADJ5CbQqB|9F{_kjbNu=53@RHZCIueN^E5#9 zJoM+(F1z}Ub*>hdbO+$dcbacQDTRHQ9bJI_aq(wE{Fi@a0v7DaZ&J6jZXy0vyN*VG zr^lt*wwADYbVxz6@`1tmXT0y&NTY1<+Dgy21ERU(f~P{fM(;WTOlT8{>1O=&7$(`k z>$@lc=tV5cmj)Ndkp4GrwohoAu#mB?b2jx@wU>-#Ttq3Rrd`eUSQjxmv`7h7*l}oI z9DjOWw z(DY%L{Qzl*+==@#Dq&OlofMh`IFVs@jAJ6rW3KnS<9)~Jz5K64oKuv`PM7&jm`)24 ztAXEotqQbB@G8t>sBQjB8+4SNKb^W@5f`_dp|L^v@OOFbkhQS1rmTl$YC(Pci(7BE z{N~@-R>vnhG_1F?7P+op93#|3mqY*OJZv*q>-$qK?bL)Lb-PV!c^yi_%S0a^@|ps} z(DK&wSx1%|Qh%dx*ga@QngZj(6kZ^w361sk4Phn{Yc48_9gXNDT%{!vma|TNB6&xtO=^G1(l;hS;VH^J0F{}f2iO_irLc! zpG|6YQ1 z&Ej`{R^i0F_W1yd7fd0qp0KuD_IwB3_73E~&-Jlx0M?xHR!)rO*3bLipVWb~xjB~^ z|G1iuH-41$NaAQ@lZ1~Do5l=8*Kf89tDQE$Wt?P~%h#PJo5q5rn!G}q-TUlac;X+G zUzgMa-CW7rhS^GmMFA)I6&VX=hNIph2=^3D>ys&9CrJ5v6F9bIFzY3->Dp(ZgPu6V zC~jGr)8*Z4oK!Z8E=K{0q;7`Vgv3N2yn(%z5E0D1JPlY}@V%%CEf&e9u#0#%m? z<*9=?TTZ`}B7A@r4V+{k5|;oR1KAH6W>a1TWyFj|53}Gy0LdRIa@8uq{ z$Y9^-ZcWNe-O*=XEf!F}JQWQgtdS$ZIcq{iM4-3O5_yM{WK)u2nA-rVXB?jPu-6R<{KUS&z(URf8& zI`I%{=5lT(lATq9CKK6U3_omGustCN)`pE67rCY1N#M~2!2%UuR$2z7gGH- z0wlQc=9sWWIoz%DMkFu&&yyvG-Rquw>p}rQv8)};0=8VtT$$7ag5R&XwxF*{mIZFo zcM?zzTlGXFpYdX0yJ9eU4KnwX#+5~l@Fivs`*!HwUP{p0ju6|T3e3!w+V?a1E`od% zQ4RstG?`2ckr#+(-J1|N=dv;ZgsextrN%slrhYY!vg&s^PeHz=yf4mo zC2&@VZKiFRvm?bCRDrh(6`Es?LTI>DSC*H8kAT7YOaL!-*4a(<9X||6Bu#?>L<8Um zca&pT635T9N4p9Z08A$0efPNmWQ2Ev>*Fjo^m_!}On!fFt{N-wq=(n&0}AIazD0Y> z!w9iYwulFZ@Zz3W(3pkgreYivz4gfyrW@w3c6~v z4OJl9a?WXH(aO=x5FHH@OA_F7;)K+;CAKx%03fCV&Vzo{lPe98|$8 zqtRr3L?Y#Nmj2gszsjrOnH7AVQr2omo3fnc_P#LZhmPl)FI1Xvm=9tS3b_4vDZehx zdN4}d3v5bQE+OE&ksqr0=A#+x)gH%7W$#-GGkNV6D4;RQ@l46v&>vz!DEy@s2%WvH zwwe3IjAzwy37yRptvM8Vy4}*mKN)IvKRo);d&8`LQa5kNX-BywPfLe(JUlY8)AQ{phP3~z;R3U>mRqa;g!dEUKs-4szP?islVK}g$uJ?O z&UQbsW6H~lgK_ziQ+W8T9M82xbW#C3?yAxXLlwQYFLk~*EuhpeRQzEmW`e)J z+)zPp-+&>`=e#LdLQi9{Y;bP*K10y6++ZGgKMId=8|XS-r3TImX!nJT7}c{Ou~&vI z>rOj_@mz*{SmIst>RB6zl{(_37Vo0*5$$^&m**q&?@moIWv5uZ_S++{o0|231C->O zE`0-son(`BB)H6RmBEUQ4j*hZdXfGbIV?3y8z4ts0^DO}vQi&j(Rv|I9Ug*+@iqZh zA46R$UiezOvtYP-6@t$Hf?;yKbX9TJtBQG#?;ZzX~GebpU?a`%*DxAlhol zCqdf~x>#jc-|muY<*MzKy*^LRIXzh(0EHDie1EA&9yYfdwUoO#qFqqNgO{gW49VKl zKM1zvvbB}psj_5_FXKTL*xonUEA5h+mp~nO!3Ca5JmLXK3UJu!&{KLMY;f z!vqLTbQvWs^bu#wF)?&0jFzGe@K4a-AO?>eCRT7y}%luMnZbR2mCqV1I;co!4_f|mX@T1Be^b4pse9*vi zl{TXp#S=<~Ktp8OL>#H0_T~$jH4H`Tt<()|E#jm~r`&TqL4FOX zVeS{uafRd_bU~&=;XmgCIvC1Hnz; zT*i2jyB|W&50~MO_#L-Wwp!&bWQgMa;2h84!-@`;N}H4R`xB4T>qRT_5x9~|(DRcg zYM%FQ=5+(C%W}J)4-}iElXG84k@n%a_6)L0hQ@kmXqJTUw*QP7u5M}RFMIG^MB(u~ zcVHx}d8S7iT{y(xHGT|(j>-T~Yp!=N@nWvV_4Z%xH>X^0#*}aDNJ@cncKaz~q_v3~ zFpKpKgp!zt%VM`;EMKnnOlaWFDPB1h7ETeY&Hhw>Jws9@jb;9(I_NBj-~rl%bAHcI zz}CKFX}eO(pOT&}|szp;xP(ASENz<5xd} zrSMLvF}tBSo?HU{Jc3Tkbe)3UqnvI!z-O^e!OD;BUUuhh=ePd)i65S$=)Y(&jVY@tx{5++Mq8?K zLpg?Fyy5+55O(Rc5Z-I>^Ggt$nKlJgSfaMvtL$USmwKQV%jprZ3`|p2KqI_CmFrhB z(E(*Iv!?$Q%aAySrhaIWKZRkQU=TcQtw{}Laxo(?F6brMf~7a`=#-GxMU~%8mG)*-MC~;dwB0J2cC&!$A~`9rdYHZm zT9^SVGH>39@vP^K1buvU30+g8W$3l>y5}NAU77ujLp#K>%Uy!kYMcc213db>R{$P* zdo(j0n@;!%0LIL14bf&7+e@aWAVg{`>Y3zv`iaUj9DT`0)1vG~?%YH&{djg&yTe8k zigX_oQ^z=tw2wDM|8gKSXhBS?07ASjBt3~*yfjipoX|dW^xyADDA??mYS~LjOT#EM zD3~)0qsBXMxah4a3rzg z_qeIIzJjuYur=~Qqu;1ST()JYC4QLQo^42f#)@k>g>qDub%%2$>BpT>ZrTlEG3#O% zY{ykB0G^Yo0kX6=sEe!t5C!keNP74gkq_Y;Vh91y3@A5|D|}4)+68rlw8$rT^m`eF zQzy1LHVs;17dA>Y$~1vzg7sfNZ5=6No5DZ#VB?ud?|*%h0M*Ar4yKUQotMl>?pg? zMsO|pe8_N?68@CFwakCV{qnZIqQg_=6lW>BlQvTw%qPy|pZf=gkk7~G?(XwpT&CX1 zX|s{VU96-0KtQL04DI&Tz&-r+0$dU}Yl9y)=-EN*twmoBMOamh@;@xS&|eF)UC{6Q zE?TlyK6O^V7DqA-ogg+opkUIZfX6p*Q)QNlS7GeDga3}(426Q08HW9#zRnvAK*FS* zjps3W^;Vx4ePb?C!ibGk5Mi=bkgo;ni9d z^QXRsOAT<=tJ(0!7Vf4lT z{_*~*?o;QX6g4gkeLBT<#vB_WOhK*zKSpXb8-i zFO2%hl?6HMf^MUIhS`VmKjN` zFQ1rjSK4k!t~X}rmzKU}fI+(h9J-J{amtpAnA_5CuVNz+N)T=y!UYi$553~{c80Rm z1Xj?)ms51&BR2t2GGh*x=rGUFm3IiI5!KAmfW`036UL5_DX`hIhBUZ&w{>DQ=k(8} zV`0mWgLBR_lcMi1b+y`7NtxO_^Qn5zMO&UXMAagn&x;T*4SAsG{6?wjCc4C%aJwhS zi};Eq=WLWd%gCEveR-KzLI_J0R>ms^iPm!F4h@mQNTW)i)dh7>#VZRH+XthW zE~xGqA6+6tV|m#eraf7uh>OuFF*k)DjyNQXvQMN@u!GSJROyJ%`#yol)+C96^m-s9 z#vFw~7H2u&&jnd#D>5T7H}@ho5-LR%f{SY(I?oWz2Sf7#Hh1hpyrzW~cN}&L9?hoN zpzYpX6r|`3$7#6U2qnWn*a}}HVyD2$o@Hac#zsM=%o-naBP!x~qRnJ-CuQtk)K?*0 z$m>YZ{kj^0J|(bhwN!m~?ld-JZMt7i(qO&PG8rfVzr%spN;Pc^Jd}mdw@!sIvtNkb zB!&x`39GoSU093IJ(&B)^@1O~w;#e*Fb7Q2pYsMqP~P9H_OK{-U;XI)PvmYjKY}>T zUeXFT1+GZFMsWL4ZzJtjg8?ao5Tyr#&4tMP5_s+31bZEA?$39r(kf1Y2zxjc6vqq! z?<^lz{EDK!U3Sy`zDp^^oqinYiU7&S?`{a0L#Cc;_JwfeI1v>Fhc)jen3FfS#86hu zP^ibhd_&{*v;pseUf7+9!UVxb%zJiTkQls(bWe=Sf_&J3$xQ$lw~w3`imj@de&nw+29QCA0!@AVIlNgd=4z%Oz82^`Ulf|lZ5$*nTGN!US7?)uTrmX zmqE0UrTtW#idz>7W=DX77Y{dL1Lpfxg;lz71VcV~{E+8XEYGl-42jj^X$$(=&ddVM zGsTGI1rfK`hffN3(H;4@4Ig%f9P#JzVTq2J4FUTE2BHIbU9sp2C3$Uj7tDn{!@G5Y ztV{3(8Le{yI$@ee>X!^53+!4CMy+`HVeBvzlO41uHs0$7F?4)U(*rAxfJ$4Dl>pCf z%AK3(tS{~oE-iCKCBsJI+?Wl*Arfud+MOQ3><PG>-^#W`V8$a?p(o%hTYc>-VPB|(#m?^ar4fBzbPu$-*wv#Rw!-&kofNv8XAa-704`VP04`BPs& zsDH4j9Z7MR`0ghv%*_`%^TuR20^C-xe4$cbgvB?lmIt}=n$!Y}c2rr4v zDOHSW@52Q)G{+3D9PEgP`IaM(1g0~=U56hn)0EWPf^gc{#r-K^%2$EfIE9%6APFZ zCpc(=_b>$yUTe2O_r35ZKJ9%QRHWlfPz*}r;A7jaBcU<#_caz~$elkkC=f=zj-ai`2cUBnoY9;}zUlq5)RvNrzlYObruCIP;`Qh1lO|NeYi z9)3KtTtKJAix|w5RXriuk=e&%EMu`n&}H12yM*J2n`Fthc7n45;GcOESfZGqem0jx z$4Bh<)!m+?yUs0Dv7QhN-idg&&2~dPzRS^}?QvAZcC}#J;Isk9qK*B z_|hhb)jo3ftoc-sR9Z+cw^kHU@*ZKcQ%Bw*OFH-o9sq#wVj}md0Ni=b(zqu~IL{mF z^K{S4x}UD~#omsfYGnYKETDoo_29gYG0$oegW0ubQIx$G#PHYsdeM!!W%qu#m%|6a zEG&afK!%Ws02o9b4Y{h3kyW;uOJAB()8bb@={#9a3~3qqkDFtEWQErlhoylp?W0JT z@+eI>=t-Cj(?R)WP9ROX>l1;AS-2rM;iV1}DPMAO!NemCC=8xn!iE-YBoUCDix zEYed3r3f}9`RP!u!3CR&D(dMU@JK*-WDvK2oOlZ|KJXYr>?5u_G?a%|_Dc{$;OFE& zegP3qcS}=UooP;MssveIx_nWdG9duGuGdc|zy2Qufe5#5l9{f@Aj#@Aq=MAf`d z!k{dAO3Z&RE)pPEI;SRMs~GFtO|cC|xv_$NOT!RJN=;aBDrT=ut3T(c-Ph!KKFS|% zdZfA-r13^i-TwUS+(DnG(l>kjc4?t%Ro0kCx?Rpyi9^^IJO#~lcm5PNY4xVU}(<)f5~ncRFzKqI9@ z=SbZ0#_}Q<_I^2V2yYvyseS2~s}aN+G5`$^4^Pk35=jreEY%z_dry)R-hyHGT*PpS zz>POk$~mQN8khcwaIH^R_a+_=Jlt^^da>(2q=T~aQu}9y;Q0G;PG*u&@i4p*vgcG} z6DuO+vu!8hGROk($8`bP>%U#@&(vKyZT48g8=%2?z4HLre?GuTs}L~KMV~${J6Iku zd0FV9E&lXEhB!c3@1*?ud&IGN=CZmdR^@VcVHaG3_wEvq#z!cRcI2^;@o9a;`hCqp zr508sSOEeOxXo~`|sFMn!x25izBHdf;mYXc<805;g>nwZYl#XOJad>9@0f=!_ z5^EgIWLihX;x~QRX#aSHDAB~kzRgX_9z=F@E(bWW^I|GEX4s(c-v-sQZy{^E{ekHJ z4@{DZZ007nV=+-dxf|W&hO*aLT|2#BXO`o6jcMP0wg2oqgfjsVnZU7%c$(Qsb;xI| zlV!XmpOP5!cMRZ~Y)Ntx2z`T!ZbVAMs}cM}dL&?_OXgu=srsi?3E`p~xve`AFuB%3 zE6jKo&}dEtcD>AuQrSU+Rbdh$^BWP9%+$4`mhf4c9+}8xW|B*>v%+ zdt0l1-L2gC?Vk|)gzEeh-o$D=%;3BY3Z;(h4hO~^loli^WhG^c7*@XX)$ykArg$Xl zE_bBU7d>@czysp<`=JgYdZ$uO!>aGA61$~SpP#1SYLe{S+aH9Tn7!>@tdb$&8Kum! z)dB&(+<$p7>}r$wtQ~)0=Y8FZGmqS>-Yh5a>WS5dmpF4w}Gn_3-!IKJ>VIzDF0!IX&gK z{iO};;y$Mp4Y|^cL--tw}FH3Q~wlERTV z602%63Cr14-=UOJjzYtdd9HASTKksvyK8E1yG_fC->aN@4L(Zu>?1lAN%$r{5;=Z9 z`R?|%THbW@V^k#nRg?BJ04Ir^v>XD);OP#U-y= zXN?v`bwNMvv1M;m%j>K=1NX{?!+A$7|N0jjh(yv0n;)F`8!v0bD^aCl`LjHPmjAd^7B@y zqsVg`L9$p8-4$g1py*x{gf^cV?(@v`<3bf@7OC08&px>tH~IaZwk!UZ=&OP>H_Qmo zS|KyMFPD1Mxld*$iUM+vr#p?7E(OsI@mrf6PmXIP9z4#!U{j6hmxQzz711;faIxoW z#PXt+pO{6@Nt(#^IG|+Iu1f1#Jxj1xHwPu%LPb_+0M&GAhYVUW^e>;W zvCkEA>F0_hnH(==e)j`v5`iP~fQ~~mZyn?T>4ta40b6TF1-pIM-?hF9JtVakoaOue z-pAi=!8b;?WJ=Rn^8!A2@6Etkolp?RI~4JHIIterOd}mWWBiMn#_yBIY3cS>H+ZGfm<_U zPDoyIz#0^9~8Z9j!&FzFy1;3Ga+NQgF+)?Ks`o zK%*X$OXL=Hb5T0l+O%(T{ImRcEz&Ax8lp{9d#_F{wydg!Jd?ALt|nL_I6D5s$@_#K z3CeoRSsVnsd6^$HaC-WsV5j?02Bo;pZ_DoOu)#^YBQ^LW$_Vl1!+fRwHwHRl7RK4$ zX0t76y-)Kc9XCHDJih05_+4ze&b>q!!QEEs5SV?i-=L&nWpq2Rr=uw-AMK6MtoV|3 zd+h6S8dTuMk@w2`s5HQC%GPF~vm?u66aT1nMkI?SnN}tBRjkO}Emf*wQ?h4?VsZhd zh^%qI1&Oy#$xn}yAB#*GZ?m00F&VWg=G-qRs23M{)5Rj;C9j2CxqL}nQ)=Cq)EO6) z`_bS~fIaI$^J=CfmpO$l$6KuPrn0(pl*H=lV*@}vFdR-*mJ@qk=GXe&z4CV5q}D6H zaellcfMgvyI?GxM1G!~vKH#JF` z4W?y9E0l@0FKLB_6M$jg-GGhYH^r{eIr|iy2dZThyS#w{0mI?4mT`ztSaaSTmL$jndqc zivIA;{{by|MnzcKIN(8ust7m>G|vrKN^;!lFOL_MjJ55ojZV5zvWIE>J)b4&Qn>lE%Epj_m_1$zf~bDhGQ;i<#-+F5%QUX?KAy%j8=@u2*EV@=Iz=71*s!MeY{rUc?lJvlmo89CH}bw%^C;A<;273mGEQRMZ~_ z!WJ(cbNPAhPl{EB%O9CuN#?w^{T8g{QZwHrU6OJC;99EBBy+?k*;-EHX!^I4T{V(M zR_d>RN2+8eT|n~=3yqLnY*t#Cltcfi88;1H+N_ps;ur+-f%1G|@8xj8+>hJg!;>Fw zD*e6ZeC|J(TJ9QsN6UEK?+4`rq7|i&SOnafjABe-9V-kRO6G+jowsXKwAs=E@4pU4 z4I0lEZ(Wl9DsVs?SBc3a*VkNXi`N^yCVjmOpw2LRpVI@|p~`LXt& z+u9t@ekm}z7;B;}MQ{&WzP>@STJAEL;`)@kFD;XsiA#7xa#I$sY=p6*P~E{ybwip) zwypXXG5Yd?T}!@r{~0P^hKFrWb>n_JsoO3kzI&p5>yEzdO`3Ay$?Jzn)j5kwHI3@^ z{m~79fkDuO+SHJ98z=~#QY|p7mg{_mP7!`3AKIMjR=sOrQo1)P-nUz1$KmuuU-_mg zfmeiONhqj4G zYObg?G^7dG9|g5?pJCJ)j;He}Aph)odzp2y&KN^_B;Hn z?GJ^juKkn!XfHV#*lmm%Hs_VeU!UjOAEl42(gZ8`e)lx7&VO)<%<}6^oK_KG1v0uY zD`>;mtw~wU!lCddMt3B`de{>)U70V!^_8nsiWUb zVi4b(qj!Zk8t*X)3PN9d{GE&?sKlEwaC3su2v89LJh%lL&Q$OlZZSyMhnR}!o7BHK z6)9UF-!KaDw-+sAU2Z!xQ0$Dx}4Sm69;sJO6JLybnOvI zuo8C4*(yNJo4jlm2dGwV%#GzzFnjl4E3^Eu=2-I6gq6DI?7QF$y?O)@IA2au~fsE}+(6QY_0 zWET*6*+E+9>|8Y_tTe6)R$r9Me_sa}k#}jB&+2?rw32E#M&s8ic|mH&icl_tYKA{K znCq|hO)X66b;o+vOp(&zyvtw*=12#o;Jas_46|Zw!e3k97D92NfQHD1nQ9EDZQ1-cYfw%(N)p@Lax4L7}@TW7G{m2J} zX`Jr@WW>lTRvGwyR-Q*<{9)Ken*Grz)Ds8H_$ovS>y9n^Vj`eJrPAqFOyC^7-0@Od z$?#c_zBv96Q0;U2#=fWQNB^WI#KpaGd53R;U!hU&gG*qF{3$j7O8@1L-xT)ULKoeh ze$S)j?+ezEoIa1_zk4r-3fwv@37WY(dVqQbX9pZW43 z?^qqBy<^N8zITwAOZ(V+{dJw??!E6wt3Oq)sVVGD?;ln5TkdljJ)!6Fd=V>f6<*A3 z2j96)mMJv5ZjR1cmv(g5sxl{D(SS1+)X+Bv@u{m8U@aT%k_2@%j0PJpJ`@owg4FY*=gq^zbt93Y%yk z?*e0F{1$(0mPE;S=rdreLTPAB+GgipHiNLVA6KGl(yUq{a-0_mE0F!;E1N!<$5Ma2 zgl_V(ndj(Es~Fl>ScS`wt(CT4)Rp?Fge^U-&GB!>v>8_s2`fN|0w05i-WT^56w`im zK~)bPvpPkQ_yHw*Pf4Wp$JqAQLBTeYAD@{kzM_>Rlx)Me!zY~}0XOu`)MnAN6i-}_ zG*2`h>lZFJp*BwqMk^UAr-;E6c|S_Hg}pL7ZNG6ihfo@qTrA&@G)E_%LewApevSAz zOxdJdI7Bgd93D*<)oq}b+ZxcU*05L2lop`kGi%%J4>rD;*%|P~CR(`EYH>0V{V2-r z+^O#AZd8$`Szi>bZ|^)&Q;IroWX_R2AV2DBx5WMKho$a@f4BHeY1&DiSJ0^Yyo)vF zfnNS0x#tgt+CSz>)D_X+2@u-`-%ay7ktp^&I{iFiB7`Mn8`ST6=i_+FQ)6ruL_>H( zcmujY68I+Wq3c5-a)4C&XFHj1TJ8%1L;o76qM>HeO#apI`boC{mPbM3>Dz;gdy^Kcsox3CdexC9v~d!fF|{`dj9Sa%#xnQMOcT0)C-Q6GccR!SJP~%g-vi1s2j# z%J7K(6h-2E32)vCgZ}ZWBL~A@ zucR<-1opX0h@-Mcz+)7GCEu1F$3&^vm?~}Ty@!@()zr)njq_J>yHPa$y5u1Fe&*JC ze8jKFbAIMQC1U)U1%G_%7`8PBtEwDZm)ME^#VUvH4^gxY)A#6}5i8!OqR}^GDPg~P z*PSpvk%SE(BAqJxOD(`CYohMm*>a&-NeY`D@0{@j!h4H~uZVS0w4t4AT!_Iz3;!4k z=4&x_HF`*=$o>q0Fa5giHk;T8UzV#vwB%;yg6#Gl)(v?^lEjhWPlp%0Z`|J_#y z2ksF+g!OHcKMI11TN@qMv^5&h$lFn64yAdDY`Ml{knY&f@$JZ#8XZ%A7<$c zUWY#fviZ1Tw=yn7mY19(+CyWDPJR;@xoS064r2_mq{c)ioQnC&cu1%w!UBpxVRioE z!l*yMwTs27K8~+VGkF&R1z=9xo9**?`gwAdb4dR zt+>2c!ju4;db^R)>lI3&HA^%7`y&I2@4J6ope041e+Sf7HuM?bjK2yl~gAU3r z339Ak!oz!i5Erkz40rc=$3ABJx_+?Yf8H}rSbKSyPDgDgvPNBVTDaB?N-HrQD2&V8 zYA%$jdtg(G;>$d+08N;`0D}ndflR4xT%#4L9-At>a@0ar#lDsoVmFDTKfl$|_|VQ0 zy+NtoG7su#{cOhH_nb-;lEW}=z^!n(#vb2B?ECUY*=r<++n5>oG@9AIWqx9J)_qgC z8u?OgWRn(^8>5~U5QGA9z>nERwj&wru~E=33>za^ww)(ioQ`)lJzLO%m04!e`XXT7 zD8?Iu{Zkgb3x7@p=&^qU|LX#yMukI~6-`R$u+0?K!(&)^#`LxDD7e z&rguKrK%N3XXLE>@JY`FZwmnI2(5A!{gVjN(DzqEaM$B6cddK$uWn5d*@?fxl6QdT z8S(|WFti@=?mAmXxi%$?%q>VxK>63F*Z3~Q%@7aKirA&k=B1|N)Tvb)b&cWwkpb)w zCq|^=589fV`cNDqv;OeY#?~*Bl^+aUBK@6yzqkW#uB%$hJ*=5?uK}2O91w7goCR5Z z*X?HX4AtTiRhv-AJ(||02A)k?vk+G;)6?o8&&5)edfPgVYurC59+}-mf$<*gq|1v` zMdoF%EYL7N;lVbbvX_8`fzZdf~lr3_q=zv=-PF{#%06sWm6_46-= zkehCq-6wAZ<5@gn8^W}YF$ei$z>{rGL6N<)OZq&af^S|czQ35n$M$KxGsb#_$|yNV zzgbvA-a1mF8UL^CK^6{98cidr;j?aP^{dXZ@AlEu^~nz8aABb^Ibv?1^77M5v^Yt( zDXCAZz2*u`zcPI+n-)SEdeIq0x4AqcP6}n4UJf%;32|)j`9!z=f~J{DQm6-47FaYnHudF7!x zwPJOwUe-bMyvm}6$q8_?Q>U_ z>M8hh5+egMaHGVWR{F>MwZC5y`42jRyPE7pdnf}-R~q4i(Pwe&Ip}$x7K|w>)}%1> zPrR4Tr5!>@m2Cgqrvq`gh+C&98?T-O&hRt6Z3yz`e#R>&w_|mwB+z;2)oaCPk`xVY z@;xptZld`>y*f?!+?>{rPEkI(;s$##7CjoM%&zRRj@^s&iqG5n3pkwYL8En-*5;}cH0E`PLM`2*?{b0qdQFOX0 z@%F#!+j77%TfK-7rJzXgrbU&r9hQF@#&>Q|irxO-2n>FP(N%ofk8_2K^=q3NC)sGN zCJPNfoSW)Ykvv{6*xIV{;Kd%5gy*=55u+y=c%sxL$zj2wm}x0{$Azi^lZnXIFET|z zC?v3gAFi1`{l#`SL+Ox9d7iu)1hD9Z*#6vcY-syfOSaCQXuw};w~eH1WI+ioS^FB(}&zdiv#E8Bn&uD!h8*Tl&$yvr!#+U zU#i%j-^88JM7;3F_EpxQH~0~uk^b)TGSb|$QvVosVmL;f$|Q!9v4m7coeVf;mT%f+ zk#=lEk{}dV*k1uSOZukhAdNy4F}Gpa=V_(|vC(GdG~=MFwhdrjnbj}}Rv*4JBRv(Q zi#skbv=MwknT-=}69J9hhSO0}AfR~?IRPP!4TOlkSD@XeuGeOsINL3sZ_E0@SW&lb zk(e@J0sOrVjI5#2d7-b1@;oeS2x$4$2Xk|u13&aR3qnON(rYc?s24&P9)EOtJ=2y- z*{es#?h88s7NHC^+Xcft={^A|W27jQt$l1Ve9V7O76y$+%=-kj(^KX;>CDMMNl)$1^Qo?t5Y!V1CK3FyxmtzIN?8-QO zE_yTaLhxnqYL}=*`UxK2W)zwKQ8#=K7P5 zUD`0s;7xpE3%B{miw$;>@*&*)2cLte@@%VGTcwBP6R6cdrDmk|ZWVBJ~Ue*h>S$HHe=*aN`5DGuS1%0AiA(qdD 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%0A;p7Z<#{X%(r{F5qu3Pz?48nA*u$Hpjs$=`=q6{ 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

ysW+`41(shp~d-*-%YQvG4F+d|! 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 Date: Thu, 22 Feb 2024 16:38:37 -0800 Subject: [PATCH 77/81] Cleanup core search parse utils (#4010) * Cleanup core search parse utils * Cleanup * Fixed sonar warnings --- .../src/pages/SearchPage.tsx | 4 +- .../medplum-provider/src/pages/SearchPage.tsx | 4 +- .../src/pages/patient/PatientSearchPage.tsx | 4 +- .../src/pages/SearchPage.tsx | 10 +-- packages/app/src/HomePage.tsx | 4 +- packages/core/src/access.ts | 7 +- packages/core/src/search/match.test.ts | 18 ++--- packages/core/src/search/search.test.ts | 65 ++++++++++++++- packages/core/src/search/search.ts | 81 ++++++++++++++----- packages/fhir-router/src/batch.ts | 8 +- packages/fhir-router/src/repo.test.ts | 10 +-- .../server/src/fhir/operations/agentpush.ts | 4 +- .../src/fhir/operations/evaluatemeasure.ts | 4 +- .../src/fhir/operations/resourcegraph.ts | 4 +- packages/server/src/fhir/repo.ts | 4 +- packages/server/src/fhir/search.test.ts | 32 +++----- packages/server/src/oauth/token.test.ts | 6 +- packages/server/src/workers/subscription.ts | 5 +- 18 files changed, 183 insertions(+), 91 deletions(-) diff --git a/examples/medplum-chart-demo/src/pages/SearchPage.tsx b/examples/medplum-chart-demo/src/pages/SearchPage.tsx index 0777d6803b..5fc1469526 100644 --- a/examples/medplum-chart-demo/src/pages/SearchPage.tsx +++ b/examples/medplum-chart-demo/src/pages/SearchPage.tsx @@ -3,7 +3,7 @@ import { DEFAULT_SEARCH_COUNT, Filter, formatSearchQuery, - parseSearchDefinition, + parseSearchRequest, SearchRequest, SortRule, } from '@medplum/core'; @@ -20,7 +20,7 @@ export function SearchPage(): JSX.Element { const [search, setSearch] = useState(); useEffect(() => { - const parsedSearch = parseSearchDefinition(location.pathname + location.search); + const parsedSearch = parseSearchRequest(location.pathname + location.search); const populatedSearch = addSearchValues(parsedSearch, medplum.getUserConfiguration()); diff --git a/examples/medplum-provider/src/pages/SearchPage.tsx b/examples/medplum-provider/src/pages/SearchPage.tsx index 0777d6803b..5fc1469526 100644 --- a/examples/medplum-provider/src/pages/SearchPage.tsx +++ b/examples/medplum-provider/src/pages/SearchPage.tsx @@ -3,7 +3,7 @@ import { DEFAULT_SEARCH_COUNT, Filter, formatSearchQuery, - parseSearchDefinition, + parseSearchRequest, SearchRequest, SortRule, } from '@medplum/core'; @@ -20,7 +20,7 @@ export function SearchPage(): JSX.Element { const [search, setSearch] = useState(); useEffect(() => { - const parsedSearch = parseSearchDefinition(location.pathname + location.search); + const parsedSearch = parseSearchRequest(location.pathname + location.search); const populatedSearch = addSearchValues(parsedSearch, medplum.getUserConfiguration()); diff --git a/examples/medplum-provider/src/pages/patient/PatientSearchPage.tsx b/examples/medplum-provider/src/pages/patient/PatientSearchPage.tsx index 682ec2ec63..725ae1f93d 100644 --- a/examples/medplum-provider/src/pages/patient/PatientSearchPage.tsx +++ b/examples/medplum-provider/src/pages/patient/PatientSearchPage.tsx @@ -1,5 +1,5 @@ import { Paper } from '@mantine/core'; -import { DEFAULT_SEARCH_COUNT, formatSearchQuery, parseSearchDefinition, SearchRequest } from '@medplum/core'; +import { DEFAULT_SEARCH_COUNT, formatSearchQuery, parseSearchRequest, SearchRequest } from '@medplum/core'; import { Patient } from '@medplum/fhirtypes'; import { Loading, MemoizedSearchControl, useMedplum } from '@medplum/react'; import { useEffect, useState } from 'react'; @@ -18,7 +18,7 @@ export function PatientSearchPage(): JSX.Element { return; } - const parsedSearch = parseSearchDefinition(location.pathname + location.search); + const parsedSearch = parseSearchRequest(location.pathname + location.search); const populatedSearch = addSearchValues(patient, parsedSearch); if ( diff --git a/examples/medplum-task-demo/src/pages/SearchPage.tsx b/examples/medplum-task-demo/src/pages/SearchPage.tsx index a1ec1af412..a1ba054821 100644 --- a/examples/medplum-task-demo/src/pages/SearchPage.tsx +++ b/examples/medplum-task-demo/src/pages/SearchPage.tsx @@ -1,5 +1,5 @@ import { Tabs } from '@mantine/core'; -import { formatSearchQuery, getReferenceString, Operator, parseSearchDefinition, SearchRequest } from '@medplum/core'; +import { formatSearchQuery, getReferenceString, Operator, parseSearchRequest, SearchRequest } from '@medplum/core'; import { Document, Loading, SearchControl, useMedplum } from '@medplum/react'; import { useEffect, useState } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; @@ -14,24 +14,24 @@ export function SearchPage(): JSX.Element { const [isNewOpen, setIsNewOpen] = useState(false); const [showTabs, setShowTabs] = useState(() => { - const search = parseSearchDefinition(window.location.pathname + window.location.search); + const search = parseSearchRequest(window.location.pathname + window.location.search); return shouldShowTabs(search); }); const tabs = ['Active', 'Completed']; const searchQuery = window.location.search; - const currentSearch = parseSearchDefinition(searchQuery); + const currentSearch = parseSearchRequest(searchQuery); const currentTab = handleInitialTab(currentSearch); useEffect(() => { - const searchQuery = parseSearchDefinition(location.pathname + location.search); + const searchQuery = parseSearchRequest(location.pathname + location.search); setShowTabs(shouldShowTabs(searchQuery)); }, [location]); useEffect(() => { // Parse the search definition from the url and get the correct fields for the resource type - const parsedSearch = parseSearchDefinition(location.pathname + location.search); + const parsedSearch = parseSearchRequest(location.pathname + location.search); if (!parsedSearch.resourceType) { navigate('/Task'); return; diff --git a/packages/app/src/HomePage.tsx b/packages/app/src/HomePage.tsx index 8f53903c36..97f513f31f 100644 --- a/packages/app/src/HomePage.tsx +++ b/packages/app/src/HomePage.tsx @@ -1,6 +1,6 @@ import { Paper } from '@mantine/core'; import { showNotification } from '@mantine/notifications'; -import { formatSearchQuery, normalizeErrorString, parseSearchDefinition, SearchRequest } from '@medplum/core'; +import { formatSearchQuery, normalizeErrorString, parseSearchRequest, SearchRequest } from '@medplum/core'; import { ResourceType } from '@medplum/fhirtypes'; import { Loading, MemoizedSearchControl, useMedplum } from '@medplum/react'; import { useEffect, useState } from 'react'; @@ -17,7 +17,7 @@ export function HomePage(): JSX.Element { useEffect(() => { // Parse the search from the URL - const parsedSearch = parseSearchDefinition(location.pathname + location.search); + const parsedSearch = parseSearchRequest(location.pathname + location.search); // Fill in the search with default values const populatedSearch = addSearchValues(parsedSearch, medplum.getUserConfiguration()); diff --git a/packages/core/src/access.ts b/packages/core/src/access.ts index f22a3b3555..8b1d67d0d9 100644 --- a/packages/core/src/access.ts +++ b/packages/core/src/access.ts @@ -1,6 +1,6 @@ import { AccessPolicy, AccessPolicyResource, Resource, ResourceType } from '@medplum/fhirtypes'; import { matchesSearchRequest } from './search/match'; -import { parseCriteriaAsSearchRequest } from './search/search'; +import { parseSearchRequest } from './search/search'; const universalAccessPolicy: AccessPolicyResource = { resourceType: '*', @@ -180,10 +180,7 @@ function matchesAccessPolicyResourcePolicy( // Deprecated - to be removed return false; } - if ( - resourcePolicy.criteria && - !matchesSearchRequest(resource, parseCriteriaAsSearchRequest(resourcePolicy.criteria)) - ) { + if (resourcePolicy.criteria && !matchesSearchRequest(resource, parseSearchRequest(resourcePolicy.criteria))) { return false; } return true; diff --git a/packages/core/src/search/match.test.ts b/packages/core/src/search/match.test.ts index 2634957a3f..44597b1c7d 100644 --- a/packages/core/src/search/match.test.ts +++ b/packages/core/src/search/match.test.ts @@ -13,7 +13,7 @@ import { import { indexSearchParameterBundle } from '../types'; import { indexStructureDefinitionBundle } from '../typeschema/types'; import { matchesSearchRequest } from './match'; -import { Operator, SearchRequest, parseSearchDefinition } from './search'; +import { Operator, SearchRequest, parseSearchRequest } from './search'; // Dimensions: // 1. Search parameter type @@ -308,25 +308,25 @@ describe('Search matching', () => { expect( matchesSearchRequest( { resourceType: 'DiagnosticReport', status: 'preliminary' } as DiagnosticReport, - parseSearchDefinition('DiagnosticReport?status=cancelled') + parseSearchRequest('DiagnosticReport?status=cancelled') ) ).toBe(false); expect( matchesSearchRequest( { resourceType: 'DiagnosticReport', status: 'preliminary' } as DiagnosticReport, - parseSearchDefinition('DiagnosticReport?status:not=cancelled') + parseSearchRequest('DiagnosticReport?status:not=cancelled') ) ).toBe(true); expect( matchesSearchRequest( { resourceType: 'DiagnosticReport', status: 'cancelled' } as DiagnosticReport, - parseSearchDefinition('DiagnosticReport?status=cancelled') + parseSearchRequest('DiagnosticReport?status=cancelled') ) ).toBe(true); expect( matchesSearchRequest( { resourceType: 'DiagnosticReport', status: 'cancelled' } as DiagnosticReport, - parseSearchDefinition('DiagnosticReport?status:not=cancelled') + parseSearchRequest('DiagnosticReport?status:not=cancelled') ) ).toBe(false); @@ -335,25 +335,25 @@ describe('Search matching', () => { expect( matchesSearchRequest( { resourceType: 'ServiceRequest', orderDetail: [{ text: 'ORDERED' }] } as ServiceRequest, - parseSearchDefinition('ServiceRequest?order-detail=VOIDED,CANCELLED') + parseSearchRequest('ServiceRequest?order-detail=VOIDED,CANCELLED') ) ).toBe(false); expect( matchesSearchRequest( { resourceType: 'ServiceRequest', orderDetail: [{ text: 'ORDERED' }] } as ServiceRequest, - parseSearchDefinition('ServiceRequest?order-detail:not=VOIDED,CANCELLED') + parseSearchRequest('ServiceRequest?order-detail:not=VOIDED,CANCELLED') ) ).toBe(true); expect( matchesSearchRequest( { resourceType: 'ServiceRequest', orderDetail: [{ text: 'VOIDED' }] } as ServiceRequest, - parseSearchDefinition('ServiceRequest?order-detail=VOIDED,CANCELLED') + parseSearchRequest('ServiceRequest?order-detail=VOIDED,CANCELLED') ) ).toBe(true); expect( matchesSearchRequest( { resourceType: 'ServiceRequest', orderDetail: [{ text: 'VOIDED' }] } as ServiceRequest, - parseSearchDefinition('ServiceRequest?order-detail:not=VOIDED,CANCELLED') + parseSearchRequest('ServiceRequest?order-detail:not=VOIDED,CANCELLED') ) ).toBe(false); }); diff --git a/packages/core/src/search/search.test.ts b/packages/core/src/search/search.test.ts index 5a8fc65ced..c41ee3639e 100644 --- a/packages/core/src/search/search.test.ts +++ b/packages/core/src/search/search.test.ts @@ -2,7 +2,15 @@ import { readJson } from '@medplum/definitions'; import { Bundle, Patient, SearchParameter } from '@medplum/fhirtypes'; import { indexSearchParameterBundle } from '../types'; import { indexStructureDefinitionBundle } from '../typeschema/types'; -import { Operator, SearchRequest, formatSearchQuery, parseSearchDefinition, parseXFhirQuery } from './search'; +import { + Operator, + SearchRequest, + formatSearchQuery, + parseSearchDefinition, + parseSearchRequest, + parseSearchUrl, + parseXFhirQuery, +} from './search'; describe('Search Utils', () => { beforeAll(() => { @@ -11,6 +19,61 @@ describe('Search Utils', () => { indexSearchParameterBundle(readJson('fhir/r4/search-parameters-medplum.json') as Bundle); }); + test('parseSearchRequest', () => { + expect(() => parseSearchRequest(null as unknown as string)).toThrow('Invalid search URL'); + expect(() => parseSearchRequest(undefined as unknown as string)).toThrow('Invalid search URL'); + expect(() => parseSearchRequest('')).toThrow('Invalid search URL'); + + expect(parseSearchRequest('Patient')).toMatchObject({ resourceType: 'Patient' }); + expect(parseSearchRequest('Patient?name=alice')).toMatchObject({ + resourceType: 'Patient', + filters: [{ code: 'name', operator: Operator.EQUALS, value: 'alice' }], + }); + expect(parseSearchRequest('Patient?_fields=id,name,birthDate')).toMatchObject({ + resourceType: 'Patient', + fields: ['id', 'name', 'birthDate'], + }); + + expect(parseSearchRequest('Patient')).toMatchObject({ resourceType: 'Patient' }); + expect(parseSearchRequest('Patient', { name: 'alice' })).toMatchObject({ + resourceType: 'Patient', + filters: [{ code: 'name', operator: Operator.EQUALS, value: 'alice' }], + }); + expect(parseSearchRequest('Patient', { name: ['alice'] })).toMatchObject({ + resourceType: 'Patient', + filters: [{ code: 'name', operator: Operator.EQUALS, value: 'alice' }], + }); + expect(parseSearchRequest('Patient', { _fields: 'id,name,birthDate' })).toMatchObject({ + resourceType: 'Patient', + fields: ['id', 'name', 'birthDate'], + }); + + expect(parseSearchRequest(new URL('https://example.com/Patient'))).toMatchObject({ resourceType: 'Patient' }); + expect(parseSearchRequest(new URL('https://example.com/Patient?name=alice'))).toMatchObject({ + resourceType: 'Patient', + filters: [{ code: 'name', operator: Operator.EQUALS, value: 'alice' }], + }); + expect(parseSearchRequest(new URL('https://example.com/Patient?_fields=id,name,birthDate'))).toMatchObject({ + resourceType: 'Patient', + fields: ['id', 'name', 'birthDate'], + }); + }); + + test('parseSearchUrl', () => { + expect(() => parseSearchUrl(null as unknown as URL)).toThrow('Invalid search URL'); + expect(() => parseSearchUrl(undefined as unknown as URL)).toThrow('Invalid search URL'); + + expect(parseSearchUrl(new URL('https://example.com/Patient'))).toMatchObject({ resourceType: 'Patient' }); + expect(parseSearchUrl(new URL('https://example.com/Patient?name=alice'))).toMatchObject({ + resourceType: 'Patient', + filters: [{ code: 'name', operator: Operator.EQUALS, value: 'alice' }], + }); + expect(parseSearchUrl(new URL('https://example.com/Patient?_fields=id,name,birthDate'))).toMatchObject({ + resourceType: 'Patient', + fields: ['id', 'name', 'birthDate'], + }); + }); + test('Parse Patient search', () => { const result = parseSearchDefinition('/x/y/z/Patient'); expect(result.resourceType).toBe('Patient'); diff --git a/packages/core/src/search/search.ts b/packages/core/src/search/search.ts index 3f7be841cb..b8c96e420f 100644 --- a/packages/core/src/search/search.ts +++ b/packages/core/src/search/search.ts @@ -123,24 +123,66 @@ const PREFIX_OPERATORS: Record = { /** * Parses a search URL into a search request. - * @param resourceType - The FHIR resource type. - * @param query - The collection of query string parameters. + * @param url - The original search URL or the FHIR resource type. + * @param query - Optional collection of additional query string parameters. * @returns A parsed SearchRequest. */ export function parseSearchRequest( - resourceType: T['resourceType'], - query: Record + url: T['resourceType'] | URL | string, + query?: Record ): SearchRequest { + if (!url) { + throw new Error('Invalid search URL'); + } + + // Parse the input into path and search parameters + let pathname: string = ''; + let searchParams: URLSearchParams | undefined = undefined; + if (typeof url === 'string') { + if (url.includes('?')) { + const [path, search] = url.split('?'); + pathname = path; + searchParams = new URLSearchParams(search); + } else { + pathname = url; + } + } else if (typeof url === 'object') { + pathname = url.pathname; + searchParams = url.searchParams; + } + + // Next, parse out the resource type from the URL + // By convention, the resource type is the last non-empty part of the path + let resourceType: ResourceType; + if (pathname.includes('/')) { + resourceType = pathname.split('/').filter(Boolean).pop() as ResourceType; + } else { + resourceType = pathname as ResourceType; + } + + // Next, parse out the search parameters + // First, we convert the URLSearchParams to an array of key-value pairs const queryArray: [string, string][] = []; - for (const [key, value] of Object.entries(query)) { - if (Array.isArray(value)) { - for (const v of value) { - queryArray.push([key, v]); + if (searchParams) { + queryArray.push(...searchParams.entries()); + } + + // Next, we merge in the query object + // This is an optional set of additional query parameters + // which should be added to the URL + if (query) { + for (const [key, value] of Object.entries(query)) { + if (Array.isArray(value)) { + for (const v of value) { + queryArray.push([key, v]); + } + } else { + queryArray.push([key, value ?? '']); } - } else { - queryArray.push([key, value ?? '']); } } + + // Finally we can move on to the actual parsing return parseSearchImpl(resourceType, queryArray); } @@ -148,24 +190,20 @@ export function parseSearchRequest( * Parses a search URL into a search request. * @param url - The search URL. * @returns A parsed SearchRequest. + * @deprecated Use parseSearchRequest instead. */ export function parseSearchUrl(url: URL): SearchRequest { - let resourceType; - for (const part of url.pathname.split('/')) { - if (part) { - resourceType = part; - } - } - return parseSearchImpl(resourceType as ResourceType, url.searchParams.entries()); + return parseSearchRequest(url); } /** * Parses a URL string into a SearchRequest. * @param url - The URL to parse. * @returns Parsed search definition. + * @deprecated Use parseSearchRequest instead. */ export function parseSearchDefinition(url: string): SearchRequest { - return parseSearchUrl(new URL(url, 'https://example.com/')); + return parseSearchRequest(url); } /** @@ -173,9 +211,10 @@ export function parseSearchDefinition(url: string * FHIR criteria strings are found on resources such as Subscription. * @param criteria - The FHIR criteria string. * @returns Parsed search definition. + * @deprecated Use parseSearchRequest instead. */ -export function parseCriteriaAsSearchRequest(criteria: string): SearchRequest { - return parseSearchUrl(new URL(criteria, 'https://api.medplum.com/')); +export function parseCriteriaAsSearchRequest(criteria: string): SearchRequest { + return parseSearchRequest(criteria); } function parseSearchImpl( @@ -420,7 +459,7 @@ export function parseXFhirQuery(query: string, variables: Record 1) { return buildBundleResponse(badRequest('Multiple matches')); diff --git a/packages/fhir-router/src/repo.test.ts b/packages/fhir-router/src/repo.test.ts index 93eb084bb2..59bf71df1a 100644 --- a/packages/fhir-router/src/repo.test.ts +++ b/packages/fhir-router/src/repo.test.ts @@ -4,7 +4,7 @@ import { indexStructureDefinitionBundle, notFound, OperationOutcomeError, - parseSearchDefinition, + parseSearchRequest, } from '@medplum/core'; import { readJson } from '@medplum/definitions'; import { Bundle, Observation, Patient, ResourceType, SearchParameter } from '@medplum/fhirtypes'; @@ -83,21 +83,21 @@ describe('MemoryRepository', () => { test('searchResources helper', async () => { const family = randomUUID(); const patient = await repo.createResource({ resourceType: 'Patient', name: [{ family }] }); - const resources = await repo.searchResources(parseSearchDefinition('Patient?family=' + family)); + const resources = await repo.searchResources(parseSearchRequest('Patient?family=' + family)); expect(resources).toHaveLength(1); expect(resources[0].id).toBe(patient.id); - const emptyResources = await repo.searchResources(parseSearchDefinition('Patient?family=' + randomUUID())); + const emptyResources = await repo.searchResources(parseSearchRequest('Patient?family=' + randomUUID())); expect(emptyResources).toHaveLength(0); }); test('searchOne helper', async () => { const family = randomUUID(); const patient = await repo.createResource({ resourceType: 'Patient', name: [{ family }] }); - const resource = await repo.searchOne(parseSearchDefinition('Patient?family=' + family)); + const resource = await repo.searchOne(parseSearchRequest('Patient?family=' + family)); expect(resource?.id).toBe(patient.id); - const emptyResource = await repo.searchOne(parseSearchDefinition('Patient?family=' + randomUUID())); + const emptyResource = await repo.searchOne(parseSearchRequest('Patient?family=' + randomUUID())); expect(emptyResource).toBeUndefined(); }); diff --git a/packages/server/src/fhir/operations/agentpush.ts b/packages/server/src/fhir/operations/agentpush.ts index e1e7dd2c01..8d7634c17e 100644 --- a/packages/server/src/fhir/operations/agentpush.ts +++ b/packages/server/src/fhir/operations/agentpush.ts @@ -5,7 +5,7 @@ import { BaseAgentRequestMessage, getReferenceString, Operator, - parseSearchDefinition, + parseSearchRequest, } from '@medplum/core'; import { Agent, Device } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; @@ -150,7 +150,7 @@ async function getDevice(repo: Repository, destination: string): Promise(parseSearchDefinition(destination)); + return repo.searchOne(parseSearchRequest(destination)); } if (isIPv4(destination)) { return { resourceType: 'Device', url: destination }; diff --git a/packages/server/src/fhir/operations/evaluatemeasure.ts b/packages/server/src/fhir/operations/evaluatemeasure.ts index 8115c05fca..7dcfa13dd3 100644 --- a/packages/server/src/fhir/operations/evaluatemeasure.ts +++ b/packages/server/src/fhir/operations/evaluatemeasure.ts @@ -1,4 +1,4 @@ -import { Operator, badRequest, created, parseSearchDefinition } from '@medplum/core'; +import { Operator, badRequest, created, parseSearchRequest } from '@medplum/core'; import { Measure, MeasureGroup, @@ -177,7 +177,7 @@ async function evaluateCount( criteria: string, params: EvaluateMeasureParameters ): Promise { - const searchDefinition = parseSearchDefinition(criteria); + const searchDefinition = parseSearchRequest(criteria); searchDefinition.total = 'accurate'; if (!searchDefinition.filters) { diff --git a/packages/server/src/fhir/operations/resourcegraph.ts b/packages/server/src/fhir/operations/resourcegraph.ts index 497b071470..4ccfee9a16 100644 --- a/packages/server/src/fhir/operations/resourcegraph.ts +++ b/packages/server/src/fhir/operations/resourcegraph.ts @@ -7,7 +7,7 @@ import { notFound, OperationOutcomeError, Operator, - parseSearchDefinition, + parseSearchRequest, PropertyType, toTypedValue, TypedValue, @@ -251,7 +251,7 @@ async function followSearchLink( const searchParams = params.replace('{ref}', getReferenceString(resource)); // Formulate the searchURL string - const searchRequest = parseSearchDefinition(`${searchResourceType}?${searchParams}`); + const searchRequest = parseSearchRequest(`${searchResourceType}?${searchParams}`); // Parse the max count from the link description, if available searchRequest.count = Math.min(parseCardinality(link.max), 5000); diff --git a/packages/server/src/fhir/repo.ts b/packages/server/src/fhir/repo.ts index f7a02f10f0..4f101c82d1 100644 --- a/packages/server/src/fhir/repo.ts +++ b/packages/server/src/fhir/repo.ts @@ -24,7 +24,7 @@ import { normalizeErrorString, normalizeOperationOutcome, notFound, - parseCriteriaAsSearchRequest, + parseSearchRequest, protectedResourceTypes, resolveId, satisfiedAccessPolicy, @@ -1017,7 +1017,7 @@ export class Repository extends BaseRepository implements FhirRepository { // Test singlet reference column let results = await repo.searchResources( - parseSearchDefinition(`Patient?identifier=${patientIdentifier}&organization:missing=false`) + parseSearchRequest(`Patient?identifier=${patientIdentifier}&organization:missing=false`) ); expect(results).toHaveLength(1); expect(results[0]?.id).toEqual(patient.id); // Test array reference column results = await repo.searchResources( - parseSearchDefinition(`Patient?identifier=${patientIdentifier}&general-practitioner:missing=false`) + parseSearchRequest(`Patient?identifier=${patientIdentifier}&general-practitioner:missing=false`) ); expect(results).toHaveLength(1); expect(results[0]?.id).toEqual(patient.id); @@ -1671,7 +1669,7 @@ describe('FHIR Search', () => { // Search chain const searchResult = await repo.search( - parseSearchDefinition( + parseSearchRequest( `Patient?general-practitioner:Practitioner._has:CareTeam:participant:category=${categorySystem}|${code}` ) ); @@ -1706,7 +1704,7 @@ describe('FHIR Search', () => { }); const result = await repo.search( - parseSearchDefinition(`Patient?_has:Observation:subject:encounter:Encounter.class=${code}`) + parseSearchRequest(`Patient?_has:Observation:subject:encounter:Encounter.class=${code}`) ); expect(result.entry?.[0]?.resource?.id).toEqual(patient.id); })); @@ -1715,7 +1713,7 @@ describe('FHIR Search', () => { withTestContext(async () => { await expect(() => repo.search( - parseSearchDefinition( + parseSearchRequest( `Patient?_has:Observation:subject:encounter:Encounter._has:DiagnosticReport:encounter:result.specimen.parent.collected=2023` ) ) @@ -1736,7 +1734,7 @@ describe('FHIR Search', () => { ], ['Patient?_has:Observation:status=active', 'Invalid search chain: _has:Observation:status'], ])('Invalid chained search parameters: %s', (searchString: string, errorMsg: string) => { - return expect(repo.search(parseSearchDefinition(searchString))).rejects.toEqual(new Error(errorMsg)); + return expect(repo.search(parseSearchRequest(searchString))).rejects.toEqual(new Error(errorMsg)); }); test('Include references success', () => @@ -2452,9 +2450,7 @@ describe('FHIR Search', () => { }); const bundle = await repo.search( - parseSearchUrl( - new URL(`https://x/Condition?subject=${getReferenceString(p)}&code:not=x&_count=1&_total=accurate`) - ) + parseSearchRequest(`https://x/Condition?subject=${getReferenceString(p)}&code:not=x&_count=1&_total=accurate`) ); expect(bundle.entry?.length).toEqual(1); @@ -2524,22 +2520,20 @@ describe('FHIR Search', () => { }); // Search with system - const bundle1 = await repo.search( - parseSearchDefinition(`Condition?code=${code}&subject:identifier=mrn|123456`) - ); + const bundle1 = await repo.search(parseSearchRequest(`Condition?code=${code}&subject:identifier=mrn|123456`)); expect(bundle1.entry?.length).toEqual(1); expect(bundleContains(bundle1, c1)).toBeTruthy(); expect(bundleContains(bundle1, c2)).not.toBeTruthy(); // Search without system - const bundle2 = await repo.search(parseSearchDefinition(`Condition?code=${code}&subject:identifier=123456`)); + const bundle2 = await repo.search(parseSearchRequest(`Condition?code=${code}&subject:identifier=123456`)); expect(bundle2.entry?.length).toEqual(2); expect(bundleContains(bundle2, c1)).toBeTruthy(); expect(bundleContains(bundle2, c2)).toBeTruthy(); // Search with count const bundle3 = await repo.search( - parseSearchDefinition(`Condition?code=${code}&subject:identifier=mrn|123456&_total=accurate`) + parseSearchRequest(`Condition?code=${code}&subject:identifier=mrn|123456&_total=accurate`) ); expect(bundle3.entry?.length).toEqual(1); expect(bundle3.total).toBe(1); @@ -2575,7 +2569,7 @@ describe('FHIR Search', () => { // Search by "subject" // This will include both Tasks, because the "subject" search parameter does not care about "type" const bundle1 = await repo.search( - parseSearchDefinition(`Task?subject:identifier=mrn|${identifier}&_total=accurate`) + parseSearchRequest(`Task?subject:identifier=mrn|${identifier}&_total=accurate`) ); expect(bundle1.total).toEqual(2); expect(bundle1.entry?.length).toEqual(2); @@ -2585,7 +2579,7 @@ describe('FHIR Search', () => { // Search by "patient" // This will only include the Task with the explicit "Patient" type, because the "patient" search parameter does care about "type" const bundle2 = await repo.search( - parseSearchDefinition(`Task?patient:identifier=mrn|${identifier}&_total=accurate`) + parseSearchRequest(`Task?patient:identifier=mrn|${identifier}&_total=accurate`) ); expect(bundle2.total).toEqual(1); expect(bundle2.entry?.length).toEqual(1); diff --git a/packages/server/src/oauth/token.test.ts b/packages/server/src/oauth/token.test.ts index 880a508b96..e19bcf1cd8 100644 --- a/packages/server/src/oauth/token.test.ts +++ b/packages/server/src/oauth/token.test.ts @@ -5,7 +5,7 @@ import { OAuthGrantType, OAuthTokenType, parseJWTPayload, - parseSearchDefinition, + parseSearchRequest, } from '@medplum/core'; import { AccessPolicy, ClientApplication, Login, Project, SmartAppLaunch } from '@medplum/fhirtypes'; import { randomUUID } from 'crypto'; @@ -509,7 +509,7 @@ describe('OAuth2 Token', () => { expect(res.status).toBe(200); // Find the login - const loginBundle = await systemRepo.search(parseSearchDefinition('Login?code=' + res.body.code)); + const loginBundle = await systemRepo.search(parseSearchRequest('Login?code=' + res.body.code)); expect(loginBundle.entry).toHaveLength(1); // Revoke the login @@ -865,7 +865,7 @@ describe('OAuth2 Token', () => { expect(res2.body.refresh_token).toBeDefined(); // Find the login - const loginBundle = await systemRepo.search(parseSearchDefinition('Login?code=' + res.body.code)); + const loginBundle = await systemRepo.search(parseSearchRequest('Login?code=' + res.body.code)); expect(loginBundle.entry).toHaveLength(1); // Revoke the login diff --git a/packages/server/src/workers/subscription.ts b/packages/server/src/workers/subscription.ts index 847ccd4b6c..811eb0c731 100644 --- a/packages/server/src/workers/subscription.ts +++ b/packages/server/src/workers/subscription.ts @@ -10,7 +10,7 @@ import { normalizeOperationOutcome, OperationOutcomeError, Operator, - parseSearchUrl, + parseSearchRequest, satisfiedAccessPolicy, serverError, stringify, @@ -19,7 +19,6 @@ import { Bot, Project, ProjectMembership, Reference, Resource, Subscription } fr import { Job, Queue, QueueBaseOptions, Worker } from 'bullmq'; import { createHmac } from 'crypto'; import fetch, { HeadersInit } from 'node-fetch'; -import { URL } from 'url'; import { MedplumServerConfig } from '../config'; import { getRequestContext, RequestContext, requestContextStore } from '../context'; import { buildAccessPolicy } from '../fhir/accesspolicy'; @@ -279,7 +278,7 @@ async function matchesCriteria( return false; } - const searchRequest = parseSearchUrl(new URL(subscriptionCriteria, 'https://api.medplum.com/')); + const searchRequest = parseSearchRequest(subscriptionCriteria); if (resource.resourceType !== searchRequest.resourceType) { ctx.logger.debug( `Ignore rest hook for different resourceType (wanted "${searchRequest.resourceType}", received "${resource.resourceType}")` From 98b943a78e5b1d9ecffb69b8e33ae19be79262e8 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Thu, 22 Feb 2024 16:38:48 -0800 Subject: [PATCH 78/81] Updated AppShell header (#3982) * Updated header prototype * Checkpoint * Tests and cleanup * Fixed initials --- .../src/App.tsx | 35 +++- packages/mock/src/subscription-manager.ts | 2 - .../react/src/AppShell/AppShell.stories.tsx | 55 +++++++ packages/react/src/AppShell/AppShell.tsx | 2 + packages/react/src/AppShell/Header.tsx | 154 ++++-------------- .../react/src/AppShell/HeaderDropdown.tsx | 101 ++++++++++++ .../NotificationIcon.test.tsx | 59 +++++++ .../src/NotificationIcon/NotificationIcon.tsx | 57 +++++++ .../ResourceAvatar/ResourceAvatar.test.tsx | 10 +- .../src/ResourceAvatar/ResourceAvatar.tsx | 12 +- .../ResourceAvatar/ResourceAvatar.utils.ts | 10 ++ packages/react/src/index.ts | 1 + 12 files changed, 373 insertions(+), 125 deletions(-) create mode 100644 packages/react/src/AppShell/HeaderDropdown.tsx create mode 100644 packages/react/src/NotificationIcon/NotificationIcon.test.tsx create mode 100644 packages/react/src/NotificationIcon/NotificationIcon.tsx create mode 100644 packages/react/src/ResourceAvatar/ResourceAvatar.utils.ts diff --git a/examples/medplum-websocket-subscriptions-demo/src/App.tsx b/examples/medplum-websocket-subscriptions-demo/src/App.tsx index 5c976d0b8b..3fd5ce7d3b 100644 --- a/examples/medplum-websocket-subscriptions-demo/src/App.tsx +++ b/examples/medplum-websocket-subscriptions-demo/src/App.tsx @@ -1,5 +1,14 @@ -import { AppShell, ErrorBoundary, Loading, Logo, useMedplum, useMedplumProfile } from '@medplum/react'; -import { IconHome } from '@tabler/icons-react'; +import { ProfileResource, getReferenceString } from '@medplum/core'; +import { + AppShell, + ErrorBoundary, + Loading, + Logo, + NotificationIcon, + useMedplum, + useMedplumProfile, +} from '@medplum/react'; +import { IconClipboardCheck, IconHome, IconMail } from '@tabler/icons-react'; import { Suspense } from 'react'; import { Route, Routes } from 'react-router-dom'; import { HomePage } from './pages/HomePage'; @@ -20,6 +29,28 @@ export function App(): JSX.Element | null { menus={[{ links: [{ label: 'Home', href: '/', icon: }] }]} resourceTypeSearchDisabled={true} headerSearchDisabled={true} + notifications={ + profile && ( + <> + } + onClick={() => console.log('foo')} + /> + } + onClick={() => console.log('foo')} + /> + + ) + } > }> diff --git a/packages/mock/src/subscription-manager.ts b/packages/mock/src/subscription-manager.ts index e849d4bb98..ba69b18607 100644 --- a/packages/mock/src/subscription-manager.ts +++ b/packages/mock/src/subscription-manager.ts @@ -53,8 +53,6 @@ export class MockSubscriptionManager extends SubscriptionManager { this.emitters.get(criteria)?.dispatchEvent(event); } - // Guess this has to do with the unique symbols on the type definition... - // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents getEmitter(criteria: string): SubscriptionEmitter | undefined { return this.emitters.get(criteria); } diff --git a/packages/react/src/AppShell/AppShell.stories.tsx b/packages/react/src/AppShell/AppShell.stories.tsx index dd54822108..e70a4c5888 100644 --- a/packages/react/src/AppShell/AppShell.stories.tsx +++ b/packages/react/src/AppShell/AppShell.stories.tsx @@ -1,14 +1,19 @@ +import { ProfileResource, getReferenceString } from '@medplum/core'; +import { useMedplumProfile } from '@medplum/react-hooks'; import { Meta } from '@storybook/react'; import { Icon2fa, IconBellRinging, + IconClipboardCheck, IconDatabaseImport, IconFingerprint, IconKey, + IconMail, IconReceipt2, IconSettings, } from '@tabler/icons-react'; import { Logo } from '../Logo/Logo'; +import { NotificationIcon } from '../NotificationIcon/NotificationIcon'; import { AppShell } from './AppShell'; import classes from './AppShell.stories.module.css'; @@ -124,3 +129,53 @@ export function DisabledResourceNavigator(): JSX.Element {

); } + +export function NotificationIcons(): JSX.Element { + const profile = useMedplumProfile(); + + return ( +
+ } + version="your.version" + menus={[ + { + title: 'My Menu', + links: [ + { href: '/notifications', label: 'Notifications', icon: }, + { href: '/billing', label: 'Billing', icon: }, + { href: '/security', label: 'Security', icon: }, + { href: '/sshkeys', label: 'SSH Keys', icon: }, + { href: '/databases', label: 'Databases', icon: }, + { href: '/auth', label: 'Authentication', icon: }, + { href: '/settings', label: 'Other Settings', icon: }, + ], + }, + ]} + displayAddBookmark={true} + notifications={ + <> + } + onClick={() => console.log('foo')} + /> + } + onClick={() => console.log('foo')} + /> + + } + > + Your application here + +
+ ); +} diff --git a/packages/react/src/AppShell/AppShell.tsx b/packages/react/src/AppShell/AppShell.tsx index 698b668d39..8dd8c8e7f5 100644 --- a/packages/react/src/AppShell/AppShell.tsx +++ b/packages/react/src/AppShell/AppShell.tsx @@ -18,6 +18,7 @@ export interface AppShellProps { readonly children: ReactNode; readonly displayAddBookmark?: boolean; readonly resourceTypeSearchDisabled?: boolean; + readonly notifications?: ReactNode; } export function AppShell(props: AppShellProps): JSX.Element { @@ -71,6 +72,7 @@ export function AppShell(props: AppShellProps): JSX.Element { logo={props.logo} version={props.version} navbarToggle={toggleNavbar} + notifications={props.notifications} /> )} {profile && navbarOpen ? ( diff --git a/packages/react/src/AppShell/Header.tsx b/packages/react/src/AppShell/Header.tsx index 829fcd903b..0d2ed338d9 100644 --- a/packages/react/src/AppShell/Header.tsx +++ b/packages/react/src/AppShell/Header.tsx @@ -1,24 +1,13 @@ -import { - Avatar, - Group, - AppShell as MantineAppShell, - MantineColorScheme, - Menu, - SegmentedControl, - Stack, - Text, - UnstyledButton, - useMantineColorScheme, -} from '@mantine/core'; -import { ProfileResource, formatHumanName, getReferenceString } from '@medplum/core'; +import { Group, AppShell as MantineAppShell, Menu, Text, UnstyledButton } from '@mantine/core'; +import { formatHumanName } from '@medplum/core'; import { HumanName } from '@medplum/fhirtypes'; -import { useMedplumContext } from '@medplum/react-hooks'; -import { IconChevronDown, IconLogout, IconSettings, IconSwitchHorizontal } from '@tabler/icons-react'; +import { useMedplumProfile } from '@medplum/react-hooks'; +import { IconChevronDown } from '@tabler/icons-react'; import cx from 'clsx'; import { ReactNode, useState } from 'react'; -import { HumanNameDisplay } from '../HumanNameDisplay/HumanNameDisplay'; import { ResourceAvatar } from '../ResourceAvatar/ResourceAvatar'; import classes from './Header.module.css'; +import { HeaderDropdown } from './HeaderDropdown'; import { HeaderSearchInput } from './HeaderSearchInput'; export interface HeaderProps { @@ -28,14 +17,12 @@ export interface HeaderProps { readonly logo: ReactNode; readonly version?: string; readonly navbarToggle: () => void; + readonly notifications?: ReactNode; } export function Header(props: HeaderProps): JSX.Element { - const context = useMedplumContext(); - const { medplum, profile, navigate } = context; - const logins = medplum.getLogins(); + const profile = useMedplumProfile(); const [userMenuOpened, setUserMenuOpened] = useState(false); - const { colorScheme, setColorScheme } = useMantineColorScheme(); return ( @@ -48,104 +35,35 @@ export function Header(props: HeaderProps): JSX.Element { )} - - setUserMenuOpened(false)} - > - - setUserMenuOpened((o) => !o)} - > - - - - {formatHumanName(profile?.name?.[0] as HumanName)} - - - - - - - - - - - {medplum.getActiveLogin()?.project.display} - - - {logins.length > 1 && } - {logins.map( - (login) => - login.profile.reference !== getReferenceString(context.profile as ProfileResource) && ( - { - medplum - .setActiveLogin(login) - .then(() => window.location.reload()) - .catch(console.log); - }} - > - - -
- - {login.profile.display} - - - {login.project.display} - -
-
-
- ) - )} - - - setColorScheme(newValue as MantineColorScheme)} - data={[ - { label: 'Light', value: 'light' }, - { label: 'Dark', value: 'dark' }, - { label: 'Auto', value: 'auto' }, - ]} - /> - - - } - onClick={() => navigate('/signin')} - > - Add another account - - } - onClick={() => navigate(`/${getReferenceString(profile as ProfileResource)}`)} - > - Account settings - - } - onClick={async () => { - await medplum.signOut(); - navigate('/signin'); - }} - > - Sign out - - - {props.version} - -
-
+ + {props.notifications} + setUserMenuOpened(false)} + > + + setUserMenuOpened((o) => !o)} + > + + + + {formatHumanName(profile?.name?.[0] as HumanName)} + + + + + + + + + +
); diff --git a/packages/react/src/AppShell/HeaderDropdown.tsx b/packages/react/src/AppShell/HeaderDropdown.tsx new file mode 100644 index 0000000000..af8a999f4d --- /dev/null +++ b/packages/react/src/AppShell/HeaderDropdown.tsx @@ -0,0 +1,101 @@ +import { + Avatar, + Group, + MantineColorScheme, + Menu, + SegmentedControl, + Stack, + Text, + useMantineColorScheme, +} from '@mantine/core'; +import { ProfileResource, getReferenceString } from '@medplum/core'; +import { HumanName } from '@medplum/fhirtypes'; +import { useMedplumContext } from '@medplum/react-hooks'; +import { IconLogout, IconSettings, IconSwitchHorizontal } from '@tabler/icons-react'; +import { HumanNameDisplay } from '../HumanNameDisplay/HumanNameDisplay'; +import { ResourceAvatar } from '../ResourceAvatar/ResourceAvatar'; + +export interface HeaderDropdownProps { + readonly version?: string; +} + +export function HeaderDropdown(props: HeaderDropdownProps): JSX.Element { + const context = useMedplumContext(); + const { medplum, profile, navigate } = context; + const logins = medplum.getLogins(); + const { colorScheme, setColorScheme } = useMantineColorScheme(); + + return ( + <> + + + + + {medplum.getActiveLogin()?.project.display} + + + {logins.length > 1 && } + {logins.map( + (login) => + login.profile.reference !== getReferenceString(context.profile as ProfileResource) && ( + { + medplum + .setActiveLogin(login) + .then(() => window.location.reload()) + .catch(console.log); + }} + > + + +
+ + {login.profile.display} + + + {login.project.display} + +
+
+
+ ) + )} + + + setColorScheme(newValue as MantineColorScheme)} + data={[ + { label: 'Light', value: 'light' }, + { label: 'Dark', value: 'dark' }, + { label: 'Auto', value: 'auto' }, + ]} + /> + + + } onClick={() => navigate('/signin')}> + Add another account + + } + onClick={() => navigate(`/${getReferenceString(profile as ProfileResource)}`)} + > + Account settings + + } + onClick={async () => { + await medplum.signOut(); + navigate('/signin'); + }} + > + Sign out + + + {props.version} + + + ); +} diff --git a/packages/react/src/NotificationIcon/NotificationIcon.test.tsx b/packages/react/src/NotificationIcon/NotificationIcon.test.tsx new file mode 100644 index 0000000000..f9f8b34b23 --- /dev/null +++ b/packages/react/src/NotificationIcon/NotificationIcon.test.tsx @@ -0,0 +1,59 @@ +import { Communication } from '@medplum/fhirtypes'; +import { MockClient } from '@medplum/mock'; +import { MedplumProvider } from '@medplum/react-hooks'; +import { IconMail } from '@tabler/icons-react'; +import 'jest-websocket-mock'; +import { act, render, screen, waitFor } from '../test-utils/render'; +import { NotificationIcon } from './NotificationIcon'; + +describe('NotificationIcon()', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + test('Criteria changed', async () => { + const medplum = new MockClient(); + + render( + + } + onClick={() => console.log('foo')} + /> + + ); + + // On first render, the count should be zero, so no indicator should be shown + expect(screen.queryByText('0')).not.toBeInTheDocument(); + expect(screen.queryByText('1')).not.toBeInTheDocument(); + + // Create a communication + const communication = await medplum.createResource({ + resourceType: 'Communication', + status: 'in-progress', + recipient: [{ reference: 'Practitioner/123' }], + }); + + // Emulate the server sending a message + await act(async () => { + medplum.getSubscriptionManager().emitEventForCriteria<'message'>('Communication?recipient=Practitioner/123', { + type: 'message', + payload: { resourceType: 'Bundle', id: communication.id, type: 'history' }, + }); + }); + + // Wait for the indicator to change + await waitFor(() => screen.getByText('1')); + + // After emitting a message, the count should be 1, and the indicator should be shown + expect(screen.getByText('1')).toBeInTheDocument(); + }); +}); diff --git a/packages/react/src/NotificationIcon/NotificationIcon.tsx b/packages/react/src/NotificationIcon/NotificationIcon.tsx new file mode 100644 index 0000000000..877fee35f4 --- /dev/null +++ b/packages/react/src/NotificationIcon/NotificationIcon.tsx @@ -0,0 +1,57 @@ +import { ActionIcon, Indicator, Tooltip } from '@mantine/core'; +import { ResourceType } from '@medplum/fhirtypes'; +import { useMedplum, useSubscription } from '@medplum/react-hooks'; +import { useCallback, useEffect, useState } from 'react'; + +export interface NotificationIconProps { + readonly iconComponent: JSX.Element; + readonly label: string; + readonly resourceType: ResourceType; + readonly countCriteria: string; + readonly subscriptionCriteria: string; + readonly onClick: () => void; +} + +export function NotificationIcon(props: NotificationIconProps): JSX.Element { + const medplum = useMedplum(); + const { label, resourceType, countCriteria, subscriptionCriteria, onClick } = props; + const [unreadCount, setUnreadCount] = useState(0); + + const updateCount = useCallback( + (cache: 'default' | 'reload') => { + medplum + .search(resourceType, countCriteria, { cache }) + .then((result) => setUnreadCount(result.total as number)) + .catch(console.error); + }, + [medplum, resourceType, countCriteria] + ); + + // Initial count + useEffect(() => { + // Cache=default to use the default cache policy, and accept most recent data + updateCount('default'); + }, [updateCount]); + + // Subscribe to the criteria + useSubscription(subscriptionCriteria, () => { + // Cache=reload to force a reload + updateCount('reload'); + }); + + const icon = ( + + + {props.iconComponent} + + + ); + + return unreadCount > 0 ? ( + + {icon} + + ) : ( + icon + ); +} diff --git a/packages/react/src/ResourceAvatar/ResourceAvatar.test.tsx b/packages/react/src/ResourceAvatar/ResourceAvatar.test.tsx index 27ce46c5d8..9c9756220a 100644 --- a/packages/react/src/ResourceAvatar/ResourceAvatar.test.tsx +++ b/packages/react/src/ResourceAvatar/ResourceAvatar.test.tsx @@ -1,9 +1,10 @@ import { createReference } from '@medplum/core'; import { HomerSimpson, MockClient } from '@medplum/mock'; import { MedplumProvider } from '@medplum/react-hooks'; -import { act, render, screen, waitFor } from '../test-utils/render'; import { MemoryRouter } from 'react-router-dom'; +import { act, render, screen, waitFor } from '../test-utils/render'; import { ResourceAvatar, ResourceAvatarProps } from './ResourceAvatar'; +import { getInitials } from './ResourceAvatar.utils'; const medplum = new MockClient(); @@ -65,4 +66,11 @@ describe('ResourceAvatar', () => { expect(screen.getByAltText('Homer Simpson')).toBeDefined(); }); + + test('getInitials', () => { + expect(getInitials('Homer Simpson')).toEqual('HS'); + expect(getInitials('Homer')).toEqual('H'); + expect(getInitials('Homer J Simpson')).toEqual('HS'); + expect(getInitials('')).toEqual(''); + }); }); diff --git a/packages/react/src/ResourceAvatar/ResourceAvatar.tsx b/packages/react/src/ResourceAvatar/ResourceAvatar.tsx index 4bb3b8871d..a748f98c96 100644 --- a/packages/react/src/ResourceAvatar/ResourceAvatar.tsx +++ b/packages/react/src/ResourceAvatar/ResourceAvatar.tsx @@ -3,6 +3,7 @@ import { getDisplayString, getImageSrc } from '@medplum/core'; import { Reference, Resource } from '@medplum/fhirtypes'; import { useCachedBinaryUrl, useResource } from '@medplum/react-hooks'; import { MedplumLink } from '../MedplumLink/MedplumLink'; +import { getInitials } from './ResourceAvatar.utils'; export interface ResourceAvatarProps extends AvatarProps { readonly value?: Reference | Resource; @@ -12,6 +13,7 @@ export interface ResourceAvatarProps extends AvatarProps { export function ResourceAvatar(props: ResourceAvatarProps): JSX.Element { const resource = useResource(props.value); const text = resource ? getDisplayString(resource) : props.alt ?? ''; + const initials = getInitials(text); const uncachedImageUrl = (resource && getImageSrc(resource)) ?? props.src; const imageUrl = useCachedBinaryUrl(uncachedImageUrl ?? undefined); const radius = props.radius ?? 'xl'; @@ -23,10 +25,16 @@ export function ResourceAvatar(props: ResourceAvatarProps): JSX.Element { if (props.link) { return ( - + + {initials} + ); } - return ; + return ( + + {initials} + + ); } diff --git a/packages/react/src/ResourceAvatar/ResourceAvatar.utils.ts b/packages/react/src/ResourceAvatar/ResourceAvatar.utils.ts new file mode 100644 index 0000000000..f347e4e4f5 --- /dev/null +++ b/packages/react/src/ResourceAvatar/ResourceAvatar.utils.ts @@ -0,0 +1,10 @@ +export function getInitials(input: string): string { + const words = input.split(' ').filter(Boolean); + if (words.length > 1) { + return words[0][0] + words[words.length - 1][0]; + } + if (words.length === 1) { + return words[0][0]; + } + return ''; +} diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 7c717f8c3c..b2a992ce18 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -51,6 +51,7 @@ export * from './MedplumLink/MedplumLink'; export * from './MoneyDisplay/MoneyDisplay'; export * from './MoneyInput/MoneyInput'; export * from './NoteDisplay/NoteDisplay'; +export * from './NotificationIcon/NotificationIcon'; export * from './OperationOutcomeAlert/OperationOutcomeAlert'; export * from './Panel/Panel'; export * from './PatientSummary/PatientSummary'; From f39e8c9437c77a46d0f8afd2bc3d85e441543c26 Mon Sep 17 00:00:00 2001 From: Duong Tran Date: Sat, 24 Feb 2024 00:08:33 +0700 Subject: [PATCH 79/81] fix: typos Medplum instead of Mepdlum (#4022) --- packages/docs/docs/fhir-datastore/resource-history.md | 2 +- packages/react-hooks/README.md | 2 +- .../src/MedplumProvider/MedplumProvider.context.ts | 6 +++--- .../react-hooks/src/MedplumProvider/MedplumProvider.tsx | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/docs/docs/fhir-datastore/resource-history.md b/packages/docs/docs/fhir-datastore/resource-history.md index fabc9a1226..86595f84e0 100644 --- a/packages/docs/docs/fhir-datastore/resource-history.md +++ b/packages/docs/docs/fhir-datastore/resource-history.md @@ -40,7 +40,7 @@ The history of a resouce can also be viewed by accessing the `/_history` endpoin To access the `/_history` endpoint, make a GET request to the url of the desired resource or resource type. -The Mepdlum SDK also provides the `readHistory` helper function to access the `/_history` endpoint. +The Medplum SDK also provides the `readHistory` helper function to access the `/_history` endpoint. diff --git a/packages/react-hooks/README.md b/packages/react-hooks/README.md index b7bd4107b3..5a2707376f 100644 --- a/packages/react-hooks/README.md +++ b/packages/react-hooks/README.md @@ -63,7 +63,7 @@ export function MyComponent() { ```ts interface MedplumContext { medplum: MedplumClient; - navigate: MepdlumNavigateFunction; + navigate: MedplumNavigateFunction; profile?: ProfileResource; loading: boolean; } diff --git a/packages/react-hooks/src/MedplumProvider/MedplumProvider.context.ts b/packages/react-hooks/src/MedplumProvider/MedplumProvider.context.ts index 14112b0d28..02bf020146 100644 --- a/packages/react-hooks/src/MedplumProvider/MedplumProvider.context.ts +++ b/packages/react-hooks/src/MedplumProvider/MedplumProvider.context.ts @@ -3,11 +3,11 @@ import { createContext, useContext } from 'react'; export const reactContext = createContext(undefined as MedplumContext | undefined); -export type MepdlumNavigateFunction = (path: string) => void; +export type MedplumNavigateFunction = (path: string) => void; export interface MedplumContext { medplum: MedplumClient; - navigate: MepdlumNavigateFunction; + navigate: MedplumNavigateFunction; profile?: ProfileResource; loading: boolean; } @@ -33,7 +33,7 @@ export function useMedplum(): MedplumClient { * Returns the Medplum navigate function. * @returns The Medplum navigate function. */ -export function useMedplumNavigate(): MepdlumNavigateFunction { +export function useMedplumNavigate(): MedplumNavigateFunction { return useMedplumContext().navigate; } diff --git a/packages/react-hooks/src/MedplumProvider/MedplumProvider.tsx b/packages/react-hooks/src/MedplumProvider/MedplumProvider.tsx index 06672f238e..52e71dbb8c 100644 --- a/packages/react-hooks/src/MedplumProvider/MedplumProvider.tsx +++ b/packages/react-hooks/src/MedplumProvider/MedplumProvider.tsx @@ -1,10 +1,10 @@ import { MedplumClient } from '@medplum/core'; import { ReactNode, useEffect, useMemo, useState } from 'react'; -import { MepdlumNavigateFunction, reactContext } from './MedplumProvider.context'; +import { MedplumNavigateFunction, reactContext } from './MedplumProvider.context'; export interface MedplumProviderProps { readonly medplum: MedplumClient; - readonly navigate?: MepdlumNavigateFunction; + readonly navigate?: MedplumNavigateFunction; readonly children: ReactNode; } From d963867722ce94e0993a079087f1ab34fa669b9a Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Fri, 23 Feb 2024 09:09:01 -0800 Subject: [PATCH 80/81] Server test setup cleanup (#4019) * Server test setup cleanup * Fixed getLogger imports * Cleanup * Bring back withTransaction --- packages/server/src/admin/invite.ts | 13 +- packages/server/src/admin/super.test.ts | 5 +- packages/server/src/agent/websockets.test.ts | 2 +- packages/server/src/app.ts | 8 +- packages/server/src/auth/login.test.ts | 2 +- packages/server/src/auth/login.ts | 4 +- packages/server/src/auth/utils.ts | 14 +- packages/server/src/context.test.ts | 60 ++++++ packages/server/src/context.ts | 46 ++-- packages/server/src/fhir/accesspolicy.test.ts | 45 ++-- packages/server/src/fhir/job.test.ts | 2 +- packages/server/src/fhir/lookups/token.ts | 8 +- .../server/src/fhir/operations/csv.test.ts | 2 +- .../server/src/fhir/operations/execute.ts | 4 +- .../server/src/fhir/operations/expand.test.ts | 4 +- .../src/fhir/operations/expunge.test.ts | 4 +- .../server/src/fhir/operations/groupexport.ts | 4 +- .../src/fhir/operations/projectclone.test.ts | 16 +- .../src/fhir/operations/projectclone.ts | 16 +- .../src/fhir/operations/resourcegraph.test.ts | 2 +- .../src/fhir/operations/resourcegraph.ts | 4 +- .../structuredefinitionexpandprofile.test.ts | 11 +- .../src/fhir/operations/utils/parameters.ts | 4 +- packages/server/src/fhir/repo.test.ts | 76 ++----- packages/server/src/fhir/repo.ts | 6 +- packages/server/src/fhir/rewrite.ts | 4 +- packages/server/src/fhir/search.test.ts | 7 +- packages/server/src/fhir/transaction.test.ts | 13 +- .../server/src/fhircast/websocket.test.ts | 2 +- packages/server/src/oauth/authorize.test.ts | 2 +- packages/server/src/oauth/authorize.ts | 4 +- packages/server/src/oauth/middleware.test.ts | 2 +- packages/server/src/oauth/token.test.ts | 8 +- packages/server/src/test.setup.ts | 203 +++++++++++------- packages/server/src/workers/cron.test.ts | 2 +- packages/server/src/workers/cron.ts | 18 +- packages/server/src/workers/download.test.ts | 9 +- packages/server/src/workers/download.ts | 27 ++- .../server/src/workers/subscription.test.ts | 24 ++- packages/server/src/workers/subscription.ts | 35 ++- packages/server/src/workers/utils.ts | 6 +- 41 files changed, 389 insertions(+), 339 deletions(-) create mode 100644 packages/server/src/context.test.ts diff --git a/packages/server/src/admin/invite.ts b/packages/server/src/admin/invite.ts index 944775f7f0..cf7b5bf725 100644 --- a/packages/server/src/admin/invite.ts +++ b/packages/server/src/admin/invite.ts @@ -16,7 +16,7 @@ import Mail from 'nodemailer/lib/mailer'; import { resetPassword } from '../auth/resetpassword'; import { bcryptHashPassword, createProfile, createProjectMembership } from '../auth/utils'; import { getConfig } from '../config'; -import { getAuthenticatedContext } from '../context'; +import { getAuthenticatedContext, getLogger } from '../context'; import { sendEmail } from '../email/email'; import { getSystemRepo, Repository } from '../fhir/repo'; import { sendResponse } from '../fhir/response'; @@ -65,7 +65,8 @@ export interface ServerInviteResponse { export async function inviteUser(request: ServerInviteRequest): Promise { const systemRepo = getSystemRepo(); - const ctx = getAuthenticatedContext(); + const logger = getLogger(); + if (request.email) { request.email = request.email.toLowerCase(); } @@ -86,15 +87,15 @@ export async function inviteUser(request: ServerInviteRequest): Promise { await initApp(app, config); requestContextStore.enterWith(AuthenticatedRequestContext.system()); - ({ project, client } = await createTestProject()); + ({ project, client } = await createTestProject({ withClient: true, superAdmin: true })); const systemRepo = getSystemRepo(); - // Mark the project as a "Super Admin" project - await systemRepo.updateResource({ ...project, superAdmin: true }); - const practitioner1 = await systemRepo.createResource({ resourceType: 'Practitioner' }); const practitioner2 = await systemRepo.createResource({ resourceType: 'Practitioner' }); diff --git a/packages/server/src/agent/websockets.test.ts b/packages/server/src/agent/websockets.test.ts index b4db207492..eec0385fc0 100644 --- a/packages/server/src/agent/websockets.test.ts +++ b/packages/server/src/agent/websockets.test.ts @@ -21,7 +21,7 @@ describe('Agent WebSockets', () => { config.vmContextBotsEnabled = true; server = await initApp(app, config); - accessToken = await initTestAuth({}, { admin: true }); + accessToken = await initTestAuth({ membership: { admin: true } }); await new Promise((resolve) => { server.listen(0, 'localhost', 511, resolve); diff --git a/packages/server/src/app.ts b/packages/server/src/app.ts index b8880682bc..1e037a5cde 100644 --- a/packages/server/src/app.ts +++ b/packages/server/src/app.ts @@ -15,6 +15,7 @@ import { attachRequestContext, AuthenticatedRequestContext, closeRequestContext, + getLogger, getRequestContext, requestContextStore, } from './context'; @@ -31,7 +32,6 @@ import { fhircastSTU2Router, fhircastSTU3Router } from './fhircast/routes'; import { healthcheckHandler } from './healthcheck'; import { cleanupHeartbeat, initHeartbeat } from './heartbeat'; import { hl7BodyParser } from './hl7/parser'; -import { globalLogger } from './logger'; import { initKeys } from './oauth/keys'; import { oauthRouter } from './oauth/routes'; import { openApiHandler } from './openapi'; @@ -124,11 +124,7 @@ function errorHandler(err: any, req: Request, res: Response, next: NextFunction) sendOutcome(res, badRequest('File too large')); return; } - try { - getRequestContext().logger.error('Unhandled error', err); - } catch { - globalLogger.error('Unhandled error', err); - } + getLogger().error('Unhandled error', err); res.status(500).json({ msg: 'Internal Server Error' }); } diff --git a/packages/server/src/auth/login.test.ts b/packages/server/src/auth/login.test.ts index 34ecfa822a..c3a5f01b88 100644 --- a/packages/server/src/auth/login.test.ts +++ b/packages/server/src/auth/login.test.ts @@ -32,7 +32,7 @@ describe('Login', () => { await initApp(app, config); // Create a test project - ({ project, client } = await createTestProject()); + ({ project, client } = await createTestProject({ withClient: true })); // Create a test user const { user } = await inviteUser({ diff --git a/packages/server/src/auth/login.ts b/packages/server/src/auth/login.ts index 18d328d8d5..ceb623b72d 100644 --- a/packages/server/src/auth/login.ts +++ b/packages/server/src/auth/login.ts @@ -2,10 +2,10 @@ import { ResourceType } from '@medplum/fhirtypes'; import { randomUUID } from 'crypto'; import { Request, Response } from 'express'; import { body } from 'express-validator'; +import { getLogger } from '../context'; import { tryLogin } from '../oauth/utils'; import { makeValidationMiddleware } from '../util/validator'; import { getProjectIdByClientId, sendLoginResult } from './utils'; -import { getRequestContext } from '../context'; export const loginValidator = makeValidationMiddleware([ body('email').isEmail().withMessage('Valid email address is required'), @@ -43,7 +43,7 @@ export async function loginHandler(req: Request, res: Response): Promise { allowNoMembership: req.body.projectId === 'new', }); - getRequestContext().logger.info('Login success', { email: req.body.email, projectId }); + getLogger().info('Login success', { email: req.body.email, projectId }); await sendLoginResult(res, login); } diff --git a/packages/server/src/auth/utils.ts b/packages/server/src/auth/utils.ts index 4b9a567e7a..b1cad56ab5 100644 --- a/packages/server/src/auth/utils.ts +++ b/packages/server/src/auth/utils.ts @@ -21,7 +21,7 @@ import { Handler, NextFunction, Request, Response } from 'express'; import fetch from 'node-fetch'; import { createHash } from 'node:crypto'; import { getConfig } from '../config'; -import { getRequestContext } from '../context'; +import { getLogger } from '../context'; import { sendOutcome } from '../fhir/outcomes'; import { getSystemRepo } from '../fhir/repo'; import { rewriteAttachments, RewriteMode } from '../fhir/rewrite'; @@ -34,8 +34,8 @@ export async function createProfile( lastName: string, email: string | undefined ): Promise { - const ctx = getRequestContext(); - ctx.logger.info('Creating profile', { resourceType, firstName, lastName }); + const logger = getLogger(); + logger.info('Creating profile', { resourceType, firstName, lastName }); let telecom: ContactPoint[] | undefined = undefined; let photo: Attachment[] | undefined = undefined; if (email) { @@ -58,7 +58,7 @@ export async function createProfile( telecom, photo, } as ProfileResource); - ctx.logger.info('Created profile', { id: result.id }); + logger.info('Created profile', { id: result.id }); return result; } @@ -68,8 +68,8 @@ export async function createProjectMembership( profile: ProfileResource, details?: Partial ): Promise { - const ctx = getRequestContext(); - ctx.logger.info('Creating project membership', { name: project.name }); + const logger = getLogger(); + logger.info('Creating project membership', { name: project.name }); const systemRepo = getSystemRepo(); const result = await systemRepo.createResource({ @@ -79,7 +79,7 @@ export async function createProjectMembership( user: createReference(user), profile: createReference(profile), }); - ctx.logger.info('Created project memberships', { id: result.id }); + logger.info('Created project memberships', { id: result.id }); return result; } diff --git a/packages/server/src/context.test.ts b/packages/server/src/context.test.ts new file mode 100644 index 0000000000..dec5a68794 --- /dev/null +++ b/packages/server/src/context.test.ts @@ -0,0 +1,60 @@ +import { Request } from 'express'; +import { + RequestContext, + getAuthenticatedContext, + getRequestContext, + getTraceId, + requestContextStore, + tryGetRequestContext, + tryRunInRequestContext, +} from './context'; +import { withTestContext } from './test.setup'; + +describe('RequestContext', () => { + test('tryGetRequestContext', async () => { + expect(tryGetRequestContext()).toBeUndefined(); + withTestContext(() => expect(tryGetRequestContext()).toBeDefined()); + }); + + test('getRequestContext', () => { + expect(() => getRequestContext()).toThrow('No request context available'); + withTestContext(() => expect(getRequestContext()).toBeDefined()); + }); + + test('getAuthenticatedContext', () => { + expect(() => getAuthenticatedContext()).toThrow('No request context available'); + requestContextStore.run(new RequestContext('request', 'trace'), () => { + expect(() => getAuthenticatedContext()).toThrow('Request is not authenticated'); + }); + withTestContext(() => expect(getAuthenticatedContext()).toBeDefined()); + }); + + test('tryRunInRequestContext', () => { + tryRunInRequestContext(undefined, undefined, () => { + expect(tryGetRequestContext()).toBeUndefined(); + }); + tryRunInRequestContext('request', 'trace', () => { + expect(tryGetRequestContext()).toBeDefined(); + }); + }); + + test('getTraceId', () => { + expect(getTraceId(mockRequest({}))).toBeUndefined(); + expect(getTraceId(mockRequest({ 'x-trace-id': 'foo' }))).toBeUndefined(); + expect(getTraceId(mockRequest({ traceparent: 'foo' }))).toBeUndefined(); + + const uuid = '00000000-0000-0000-0000-000000000000'; + expect(getTraceId(mockRequest({ 'x-trace-id': uuid }))).toEqual(uuid); + + const tpid = '00-12345678901234567890123456789012-3456789012345678-01'; + expect(getTraceId(mockRequest({ traceparent: tpid }))).toEqual(tpid); + }); +}); + +function mockRequest(headers: Record): Request { + return { + header(name: string): string | undefined { + return headers[name]; + }, + } as unknown as Request; +} diff --git a/packages/server/src/context.ts b/packages/server/src/context.ts index a29716fbad..890357221b 100644 --- a/packages/server/src/context.ts +++ b/packages/server/src/context.ts @@ -28,6 +28,8 @@ export class RequestContext { } } +const systemLogger = new Logger(write, undefined, LogLevel.ERROR); + export class AuthenticatedRequestContext extends RequestContext { readonly repo: Repository; readonly project: Project; @@ -60,7 +62,6 @@ export class AuthenticatedRequestContext extends RequestContext { } static system(ctx?: { requestId?: string; traceId?: string }): AuthenticatedRequestContext { - const systemLogger = new Logger(write, undefined, LogLevel.ERROR); return new AuthenticatedRequestContext( new RequestContext(ctx?.requestId ?? '', ctx?.traceId ?? ''), {} as unknown as Login, @@ -74,6 +75,10 @@ export class AuthenticatedRequestContext extends RequestContext { export const requestContextStore = new AsyncLocalStorage(); +export function tryGetRequestContext(): RequestContext | undefined { + return requestContextStore.getStore(); +} + export function getRequestContext(): RequestContext { const ctx = requestContextStore.getStore(); if (!ctx) { @@ -102,23 +107,32 @@ export function closeRequestContext(): void { } } -const traceIdHeaderMap: { - [key: string]: (traceId: string) => boolean; -} = { - 'x-trace-id': (value) => isUUID(value), - traceparent: (value) => !!parseTraceparent(value), -} as const; -const traceIdHeaders = Object.entries(traceIdHeaderMap); - -const getTraceId = (req: Request): string | undefined => { - for (const [headerKey, isTraceId] of traceIdHeaders) { - const value = req.header(headerKey); - if (value && isTraceId(value)) { - return value; - } +export function getLogger(): Logger { + const ctx = requestContextStore.getStore(); + return ctx ? ctx.logger : systemLogger; +} + +export function tryRunInRequestContext(requestId: string | undefined, traceId: string | undefined, fn: () => T): T { + if (requestId && traceId) { + return requestContextStore.run(new RequestContext(requestId, traceId), fn); + } else { + return fn(); } +} + +export function getTraceId(req: Request): string | undefined { + const xTraceId = req.header('x-trace-id'); + if (xTraceId && isUUID(xTraceId)) { + return xTraceId; + } + + const traceparent = req.header('traceparent'); + if (traceparent && parseTraceparent(traceparent)) { + return traceparent; + } + return undefined; -}; +} function requestIds(req: Request): { requestId: string; traceId: string } { const requestId = randomUUID(); diff --git a/packages/server/src/fhir/accesspolicy.test.ts b/packages/server/src/fhir/accesspolicy.test.ts index e85b69600a..4a91ab7e8d 100644 --- a/packages/server/src/fhir/accesspolicy.test.ts +++ b/packages/server/src/fhir/accesspolicy.test.ts @@ -29,7 +29,7 @@ import { inviteUser } from '../admin/invite'; import { initAppServices, shutdownApp } from '../app'; import { registerNew } from '../auth/register'; import { loadTestConfig } from '../config'; -import { withTestContext } from '../test.setup'; +import { createTestProject, withTestContext } from '../test.setup'; import { getRepoForLogin } from './accesspolicy'; import { getSystemRepo, Repository } from './repo'; @@ -42,11 +42,8 @@ describe('AccessPolicy', () => { await initAppServices(config); }); - beforeEach(() => { - testProject = { - resourceType: 'Project', - id: randomUUID(), - }; + beforeEach(async () => { + testProject = (await createTestProject()).project; }); afterAll(async () => { @@ -608,27 +605,22 @@ describe('AccessPolicy', () => { test('Multiple entries per resource type', () => withTestContext(async () => { - const accessPolicy: AccessPolicy = { - resourceType: 'AccessPolicy', - resource: [ - { - resourceType: 'ServiceRequest', - criteria: `ServiceRequest?status=active`, - }, - { - resourceType: 'ServiceRequest', - criteria: `ServiceRequest?status=completed`, - readonly: true, - }, - ], - }; - - const repo = new Repository({ - extendedMode: true, - author: { - reference: 'Practitioner/123', + const { repo } = await createTestProject({ + withRepo: true, + accessPolicy: { + resourceType: 'AccessPolicy', + resource: [ + { + resourceType: 'ServiceRequest', + criteria: `ServiceRequest?status=active`, + }, + { + resourceType: 'ServiceRequest', + criteria: `ServiceRequest?status=completed`, + readonly: true, + }, + ], }, - accessPolicy: accessPolicy, }); // User can create a ServiceRequest with status=active @@ -1527,6 +1519,7 @@ describe('AccessPolicy', () => { const membership = await systemRepo.createResource({ resourceType: 'ProjectMembership', + meta: { project: testProject.id }, user: { reference: 'User/' + randomUUID() }, project: { reference: 'Project/' + testProject.id }, profile: { reference: 'Practitioner/' + randomUUID() }, diff --git a/packages/server/src/fhir/job.test.ts b/packages/server/src/fhir/job.test.ts index 7b990293c9..b391dfe45d 100644 --- a/packages/server/src/fhir/job.test.ts +++ b/packages/server/src/fhir/job.test.ts @@ -24,7 +24,7 @@ describe('Job status', () => { }); beforeEach(async () => { - const testProject = await createTestProject(); + const testProject = await createTestProject({ withAccessToken: true }); accessToken = testProject.accessToken; asyncJobManager = new AsyncJobExecutor( new Repository({ diff --git a/packages/server/src/fhir/lookups/token.ts b/packages/server/src/fhir/lookups/token.ts index 1360622de7..5ed1f49087 100644 --- a/packages/server/src/fhir/lookups/token.ts +++ b/packages/server/src/fhir/lookups/token.ts @@ -22,7 +22,7 @@ import { SearchParameter, } from '@medplum/fhirtypes'; import { PoolClient } from 'pg'; -import { getRequestContext } from '../../context'; +import { getLogger } from '../../context'; import { Column, Condition, Conjunction, Disjunction, Exists, Expression, Negation, SelectQuery } from '../sql'; import { LookupTable } from './lookuptable'; import { compareArrays, deriveIdentifierSearchParameter } from './util'; @@ -483,17 +483,15 @@ function buildWhereCondition(tableName: string, operator: FhirOperator, query: s } function buildValueCondition(tableName: string, operator: FhirOperator, value: string): Expression { - const ctx = getRequestContext(); - const column = new Column(tableName, 'value'); if (operator === FhirOperator.TEXT) { - ctx.logger.warn('Potentially expensive token lookup query', { operator }); + getLogger().warn('Potentially expensive token lookup query', { operator }); return new Conjunction([ new Condition(new Column(tableName, 'system'), '=', 'text'), new Condition(column, 'TSVECTOR_SIMPLE', value.trim() + ':*'), ]); } else if (operator === FhirOperator.CONTAINS) { - ctx.logger.warn('Potentially expensive token lookup query', { operator }); + getLogger().warn('Potentially expensive token lookup query', { operator }); return new Condition(column, 'LIKE', value.trim() + '%'); } else { return new Condition(column, '=', value.trim()); diff --git a/packages/server/src/fhir/operations/csv.test.ts b/packages/server/src/fhir/operations/csv.test.ts index b2c2a2356d..0bd0cb8a19 100644 --- a/packages/server/src/fhir/operations/csv.test.ts +++ b/packages/server/src/fhir/operations/csv.test.ts @@ -14,7 +14,7 @@ describe('CSV Export', () => { beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); - accessToken = await initTestAuth({ strictMode: false }); + accessToken = await initTestAuth({ project: { strictMode: false } }); }); afterAll(async () => { diff --git a/packages/server/src/fhir/operations/execute.ts b/packages/server/src/fhir/operations/execute.ts index 277772e43e..e02b65e009 100644 --- a/packages/server/src/fhir/operations/execute.ts +++ b/packages/server/src/fhir/operations/execute.ts @@ -33,7 +33,7 @@ import vm from 'node:vm'; import { TextDecoder, TextEncoder } from 'util'; import { asyncWrap } from '../../async'; import { getConfig } from '../../config'; -import { getAuthenticatedContext, getRequestContext } from '../../context'; +import { getAuthenticatedContext, getLogger } from '../../context'; import { generateAccessToken } from '../../oauth/keys'; import { recordHistogramValue } from '../../otel/otel'; import { AuditEventOutcome, logAuditEvent } from '../../util/auditevent'; @@ -248,7 +248,7 @@ async function writeBotInputToStorage(request: BotExecutionRequest): Promise { beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); - accessToken = await initTestAuth({ features: ['terminology'] }); + accessToken = await initTestAuth({ project: { features: ['terminology'] } }); }); afterAll(async () => { diff --git a/packages/server/src/fhir/operations/expunge.test.ts b/packages/server/src/fhir/operations/expunge.test.ts index 1f8ecda15c..5b9344eee2 100644 --- a/packages/server/src/fhir/operations/expunge.test.ts +++ b/packages/server/src/fhir/operations/expunge.test.ts @@ -66,7 +66,7 @@ describe('Expunge', () => { }); test('Expunge project compartment', async () => { - const { project, client, membership } = await createTestProject(); + const { project, client, membership } = await createTestProject({ withClient: true }); expect(project).toBeDefined(); expect(client).toBeDefined(); expect(membership).toBeDefined(); @@ -105,7 +105,7 @@ describe('Expunge', () => { test('Expunger.expunge() expunges all resource types', async () => { //setup - const { project, client, membership } = await createTestProject(); + const { project, client, membership } = await createTestProject({ withClient: true }); expect(project).toBeDefined(); expect(client).toBeDefined(); expect(membership).toBeDefined(); diff --git a/packages/server/src/fhir/operations/groupexport.ts b/packages/server/src/fhir/operations/groupexport.ts index 4329bccb69..8d64c3a610 100644 --- a/packages/server/src/fhir/operations/groupexport.ts +++ b/packages/server/src/fhir/operations/groupexport.ts @@ -2,11 +2,11 @@ import { accepted } from '@medplum/core'; import { Group, Patient, Project } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { getConfig } from '../../config'; +import { getAuthenticatedContext, getLogger } from '../../context'; import { sendOutcome } from '../outcomes'; import { Repository } from '../repo'; import { getPatientEverything } from './patienteverything'; import { BulkExporter } from './utils/bulkexporter'; -import { getAuthenticatedContext, getRequestContext } from '../../context'; /** * Handles a Group export request. @@ -64,7 +64,7 @@ export async function groupExportResources( await exporter.writeResource(resource); } } catch (err) { - getRequestContext().logger.warn('Unable to read patient for group export', { + getLogger().warn('Unable to read patient for group export', { reference: member.entity.reference, }); } diff --git a/packages/server/src/fhir/operations/projectclone.test.ts b/packages/server/src/fhir/operations/projectclone.test.ts index 275563bb2f..76a05830ae 100644 --- a/packages/server/src/fhir/operations/projectclone.test.ts +++ b/packages/server/src/fhir/operations/projectclone.test.ts @@ -119,7 +119,7 @@ describe('Project clone', () => { }); test('Success with project name in body', async () => { - const { project } = await createTestProject(); + const { project } = await createTestProject({ withClient: true }); const newProjectName = 'A New Name for cloned project'; expect(project).toBeDefined(); @@ -212,7 +212,7 @@ describe('Project clone', () => { }); test('Success with resource type in body', async () => { - const { project } = await createTestProject(); + const { project } = await createTestProject({ withClient: true }); const resourceTypes = ['ProjectMembership']; expect(project).toBeDefined(); @@ -248,7 +248,7 @@ describe('Project clone', () => { }); test('Success with includeIds in body', async () => { - const { project, membership } = await createTestProject(); + const { project, membership } = await createTestProject({ withClient: true }); const includeIds = [membership.id]; expect(project).toBeDefined(); @@ -284,7 +284,7 @@ describe('Project clone', () => { }); test('Success with excludeIds in body', async () => { - const { project, membership } = await createTestProject(); + const { project, membership } = await createTestProject({ withClient: true }); const excludeIds = [membership.id]; expect(project).toBeDefined(); @@ -320,12 +320,11 @@ describe('Project clone', () => { }); test('Success with Bot attachments', async () => { - const { project } = await createTestProject(); + const { project, repo } = await createTestProject({ withRepo: true }); expect(project).toBeDefined(); - const sourceCodeBinary = await systemRepo.createResource({ + const sourceCodeBinary = await repo.createResource({ resourceType: 'Binary', - meta: { project: project.id }, contentType: ContentType.JAVASCRIPT, }); @@ -336,9 +335,8 @@ describe('Project clone', () => { Readable.from('console.log("Hello world");') ); - const bot = await systemRepo.createResource({ + const bot = await repo.createResource({ resourceType: 'Bot', - meta: { project: project.id }, name: 'Test Bot', sourceCode: { url: getReferenceString(sourceCodeBinary), diff --git a/packages/server/src/fhir/operations/projectclone.ts b/packages/server/src/fhir/operations/projectclone.ts index 1b0f1f94d6..32c23ec67b 100644 --- a/packages/server/src/fhir/operations/projectclone.ts +++ b/packages/server/src/fhir/operations/projectclone.ts @@ -46,7 +46,6 @@ class ProjectCloner { const resourceTypes = getResourceTypes(); const allResources: Resource[] = []; const maxResourcesPerResourceType = 1000; - let newProject: Project | undefined = undefined; for (const resourceType of resourceTypes) { const bundle = await repo.search({ @@ -58,25 +57,28 @@ class ProjectCloner { for (const entry of bundle.entry) { if (entry.resource && this.isResourceAllowed(entry.resource)) { this.idMap.set(entry.resource.id as string, randomUUID()); - allResources.push(entry.resource); + if (entry.resource.resourceType !== 'Project') { + allResources.push(entry.resource); + } } } } } + // Create the project first - otherwise project references will fail + const newProject = await repo.updateResource(this.rewriteIds(project)); + + // Then create all other resources for (const resource of allResources) { // Use updateResource to create with specified ID // That feature is only available to super admins const result = await repo.updateResource(this.rewriteIds(resource)); - if (result.resourceType === 'Project') { - newProject = result; - } if (resource.resourceType === 'Binary') { await getBinaryStorage().copyBinary(resource, result as Binary); } } - return newProject as Project; + return newProject; } isResourceAllowed(resource: Resource): boolean { @@ -108,7 +110,7 @@ class ProjectCloner { return true; } - rewriteIds(resource: Resource): Resource { + rewriteIds(resource: T): T { const resourceObj = JSON.parse(JSON.stringify(resource, (k, v) => this.rewriteKeyReplacer(k, v))); if (this.projectName) { diff --git a/packages/server/src/fhir/operations/resourcegraph.test.ts b/packages/server/src/fhir/operations/resourcegraph.test.ts index 4e66f4c8a7..9ea33eedf8 100644 --- a/packages/server/src/fhir/operations/resourcegraph.test.ts +++ b/packages/server/src/fhir/operations/resourcegraph.test.ts @@ -29,7 +29,7 @@ describe('Resource $graph', () => { beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); - defaultAccessToken = await initTestAuth({ strictMode: false }); + defaultAccessToken = await initTestAuth({ project: { strictMode: false } }); }); afterAll(async () => { diff --git a/packages/server/src/fhir/operations/resourcegraph.ts b/packages/server/src/fhir/operations/resourcegraph.ts index 4ccfee9a16..39892d6422 100644 --- a/packages/server/src/fhir/operations/resourcegraph.ts +++ b/packages/server/src/fhir/operations/resourcegraph.ts @@ -21,7 +21,7 @@ import { ResourceType, } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; -import { getAuthenticatedContext, getRequestContext } from '../../context'; +import { getAuthenticatedContext, getLogger } from '../../context'; import { Repository } from '../repo'; import { sendResponse } from '../response'; @@ -214,7 +214,7 @@ async function followCanonicalElements( filters: [{ code: 'url', operator: Operator.EQUALS, value: url }], }); if (linkedResources.length > 1) { - getRequestContext().logger.warn('Found more than 1 resource with canonical URL', { url }); + getLogger().warn('Found more than 1 resource with canonical URL', { url }); } // Cache here to speed up subsequent loop iterations diff --git a/packages/server/src/fhir/operations/structuredefinitionexpandprofile.test.ts b/packages/server/src/fhir/operations/structuredefinitionexpandprofile.test.ts index 013e7c8b9b..2422e3bc2d 100644 --- a/packages/server/src/fhir/operations/structuredefinitionexpandprofile.test.ts +++ b/packages/server/src/fhir/operations/structuredefinitionexpandprofile.test.ts @@ -1,11 +1,11 @@ import { ContentType, HTTP_HL7_ORG } from '@medplum/core'; +import { readJson } from '@medplum/definitions'; +import { Bundle, ElementDefinition, StructureDefinition, StructureDefinitionSnapshot } from '@medplum/fhirtypes'; import express from 'express'; import request from 'supertest'; -import { loadTestConfig } from '../../config'; import { initApp, shutdownApp } from '../../app'; -import { createTestProject } from '../../test.setup'; -import { Bundle, ElementDefinition, StructureDefinition, StructureDefinitionSnapshot } from '@medplum/fhirtypes'; -import { readJson } from '@medplum/definitions'; +import { loadTestConfig } from '../../config'; +import { initTestAuth } from '../../test.setup'; jest.mock('node-fetch'); @@ -39,8 +39,7 @@ describe('StructureDefinition $expand-profile', () => { beforeEach(async () => { // A new project per test since tests are dependent on SDs being within search scope or not. - const project = await createTestProject(); - accessToken = project.accessToken; + accessToken = await initTestAuth(); }); afterAll(async () => { diff --git a/packages/server/src/fhir/operations/utils/parameters.ts b/packages/server/src/fhir/operations/utils/parameters.ts index 05d00c1bc4..3e2a2fb9fc 100644 --- a/packages/server/src/fhir/operations/utils/parameters.ts +++ b/packages/server/src/fhir/operations/utils/parameters.ts @@ -16,7 +16,7 @@ import { ParametersParameter, } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; -import { getRequestContext } from '../../../context'; +import { getLogger } from '../../../context'; import { sendOutcome } from '../../outcomes'; import { sendResponse } from '../../response'; @@ -175,7 +175,7 @@ export async function sendOutputParameters( validateResource(response); res.status(getStatus(outcome)).json(response); } catch (err: any) { - getRequestContext().logger.error('Malformed operation output Parameters', { error: err.toString() }); + getLogger().error('Malformed operation output Parameters', { error: err.toString() }); sendOutcome(res, serverError(err)); } } diff --git a/packages/server/src/fhir/repo.test.ts b/packages/server/src/fhir/repo.test.ts index 884980373d..7bb1c2d486 100644 --- a/packages/server/src/fhir/repo.test.ts +++ b/packages/server/src/fhir/repo.test.ts @@ -1,4 +1,13 @@ -import { badRequest, createReference, forbidden, isOk, notFound, OperationOutcomeError, Operator } from '@medplum/core'; +import { + badRequest, + createReference, + forbidden, + getReferenceString, + isOk, + notFound, + OperationOutcomeError, + Operator, +} from '@medplum/core'; import { BundleEntry, ElementDefinition, @@ -19,7 +28,7 @@ import { initAppServices, shutdownApp } from '../app'; import { registerNew, RegisterRequest } from '../auth/register'; import { loadTestConfig } from '../config'; import { getDatabasePool } from '../database'; -import { bundleContains, withTestContext } from '../test.setup'; +import { bundleContains, createTestProject, withTestContext } from '../test.setup'; import { getRepoForLogin } from './accesspolicy'; import { getSystemRepo, Repository } from './repo'; @@ -177,28 +186,20 @@ describe('FHIR Repo', () => { test('meta.project preserved after attempting to remove it', () => withTestContext(async () => { - const clientApp = 'ClientApplication/' + randomUUID(); - const projectId = randomUUID(); - const repo = new Repository({ - extendedMode: true, - projects: [projectId], - author: { - reference: clientApp, - }, - }); + const { project, repo } = await createTestProject({ withClient: true, withRepo: true }); const patient1 = await repo.createResource({ resourceType: 'Patient', name: [{ given: ['Update1'], family: 'Update1' }], }); expect(patient1.meta?.project).toBeDefined(); - expect(patient1.meta?.project).toEqual(projectId); + expect(patient1.meta?.project).toEqual(project.id); const patientWithoutProject = { ...patient1 }; delete (patientWithoutProject.meta as any).project; const patient2 = await systemRepo.updateResource(patientWithoutProject); expect(patient2.meta?.project).toBeDefined(); - expect(patient2.meta?.project).toEqual(projectId); + expect(patient2.meta?.project).toEqual(project.id); })); test('Update patient no changes', () => @@ -239,15 +240,7 @@ describe('FHIR Repo', () => { })); test('Create Patient with custom ID', async () => { - const author = 'Practitioner/' + randomUUID(); - - const repo = new Repository({ - projects: [randomUUID()], - extendedMode: true, - author: { - reference: author, - }, - }); + const { repo } = await createTestProject({ withRepo: true }); // Try to "update" a resource, which does not exist. // Some FHIR systems allow users to set ID's. @@ -292,21 +285,14 @@ describe('FHIR Repo', () => { test('Create Patient as ClientApplication with no author', () => withTestContext(async () => { - const clientApp = 'ClientApplication/' + randomUUID(); - - const repo = new Repository({ - extendedMode: true, - author: { - reference: clientApp, - }, - }); + const { client, repo } = await createTestProject({ withClient: true, withRepo: true }); const patient = await repo.createResource({ resourceType: 'Patient', name: [{ given: ['Alice'], family: 'Smith' }], }); - expect(patient.meta?.author?.reference).toEqual(clientApp); + expect(patient.meta?.author?.reference).toEqual(getReferenceString(client)); })); test('Create Patient as Practitioner with no author', () => @@ -540,12 +526,7 @@ describe('FHIR Repo', () => { }); test('Reindex resource as non-admin', async () => { - const repo = new Repository({ - projects: [randomUUID()], - author: { - reference: 'Practitioner/' + randomUUID(), - }, - }); + const { repo } = await createTestProject({ withRepo: true }); try { await repo.reindexResource('Practitioner', randomUUID()); @@ -885,15 +866,7 @@ describe('FHIR Repo', () => { test('Profile validation', async () => withTestContext(async () => { - const clientApp = 'ClientApplication/' + randomUUID(); - const projectId = randomUUID(); - const repo = new Repository({ - strictMode: true, - projects: [projectId], - author: { - reference: clientApp, - }, - }); + const { repo } = await createTestProject({ withRepo: true }); const profile = JSON.parse( readFileSync(resolve(__dirname, '__test__/us-core-patient.json'), 'utf8') @@ -928,16 +901,7 @@ describe('FHIR Repo', () => { test('Profile update', async () => withTestContext(async () => { - const clientApp = 'ClientApplication/' + randomUUID(); - const projectId = randomUUID(); - const repo = new Repository({ - extendedMode: true, - strictMode: true, - projects: [projectId], - author: { - reference: clientApp, - }, - }); + const { repo } = await createTestProject({ withRepo: true }); const originalProfile = JSON.parse( readFileSync(resolve(__dirname, '__test__/us-core-patient.json'), 'utf8') diff --git a/packages/server/src/fhir/repo.ts b/packages/server/src/fhir/repo.ts index 4f101c82d1..68473925a7 100644 --- a/packages/server/src/fhir/repo.ts +++ b/packages/server/src/fhir/repo.ts @@ -53,7 +53,7 @@ import { Pool, PoolClient } from 'pg'; import { Operation, applyPatch } from 'rfc6902'; import validator from 'validator'; import { getConfig } from '../config'; -import { getRequestContext } from '../context'; +import { getLogger, getRequestContext } from '../context'; import { getDatabasePool } from '../database'; import { globalLogger } from '../logger'; import { getRedis } from '../redis'; @@ -739,7 +739,7 @@ export class Repository extends BaseRepository implements FhirRepository('Binary', id); } } catch (err: any) { - getRequestContext().logger.debug('Error reading binary to generate presigned URL', err); + getLogger().debug('Error reading binary to generate presigned URL', err); return `Binary/${id}`; } return getPresignedUrl(binary); diff --git a/packages/server/src/fhir/search.test.ts b/packages/server/src/fhir/search.test.ts index 75cf931ac8..b88d7bf9aa 100644 --- a/packages/server/src/fhir/search.test.ts +++ b/packages/server/src/fhir/search.test.ts @@ -28,6 +28,7 @@ import { Patient, PlanDefinition, Practitioner, + Project, Provenance, Questionnaire, QuestionnaireResponse, @@ -3200,8 +3201,8 @@ describe('FHIR Search', () => { test('Filter by _project', () => withTestContext(async () => { - const project1 = randomUUID(); - const project2 = randomUUID(); + const project1 = (await systemRepo.createResource({ resourceType: 'Project' })).id as string; + const project2 = (await systemRepo.createResource({ resourceType: 'Project' })).id as string; const patient1 = await systemRepo.createResource({ resourceType: 'Patient', @@ -3358,7 +3359,7 @@ describe('FHIR Search', () => { test('Sort by _lastUpdated', () => withTestContext(async () => { - const project = randomUUID(); + const project = (await systemRepo.createResource({ resourceType: 'Project' })).id as string; const patient1 = await systemRepo.createResource({ resourceType: 'Patient', diff --git a/packages/server/src/fhir/transaction.test.ts b/packages/server/src/fhir/transaction.test.ts index 9fc5fc7606..f553c75d70 100644 --- a/packages/server/src/fhir/transaction.test.ts +++ b/packages/server/src/fhir/transaction.test.ts @@ -1,9 +1,8 @@ import { OperationOutcomeError, Operator } from '@medplum/core'; import { Patient } from '@medplum/fhirtypes'; -import { randomUUID } from 'crypto'; import { initAppServices, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; -import { withTestContext } from '../test.setup'; +import { createTestProject, withTestContext } from '../test.setup'; import { Repository } from './repo'; describe.skip('FHIR Repo Transactions', () => { @@ -13,15 +12,7 @@ describe.skip('FHIR Repo Transactions', () => { const config = await loadTestConfig(); await initAppServices(config); - const clientApp = 'ClientApplication/' + randomUUID(); - const projectId = randomUUID(); - repo = new Repository({ - extendedMode: true, - project: projectId, - author: { - reference: clientApp, - }, - }); + repo = (await createTestProject({ withRepo: true })).repo; }); afterAll(async () => { diff --git a/packages/server/src/fhircast/websocket.test.ts b/packages/server/src/fhircast/websocket.test.ts index 39b17c783d..f0237334f8 100644 --- a/packages/server/src/fhircast/websocket.test.ts +++ b/packages/server/src/fhircast/websocket.test.ts @@ -16,7 +16,7 @@ describe('FHIRcast WebSocket', () => { beforeAll(async () => { config = await loadTestConfig(); server = await initApp(app, config); - accessToken = await initTestAuth({}, { admin: true }); + accessToken = await initTestAuth({ membership: { admin: true } }); await new Promise((resolve) => { server.listen(0, 'localhost', 511, resolve); }); diff --git a/packages/server/src/oauth/authorize.test.ts b/packages/server/src/oauth/authorize.test.ts index 4d54fb24a2..2c93c11ac5 100644 --- a/packages/server/src/oauth/authorize.test.ts +++ b/packages/server/src/oauth/authorize.test.ts @@ -28,7 +28,7 @@ describe('OAuth Authorize', () => { await initApp(app, config); // Create a test project - ({ project, client } = await createTestProject()); + ({ project, client } = await createTestProject({ withClient: true })); // Create a test user const { user } = await inviteUser({ diff --git a/packages/server/src/oauth/authorize.ts b/packages/server/src/oauth/authorize.ts index bbf160fad5..88678b59d7 100644 --- a/packages/server/src/oauth/authorize.ts +++ b/packages/server/src/oauth/authorize.ts @@ -4,7 +4,7 @@ import { Request, Response } from 'express'; import { URL } from 'url'; import { asyncWrap } from '../async'; import { getConfig } from '../config'; -import { getRequestContext } from '../context'; +import { getLogger } from '../context'; import { getSystemRepo } from '../fhir/repo'; import { MedplumIdTokenClaims, verifyJwt } from './keys'; import { getClientApplication } from './utils'; @@ -187,7 +187,7 @@ async function getExistingLoginFromIdTokenHint(req: Request): Promise { }); test('Basic auth with super admin client', async () => { - const { client } = await createTestProject({ superAdmin: true }); + const { client } = await createTestProject({ superAdmin: true, withClient: true }); const res = await request(app) .get('/fhir/R4/Project?_total=accurate') .set('Authorization', 'Basic ' + Buffer.from(client.id + ':' + client.secret).toString('base64')); diff --git a/packages/server/src/oauth/token.test.ts b/packages/server/src/oauth/token.test.ts index e19bcf1cd8..b1a8be64d0 100644 --- a/packages/server/src/oauth/token.test.ts +++ b/packages/server/src/oauth/token.test.ts @@ -74,7 +74,7 @@ describe('OAuth2 Token', () => { await initApp(app, config); // Create a test project - ({ project, client } = await createTestProject()); + ({ project, client } = await createTestProject({ withClient: true })); // Create a 2nd client with PKCE optional pkceOptionalClient = await systemRepo.createResource({ @@ -280,7 +280,7 @@ describe('OAuth2 Token', () => { }); test('Token for client in super admin', async () => { - const { client } = await createTestProject({ superAdmin: true }); + const { client } = await createTestProject({ superAdmin: true, withClient: true }); const res1 = await request(app).post('/oauth2/token').type('form').send({ grant_type: 'client_credentials', client_id: client.id, @@ -298,7 +298,7 @@ describe('OAuth2 Token', () => { }); test('Token for client in "off" status', async () => { - const { client } = await createTestProject(); + const { client } = await createTestProject({ withClient: true }); await withTestContext(() => systemRepo.updateResource({ ...client, status: 'off' })); const res = await request(app).post('/oauth2/token').type('form').send({ @@ -312,7 +312,7 @@ describe('OAuth2 Token', () => { }); test('Token for client in "active" status', async () => { - const { client } = await createTestProject(); + const { client } = await createTestProject({ withClient: true }); await withTestContext(() => systemRepo.updateResource({ ...client, status: 'active' })); const res = await request(app).post('/oauth2/token').type('form').send({ diff --git a/packages/server/src/test.setup.ts b/packages/server/src/test.setup.ts index 897881ef46..327a57f398 100644 --- a/packages/server/src/test.setup.ts +++ b/packages/server/src/test.setup.ts @@ -15,102 +15,145 @@ import { Express } from 'express'; import request from 'supertest'; import { inviteUser } from './admin/invite'; import { AuthenticatedRequestContext, requestContextStore } from './context'; -import { getSystemRepo } from './fhir/repo'; +import { getSystemRepo, Repository } from './fhir/repo'; import { generateAccessToken } from './oauth/keys'; import { tryLogin } from './oauth/utils'; -export async function createTestProject( - projectOptions?: Partial, - membershipOptions?: Partial -): Promise<{ +export interface TestProjectOptions { + project?: Partial; + accessPolicy?: Partial; + membership?: Partial; + superAdmin?: boolean; + withClient?: boolean; + withAccessToken?: boolean; + withRepo?: boolean; +} + +export type TestProjectResult = { project: Project; - client: ClientApplication; - membership: ProjectMembership; - login: Login; - accessToken: string; -}> { - requestContextStore.enterWith(AuthenticatedRequestContext.system()); + accessPolicy: T['accessPolicy'] extends AccessPolicy ? AccessPolicy : undefined; + client: T['withClient'] extends true ? ClientApplication : undefined; + membership: T['withClient'] extends true ? ProjectMembership : undefined; + login: T['withAccessToken'] extends true ? Login : undefined; + accessToken: T['withAccessToken'] extends true ? string : undefined; + repo: T['withRepo'] extends true ? Repository : undefined; +}; - const systemRepo = getSystemRepo(); +export async function createTestProject( + options: T = {} as T +): Promise> { + return requestContextStore.run(AuthenticatedRequestContext.system(), async () => { + const systemRepo = getSystemRepo(); - const project = await systemRepo.createResource({ - resourceType: 'Project', - name: 'Test Project', - owner: { - reference: 'User/' + randomUUID(), - }, - strictMode: true, - features: ['bots', 'email', 'graphql-introspection', 'cron'], - secret: [ - { - name: 'foo', - valueString: 'bar', + const project = await systemRepo.createResource({ + resourceType: 'Project', + name: 'Test Project', + owner: { + reference: 'User/' + randomUUID(), }, - ], - ...projectOptions, - }); + strictMode: true, + features: ['bots', 'email', 'graphql-introspection', 'cron'], + secret: [ + { + name: 'foo', + valueString: 'bar', + }, + ], + superAdmin: options?.superAdmin, + ...options?.project, + }); - const client = await systemRepo.createResource({ - resourceType: 'ClientApplication', - secret: randomUUID(), - redirectUri: 'https://example.com/', - meta: { - project: project.id as string, - }, - name: 'Test Client Application', - }); + let client: ClientApplication | undefined = undefined; + let accessPolicy: AccessPolicy | undefined = undefined; + let membership: ProjectMembership | undefined = undefined; + let login: Login | undefined = undefined; + let accessToken: string | undefined = undefined; + let repo: Repository | undefined = undefined; - const membership = await systemRepo.createResource({ - resourceType: 'ProjectMembership', - user: createReference(client), - profile: createReference(client), - project: createReference(project), - ...membershipOptions, - }); + if (options?.withClient || options?.withAccessToken || options?.withRepo) { + client = await systemRepo.createResource({ + resourceType: 'ClientApplication', + secret: randomUUID(), + redirectUri: 'https://example.com/', + meta: { + project: project.id as string, + }, + name: 'Test Client Application', + }); - const scope = 'openid'; - - const login = await systemRepo.createResource({ - resourceType: 'Login', - authMethod: 'client', - user: createReference(client), - client: createReference(client), - membership: createReference(membership), - authTime: new Date().toISOString(), - superAdmin: projectOptions?.superAdmin, - scope, - }); + if (options?.accessPolicy) { + accessPolicy = await systemRepo.createResource({ + resourceType: 'AccessPolicy', + meta: { project: project.id }, + ...options.accessPolicy, + }); + } - const accessToken = await generateAccessToken({ - login_id: login.id as string, - sub: client.id as string, - username: client.id as string, - client_id: client.id as string, - profile: client.resourceType + '/' + client.id, - scope, - }); + membership = await systemRepo.createResource({ + resourceType: 'ProjectMembership', + user: createReference(client), + profile: createReference(client), + project: createReference(project), + accessPolicy: accessPolicy && createReference(accessPolicy), + ...options?.membership, + }); - return { - project, - client, - membership, - login, - accessToken, - }; + if (options?.withAccessToken) { + const scope = 'openid'; + + login = await systemRepo.createResource({ + resourceType: 'Login', + authMethod: 'client', + user: createReference(client), + client: createReference(client), + membership: createReference(membership), + authTime: new Date().toISOString(), + superAdmin: options?.superAdmin, + scope, + }); + + accessToken = await generateAccessToken({ + login_id: login.id as string, + sub: client.id as string, + username: client.id as string, + client_id: client.id as string, + profile: client.resourceType + '/' + client.id, + scope, + }); + } + + if (options?.withRepo) { + repo = new Repository({ + projects: [project.id as string], + author: createReference(client), + superAdmin: options?.superAdmin, + projectAdmin: options?.membership?.admin, + accessPolicy, + strictMode: project.strictMode, + extendedMode: true, + checkReferencesOnWrite: project.checkReferencesOnWrite, + }); + } + } + + return { + project, + accessPolicy, + client, + membership, + login, + accessToken, + repo, + } as TestProjectResult; + }); } -export async function createTestClient( - projectOptions?: Partial, - membershipOptions?: Partial -): Promise { - return (await createTestProject(projectOptions, membershipOptions)).client; +export async function createTestClient(options?: TestProjectOptions): Promise { + return (await createTestProject({ ...options, withClient: true })).client as ClientApplication; } -export async function initTestAuth( - projectOptions?: Partial, - membershipOptions?: Partial -): Promise { - return (await createTestProject(projectOptions, membershipOptions)).accessToken; +export async function initTestAuth(options?: TestProjectOptions): Promise { + return (await createTestProject({ ...options, withAccessToken: true })).accessToken as string; } export async function addTestUser( diff --git a/packages/server/src/workers/cron.test.ts b/packages/server/src/workers/cron.test.ts index 6c1cfc81d9..86cc98bbcc 100644 --- a/packages/server/src/workers/cron.test.ts +++ b/packages/server/src/workers/cron.test.ts @@ -20,7 +20,7 @@ describe('Cron Worker', () => { await initAppServices(config); // Create a project - const botProjectDetails = await createTestProject(); + const botProjectDetails = await createTestProject({ withClient: true }); botProject = botProjectDetails.project; botRepo = new Repository({ extendedMode: true, diff --git a/packages/server/src/workers/cron.ts b/packages/server/src/workers/cron.ts index 963cccbec1..81441a06b7 100644 --- a/packages/server/src/workers/cron.ts +++ b/packages/server/src/workers/cron.ts @@ -3,7 +3,7 @@ import { Bot, Project, Resource, Timing } from '@medplum/fhirtypes'; import { Job, Queue, QueueBaseOptions, Worker } from 'bullmq'; import { isValidCron } from 'cron-validator'; import { MedplumServerConfig } from '../config'; -import { getRequestContext } from '../context'; +import { getLogger } from '../context'; import { executeBot } from '../fhir/operations/execute'; import { getSystemRepo } from '../fhir/repo'; import { globalLogger } from '../logger'; @@ -97,19 +97,19 @@ export function getCronQueue(): Queue | undefined { * @param resource - The resource that was created or updated. */ export async function addCronJobs(resource: Resource): Promise { - const ctx = getRequestContext(); if (resource.resourceType !== 'Bot') { // For now we have only the bot to execute on a timed job return; } + const logger = getLogger(); const bot = resource; // Adding a new feature for project that allows users to add a cron const systemRepo = getSystemRepo(); const project = await systemRepo.readResource('Project', resource.meta?.project as string); if (!project.features?.includes('cron')) { - ctx.logger.debug('Cron not enabled. Cron needs to be enabled in project to create cron job for bot'); + logger.debug('Cron not enabled. Cron needs to be enabled in project to create cron job for bot'); return; } @@ -118,17 +118,17 @@ export async function addCronJobs(resource: Resource): Promise { if (bot.cronTiming) { cron = convertTimingToCron(bot.cronTiming); if (!cron) { - ctx.logger.debug('cronTiming had the wrong format for a timed cron job'); + logger.debug('cronTiming had the wrong format for a timed cron job'); return; } } else if (bot.cronString && isValidCron(bot.cronString)) { cron = bot.cronString; } else if (bot.cronString === '') { await removeBullMQJobByKey(bot.id as string); - ctx.logger.debug(`no job for bot: ${bot.id}`); + logger.debug(`no job for bot: ${bot.id}`); return; } else { - ctx.logger.debug('cronString had the wrong format for a timed cron job'); + logger.debug('cronString had the wrong format for a timed cron job'); return; } @@ -152,15 +152,11 @@ export async function addCronJobs(resource: Resource): Promise { * @param repeatable - The repeat format that instructs BullMQ when to run the job */ async function addCronJobData(job: CronJobData, repeatable: Repeatable): Promise { - const ctx = getRequestContext(); // Check if there was a job previously for this bot, if there was, we remove it. await removeBullMQJobByKey(job.botId); - ctx.logger.debug('Adding Cron job'); // Parameters of queue.add https://api.docs.bullmq.io/classes/Queue.html#add if (queue) { await queue.add(jobName, job, repeatable); - } else { - ctx.logger.debug('Cron queue not initialized'); } } @@ -226,6 +222,6 @@ export async function removeBullMQJobByKey(botId: string): Promise { // There likely should not be more than one repeatable job per bot id. for (const p of previousJobs) { await queue?.removeRepeatableByKey(p.key); - getRequestContext().logger.debug(`Found a previous job for bot ${botId}, updating...`); + getLogger().debug(`Found a previous job for bot ${botId}, updating...`); } } diff --git a/packages/server/src/workers/download.test.ts b/packages/server/src/workers/download.test.ts index 0f8e929eed..777c1b8896 100644 --- a/packages/server/src/workers/download.test.ts +++ b/packages/server/src/workers/download.test.ts @@ -7,8 +7,8 @@ import { Readable } from 'stream'; import { initAppServices, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; import { Repository } from '../fhir/repo'; +import { createTestProject, withTestContext } from '../test.setup'; import { closeDownloadWorker, execDownloadJob, getDownloadQueue } from './download'; -import { withTestContext } from '../test.setup'; jest.mock('node-fetch'); @@ -19,12 +19,7 @@ describe('Download Worker', () => { const config = await loadTestConfig(); await initAppServices(config); - repo = new Repository({ - project: randomUUID(), - author: { - reference: 'ClientApplication/' + randomUUID(), - }, - }); + repo = (await createTestProject({ withRepo: true })).repo; }); afterAll(async () => { diff --git a/packages/server/src/workers/download.ts b/packages/server/src/workers/download.ts index 3bb3da9858..6a39fff448 100644 --- a/packages/server/src/workers/download.ts +++ b/packages/server/src/workers/download.ts @@ -4,7 +4,7 @@ import { Job, Queue, QueueBaseOptions, Worker } from 'bullmq'; import fetch from 'node-fetch'; import { Readable } from 'stream'; import { getConfig, MedplumServerConfig } from '../config'; -import { getRequestContext, RequestContext, requestContextStore } from '../context'; +import { getRequestContext, tryGetRequestContext, tryRunInRequestContext } from '../context'; import { getSystemRepo } from '../fhir/repo'; import { getBinaryStorage } from '../fhir/storage'; import { globalLogger } from '../logger'; @@ -25,8 +25,8 @@ export interface DownloadJobData { readonly resourceType: string; readonly id: string; readonly url: string; - readonly requestId: string; - readonly traceId: string; + readonly requestId?: string; + readonly traceId?: string; } const queueName = 'DownloadQueue'; @@ -58,8 +58,7 @@ export function initDownloadWorker(config: MedplumServerConfig): void { worker = new Worker( queueName, - (job) => - requestContextStore.run(new RequestContext(job.data.requestId, job.data.traceId), () => execDownloadJob(job)), + (job) => tryRunInRequestContext(job.data.requestId, job.data.traceId, () => execDownloadJob(job)), { ...defaultOptions, ...config.bullmq, @@ -110,15 +109,15 @@ export function getDownloadQueue(): Queue | undefined { * @param resource - The resource that was created or updated. */ export async function addDownloadJobs(resource: Resource): Promise { - const ctx = getRequestContext(); + const ctx = tryGetRequestContext(); for (const attachment of getAttachments(resource)) { if (isExternalUrl(attachment.url)) { await addDownloadJobData({ resourceType: resource.resourceType, id: resource.id as string, url: attachment.url, - requestId: ctx.requestId, - traceId: ctx.traceId, + requestId: ctx?.requestId, + traceId: ctx?.traceId, }); } } @@ -148,12 +147,8 @@ function isExternalUrl(url: string | undefined): url is string { * @param job - The download job details. */ async function addDownloadJobData(job: DownloadJobData): Promise { - const ctx = getRequestContext(); - ctx.logger.debug(`Adding Download job`); if (queue) { await queue.add(jobName, job); - } else { - ctx.logger.debug(`Download queue not initialized`); } } @@ -185,9 +180,11 @@ export async function execDownloadJob(job: Job): Promise const headers: HeadersInit = {}; const traceId = job.data.traceId; - headers['x-trace-id'] = traceId; - if (parseTraceparent(traceId)) { - headers['traceparent'] = traceId; + if (traceId) { + headers['x-trace-id'] = traceId; + if (parseTraceparent(traceId)) { + headers['traceparent'] = traceId; + } } try { diff --git a/packages/server/src/workers/subscription.test.ts b/packages/server/src/workers/subscription.test.ts index cb87ac6f14..a619467371 100644 --- a/packages/server/src/workers/subscription.test.ts +++ b/packages/server/src/workers/subscription.test.ts @@ -59,8 +59,11 @@ describe('Subscription Worker', () => { // Create one simple project with no advanced features enabled const { project, client } = await withTestContext(() => createTestProject({ - name: 'Test Project', - features: [], + withClient: true, + project: { + name: 'Test Project', + features: [], + }, }) ); @@ -71,7 +74,7 @@ describe('Subscription Worker', () => { }); // Create another project, this one with bots enabled - const botProjectDetails = await createTestProject(); + const botProjectDetails = await createTestProject({ withClient: true }); botRepo = new Repository({ extendedMode: true, projects: [botProjectDetails.project.id as string], @@ -1346,21 +1349,26 @@ describe('Subscription Worker', () => { const url = 'https://example.com/subscription'; + // Create an access policy in different project + // This should trigger an error when the subscription is executed const accessPolicy = await repo.createResource({ resourceType: 'AccessPolicy', resource: [{ resourceType: 'Patient', readonly: false }], }); - const { project, client } = await createTestProject( - { + const { project, client } = await createTestProject({ + withClient: true, + project: { name: 'AccessPolicy Throw Project', owner: { reference: 'User/' + randomUUID(), }, features: [], }, - { accessPolicy: createReference(accessPolicy) } - ); + membership: { + accessPolicy: createReference(accessPolicy), + }, + }); const apTestRepo = new Repository({ extendedMode: true, @@ -1415,7 +1423,7 @@ describe('Subscription Worker', () => { }) ); - expect(console.log).toHaveBeenCalledTimes(2); + expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Error occurred while checking access policy')); globalLogger.level = LogLevel.NONE; console.log = originalConsoleLog; diff --git a/packages/server/src/workers/subscription.ts b/packages/server/src/workers/subscription.ts index 811eb0c731..0d370ebe65 100644 --- a/packages/server/src/workers/subscription.ts +++ b/packages/server/src/workers/subscription.ts @@ -20,7 +20,7 @@ import { Job, Queue, QueueBaseOptions, Worker } from 'bullmq'; import { createHmac } from 'crypto'; import fetch, { HeadersInit } from 'node-fetch'; import { MedplumServerConfig } from '../config'; -import { getRequestContext, RequestContext, requestContextStore } from '../context'; +import { getLogger, getRequestContext, tryGetRequestContext, tryRunInRequestContext } from '../context'; import { buildAccessPolicy } from '../fhir/accesspolicy'; import { executeBot } from '../fhir/operations/execute'; import { getSystemRepo, Repository } from '../fhir/repo'; @@ -59,8 +59,8 @@ export interface SubscriptionJobData { readonly versionId: string; readonly interaction: 'create' | 'update' | 'delete'; readonly requestTime: string; - readonly requestId: string; - readonly traceId: string; + readonly requestId?: string; + readonly traceId?: string; } const queueName = 'SubscriptionQueue'; @@ -92,8 +92,7 @@ export function initSubscriptionWorker(config: MedplumServerConfig): void { worker = new Worker( queueName, - (job) => - requestContextStore.run(new RequestContext(job.data.requestId, job.data.traceId), () => execSubscriptionJob(job)), + (job) => tryRunInRequestContext(job.data.requestId, job.data.traceId, () => execSubscriptionJob(job)), { ...defaultOptions, ...config.bullmq, @@ -200,12 +199,13 @@ async function checkAccessPolicy(resource: Resource, project: Project, subscript * @param context - The background job context. */ export async function addSubscriptionJobs(resource: Resource, context: BackgroundJobContext): Promise { - const ctx = getRequestContext(); if (resource.resourceType === 'AuditEvent') { // Never send subscriptions for audit events return; } + const ctx = tryGetRequestContext(); + const logger = getLogger(); const systemRepo = getSystemRepo(); let project: Project | undefined; try { @@ -222,14 +222,13 @@ export async function addSubscriptionJobs(resource: Resource, context: Backgroun return; } if (!project) { - ctx.logger.debug('Did not evaluate subscriptions for resource without project'); - globalLogger.warn(`[Subscription Access Policy]: No project for resource '${getReferenceString(resource)}'`); + logger.warn(`[Subscription Access Policy]: No project for resource '${getReferenceString(resource)}'`); return; } const requestTime = new Date().toISOString(); const subscriptions = await getSubscriptions(resource, project); - ctx.logger.debug(`Evaluate ${subscriptions.length} subscription(s)`); + logger.debug(`Evaluate ${subscriptions.length} subscription(s)`); for (const subscription of subscriptions) { const criteria = await matchesCriteria(resource, subscription, context); @@ -242,8 +241,8 @@ export async function addSubscriptionJobs(resource: Resource, context: Backgroun versionId: resource.meta?.versionId as string, interaction: context.interaction, requestTime, - requestId: ctx.requestId, - traceId: ctx.traceId, + requestId: ctx?.requestId, + traceId: ctx?.traceId, }); } } @@ -317,7 +316,7 @@ function matchesChannelType(subscription: Subscription): boolean { if (channelType === 'rest-hook') { const url = subscription.channel?.endpoint; if (!url) { - getRequestContext().logger.debug(`Ignore rest-hook missing URL`); + getLogger().debug(`Ignore rest-hook missing URL`); return false; } @@ -336,12 +335,8 @@ function matchesChannelType(subscription: Subscription): boolean { * @param job - The subscription job details. */ async function addSubscriptionJobData(job: SubscriptionJobData): Promise { - const ctx = getRequestContext(); - ctx.logger.debug(`Adding Subscription job`); if (queue) { await queue.add(jobName, job); - } else { - ctx.logger.debug(`Subscription queue not initialized`); } } @@ -505,9 +500,11 @@ async function sendRestHook( headers['X-Medplum-Deleted-Resource'] = `${resource.resourceType}/${resource.id}`; } const traceId = job.data.traceId; - headers['x-trace-id'] = traceId; - if (parseTraceparent(traceId)) { - headers['traceparent'] = traceId; + if (traceId) { + headers['x-trace-id'] = traceId; + if (parseTraceparent(traceId)) { + headers['traceparent'] = traceId; + } } const body = interaction === 'delete' ? '{}' : stringify(resource); diff --git a/packages/server/src/workers/utils.ts b/packages/server/src/workers/utils.ts index 9e78db634e..01054eb180 100644 --- a/packages/server/src/workers/utils.ts +++ b/packages/server/src/workers/utils.ts @@ -10,7 +10,7 @@ import { Resource, Subscription, } from '@medplum/fhirtypes'; -import { getRequestContext } from '../context'; +import { getLogger } from '../context'; import { getSystemRepo } from '../fhir/repo'; import { AuditEventOutcome } from '../util/auditevent'; @@ -158,7 +158,7 @@ export function isJobSuccessful(subscription: Subscription, status: number): boo const lowerBound = Number(codeRange[0]); const upperBound = Number(codeRange[1]); if (!(Number.isInteger(lowerBound) && Number.isInteger(upperBound))) { - getRequestContext().logger.debug( + getLogger().debug( `${lowerBound} and ${upperBound} aren't an integer, configured status codes need to be changed. Resorting to default codes` ); return defaultStatusCheck(status); @@ -169,7 +169,7 @@ export function isJobSuccessful(subscription: Subscription, status: number): boo } else { const codeValue = Number(code); if (!Number.isInteger(codeValue)) { - getRequestContext().logger.debug( + getLogger().debug( `${code} isn't an integer, configured status codes need to be changed. Resorting to default codes` ); return defaultStatusCheck(status); From ffd7ce14527a75258ac1d92957a1b06e177dde6a Mon Sep 17 00:00:00 2001 From: Rahul Agarwal Date: Fri, 23 Feb 2024 10:27:44 -0800 Subject: [PATCH 81/81] Update peer dep major versions (#4016) * Update peer dep major versions * Update package-lock --- package-lock.json | 11 ++++++----- packages/eslint-config/package.json | 11 ++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 25d61b7380..0c56ebe706 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58566,12 +58566,13 @@ "node": ">=18.0.0" }, "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^6", - "@typescript-eslint/parser": "^6", + "@typescript-eslint/eslint-plugin": "^7", + "@typescript-eslint/parser": "^7", "eslint": "^8", - "eslint-plugin-jsdoc": "^46", - "eslint-plugin-json-files": "^3", - "eslint-plugin-react-hooks": "^4" + "eslint-plugin-jsdoc": "^48", + "eslint-plugin-json-files": "^4", + "eslint-plugin-react-hooks": "^4", + "eslint-plugin-react-refresh": "^0" } }, "packages/examples": { diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 7a4ed40c3d..776a6107bb 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -28,12 +28,13 @@ "eslint-plugin-react-refresh": "0.4.5" }, "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^6", - "@typescript-eslint/parser": "^6", + "@typescript-eslint/eslint-plugin": "^7", + "@typescript-eslint/parser": "^7", "eslint": "^8", - "eslint-plugin-jsdoc": "^46", - "eslint-plugin-json-files": "^3", - "eslint-plugin-react-hooks": "^4" + "eslint-plugin-jsdoc": "^48", + "eslint-plugin-json-files": "^4", + "eslint-plugin-react-hooks": "^4", + "eslint-plugin-react-refresh": "^0" }, "engines": { "node": ">=18.0.0"