From 8116b4527b69118d55bf3e7a050b1b2163d671cc Mon Sep 17 00:00:00 2001 From: Derrick Farris Date: Mon, 8 Apr 2024 21:45:19 -0700 Subject: [PATCH 01/52] fix(agent): check for `ping` command, report transmit errors back on `Agent/$push` (#4328) * wip(ping): almost fixed ping failing when util missing * wip(agent): get agent tests passing * refactor(agent): `exec` -> `execAsync` * fix(agent): send `OperationOutcome` when `AgentTrasmitResponse.statusCode` >= 400 * fix(agent): fix types, add ping count arg * fix(ci): install ping util before running tests * fix(ci): use `sudo` o.o * ci: check where `iputils-ping` is installed * ci: test if ping is accessible * fix(agent): use proper help command on linux * wip(ci): test if `ping` is found in PATH * fix(agent): change how checking for ping on linux * test(agent): add more tests for error cases, test `Agent/$push` * chore(sonar): erase reject type error * feat(agent): add `count` parameter for ping UI * test(agent): add test for no ip entered * cleanup(agent): remove unnecessary `isPingUtilAvailable` --- packages/agent/src/app.ts | 80 ++- packages/agent/src/net-utils.test.ts | 498 ++++++++++++++---- packages/app/src/resource/ToolsPage.test.tsx | 71 ++- packages/app/src/resource/ToolsPage.tsx | 29 +- packages/core/src/agent.ts | 1 + .../src/fhir/operations/agentpush.test.ts | 146 ++++- .../server/src/fhir/operations/agentpush.ts | 21 +- packages/server/src/index.test.ts | 4 +- 8 files changed, 714 insertions(+), 136 deletions(-) diff --git a/packages/agent/src/app.ts b/packages/agent/src/app.ts index 1cfe398240..f349523f70 100644 --- a/packages/agent/src/app.ts +++ b/packages/agent/src/app.ts @@ -1,5 +1,4 @@ import { - AgentError, AgentMessage, AgentTransmitRequest, AgentTransmitResponse, @@ -12,16 +11,27 @@ import { } from '@medplum/core'; import { Endpoint, Reference } from '@medplum/fhirtypes'; import { Hl7Client } from '@medplum/hl7'; -import { exec as _exec } from 'node:child_process'; +import { ExecException, ExecOptions, exec } from 'node:child_process'; import { isIPv4, isIPv6 } from 'node:net'; -import { promisify } from 'node:util'; -import { platform } from 'os'; +import { platform } from 'node:os'; import WebSocket from 'ws'; import { Channel } from './channel'; import { AgentDicomChannel } from './dicom'; import { AgentHl7Channel } from './hl7'; -const exec = promisify(_exec); +async function execAsync(command: string, options: ExecOptions): Promise<{ stdout: string; stderr: string }> { + return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => { + exec(command, options, (ex: ExecException | null, stdout: string, stderr: string) => { + if (ex) { + const err = ex as Error; + reject(err); + } + resolve({ stdout, stderr }); + }); + }); +} + +export const DEFAULT_PING_TIMEOUT = 3600; export class App { static instance: App; @@ -265,10 +275,16 @@ export class App { } } + // This covers Windows, Linux, and Mac + private getPingCommand(ip: string, count = 1): string { + return platform() === 'win32' ? `ping /n ${count} ${ip}` : `ping -c ${count} ${ip}`; + } + 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.'; + if (message.body && !message.body.startsWith('PING')) { + const warnMsg = + 'Message body present but unused. Body for a ping request should be empty or a message formatted as `PING[ count]`.'; this.log.warn(warnMsg); } if (!isIPv4(message.remote)) { @@ -279,13 +295,27 @@ export class App { 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}` - ); + + const pingCountAsStr = message.body.startsWith('PING') ? message.body.split(' ')?.[1] ?? '' : ''; + let pingCount: number | undefined = undefined; + + if (pingCountAsStr !== '') { + pingCount = Number.parseInt(pingCountAsStr, 10); + if (Number.isNaN(pingCount)) { + throw new Error( + `Unable to ping ${message.remote} "${pingCountAsStr}" times. "${pingCountAsStr}" is not a number.` + ); + } + } + + const { stdout, stderr } = await execAsync(this.getPingCommand(message.remote, pingCount), { + timeout: DEFAULT_PING_TIMEOUT, + }); + if (stderr) { - throw new Error(`Received on stderr:\n\n${stderr}`); + throw new Error(`Received on stderr:\n\n${stderr.trim()}`); } + const result = stdout.trim(); this.log.info(`Ping result for ${message.remote}:\n\n${result}`); this.addToWebSocketQueue({ @@ -294,14 +324,20 @@ export class App { contentType: ContentType.PING, remote: message.remote, callback: message.callback, + statusCode: 200, body: result, } 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); + type: 'agent:transmit:response', + channel: message.channel, + contentType: ContentType.TEXT, + remote: message.remote, + callback: message.callback, + statusCode: 500, + body: normalizeErrorString(err), + } satisfies AgentTransmitResponse); } } @@ -327,7 +363,7 @@ export class App { const address = new URL(message.remote); const client = new Hl7Client({ host: address.hostname, - port: parseInt(address.port, 10), + port: Number.parseInt(address.port, 10), }); client @@ -340,11 +376,21 @@ export class App { remote: message.remote, callback: message.callback, contentType: ContentType.HL7_V2, + statusCode: 200, body: response.toString(), - }); + } satisfies AgentTransmitResponse); }) .catch((err) => { this.log.error(`HL7 error: ${normalizeErrorString(err)}`); + this.addToWebSocketQueue({ + type: 'agent:transmit:response', + channel: message.channel, + remote: message.remote, + callback: message.callback, + contentType: ContentType.TEXT, + statusCode: 500, + body: normalizeErrorString(err), + } satisfies AgentTransmitResponse); }) .finally(() => { client.close(); diff --git a/packages/agent/src/net-utils.test.ts b/packages/agent/src/net-utils.test.ts index 5c156e6ef8..8071585c69 100644 --- a/packages/agent/src/net-utils.test.ts +++ b/packages/agent/src/net-utils.test.ts @@ -1,7 +1,17 @@ -import { AgentMessage, allOk, ContentType, LogLevel, sleep } from '@medplum/core'; +import { + AgentMessage, + AgentTransmitRequest, + AgentTransmitResponse, + allOk, + ContentType, + generateId, + LogLevel, + sleep, +} from '@medplum/core'; import { Agent, Resource } from '@medplum/fhirtypes'; import { MockClient } from '@medplum/mock'; import { Client, Server } from 'mock-socket'; +import child_process, { ChildProcess } from 'node:child_process'; import { App } from './app'; jest.mock('node-windows'); @@ -9,120 +19,418 @@ 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; + let originalLog: typeof console.log; - beforeAll(async () => { + beforeAll(() => { + originalLog = console.log; console.log = jest.fn(); + }); + + afterAll(() => { + console.log = originalLog; + }); + + describe('Ping -- Within One App Instance', () => { + let mockServer: Server; + let wsClient: Client; + let app: App; + let onMessage: (command: AgentMessage) => void; + let timer: ReturnType | undefined; + + beforeAll(async () => { + 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) => { + wsClient = 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); - medplum.router.router.add('POST', ':resourceType/:id/$execute', async () => { - return [allOk, {} as Resource]; + // 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 (!wsClient) { + await sleep(100); + } }); - 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); - } + afterAll(async () => { + app.stop(); + await new Promise((resolve) => { + mockServer.stop(resolve); }); + // @ts-expect-error We know by the time it's used again this will be redefined + wsClient = undefined; }); - const agent = await medplum.createResource({ - resourceType: 'Agent', - } as Agent); + afterEach(() => { + clearTimeout(timer); + }); - // Start the app - app = new App(medplum, agent.id as string, LogLevel.INFO); - await app.start(); + test('Valid ping', async () => { + let resolve: (value: AgentMessage) => void; + let reject: (error: Error) => void; - // Wait for the WebSocket to connect - // eslint-disable-next-line no-unmodified-loop-condition - while (!mySocket) { - await sleep(100); - } + const messageReceived = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); - wsClient = mySocket as unknown as Client; - }); + onMessage = (command) => resolve(command); + expect(wsClient).toBeDefined(); - afterAll(() => { - app.stop(); - mockServer.stop(); - }); + const callback = generateId(); + wsClient.send( + Buffer.from( + JSON.stringify({ + type: 'agent:transmit:request', + contentType: ContentType.PING, + remote: '127.0.0.1', + callback, + body: 'PING', + } satisfies AgentTransmitRequest) + ) + ); + + timer = setTimeout(() => { + reject(new Error('Timeout')); + }, 3500); + + await expect(messageReceived).resolves.toMatchObject>({ + type: 'agent:transmit:response', + callback, + statusCode: 200, + body: expect.stringMatching(/ping statistics/), + }); + }); + + test('Non-IP remote', async () => { + let resolve: (value: AgentMessage) => void; + let reject: (error: Error) => void; - 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(); + + const callback = generateId(); + wsClient.send( + Buffer.from( + JSON.stringify({ + type: 'agent:transmit:request', + contentType: ContentType.PING, + remote: 'https://localhost:3001', + callback, + body: 'PING 1', + } satisfies AgentTransmitRequest) + ) + ); + + timer = setTimeout(() => { + reject(new Error('Timeout')); + }, 3500); + + await expect(messageReceived).resolves.toMatchObject>({ + type: 'agent:transmit:response', + contentType: ContentType.TEXT, + statusCode: 500, + callback, + body: expect.stringMatching(/invalid ip/i), + }); + }); + + test('Invalid ping body -- Random message', async () => { + let resolve: (value: AgentMessage) => void; + let reject: (error: Error) => void; + + const messageReceived = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); - const messageReceived = new Promise((_resolve, _reject) => { - resolve = _resolve; - reject = _reject; + onMessage = (command) => resolve(command); + + expect(wsClient).toBeDefined(); + + const callback = generateId(); + wsClient.send( + Buffer.from( + JSON.stringify({ + type: 'agent:transmit:request', + contentType: ContentType.PING, + remote: '127.0.0.1', + callback, + body: 'Hello, Medplum!', + } satisfies AgentTransmitRequest) + ) + ); + + timer = setTimeout(() => { + reject(new Error('Timeout')); + }, 3500); + + await expect(messageReceived).resolves.toMatchObject>({ + type: 'agent:transmit:response', + contentType: ContentType.PING, + statusCode: 200, + callback, + body: expect.stringMatching(/ping statistics/i), + }); + + expect(console.log).toHaveBeenCalledWith(expect.stringMatching(/message body present but unused/i)); }); - 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('Invalid ping body -- non-numeric first arg', 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(); + + const callback = generateId(); + wsClient.send( + Buffer.from( + JSON.stringify({ + type: 'agent:transmit:request', + contentType: ContentType.PING, + remote: '127.0.0.1', + callback, + body: 'PING JOHN', + } satisfies AgentTransmitRequest) + ) + ); + + timer = setTimeout(() => { + reject(new Error('Timeout')); + }, 3500); + + await expect(messageReceived).resolves.toMatchObject>({ + type: 'agent:transmit:response', + contentType: ContentType.TEXT, + statusCode: 500, + callback, + body: expect.stringMatching(/is not a number/i), + }); + }); }); - test('Ping -- non-IP remote', async () => { - let resolve: (value: AgentMessage) => void; - let reject: (error: Error) => void; + describe('Ping -- Edge Cases', () => { + let mockServer: Server; + let wsClient: Client; + let app: App; + let onMessage: (command: AgentMessage) => void; + let timer: ReturnType; + + beforeEach(async () => { + 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) => { + wsClient = 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(); - const messageReceived = new Promise((_resolve, _reject) => { - resolve = _resolve; - reject = _reject; + // Wait for the WebSocket to connect + // eslint-disable-next-line no-unmodified-loop-condition + while (!wsClient) { + await sleep(100); + } }); - 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); + afterEach(async () => { + app.stop(); + await new Promise((resolve) => { + mockServer.stop(resolve); + }); + clearTimeout(timer); + // @ts-expect-error We know that in each beforeEach this is redefined + wsClient = undefined; + }); + + test('Ping times out', async () => { + let resolve: (value: AgentMessage) => void; + let reject: (error: Error) => void; + + let messageReceived = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + + onMessage = (command) => resolve(command); + + expect(wsClient).toBeDefined(); + + let callback = generateId(); + wsClient.send( + Buffer.from( + JSON.stringify({ + type: 'agent:transmit:request', + contentType: ContentType.PING, + remote: '127.0.0.1', + callback, + body: 'PING 1', + } satisfies AgentTransmitRequest) + ) + ); + + timer = setTimeout(() => { + reject(new Error('Timeout')); + }, 3500); + + // We can ping localhost, woohoo + await expect(messageReceived).resolves.toMatchObject>({ + type: 'agent:transmit:response', + body: expect.stringMatching(/ping statistics/i), + contentType: ContentType.PING, + callback, + statusCode: 200, + }); + + // Setup for a ping that will timeout + messageReceived = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + + clearTimeout(timer); + timer = setTimeout(() => { + reject(new Error('Timeout')); + }, 3500); + + onMessage = (command) => resolve(command); + + // We are gonna make ping fail after a timeout + jest.spyOn(child_process, 'exec').mockImplementationOnce((_command, _options, callback): ChildProcess => { + setTimeout(() => { + callback?.(new Error('Ping command timeout'), '', ''); + }, 50); + return new ChildProcess(); + }); + + callback = generateId(); + wsClient.send( + Buffer.from( + JSON.stringify({ + type: 'agent:transmit:request', + contentType: ContentType.PING, + remote: '127.0.0.1', + callback, + body: 'PING 1', + } satisfies AgentTransmitRequest) + ) + ); + + // We should get a timeout error + await expect(messageReceived).resolves.toMatchObject>({ + type: 'agent:transmit:response', + contentType: ContentType.TEXT, + callback, + statusCode: 500, + body: expect.stringMatching(/Ping command timeout/), + }); + }); + + test('No ping command available', async () => { + // We are gonna make ping fail after a timeout + jest.spyOn(child_process, 'exec').mockImplementationOnce((_command, _options, callback): ChildProcess => { + setTimeout(() => { + callback?.(new Error('Ping not found'), '', ''); + }, 50); + return new ChildProcess(); + }); + + 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(); + + const callback = generateId(); + wsClient.send( + Buffer.from( + JSON.stringify({ + type: 'agent:transmit:request', + contentType: ContentType.PING, + remote: '127.0.0.1', + callback, + body: 'PING 1', + } satisfies AgentTransmitRequest) + ) + ); + + timer = setTimeout(() => { + reject(new Error('Timeout')); + }, 3500); + + await expect(messageReceived).resolves.toMatchObject>({ + type: 'agent:transmit:response', + contentType: ContentType.TEXT, + statusCode: 500, + callback, + body: expect.stringMatching(/Ping not found/), + }); + }); }); }); diff --git a/packages/app/src/resource/ToolsPage.test.tsx b/packages/app/src/resource/ToolsPage.test.tsx index 6b11f9615d..7dda14d446 100644 --- a/packages/app/src/resource/ToolsPage.test.tsx +++ b/packages/app/src/resource/ToolsPage.test.tsx @@ -1,5 +1,5 @@ import { Notifications } from '@mantine/notifications'; -import { allOk, getReferenceString } from '@medplum/core'; +import { ContentType, allOk, getReferenceString } from '@medplum/core'; import { Agent } from '@medplum/fhirtypes'; import { MockClient } from '@medplum/mock'; import { MedplumProvider } from '@medplum/react'; @@ -64,7 +64,7 @@ describe('ToolsPage', () => { expect(screen.getAllByText(agent.name)[0]).toBeInTheDocument(); await act(async () => { - fireEvent.change(screen.getByPlaceholderText('IP Address'), { target: { value: '8.8.8.8' } }); + fireEvent.change(screen.getByLabelText('IP Address'), { target: { value: '8.8.8.8' } }); fireEvent.click(screen.getByLabelText('Ping')); }); @@ -80,7 +80,7 @@ describe('ToolsPage', () => { expect(screen.getAllByText(agent.name)[0]).toBeInTheDocument(); await act(async () => { - fireEvent.change(screen.getByPlaceholderText('IP Address'), { target: { value: 'abc123' } }); + fireEvent.change(screen.getByLabelText('IP Address'), { target: { value: 'abc123' } }); fireEvent.click(screen.getByLabelText('Ping')); }); @@ -98,7 +98,7 @@ describe('ToolsPage', () => { expect(screen.getAllByText(agent.name)[0]).toBeInTheDocument(); await act(async () => { - fireEvent.change(screen.getByPlaceholderText('IP Address'), { target: { value: '8.8.8.8' } }); + fireEvent.change(screen.getByLabelText('IP Address'), { target: { value: '8.8.8.8' } }); fireEvent.click(screen.getByLabelText('Ping')); }); @@ -106,4 +106,67 @@ describe('ToolsPage', () => { medplum.setAgentAvailable(true); }); + + test('Setting count for ping', async () => { + const pushToAgentSpy = jest.spyOn(medplum, 'pushToAgent'); + + // 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.getAllByText(agent.name)[0]).toBeInTheDocument(); + + await act(async () => { + fireEvent.change(screen.getByLabelText('IP Address'), { target: { value: '8.8.8.8' } }); + }); + + await act(async () => { + fireEvent.change(screen.getByLabelText('Ping Count'), { target: { value: '2' } }); + fireEvent.click(screen.getByLabelText('Ping')); + }); + + await expect(screen.findByText('statistics', { exact: false })).resolves.toBeInTheDocument(); + expect(pushToAgentSpy).toHaveBeenLastCalledWith( + { reference: getReferenceString(agent) }, + '8.8.8.8', + 'PING 2', + ContentType.PING, + true + ); + pushToAgentSpy.mockRestore(); + }); + + test('No IP entered for ping', async () => { + const pushToAgentSpy = jest.spyOn(medplum, 'pushToAgent'); + + // 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.getAllByText(agent.name)[0]).toBeInTheDocument(); + + await act(async () => { + fireEvent.click(screen.getByLabelText('Ping')); + }); + + await expect(screen.findByText('statistics', { exact: false })).rejects.toThrow(); + expect(pushToAgentSpy).not.toHaveBeenCalled(); + pushToAgentSpy.mockRestore(); + }); }); diff --git a/packages/app/src/resource/ToolsPage.tsx b/packages/app/src/resource/ToolsPage.tsx index 2a18c396cf..041104be14 100644 --- a/packages/app/src/resource/ToolsPage.tsx +++ b/packages/app/src/resource/ToolsPage.tsx @@ -1,4 +1,4 @@ -import { ActionIcon, Button, Divider, Table, TextInput, Title } from '@mantine/core'; +import { ActionIcon, Button, Divider, Group, NumberInput, Table, TextInput, Title } from '@mantine/core'; import { showNotification } from '@mantine/notifications'; import { ContentType, formatDateTime, normalizeErrorString } from '@medplum/core'; import { Agent, Parameters, Reference } from '@medplum/fhirtypes'; @@ -32,12 +32,13 @@ export function ToolsPage(): JSX.Element | null { const handlePing = useCallback( (formData: Record) => { const ip = formData.ip; + const pingCount = formData.pingCount || 1; if (!ip) { return; } setPinging(true); medplum - .pushToAgent(reference, ip, 'PING', ContentType.PING, true) + .pushToAgent(reference, ip, `PING ${pingCount}`, ContentType.PING, true) .then((pingResult: string) => setLastPing(pingResult)) .catch((err: unknown) => showError(normalizeErrorString(err))) .finally(() => setPinging(false)); @@ -91,16 +92,20 @@ export function ToolsPage(): JSX.Element | null { Send a ping command from the agent to an IP address. Use this tool to troubleshoot local network connectivity.

- - - - } - /> + + + + + } + /> + + {!pinging && lastPing && ( <> diff --git a/packages/core/src/agent.ts b/packages/core/src/agent.ts index 48e0d3c6ff..656d0d9027 100644 --- a/packages/core/src/agent.ts +++ b/packages/core/src/agent.ts @@ -42,6 +42,7 @@ export interface AgentTransmitResponse extends BaseAgentMessage { channel?: string; remote: string; contentType: string; + statusCode?: number; body: string; } diff --git a/packages/server/src/fhir/operations/agentpush.test.ts b/packages/server/src/fhir/operations/agentpush.test.ts index 111c8a9b6a..086520c643 100644 --- a/packages/server/src/fhir/operations/agentpush.test.ts +++ b/packages/server/src/fhir/operations/agentpush.test.ts @@ -1,11 +1,20 @@ -import { allOk, ContentType, getReferenceString } from '@medplum/core'; -import { Agent, Device } from '@medplum/fhirtypes'; -import { randomUUID } from 'crypto'; +import { + AgentTransmitRequest, + AgentTransmitResponse, + allOk, + ContentType, + getReferenceString, + sleep, +} from '@medplum/core'; +import { Agent, Device, OperationOutcome } from '@medplum/fhirtypes'; import express from 'express'; +import { randomUUID } from 'node:crypto'; import request from 'supertest'; import { initApp, shutdownApp } from '../../app'; import { loadTestConfig } from '../../config'; +import { getRedis } from '../../redis'; import { initTestAuth } from '../../test.setup'; +import { AgentPushParameters } from './agentpush'; const app = express(); let accessToken: string; @@ -248,4 +257,135 @@ describe('Agent Push', () => { expect(res.status).toBe(400); expect(res.body.issue[0].details.text).toEqual('Invalid wait timeout'); }); + + test('Ping -- Successful', async () => { + const redis = getRedis(); + const publishSpy = jest.spyOn(redis, 'publish'); + + let resolve!: (value: request.Response) => void | PromiseLike; + let reject!: (err: Error) => void; + + const deferredResponse = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + + request(app) + .post(`/fhir/R4/Agent/${agent.id}/$push`) + .set('Content-Type', ContentType.JSON) + .set('Authorization', 'Bearer ' + accessToken) + .send({ + contentType: ContentType.PING, + body: 'PING', + destination: '8.8.8.8', + waitForResponse: true, + } satisfies AgentPushParameters) + .then(resolve) + .catch(reject); + + let shouldThrow = false; + const timer = setTimeout(() => { + shouldThrow = true; + }, 3500); + + while (!publishSpy.mock.lastCall) { + if (shouldThrow) { + throw new Error('Timeout'); + } + await sleep(50); + } + clearTimeout(timer); + + const transmitRequestStr = publishSpy.mock.lastCall?.[1]?.toString() as string; + expect(transmitRequestStr).toBeDefined(); + const transmitRequest = JSON.parse(transmitRequestStr) as AgentTransmitRequest; + + await getRedis().publish( + transmitRequest.callback as string, + JSON.stringify({ + ...transmitRequest, + type: 'agent:transmit:response', + statusCode: 200, + contentType: ContentType.TEXT, + body: ` +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.316 ms + +--- 8.8.8.8 ping statistics --- +1 packets transmitted, 1 packets received, 0.0% packet loss +round-trip min/avg/max/stddev = 10.316/10.316/10.316/nan ms`, + } satisfies AgentTransmitResponse) + ); + + const res = await deferredResponse; + expect(res.status).toEqual(200); + expect(res.text).toEqual(expect.stringMatching(/ping statistics/i)); + + publishSpy.mockRestore(); + }); + + test('Ping -- Error', async () => { + const redis = getRedis(); + const publishSpy = jest.spyOn(redis, 'publish'); + + let resolve!: (value: request.Response) => void | PromiseLike; + let reject!: (err: Error) => void; + + const deferredResponse = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + + request(app) + .post(`/fhir/R4/Agent/${agent.id}/$push`) + .set('Content-Type', ContentType.JSON) + .set('Authorization', 'Bearer ' + accessToken) + .send({ + contentType: ContentType.PING, + body: 'PING', + destination: '8.8.8.8', + waitForResponse: true, + } satisfies AgentPushParameters) + .then(resolve) + .catch(reject); + + let shouldThrow = false; + const timer = setTimeout(() => { + shouldThrow = true; + }, 3500); + + while (!publishSpy.mock.lastCall) { + if (shouldThrow) { + throw new Error('Timeout'); + } + await sleep(50); + } + clearTimeout(timer); + + const transmitRequestStr = publishSpy.mock.lastCall?.[1]?.toString() as string; + expect(transmitRequestStr).toBeDefined(); + const transmitRequest = JSON.parse(transmitRequestStr) as AgentTransmitRequest; + + await getRedis().publish( + transmitRequest.callback as string, + JSON.stringify({ + ...transmitRequest, + type: 'agent:transmit:response', + statusCode: 500, + contentType: ContentType.TEXT, + body: 'Error: Unable to ping "8.8.8.8"', + } satisfies AgentTransmitResponse) + ); + + const res = await deferredResponse; + expect(res.status).toEqual(500); + + const body = res.body as OperationOutcome; + expect(body).toBeDefined(); + expect(body.issue[0].severity).toEqual('error'); + expect(body.issue[0]?.details?.text).toEqual(expect.stringMatching(/internal server error/i)); + expect(body.issue[0]?.diagnostics).toEqual(expect.stringMatching(/unable to ping/i)); + + publishSpy.mockRestore(); + }); }); diff --git a/packages/server/src/fhir/operations/agentpush.ts b/packages/server/src/fhir/operations/agentpush.ts index 9f9c340355..52107a2680 100644 --- a/packages/server/src/fhir/operations/agentpush.ts +++ b/packages/server/src/fhir/operations/agentpush.ts @@ -1,4 +1,12 @@ -import { AgentTransmitRequest, allOk, badRequest, BaseAgentRequestMessage, getReferenceString } from '@medplum/core'; +import { + AgentTransmitRequest, + AgentTransmitResponse, + allOk, + badRequest, + BaseAgentRequestMessage, + getReferenceString, + serverError, +} from '@medplum/core'; import { Agent } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { randomUUID } from 'node:crypto'; @@ -89,8 +97,15 @@ export const agentPushHandler = asyncWrap(async (req: Request, res: Response) => const redisSubscriber = getRedis().duplicate(); await redisSubscriber.subscribe(message.callback); redisSubscriber.on('message', (_channel: string, message: string) => { - const response = JSON.parse(message); - res.status(200).type(response.contentType).send(response.body); + const response = JSON.parse(message) as AgentTransmitResponse; + if (response.statusCode && response.statusCode >= 400) { + sendOutcome(res, serverError(new Error(response.body))); + } else { + res + .status(response.statusCode ?? 200) + .type(response.contentType) + .send(response.body); + } cleanup(); }); diff --git a/packages/server/src/index.test.ts b/packages/server/src/index.test.ts index c1e358df2c..44fa1aae27 100644 --- a/packages/server/src/index.test.ts +++ b/packages/server/src/index.test.ts @@ -1,4 +1,4 @@ -import http from 'http'; +import http from 'node:http'; import { shutdownApp } from './app'; import { main } from './index'; @@ -19,7 +19,7 @@ jest.mock('express', () => { }); jest.mock('pg', () => { - const original = jest.requireActual('express'); + const original = jest.requireActual('pg'); class MockPoolClient { async query(sql: string): Promise { From 6af2fa78c9671c3fc7c12c668b4d9103ad3a40c7 Mon Sep 17 00:00:00 2001 From: ksmith94 <102421938+ksmith94@users.noreply.github.com> Date: Tue, 9 Apr 2024 12:19:52 -0400 Subject: [PATCH 02/52] Storybook Themes (#4337) * Added Theme Add-on to storybook * Removing fonts. Adding a new theme * Updating package version * Add additional themes and move to separate page * Update example names * Add custom fonts * add colors to theme * Update theme names --------- Co-authored-by: Rahul Agarwal --- package-lock.json | 20 + packages/react/.storybook/main.ts | 1 + packages/react/.storybook/preview-head.html | 6 + packages/react/.storybook/preview.tsx | 26 +- packages/react/.storybook/themes.ts | 481 ++++++++++++++++++++ packages/react/package.json | 1 + 6 files changed, 512 insertions(+), 23 deletions(-) create mode 100644 packages/react/.storybook/preview-head.html create mode 100644 packages/react/.storybook/themes.ts diff --git a/package-lock.json b/package-lock.json index e93c1e4ae6..ba307e0a32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50777,6 +50777,25 @@ "url": "https://opencollective.com/storybook" } }, + "node_modules/storybook-addon-mantine": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/storybook-addon-mantine/-/storybook-addon-mantine-4.0.2.tgz", + "integrity": "sha512-nOxaIDTuZ0+Fh3vgsn3JRyCW/uCYBe9m0JxIQyXjMCa0ecl8op6wWdewAQ6IoeMSw33l/gZWHL0BLyTuqqX+kg==", + "dev": true, + "peerDependencies": { + "@mantine/core": "^7.0.0", + "@mantine/hooks": "^7.0.0", + "@storybook/blocks": "^8.0.0", + "@storybook/components": "^8.0.0", + "@storybook/core-events": "^8.0.0", + "@storybook/manager-api": "^8.0.0", + "@storybook/preview-api": "^8.0.0", + "@storybook/theming": "^8.0.0", + "@storybook/types": "^8.0.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/stream": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stream/-/stream-0.0.2.tgz", @@ -55919,6 +55938,7 @@ "sinon": "17.0.1", "storybook": "8.0.6", "typescript": "5.4.4", + "storybook-addon-mantine": "4.0.2", "vite-plugin-turbosnap": "^1.0.3" }, "engines": { diff --git a/packages/react/.storybook/main.ts b/packages/react/.storybook/main.ts index 32dc913395..0e88894b41 100644 --- a/packages/react/.storybook/main.ts +++ b/packages/react/.storybook/main.ts @@ -16,6 +16,7 @@ const config: StorybookConfig = { }, }, }, + 'storybook-addon-mantine', ], staticDirs: ['../public'], framework: { diff --git a/packages/react/.storybook/preview-head.html b/packages/react/.storybook/preview-head.html new file mode 100644 index 0000000000..314bf801a1 --- /dev/null +++ b/packages/react/.storybook/preview-head.html @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/packages/react/.storybook/preview.tsx b/packages/react/.storybook/preview.tsx index 44092fa60e..03bc56098a 100644 --- a/packages/react/.storybook/preview.tsx +++ b/packages/react/.storybook/preview.tsx @@ -1,9 +1,9 @@ -import { MantineProvider, MantineThemeOverride } from '@mantine/core'; import '@mantine/core/styles.css'; import { MockClient } from '@medplum/mock'; import { MedplumProvider } from '@medplum/react-hooks'; import { BrowserRouter } from 'react-router-dom'; import { createGlobalTimer } from '../src/stories/MockDateWrapper.utils'; +import { themes } from './themes'; export const parameters = { layout: 'fullscreen', @@ -26,32 +26,12 @@ medplum.get('/').then(() => { clock.restore(); }); -const theme: MantineThemeOverride = { - 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', - }, -}; - export const decorators = [ + themes, (Story) => ( - - - + ), diff --git a/packages/react/.storybook/themes.ts b/packages/react/.storybook/themes.ts new file mode 100644 index 0000000000..c6dca1c83a --- /dev/null +++ b/packages/react/.storybook/themes.ts @@ -0,0 +1,481 @@ +import { createTheme } from '@mantine/core'; +import { withMantineThemes } from 'storybook-addon-mantine'; + +const medplumDefault = 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 fooMedical = createTheme({ + colors: { + // Replace or adjust with the exact colors used for your design + primary: [ + '#f7f7f7', // primary[0] + '#eef6f4', // primary[1] + '#e3eff2', // primary[2] + '#d5ebec', // primary[3] + '#cfe7e9', // primary[4] + '#b0d7db', // primary[5] + '#39acbc', // primary[6] + '#005450', // primary[7] + '#004d49', // primary[8] + '#00353a', // primary[9] (adjusted for a darker shade) + ], + secondary: [ + '#fff7eb', // secondary[0] + '#ffedce', // secondary[1] + '#fae3c3', // secondary[2] + '#e9d1b9', // secondary[3] + '#e8c9a6', // secondary[4] + '#f1dfca', // secondary[5] + '#ffc776', // secondary[6] + '#fa645a', // secondary[7] + '#b57931', // secondary[8] + '#935923', // secondary[9] (adjusted for a darker shade) + ], + }, + primaryColor: 'primary', + fontFamily: 'Ginto, helvetica', + radius: { + xs: '.5rem', + sm: '.75rem', + md: '1rem', + lg: '1.5rem', + xl: '2.5rem', + }, + spacing: { + xs: '.25rem', + sm: '.33rem', + md: '.5rem', + lg: '.66rem', + xl: '1rem', + }, + defaultRadius: 'xl', + shadows: { + xs: '0px 0px 0px rgba(0, 0, 0, 0)', + md: '2px 2px 1.5px rgba(0, 0, 0, .25)', + xl: '5px 5px 3px rgba(0, 0, 0, .25)', + }, + headings: { + fontFamily: 'GT Super Display, serif', + sizes: { + h1: { fontSize: '30px', lineHeight: '1.4' }, + h2: { fontSize: '24px', lineHeight: '1.35' }, + h3: { fontSize: '20px', lineHeight: '1.3' }, + h4: { fontSize: '18px', lineHeight: '1.25' }, + h5: { fontSize: '16px', lineHeight: '1.2' }, + h6: { fontSize: '14px', lineHeight: '1.15' }, + }, + }, +}); + +const bonFoo = createTheme({ + components: { + Paper: { + defaultProps: { + p: 'sm', + shadow: 'xs', + }, + }, + Table: { + defaultProps: { + striped: false, + // m: '16px', + }, + }, + }, + fontFamily: + '-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji', + shadows: { + xs: 'none', + sm: 'none', + md: 'none', + lg: 'none', + xl: 'none', + }, + spacing: { + xs: '8px', + sm: '10px', + md: '12px', + lg: '14px', + xl: '16px', + }, + colors: { + destructive: [ + '#FFF5F5', + '#FFE3E3', + '#FFC9C9', + '#FFA8A8', + '#FF8787', + '#FF6B6B', + '#FA5252', + '#F03E3E', + '#E03131', + '#C92A2A', + ], + dark: [ + '#C1C2C5', + '#A6A7AB', + '#909296', + '#5C5F66', + '#373A40', + '#2C2E33', + '#25262B', + '#1A1B1E', + '#141517', + '#101113', + ], + primary: [ + '#E7F5FF', + '#D0EBFF', + '#A5D8FF', + '#74C0FC', + '#4DABF7', + '#339AF0', + '#228BE6', + '#1C7ED6', + '#1971C2', + '#1864AB', + ], + neutral: [ + '#F8F9FA', + '#F1F3F5', + '#E9ECEF', + '#DEE2E6', + '#CED4DA', + '#ADB5BD', + '#868E96', + '#495057', + '#343A40', + '#212529', + ], + }, +}); + +const plumMedical = createTheme({ + components: { + Divider: { + defaultProps: { + my: '0', + }, + }, + }, + colors: { + primary: [ + '#eef6f4', + '#00D7AB', + '#00B395', + '#00907E', + '#008062', + '#005450', + '#004D49', + '#003F3B', + '#003231', + '#002824', + ], + destructive: [ + '#e8d3cf', + '#ddb3b0', + '#d59091', + '#cf6e77', + '#ca4956', + '#bc2f3d', + '#a0222f', + '#821722', + '#620e16', + '#400707', + ], + }, + primaryColor: 'primary', + primaryShade: 6, + shadows: { + xs: '0px 0px 0px rgba(0, 0, 0, 0)', + sm: '0px 0px 0px rgba(0, 0, 0, 0)', + md: '0px 0px 0px rgba(0, 0, 0, 0)', + lg: '0px 0px 0px rgba(0, 0, 0, 0)', + xl: '0px 0px 0px rgba(0, 0, 0, 0)', + }, + fontFamily: '"Ginto", helvetica, "sans-serif"', + headings: { fontFamily: 'GT Super Display, Times New Roman, "serif"' }, + fontSizes: { + xs: '12px', + sm: '14px', + md: '18px', + lg: '22px', + xl: '30px', + }, + radius: { + xs: '5px', + sm: '7px', + md: '9px', + lg: '11px', + xl: '13px', + }, + defaultRadius: 'lg', + spacing: { + xs: '.5rem', + sm: '1rem', + md: '1.5rem', + lg: '2rem', + xl: '2.5rem', + }, +}); + +const materialUi = createTheme({ + fontFamily: 'Roboto, Helvetica, Arial, "sans-serif"', + fontSizes: { + xs: '.7rem', + sm: '.85rem', + md: '1rem', + lg: '1.2rem', + xl: '1.4rem', + }, + colors: { + primary: [ + '#cce5ff', + '#99ccff', + '#66b2ff', + '#3399ff', + '#0073e6', + '#0288d1', + '#006bd6', + '#0061c2', + '#004c99', + '#0037a5', + ], + }, + primaryColor: 'primary', + primaryShade: 4, + shadows: { + xs: '0px 2px 1px -1px rgba(0, 0, 0, 0.2)', + sm: '0px 2px 1px -1px rgba(0, 0, 0, 0.2)', + md: '0px 1px 1px 0px rgba(0, 0, 0, 0.14)', + lg: '0px 1px 1px 0px rgba(0, 0, 0, 0.14)', + xl: '0px 1px 3px 0px rgba(0, 0, 0, 0.12)', + }, + radius: { + xs: '0px', + sm: '2px', + md: '4px', + lg: '6px', + xl: '8px', + }, + spacing: { + xs: '4px 8px', + sm: '6px 12px', + md: '8px 16px', + lg: '10px 20px', + xl: '12px 24px', + }, +}); + +const sciFi = createTheme({ + fontFamily: '"Gill Sans", arial, "sans-serif"', + colors: { + primary: [ + '#FFFBB7', + '#FFF891', + '#FFF56A', + '#FFF244', + '#FFE81F', + '#FFD900', + '#ffb300', + '#f59b00', + '#eb8500', + '#e07000', + ], + }, + primaryColor: 'primary', + primaryShade: 6, + black: '#412538', + radius: { + xs: '20px 10px', + sm: '30px 15px', + md: '40px 20px', + lg: '50px 25px', + xl: '60px 30px', + }, + shadows: { + xs: '2px 1px 1px -1px #939393', + md: '3px 2px 1px -1px #939393', + xl: '4px 3px 1px -1px #939393', + }, + spacing: { + xs: '1px', + sm: '3px', + md: '5px', + lg: '7px', + xl: '9px', + }, + lineHeights: { + xs: '12px', + sm: '16px', + md: '20px', + lg: '24px', + xl: '30px', + }, +}); + +const cursive = createTheme({ + fontFamily: '"Brush Script MT", serif', + colors: { + primary: [ + '#fce8e8', + '#f7cfd5', + '#f1bcc9', + '#e7a6c0', + '#d987b0', + '#c770a4', + '#b65d9c', + '#a85d9a', + '#845282', + '#604965', + ], + }, + primaryColor: 'primary', + primaryShade: 5, + radius: { + xs: '0', + sm: '0', + md: '0', + lg: '0', + xl: '0', + }, + shadows: { + xs: '4px 4px 3px grey', + sm: '8px 8px 3px grey', + md: '12px 12px 3px grey', + lg: '16px 16px 3px grey', + xl: '20px 20px 3px grey', + }, +}); + +const caesar = createTheme({ + fontFamily: '"Caesar Dressing", serif', + fontSizes: { + xs: '.8rem', + sm: '.9rem', + md: '1rem', + lg: '1.1rem', + xl: '1.2rem', + }, + colors: { + primary: [ + '#fd5d6b', + '#fb3737', + '#f81b1b', + '#d70909', + '#a00808', + '#810e0e', + '#601410', + '#4b1711', + '#34150f', + '#25120e', + ], + }, + primaryColor: 'primary', + primaryShade: 4, + shadows: { + xs: '3px 3px 2px grey', + xl: '5px 5px 2px grey', + }, +}); + +const wordArt = createTheme({ + fontFamily: '"Bungee Spice", "sans-serif"', + defaultRadius: '0px', + shadows: { + xs: '0px 0px 0px', + sm: '0px 0px 0px', + md: '0px 0px 0px', + lg: '0px 0px 0px', + xl: '0px 0px 0px', + }, + colors: { + primary: [ + '#bcfeae', + '#90fa85', + '#64f55c', + '#34ed31', + '#1acf17', + '#1da21a', + '#1c7e1b', + '#1d5e20', + '#183f1c', + '#122b17', + ], + }, + primaryColor: 'primary', + primaryShade: 4, + spacing: { + xs: '12px', + sm: '16px', + md: '20px', + lg: '24px', + xl: '30px', + }, +}); + +export const themes = withMantineThemes({ + themes: [ + { + id: 'medplumDefault', + name: 'Medplum Default', + ...medplumDefault, + }, + { + id: 'foomedical', + name: 'Foo Medical', + ...fooMedical, + }, + { + id: 'bonfoo', + name: 'Bon Foo', + ...bonFoo, + }, + { + id: 'plumMedical', + name: 'PlumMedical', + ...plumMedical, + }, + { + id: 'materialUi', + name: 'Material UI', + ...materialUi, + }, + { + id: 'sci-fi', + name: 'SciFi', + ...sciFi, + }, + { + id: 'cursive', + name: 'Cursive', + ...cursive, + }, + { + id: 'caesar', + name: 'Caesar', + ...caesar, + }, + { + id: 'word-art', + name: 'Word Art', + ...wordArt, + }, + ], +}); diff --git a/packages/react/package.json b/packages/react/package.json index 89d71cdab7..ae782633e8 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -105,6 +105,7 @@ "sinon": "17.0.1", "storybook": "8.0.6", "typescript": "5.4.4", + "storybook-addon-mantine": "4.0.2", "vite-plugin-turbosnap": "^1.0.3" }, "peerDependencies": { From 24bfb5558d5ad1254214bb9a869308dd9d9f0f40 Mon Sep 17 00:00:00 2001 From: Jesse Carter Date: Tue, 9 Apr 2024 10:41:05 -0600 Subject: [PATCH 03/52] Update index.md (#4347) Just a small fix for the docs --- packages/docs/docs/administration/provider-directory/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/docs/docs/administration/provider-directory/index.md b/packages/docs/docs/administration/provider-directory/index.md index ee8da13c8f..5d14afe491 100644 --- a/packages/docs/docs/administration/provider-directory/index.md +++ b/packages/docs/docs/administration/provider-directory/index.md @@ -4,7 +4,7 @@ sidebar_position: 7 # Modeling your Provider Directory -Provider Directories are critical databases housing essential details about healthcare providers, from individual practitioners to entire organizations. Accurate and standardized modeling of these directories, ensures better a better patient experience via improved coordination coordination of care and operational efficiency. +Provider Directories are critical databases housing essential details about healthcare providers, from individual practitioners to entire organizations. Accurate and standardized modeling of these directories, ensures a better patient experience via improved coordination coordination of care and operational efficiency. This section provide a series of guides on how to properly model your organization's provider directory. It focuses on a few key challenges: From 7bd85be0a674d9f2a7f81a75437ca941540de141 Mon Sep 17 00:00:00 2001 From: jmalobicky Date: Tue, 9 Apr 2024 16:58:50 -0400 Subject: [PATCH 04/52] Add config to set default Project Features (#4330) * Add config to set default Project Features * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- packages/docs/docs/self-hosting/config-settings.md | 1 + packages/server/src/config.ts | 12 ++++++++++++ packages/server/src/fhir/operations/projectinit.ts | 3 +++ 3 files changed, 16 insertions(+) diff --git a/packages/docs/docs/self-hosting/config-settings.md b/packages/docs/docs/self-hosting/config-settings.md index 85fde2ddce..61993bf989 100644 --- a/packages/docs/docs/self-hosting/config-settings.md +++ b/packages/docs/docs/self-hosting/config-settings.md @@ -156,6 +156,7 @@ Optionally override the trusted CA certificates. Default is to trust the well-kn | `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` | +| `defaultProjectFeatures` | Optional default project features. See [Project Settings](/docs/access/projects#settings) | | `init` | | :::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 0bc5f9d5b9..1268844d13 100644 --- a/packages/server/src/config.ts +++ b/packages/server/src/config.ts @@ -52,6 +52,17 @@ export interface MedplumServerConfig { heartbeatEnabled?: boolean; accurateCountThreshold: number; defaultBotRuntimeVersion: 'awslambda' | 'vmcontext'; + defaultProjectFeatures?: + | ( + | 'email' + | 'bots' + | 'cron' + | 'google-auth-required' + | 'graphql-introspection' + | 'terminology' + | 'websocket-subscriptions' + )[] + | undefined; /** Temporary feature flag, to be removed */ chainedSearchWithReferenceTables?: boolean; @@ -334,6 +345,7 @@ function addDefaults(config: MedplumServerConfig): MedplumServerConfig { config.shutdownTimeoutMilliseconds = config.shutdownTimeoutMilliseconds ?? 30000; config.accurateCountThreshold = config.accurateCountThreshold ?? 1000000; config.defaultBotRuntimeVersion = config.defaultBotRuntimeVersion ?? 'awslambda'; + config.defaultProjectFeatures = config.defaultProjectFeatures ?? []; return config; } diff --git a/packages/server/src/fhir/operations/projectinit.ts b/packages/server/src/fhir/operations/projectinit.ts index d77b69528d..6656a03757 100644 --- a/packages/server/src/fhir/operations/projectinit.ts +++ b/packages/server/src/fhir/operations/projectinit.ts @@ -16,6 +16,7 @@ import { getAuthenticatedContext, getRequestContext } from '../../context'; import { getUserByEmailWithoutProject } from '../../oauth/utils'; import { getSystemRepo } from '../repo'; import { buildOutputParameters, parseInputParameters } from './utils/parameters'; +import { getConfig } from '../../config'; const projectInitOperation: OperationDefinition = { resourceType: 'OperationDefinition', @@ -127,6 +128,7 @@ export async function createProject( }> { const ctx = getRequestContext(); const systemRepo = getSystemRepo(); + const config = getConfig(); ctx.logger.info('Project creation request received', { name: projectName }); const project = await systemRepo.createResource({ @@ -134,6 +136,7 @@ export async function createProject( name: projectName, owner: admin ? createReference(admin) : undefined, strictMode: true, + features: config.defaultProjectFeatures, }); ctx.logger.info('Project created', { From e59b7bb505bc918831188b9b087e079c06ed51a6 Mon Sep 17 00:00:00 2001 From: Derrick Farris Date: Tue, 9 Apr 2024 16:33:23 -0700 Subject: [PATCH 05/52] tweak(redis): add `getRedisSubscriber` for graceful shutdown safety (#4336) * tweak(redis): add `getRedisSubscriber` for graceful shutdown safety * chore(redis): update JSDoc comments * test(redis): fix subscriber test hang * refactor(server): all subscribers cleaned up on `closeRedis()` --- packages/server/src/agent/websockets.ts | 4 +- .../server/src/fhir/operations/agentpush.ts | 4 +- packages/server/src/fhircast/websocket.ts | 4 +- packages/server/src/redis.test.ts | 78 ++++++++++++++++++- packages/server/src/redis.ts | 54 ++++++++++++- .../server/src/subscriptions/websockets.ts | 4 +- packages/server/src/websockets.ts | 4 +- .../server/src/workers/subscription.test.ts | 17 ++-- 8 files changed, 149 insertions(+), 20 deletions(-) diff --git a/packages/server/src/agent/websockets.ts b/packages/server/src/agent/websockets.ts index d6cfba398e..154f578535 100644 --- a/packages/server/src/agent/websockets.ts +++ b/packages/server/src/agent/websockets.ts @@ -16,7 +16,7 @@ import { getRepoForLogin } from '../fhir/accesspolicy'; import { executeBot } from '../fhir/operations/execute'; import { heartbeat } from '../heartbeat'; import { getLoginForAccessToken } from '../oauth/utils'; -import { getRedis } from '../redis'; +import { getRedis, getRedisSubscriber } from '../redis'; const STATUS_EX_SECONDS = 24 * 60 * 60; // 24 hours in seconds @@ -115,7 +115,7 @@ export async function handleAgentConnection(socket: ws.WebSocket, request: Incom const agent = await repo.readResource('Agent', agentId); // Connect to Redis - redisSubscriber = getRedis().duplicate(); + redisSubscriber = getRedisSubscriber(); await redisSubscriber.subscribe(getReferenceString(agent)); redisSubscriber.on('message', (_channel: string, message: string) => { // When a message is received, send it to the agent diff --git a/packages/server/src/fhir/operations/agentpush.ts b/packages/server/src/fhir/operations/agentpush.ts index 52107a2680..0bee00b899 100644 --- a/packages/server/src/fhir/operations/agentpush.ts +++ b/packages/server/src/fhir/operations/agentpush.ts @@ -12,7 +12,7 @@ import { Request, Response } from 'express'; import { randomUUID } from 'node:crypto'; import { asyncWrap } from '../../async'; import { getAuthenticatedContext } from '../../context'; -import { getRedis } from '../../redis'; +import { getRedis, getRedisSubscriber } from '../../redis'; import { sendOutcome } from '../outcomes'; import { getAgentForRequest, getDevice } from './agentutils'; import { parseParameters } from './utils/parameters'; @@ -94,7 +94,7 @@ export const agentPushHandler = asyncWrap(async (req: Request, res: Response) => // Otherwise, open a new redis connection in "subscribe" state message.callback = getReferenceString(agent) + '-' + randomUUID(); - const redisSubscriber = getRedis().duplicate(); + const redisSubscriber = getRedisSubscriber(); await redisSubscriber.subscribe(message.callback); redisSubscriber.on('message', (_channel: string, message: string) => { const response = JSON.parse(message) as AgentTransmitResponse; diff --git a/packages/server/src/fhircast/websocket.ts b/packages/server/src/fhircast/websocket.ts index cb4ee23b25..0a0faf9a7b 100644 --- a/packages/server/src/fhircast/websocket.ts +++ b/packages/server/src/fhircast/websocket.ts @@ -4,7 +4,7 @@ import { IncomingMessage } from 'http'; import ws from 'ws'; import { DEFAULT_HEARTBEAT_MS, heartbeat } from '../heartbeat'; import { globalLogger } from '../logger'; -import { getRedis } from '../redis'; +import { getRedis, getRedisSubscriber } from '../redis'; /** * Handles a new WebSocket connection to the FHIRCast hub. @@ -20,7 +20,7 @@ export async function handleFhircastConnection(socket: ws.WebSocket, request: In // Once the client enters the subscribed state it is not supposed to issue any other commands, // except for additional SUBSCRIBE, PSUBSCRIBE, UNSUBSCRIBE and PUNSUBSCRIBE commands. const redis = getRedis(); - const redisSubscriber = redis.duplicate(); + const redisSubscriber = getRedisSubscriber(); // Subscribe to the topic await redisSubscriber.subscribe(topic); diff --git a/packages/server/src/redis.test.ts b/packages/server/src/redis.test.ts index 1dc387c67d..a132ffb2b1 100644 --- a/packages/server/src/redis.test.ts +++ b/packages/server/src/redis.test.ts @@ -1,9 +1,15 @@ -import { loadTestConfig } from './config'; -import { closeRedis, getRedis, initRedis } from './redis'; +import { Redis } from 'ioredis'; +import { MedplumServerConfig, loadTestConfig } from './config'; +import { closeRedis, getRedis, getRedisSubscriber, getRedisSubscriberCount, initRedis } from './redis'; describe('Redis', () => { + let config: MedplumServerConfig; + + beforeAll(async () => { + config = await loadTestConfig(); + }); + test('Get redis', async () => { - const config = await loadTestConfig(); initRedis(config.redis); expect(getRedis()).toBeDefined(); await closeRedis(); @@ -13,4 +19,70 @@ describe('Redis', () => { expect(() => getRedis()).toThrow(); await expect(closeRedis()).resolves.toBeUndefined(); }); + + describe('getRedisSubscriber', () => { + test('Not initialized', async () => { + await closeRedis(); + expect(() => getRedisSubscriber()).toThrow(); + }); + + test('Getting a subscriber', async () => { + initRedis(config.redis); + const subscriber = getRedisSubscriber(); + expect(subscriber).toBeInstanceOf(Redis); + await closeRedis(); + }); + + test('Hanging subscriber still disconnects on closeRedis', async () => { + initRedis(config.redis); + const subscriber = getRedisSubscriber(); + + let reject: (err: Error) => void; + const closePromise = new Promise((resolve, _reject) => { + subscriber.on('end', () => { + resolve(); + }); + reject = _reject; + }); + + expect(subscriber).toBeDefined(); + await closeRedis(); + + const timer = setTimeout(() => { + reject(new Error('Timeout')); + }, 3500); + + await expect(closePromise).resolves.toBeUndefined(); + clearTimeout(timer); + }); + + test('Disconnecting a subscriber removes it from the list', async () => { + initRedis(config.redis); + expect(getRedisSubscriberCount()).toEqual(0); + const subscriber = getRedisSubscriber(); + expect(getRedisSubscriberCount()).toEqual(1); + subscriber.disconnect(); + + let reject: (err: Error) => void; + const closePromise = new Promise((resolve, _reject) => { + subscriber.on('end', () => { + resolve(); + }); + reject = _reject; + }); + + expect(subscriber).toBeDefined(); + await closeRedis(); + + const timer = setTimeout(() => { + reject(new Error('Timeout')); + }, 3500); + + await expect(closePromise).resolves.toBeUndefined(); + expect(getRedisSubscriberCount()).toEqual(0); + clearTimeout(timer); + + await closeRedis(); + }); + }); }); diff --git a/packages/server/src/redis.ts b/packages/server/src/redis.ts index 0ae0bbedf3..c99db6b296 100644 --- a/packages/server/src/redis.ts +++ b/packages/server/src/redis.ts @@ -3,6 +3,7 @@ import Redis from 'ioredis'; import { MedplumRedisConfig } from './config'; let redis: Redis | undefined = undefined; +let redisSubscribers: Set | undefined = undefined; export function initRedis(config: MedplumRedisConfig): void { redis = new Redis(config); @@ -11,15 +12,66 @@ export function initRedis(config: MedplumRedisConfig): void { export async function closeRedis(): Promise { if (redis) { const tmpRedis = redis; + const tmpSubscribers = redisSubscribers; redis = undefined; + redisSubscribers = undefined; + if (tmpSubscribers) { + for (const subscriber of tmpSubscribers) { + subscriber.disconnect(); + } + } await tmpRedis.quit(); await sleep(100); } } -export function getRedis(): Redis { +/** + * Gets the global `Redis` instance. + * + * The `duplicate` method is intentionally omitted to prevent accidental calling of `Redis.quit` + * which can cause the global instance to fail to shutdown gracefully later on. + * + * Instead {@link getRedisSubscriber} should be called to obtain a `Redis` instance for use as a subscriber-mode client. + * + * @returns The global `Redis` instance. + */ +export function getRedis(): Redis & { duplicate: never } { if (!redis) { throw new Error('Redis not initialized'); } + // @ts-expect-error We don't want anyone to call `duplicate on the redis global instance + // This is because we want to gracefully `quit` and duplicated Redis instances will return redis; } + +/** + * Gets a `Redis` instance for use in subscriber mode. + * + * The synchronous `.disconnect()` on this instance should be called instead of `.quit()` when you want to disconnect. + * + * @returns A `Redis` instance to use as a subscriber client. + */ +export function getRedisSubscriber(): Redis & { quit: never } { + if (!redis) { + throw new Error('Redis not initialized'); + } + if (!redisSubscribers) { + redisSubscribers = new Set(); + } + + const subscriber = redis.duplicate(); + redisSubscribers.add(subscriber); + + subscriber.on('end', () => { + redisSubscribers?.delete(subscriber); + }); + + return subscriber as Redis & { quit: never }; +} + +/** + * @returns The amount of active `Redis` subscriber instances. + */ +export function getRedisSubscriberCount(): number { + return redisSubscribers?.size ?? 0; +} diff --git a/packages/server/src/subscriptions/websockets.ts b/packages/server/src/subscriptions/websockets.ts index 0a07686fd7..910499cb58 100644 --- a/packages/server/src/subscriptions/websockets.ts +++ b/packages/server/src/subscriptions/websockets.ts @@ -10,7 +10,7 @@ import { getFullUrl } from '../fhir/response'; import { heartbeat } from '../heartbeat'; import { globalLogger } from '../logger'; import { verifyJwt } from '../oauth/keys'; -import { getRedis } from '../redis'; +import { getRedis, getRedisSubscriber } from '../redis'; interface BaseSubscriptionClientMsg { type: string; @@ -44,7 +44,7 @@ export async function handleR4SubscriptionConnection(socket: ws.WebSocket): Prom // According to Redis documentation: http://redis.io/commands/subscribe // Once the client enters the subscribed state it is not supposed to issue any other commands, // except for additional SUBSCRIBE, PSUBSCRIBE, UNSUBSCRIBE and PUNSUBSCRIBE commands. - redisSubscriber = redis.duplicate(); + redisSubscriber = getRedisSubscriber(); redisSubscriber.on('message', (channel: string, message: string) => { globalLogger.debug('[WS] redis message', { channel, message }); diff --git a/packages/server/src/websockets.ts b/packages/server/src/websockets.ts index 090fddaa2b..a3f528c495 100644 --- a/packages/server/src/websockets.ts +++ b/packages/server/src/websockets.ts @@ -8,7 +8,7 @@ import { getConfig } from './config'; import { RequestContext, requestContextStore } from './context'; import { handleFhircastConnection } from './fhircast/websocket'; import { globalLogger } from './logger'; -import { getRedis } from './redis'; +import { getRedis, getRedisSubscriber } from './redis'; import { handleR4SubscriptionConnection } from './subscriptions/websockets'; const handlerMap = new Map Promise>(); @@ -104,7 +104,7 @@ async function handleEchoConnection(socket: ws.WebSocket): Promise { // According to Redis documentation: http://redis.io/commands/subscribe // Once the client enters the subscribed state it is not supposed to issue any other commands, // except for additional SUBSCRIBE, PSUBSCRIBE, UNSUBSCRIBE and PUNSUBSCRIBE commands. - const redisSubscriber = getRedis().duplicate(); + const redisSubscriber = getRedisSubscriber(); const channel = randomUUID(); await redisSubscriber.subscribe(channel); diff --git a/packages/server/src/workers/subscription.test.ts b/packages/server/src/workers/subscription.test.ts index c15139c2d7..cb35074585 100644 --- a/packages/server/src/workers/subscription.test.ts +++ b/packages/server/src/workers/subscription.test.ts @@ -26,7 +26,7 @@ import { loadTestConfig } from '../config'; import { getDatabasePool } from '../database'; import { Repository, getSystemRepo } from '../fhir/repo'; import { globalLogger } from '../logger'; -import { getRedis } from '../redis'; +import { getRedisSubscriber } from '../redis'; import { createTestProject, withTestContext } from '../test.setup'; import { AuditEventOutcome } from '../util/auditevent'; import { closeSubscriptionWorker, execSubscriptionJob, getSubscriptionQueue } from './subscription'; @@ -1517,7 +1517,7 @@ describe('Subscription Worker', () => { expect(subscription.id).toBeDefined(); // Subscribe to the topic - const subscriber = getRedis().duplicate(); + const subscriber = getRedisSubscriber(); await subscriber.subscribe(subscription.id as string); let resolve: () => void; @@ -1564,6 +1564,7 @@ describe('Subscription Worker', () => { expect(queue.add).toHaveBeenCalled(); await deferredPromise; + // @ts-expect-error Okay to await quit in tests await subscriber.quit(); })); @@ -1591,7 +1592,7 @@ describe('Subscription Worker', () => { expect(subscription.id).toBeDefined(); // Subscribe to the topic - const subscriber = getRedis().duplicate(); + const subscriber = getRedisSubscriber(); await subscriber.subscribe(subscription.id as string); let resolve: () => void; @@ -1622,6 +1623,7 @@ describe('Subscription Worker', () => { }, 150); await deferredPromise; + // @ts-expect-error Okay to await quit in tests await subscriber.quit(); expect(console.log).toHaveBeenLastCalledWith(expect.stringMatching(/WebSocket Subscriptions/)); @@ -1670,7 +1672,7 @@ describe('Subscription Worker', () => { expect(subscription.id).toBeDefined(); // Subscribe to the topic - const subscriber = getRedis().duplicate(); + const subscriber = getRedisSubscriber(); await subscriber.subscribe(subscription.id as string); let resolve: () => void; @@ -1698,6 +1700,7 @@ describe('Subscription Worker', () => { setTimeout(() => resolve(), 300); await deferredPromise; + // @ts-expect-error Okay to await quit in tests await subscriber.quit(); expect(console.log).toHaveBeenCalledWith( @@ -1748,7 +1751,7 @@ describe('Subscription Worker', () => { await superAdminRepo.deleteResource('ProjectMembership', membership.id as string); // Subscribe to the topic - const subscriber = getRedis().duplicate(); + const subscriber = getRedisSubscriber(); await subscriber.subscribe(subscription.id as string); let resolve: () => void; @@ -1776,6 +1779,7 @@ describe('Subscription Worker', () => { setTimeout(() => resolve(), 300); await deferredPromise; + // @ts-expect-error Okay to await quit in tests await subscriber.quit(); expect(console.log).toHaveBeenCalledWith( @@ -1826,7 +1830,7 @@ describe('Subscription Worker', () => { await superAdminRepo.deleteResource('AccessPolicy', accessPolicy.id as string); // Subscribe to the topic - const subscriber = getRedis().duplicate(); + const subscriber = getRedisSubscriber(); await subscriber.subscribe(subscription.id as string); let resolve: () => void; @@ -1854,6 +1858,7 @@ describe('Subscription Worker', () => { setTimeout(() => resolve(), 300); await deferredPromise; + // @ts-expect-error Okay to await quit in test await subscriber.quit(); expect(console.log).toHaveBeenCalledWith( From 2bc266923c934c821edb01e349e4fbfd04e0bf75 Mon Sep 17 00:00:00 2001 From: James Wu <88938117+thejwuscript@users.noreply.github.com> Date: Wed, 10 Apr 2024 12:03:30 +0900 Subject: [PATCH 06/52] Clear auth state on auth failure (#4341) * Clear all previous auth state on 401 * Update docs on medplumClient * Prevent calling sessionStorage in Node env --- packages/core/src/client.ts | 6 ++++-- packages/docs/docs/auth/methods/index.md | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 49592e182a..b5305ff7a6 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -906,7 +906,9 @@ export class MedplumClient extends EventTarget { */ clear(): void { this.storage.clear(); - sessionStorage.clear(); + if (typeof window !== 'undefined') { + sessionStorage.clear(); + } this.clearActiveLogin(); } @@ -3310,7 +3312,7 @@ export class MedplumClient extends EventTarget { if (this.refresh()) { return this.request(method, url, options); } - this.clearActiveLogin(); + this.clear(); if (this.onUnauthenticated) { this.onUnauthenticated(); } diff --git a/packages/docs/docs/auth/methods/index.md b/packages/docs/docs/auth/methods/index.md index b34f45ab3d..b9ce149957 100644 --- a/packages/docs/docs/auth/methods/index.md +++ b/packages/docs/docs/auth/methods/index.md @@ -114,6 +114,7 @@ All three implementations types will have tokens or client credentials with syst - Consider disabling local storage on device for shared workstations or in accordance with institution policy. - Organizations with mobile devices or laptops should enable a Mobile Device Management (MDM) solution for workstations - [IP restrictions](/docs/access/ip-access-rules) can be enabled when especially sensitive data, such as personal health information (PHI), is being accessed. +- Reusing the same `MedplumClient` instance for different users is discouraged. Consider creating new instances of `MedplumClient` instead. ### Server Authentication @@ -122,6 +123,7 @@ All three implementations types will have tokens or client credentials with syst - Restrict access to host via VPC or other mechanism - do not allow access from general internet. - Use a secrets management to store access keys - do not store credentials on disk. - Ensure host is patched and has security updates applied regularly. +- Consider creating a new instance of `MedplumClient`, particularly when switching to another user. ### Host authentication From 48cb0e97db6527329b203a4846b9afdd2cffcd81 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Tue, 9 Apr 2024 20:43:37 -0700 Subject: [PATCH 07/52] Agent SerialPort connections (#4348) * Agent SerialPort connections * Fixed hibp mocks in projectinit.test.ts --- package-lock.json | 2994 +++++++++++++++-- packages/agent/package.json | 1 + packages/agent/src/app.ts | 3 + packages/agent/src/serialport.test.ts | 173 + packages/agent/src/serialport.ts | 184 + .../src/fhir/operations/projectinit.test.ts | 24 +- 6 files changed, 3157 insertions(+), 222 deletions(-) create mode 100644 packages/agent/src/serialport.test.ts create mode 100644 packages/agent/src/serialport.ts diff --git a/package-lock.json b/package-lock.json index ba307e0a32..34f00c0d53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -812,16 +812,14 @@ "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-cloudformation": { + "node_modules/@aws-sdk/client-acm/node_modules/@aws-sdk/client-sso": { "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudformation/-/client-cloudformation-3.549.0.tgz", - "integrity": "sha512-mEJUP2guSOdku5/+LIiKwAA9WE+nHKEtKPGOyOL0x72lkZCsfM3QSr35Xi6p1Aip3XB/LI+UCuoYpjiaceSpSw==", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.549.0.tgz", + "integrity": "sha512-lz+yflOAj5Q263FlCsKpNqttaCb2NPh8jC76gVCqCt7TPxRDBYVaqg0OZYluDaETIDNJi4DwN2Azcck7ilwuPw==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.549.0", "@aws-sdk/core": "3.549.0", - "@aws-sdk/credential-provider-node": "3.549.0", "@aws-sdk/middleware-host-header": "3.535.0", "@aws-sdk/middleware-logger": "3.535.0", "@aws-sdk/middleware-recursion-detection": "3.535.0", @@ -856,24 +854,21 @@ "@smithy/util-middleware": "^2.2.0", "@smithy/util-retry": "^2.2.0", "@smithy/util-utf8": "^2.3.0", - "@smithy/util-waiter": "^2.2.0", - "tslib": "^2.6.2", - "uuid": "^9.0.1" + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-cloudfront": { + "node_modules/@aws-sdk/client-acm/node_modules/@aws-sdk/client-sso-oidc": { "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudfront/-/client-cloudfront-3.549.0.tgz", - "integrity": "sha512-XoTKP2sttLl7azal3baptnk4r1zSVX5KjRVim9dlLFViCRrSxpWwQNXKyKI53Hk1rsy0CNxJzPYd4/ag11kOCQ==", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.549.0.tgz", + "integrity": "sha512-FbB4A78ILAb8sM4TfBd+3CrQcfZIhe0gtVZNbaxpq5cJZh1K7oZ8vPfKw4do9JWkDUXPLsD9Bwz12f8/JpAb6Q==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", "@aws-sdk/client-sts": "3.549.0", "@aws-sdk/core": "3.549.0", - "@aws-sdk/credential-provider-node": "3.549.0", "@aws-sdk/middleware-host-header": "3.535.0", "@aws-sdk/middleware-logger": "3.535.0", "@aws-sdk/middleware-recursion-detection": "3.535.0", @@ -883,7 +878,6 @@ "@aws-sdk/util-endpoints": "3.540.0", "@aws-sdk/util-user-agent-browser": "3.535.0", "@aws-sdk/util-user-agent-node": "3.535.0", - "@aws-sdk/xml-builder": "3.535.0", "@smithy/config-resolver": "^2.2.0", "@smithy/core": "^1.4.1", "@smithy/fetch-http-handler": "^2.5.0", @@ -908,19 +902,130 @@ "@smithy/util-endpoints": "^1.2.0", "@smithy/util-middleware": "^2.2.0", "@smithy/util-retry": "^2.2.0", - "@smithy/util-stream": "^2.2.0", "@smithy/util-utf8": "^2.3.0", - "@smithy/util-waiter": "^2.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": "^3.549.0" } }, - "node_modules/@aws-sdk/client-cloudwatch-logs": { + "node_modules/@aws-sdk/client-acm/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.535.0.tgz", + "integrity": "sha512-kdj1wCmOMZ29jSlUskRqN04S6fJ4dvt0Nq9Z32SA6wO7UG8ht6Ot9h/au/eTWJM3E1somZ7D771oK7dQt9b8yw==", + "dependencies": { + "@aws-sdk/types": "3.535.0", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/util-stream": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-acm/node_modules/@aws-sdk/credential-provider-ini": { "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.549.0.tgz", - "integrity": "sha512-LAB4MWkx41wF+IImj4nucMmgAkGFD51sR5JkXsUoB5leLo0Oz84ZEFZ8RfqshF6ReoW5lmSRyB8x1663KdRcLA==", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.549.0.tgz", + "integrity": "sha512-k6IIrluZjQpzui5Din8fW3bFFhHaJ64XrsfYx0Ks1mb7xan84dJxmYP3tdDDmLzUeJv5h95ag88taHfjY9rakA==", + "dependencies": { + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/credential-provider-env": "3.535.0", + "@aws-sdk/credential-provider-process": "3.535.0", + "@aws-sdk/credential-provider-sso": "3.549.0", + "@aws-sdk/credential-provider-web-identity": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-acm/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.549.0.tgz", + "integrity": "sha512-f3YgalsMuywEAVX4AUm9tojqrBdfpAac0+D320ePzas0Ntbp7ItYu9ceKIhgfzXO3No7P3QK0rCrOxL+ABTn8Q==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.535.0", + "@aws-sdk/credential-provider-http": "3.535.0", + "@aws-sdk/credential-provider-ini": "3.549.0", + "@aws-sdk/credential-provider-process": "3.535.0", + "@aws-sdk/credential-provider-sso": "3.549.0", + "@aws-sdk/credential-provider-web-identity": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-acm/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.549.0.tgz", + "integrity": "sha512-BGopRKHs7W8zkoH8qmSHrjudj263kXbhVkAUPxVUz0I28+CZNBgJC/RfVCbOpzmysIQEpwSqvOv1y0k+DQzIJQ==", + "dependencies": { + "@aws-sdk/client-sso": "3.549.0", + "@aws-sdk/token-providers": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-acm/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.549.0.tgz", + "integrity": "sha512-QzclVXPxuwSI7515l34sdvliVq5leroO8P7RQFKRgfyQKO45o1psghierwG3PgV6jlMiv78FIAGJBr/n4qZ7YA==", + "dependencies": { + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-acm/node_modules/@aws-sdk/token-providers": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.549.0.tgz", + "integrity": "sha512-rJyeXkXknLukRFGuMQOgKnPBa+kLODJtOqEBf929SpQ96f1I6ytdndmWbB5B/OQN5Fu5DOOQUQqJypDQVl5ibQ==", + "dependencies": { + "@aws-sdk/client-sso-oidc": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudformation": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudformation/-/client-cloudformation-3.549.0.tgz", + "integrity": "sha512-mEJUP2guSOdku5/+LIiKwAA9WE+nHKEtKPGOyOL0x72lkZCsfM3QSr35Xi6p1Aip3XB/LI+UCuoYpjiaceSpSw==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", @@ -938,9 +1043,6 @@ "@aws-sdk/util-user-agent-node": "3.535.0", "@smithy/config-resolver": "^2.2.0", "@smithy/core": "^1.4.1", - "@smithy/eventstream-serde-browser": "^2.2.0", - "@smithy/eventstream-serde-config-resolver": "^2.2.0", - "@smithy/eventstream-serde-node": "^2.2.0", "@smithy/fetch-http-handler": "^2.5.0", "@smithy/hash-node": "^2.2.0", "@smithy/invalid-dependency": "^2.2.0", @@ -964,6 +1066,7 @@ "@smithy/util-middleware": "^2.2.0", "@smithy/util-retry": "^2.2.0", "@smithy/util-utf8": "^2.3.0", + "@smithy/util-waiter": "^2.2.0", "tslib": "^2.6.2", "uuid": "^9.0.1" }, @@ -971,16 +1074,14 @@ "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-ecs": { + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/client-sso": { "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ecs/-/client-ecs-3.549.0.tgz", - "integrity": "sha512-3eiIs8O0fDKyjy6ch7Y7Xgb2GWt8/n+AUR4uQXP0jQWnmou/UAqwYKp5S8tqusxDV8ZG47maBFd/dZ+xOlyq2A==", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.549.0.tgz", + "integrity": "sha512-lz+yflOAj5Q263FlCsKpNqttaCb2NPh8jC76gVCqCt7TPxRDBYVaqg0OZYluDaETIDNJi4DwN2Azcck7ilwuPw==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.549.0", "@aws-sdk/core": "3.549.0", - "@aws-sdk/credential-provider-node": "3.549.0", "@aws-sdk/middleware-host-header": "3.535.0", "@aws-sdk/middleware-logger": "3.535.0", "@aws-sdk/middleware-recursion-detection": "3.535.0", @@ -1015,24 +1116,21 @@ "@smithy/util-middleware": "^2.2.0", "@smithy/util-retry": "^2.2.0", "@smithy/util-utf8": "^2.3.0", - "@smithy/util-waiter": "^2.2.0", - "tslib": "^2.6.2", - "uuid": "^9.0.1" + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-lambda": { + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/client-sso-oidc": { "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.549.0.tgz", - "integrity": "sha512-VdLltPf6fUDBFHPJBtYsWnM+nvdau7KuRlwwGnYzmYap79ifE538JTXE6AGyGcIp3YP44BlFtWgvW26kvDbX9g==", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.549.0.tgz", + "integrity": "sha512-FbB4A78ILAb8sM4TfBd+3CrQcfZIhe0gtVZNbaxpq5cJZh1K7oZ8vPfKw4do9JWkDUXPLsD9Bwz12f8/JpAb6Q==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", "@aws-sdk/client-sts": "3.549.0", "@aws-sdk/core": "3.549.0", - "@aws-sdk/credential-provider-node": "3.549.0", "@aws-sdk/middleware-host-header": "3.535.0", "@aws-sdk/middleware-logger": "3.535.0", "@aws-sdk/middleware-recursion-detection": "3.535.0", @@ -1044,9 +1142,6 @@ "@aws-sdk/util-user-agent-node": "3.535.0", "@smithy/config-resolver": "^2.2.0", "@smithy/core": "^1.4.1", - "@smithy/eventstream-serde-browser": "^2.2.0", - "@smithy/eventstream-serde-config-resolver": "^2.2.0", - "@smithy/eventstream-serde-node": "^2.2.0", "@smithy/fetch-http-handler": "^2.5.0", "@smithy/hash-node": "^2.2.0", "@smithy/invalid-dependency": "^2.2.0", @@ -1069,39 +1164,141 @@ "@smithy/util-endpoints": "^1.2.0", "@smithy/util-middleware": "^2.2.0", "@smithy/util-retry": "^2.2.0", - "@smithy/util-stream": "^2.2.0", "@smithy/util-utf8": "^2.3.0", - "@smithy/util-waiter": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": "^3.549.0" + } + }, + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.535.0.tgz", + "integrity": "sha512-kdj1wCmOMZ29jSlUskRqN04S6fJ4dvt0Nq9Z32SA6wO7UG8ht6Ot9h/au/eTWJM3E1somZ7D771oK7dQt9b8yw==", + "dependencies": { + "@aws-sdk/types": "3.535.0", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/util-stream": "^2.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-s3": { - "version": "3.550.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.550.0.tgz", - "integrity": "sha512-45jjDQI0Q37PIteWhywhlExxYaiUeOsTsbE62b+U/FOjYV8tirC8uBY9eHeHaP4IPVGHeQWvEYrFJHNU+qsQLQ==", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.549.0.tgz", + "integrity": "sha512-k6IIrluZjQpzui5Din8fW3bFFhHaJ64XrsfYx0Ks1mb7xan84dJxmYP3tdDDmLzUeJv5h95ag88taHfjY9rakA==", + "dependencies": { + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/credential-provider-env": "3.535.0", + "@aws-sdk/credential-provider-process": "3.535.0", + "@aws-sdk/credential-provider-sso": "3.549.0", + "@aws-sdk/credential-provider-web-identity": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.549.0.tgz", + "integrity": "sha512-f3YgalsMuywEAVX4AUm9tojqrBdfpAac0+D320ePzas0Ntbp7ItYu9ceKIhgfzXO3No7P3QK0rCrOxL+ABTn8Q==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.535.0", + "@aws-sdk/credential-provider-http": "3.535.0", + "@aws-sdk/credential-provider-ini": "3.549.0", + "@aws-sdk/credential-provider-process": "3.535.0", + "@aws-sdk/credential-provider-sso": "3.549.0", + "@aws-sdk/credential-provider-web-identity": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.549.0.tgz", + "integrity": "sha512-BGopRKHs7W8zkoH8qmSHrjudj263kXbhVkAUPxVUz0I28+CZNBgJC/RfVCbOpzmysIQEpwSqvOv1y0k+DQzIJQ==", + "dependencies": { + "@aws-sdk/client-sso": "3.549.0", + "@aws-sdk/token-providers": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.549.0.tgz", + "integrity": "sha512-QzclVXPxuwSI7515l34sdvliVq5leroO8P7RQFKRgfyQKO45o1psghierwG3PgV6jlMiv78FIAGJBr/n4qZ7YA==", + "dependencies": { + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/token-providers": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.549.0.tgz", + "integrity": "sha512-rJyeXkXknLukRFGuMQOgKnPBa+kLODJtOqEBf929SpQ96f1I6ytdndmWbB5B/OQN5Fu5DOOQUQqJypDQVl5ibQ==", + "dependencies": { + "@aws-sdk/client-sso-oidc": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudfront": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudfront/-/client-cloudfront-3.549.0.tgz", + "integrity": "sha512-XoTKP2sttLl7azal3baptnk4r1zSVX5KjRVim9dlLFViCRrSxpWwQNXKyKI53Hk1rsy0CNxJzPYd4/ag11kOCQ==", "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.549.0", "@aws-sdk/core": "3.549.0", "@aws-sdk/credential-provider-node": "3.549.0", - "@aws-sdk/middleware-bucket-endpoint": "3.535.0", - "@aws-sdk/middleware-expect-continue": "3.535.0", - "@aws-sdk/middleware-flexible-checksums": "3.535.0", "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-location-constraint": "3.535.0", "@aws-sdk/middleware-logger": "3.535.0", "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-sdk-s3": "3.535.0", - "@aws-sdk/middleware-signing": "3.535.0", - "@aws-sdk/middleware-ssec": "3.537.0", "@aws-sdk/middleware-user-agent": "3.540.0", "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/signature-v4-multi-region": "3.535.0", "@aws-sdk/types": "3.535.0", "@aws-sdk/util-endpoints": "3.540.0", "@aws-sdk/util-user-agent-browser": "3.535.0", @@ -1109,15 +1306,9 @@ "@aws-sdk/xml-builder": "3.535.0", "@smithy/config-resolver": "^2.2.0", "@smithy/core": "^1.4.1", - "@smithy/eventstream-serde-browser": "^2.2.0", - "@smithy/eventstream-serde-config-resolver": "^2.2.0", - "@smithy/eventstream-serde-node": "^2.2.0", "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-blob-browser": "^2.2.0", "@smithy/hash-node": "^2.2.0", - "@smithy/hash-stream-node": "^2.2.0", "@smithy/invalid-dependency": "^2.2.0", - "@smithy/md5-js": "^2.2.0", "@smithy/middleware-content-length": "^2.2.0", "@smithy/middleware-endpoint": "^2.5.0", "@smithy/middleware-retry": "^2.3.0", @@ -1135,6 +1326,7 @@ "@smithy/util-defaults-mode-browser": "^2.2.0", "@smithy/util-defaults-mode-node": "^2.3.0", "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", "@smithy/util-retry": "^2.2.0", "@smithy/util-stream": "^2.2.0", "@smithy/util-utf8": "^2.3.0", @@ -1145,16 +1337,14 @@ "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-secrets-manager": { + "node_modules/@aws-sdk/client-cloudfront/node_modules/@aws-sdk/client-sso": { "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.549.0.tgz", - "integrity": "sha512-UcfU1vdghAJMWK1T6NnNe0ZhOIfSY6s0OBooXSybDgZvH47g+rYD/4VdtZRWRSjdCcF4PA+hfRa7vhfOyXdW/A==", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.549.0.tgz", + "integrity": "sha512-lz+yflOAj5Q263FlCsKpNqttaCb2NPh8jC76gVCqCt7TPxRDBYVaqg0OZYluDaETIDNJi4DwN2Azcck7ilwuPw==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.549.0", "@aws-sdk/core": "3.549.0", - "@aws-sdk/credential-provider-node": "3.549.0", "@aws-sdk/middleware-host-header": "3.535.0", "@aws-sdk/middleware-logger": "3.535.0", "@aws-sdk/middleware-recursion-detection": "3.535.0", @@ -1189,23 +1379,21 @@ "@smithy/util-middleware": "^2.2.0", "@smithy/util-retry": "^2.2.0", "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2", - "uuid": "^9.0.1" + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-sesv2": { + "node_modules/@aws-sdk/client-cloudfront/node_modules/@aws-sdk/client-sso-oidc": { "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.549.0.tgz", - "integrity": "sha512-o+78gx6E4aKB9dqbUfpCo7SP24zQn8BKmh17hWOXB6ryDqZUHeaoeJh0gtcrE6EXEsEtqGlXIIaTu9kYdYDepA==", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.549.0.tgz", + "integrity": "sha512-FbB4A78ILAb8sM4TfBd+3CrQcfZIhe0gtVZNbaxpq5cJZh1K7oZ8vPfKw4do9JWkDUXPLsD9Bwz12f8/JpAb6Q==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", "@aws-sdk/client-sts": "3.549.0", "@aws-sdk/core": "3.549.0", - "@aws-sdk/credential-provider-node": "3.549.0", "@aws-sdk/middleware-host-header": "3.535.0", "@aws-sdk/middleware-logger": "3.535.0", "@aws-sdk/middleware-recursion-detection": "3.535.0", @@ -1244,12 +1432,125 @@ }, "engines": { "node": ">=14.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": "^3.549.0" } }, - "node_modules/@aws-sdk/client-ssm": { + "node_modules/@aws-sdk/client-cloudfront/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.535.0.tgz", + "integrity": "sha512-kdj1wCmOMZ29jSlUskRqN04S6fJ4dvt0Nq9Z32SA6wO7UG8ht6Ot9h/au/eTWJM3E1somZ7D771oK7dQt9b8yw==", + "dependencies": { + "@aws-sdk/types": "3.535.0", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/util-stream": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudfront/node_modules/@aws-sdk/credential-provider-ini": { "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ssm/-/client-ssm-3.549.0.tgz", - "integrity": "sha512-h5EvM1e09+ybmaCFXVjibWGyBSJ7yNIgKFq1SoifnZ5O4nf6SNNAhbuea1Gq4JCcjvDfzJ4Wl3EqVHf/Jb9oJg==", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.549.0.tgz", + "integrity": "sha512-k6IIrluZjQpzui5Din8fW3bFFhHaJ64XrsfYx0Ks1mb7xan84dJxmYP3tdDDmLzUeJv5h95ag88taHfjY9rakA==", + "dependencies": { + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/credential-provider-env": "3.535.0", + "@aws-sdk/credential-provider-process": "3.535.0", + "@aws-sdk/credential-provider-sso": "3.549.0", + "@aws-sdk/credential-provider-web-identity": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudfront/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.549.0.tgz", + "integrity": "sha512-f3YgalsMuywEAVX4AUm9tojqrBdfpAac0+D320ePzas0Ntbp7ItYu9ceKIhgfzXO3No7P3QK0rCrOxL+ABTn8Q==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.535.0", + "@aws-sdk/credential-provider-http": "3.535.0", + "@aws-sdk/credential-provider-ini": "3.549.0", + "@aws-sdk/credential-provider-process": "3.535.0", + "@aws-sdk/credential-provider-sso": "3.549.0", + "@aws-sdk/credential-provider-web-identity": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudfront/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.549.0.tgz", + "integrity": "sha512-BGopRKHs7W8zkoH8qmSHrjudj263kXbhVkAUPxVUz0I28+CZNBgJC/RfVCbOpzmysIQEpwSqvOv1y0k+DQzIJQ==", + "dependencies": { + "@aws-sdk/client-sso": "3.549.0", + "@aws-sdk/token-providers": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudfront/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.549.0.tgz", + "integrity": "sha512-QzclVXPxuwSI7515l34sdvliVq5leroO8P7RQFKRgfyQKO45o1psghierwG3PgV6jlMiv78FIAGJBr/n4qZ7YA==", + "dependencies": { + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudfront/node_modules/@aws-sdk/token-providers": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.549.0.tgz", + "integrity": "sha512-rJyeXkXknLukRFGuMQOgKnPBa+kLODJtOqEBf929SpQ96f1I6ytdndmWbB5B/OQN5Fu5DOOQUQqJypDQVl5ibQ==", + "dependencies": { + "@aws-sdk/client-sso-oidc": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.549.0.tgz", + "integrity": "sha512-LAB4MWkx41wF+IImj4nucMmgAkGFD51sR5JkXsUoB5leLo0Oz84ZEFZ8RfqshF6ReoW5lmSRyB8x1663KdRcLA==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", @@ -1267,6 +1568,9 @@ "@aws-sdk/util-user-agent-node": "3.535.0", "@smithy/config-resolver": "^2.2.0", "@smithy/core": "^1.4.1", + "@smithy/eventstream-serde-browser": "^2.2.0", + "@smithy/eventstream-serde-config-resolver": "^2.2.0", + "@smithy/eventstream-serde-node": "^2.2.0", "@smithy/fetch-http-handler": "^2.5.0", "@smithy/hash-node": "^2.2.0", "@smithy/invalid-dependency": "^2.2.0", @@ -1290,7 +1594,6 @@ "@smithy/util-middleware": "^2.2.0", "@smithy/util-retry": "^2.2.0", "@smithy/util-utf8": "^2.3.0", - "@smithy/util-waiter": "^2.2.0", "tslib": "^2.6.2", "uuid": "^9.0.1" }, @@ -1298,7 +1601,7 @@ "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-sso": { + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/client-sso": { "version": "3.549.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.549.0.tgz", "integrity": "sha512-lz+yflOAj5Q263FlCsKpNqttaCb2NPh8jC76gVCqCt7TPxRDBYVaqg0OZYluDaETIDNJi4DwN2Azcck7ilwuPw==", @@ -1346,7 +1649,7 @@ "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc": { + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/client-sso-oidc": { "version": "3.549.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.549.0.tgz", "integrity": "sha512-FbB4A78ILAb8sM4TfBd+3CrQcfZIhe0gtVZNbaxpq5cJZh1K7oZ8vPfKw4do9JWkDUXPLsD9Bwz12f8/JpAb6Q==", @@ -1398,93 +1701,78 @@ "@aws-sdk/credential-provider-node": "^3.549.0" } }, - "node_modules/@aws-sdk/client-sts": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.549.0.tgz", - "integrity": "sha512-63IreJ598Dzvpb+6sy81KfIX5iQxnrWSEtlyeCdC2GO6gmSQVwJzc9kr5pAC83lHmlZcm/Q3KZr3XBhRQqP0og==", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.535.0.tgz", + "integrity": "sha512-kdj1wCmOMZ29jSlUskRqN04S6fJ4dvt0Nq9Z32SA6wO7UG8ht6Ot9h/au/eTWJM3E1somZ7D771oK7dQt9b8yw==", "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.549.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.540.0", - "@aws-sdk/region-config-resolver": "3.535.0", "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.540.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", "@smithy/node-http-handler": "^2.5.0", + "@smithy/property-provider": "^2.2.0", "@smithy/protocol-http": "^3.3.0", "@smithy/smithy-client": "^2.5.0", "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", + "@smithy/util-stream": "^2.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" - }, - "peerDependencies": { - "@aws-sdk/credential-provider-node": "^3.549.0" } }, - "node_modules/@aws-sdk/cloudfront-signer": { - "version": "3.541.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/cloudfront-signer/-/cloudfront-signer-3.541.0.tgz", - "integrity": "sha512-WBC11kKMWpuvvBmoKfsdAj0JuS0Atu6whd3dbdMxQmhar+G8iEh2gBoFlExE3Ixzn991bY7w3/NY8yb9TpwA8g==", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.549.0.tgz", + "integrity": "sha512-k6IIrluZjQpzui5Din8fW3bFFhHaJ64XrsfYx0Ks1mb7xan84dJxmYP3tdDDmLzUeJv5h95ag88taHfjY9rakA==", "dependencies": { - "@smithy/url-parser": "^2.2.0", + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/credential-provider-env": "3.535.0", + "@aws-sdk/credential-provider-process": "3.535.0", + "@aws-sdk/credential-provider-sso": "3.549.0", + "@aws-sdk/credential-provider-web-identity": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/core": { + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-node": { "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.549.0.tgz", - "integrity": "sha512-jC61OxJn72r/BbuDRCcluiw05Xw9eVLG0CwxQpF3RocxfxyZqlrGYaGecZ8Wy+7g/3sqGRC/Ar5eUhU1YcLx7w==", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.549.0.tgz", + "integrity": "sha512-f3YgalsMuywEAVX4AUm9tojqrBdfpAac0+D320ePzas0Ntbp7ItYu9ceKIhgfzXO3No7P3QK0rCrOxL+ABTn8Q==", "dependencies": { - "@smithy/core": "^1.4.1", - "@smithy/protocol-http": "^3.3.0", - "@smithy/signature-v4": "^2.2.0", - "@smithy/smithy-client": "^2.5.0", + "@aws-sdk/credential-provider-env": "3.535.0", + "@aws-sdk/credential-provider-http": "3.535.0", + "@aws-sdk/credential-provider-ini": "3.549.0", + "@aws-sdk/credential-provider-process": "3.535.0", + "@aws-sdk/credential-provider-sso": "3.549.0", + "@aws-sdk/credential-provider-web-identity": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", "@smithy/types": "^2.12.0", - "fast-xml-parser": "4.2.5", "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.535.0.tgz", - "integrity": "sha512-XppwO8c0GCGSAvdzyJOhbtktSEaShg14VJKg8mpMa1XcgqzmcqqHQjtDWbx5rZheY1VdpXZhpEzJkB6LpQejpA==", - "dependencies": { - "@aws-sdk/types": "3.535.0", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.549.0.tgz", + "integrity": "sha512-BGopRKHs7W8zkoH8qmSHrjudj263kXbhVkAUPxVUz0I28+CZNBgJC/RfVCbOpzmysIQEpwSqvOv1y0k+DQzIJQ==", + "dependencies": { + "@aws-sdk/client-sso": "3.549.0", + "@aws-sdk/token-providers": "3.549.0", + "@aws-sdk/types": "3.535.0", "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", "@smithy/types": "^2.12.0", "tslib": "^2.6.2" }, @@ -1492,7 +1780,190 @@ "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http": { + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.549.0.tgz", + "integrity": "sha512-QzclVXPxuwSI7515l34sdvliVq5leroO8P7RQFKRgfyQKO45o1psghierwG3PgV6jlMiv78FIAGJBr/n4qZ7YA==", + "dependencies": { + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/token-providers": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.549.0.tgz", + "integrity": "sha512-rJyeXkXknLukRFGuMQOgKnPBa+kLODJtOqEBf929SpQ96f1I6ytdndmWbB5B/OQN5Fu5DOOQUQqJypDQVl5ibQ==", + "dependencies": { + "@aws-sdk/client-sso-oidc": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-ecs": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ecs/-/client-ecs-3.549.0.tgz", + "integrity": "sha512-3eiIs8O0fDKyjy6ch7Y7Xgb2GWt8/n+AUR4uQXP0jQWnmou/UAqwYKp5S8tqusxDV8ZG47maBFd/dZ+xOlyq2A==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/core": "3.549.0", + "@aws-sdk/credential-provider-node": "3.549.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.1", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.0", + "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.0", + "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "@smithy/util-waiter": "^2.2.0", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/client-sso": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.549.0.tgz", + "integrity": "sha512-lz+yflOAj5Q263FlCsKpNqttaCb2NPh8jC76gVCqCt7TPxRDBYVaqg0OZYluDaETIDNJi4DwN2Azcck7ilwuPw==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.549.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.1", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.0", + "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.0", + "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.549.0.tgz", + "integrity": "sha512-FbB4A78ILAb8sM4TfBd+3CrQcfZIhe0gtVZNbaxpq5cJZh1K7oZ8vPfKw4do9JWkDUXPLsD9Bwz12f8/JpAb6Q==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/core": "3.549.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.1", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.0", + "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.0", + "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": "^3.549.0" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/credential-provider-http": { "version": "3.535.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.535.0.tgz", "integrity": "sha512-kdj1wCmOMZ29jSlUskRqN04S6fJ4dvt0Nq9Z32SA6wO7UG8ht6Ot9h/au/eTWJM3E1somZ7D771oK7dQt9b8yw==", @@ -1511,7 +1982,7 @@ "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini": { + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/credential-provider-ini": { "version": "3.549.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.549.0.tgz", "integrity": "sha512-k6IIrluZjQpzui5Din8fW3bFFhHaJ64XrsfYx0Ks1mb7xan84dJxmYP3tdDDmLzUeJv5h95ag88taHfjY9rakA==", @@ -1532,7 +2003,272 @@ "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/credential-provider-node": { + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.549.0.tgz", + "integrity": "sha512-f3YgalsMuywEAVX4AUm9tojqrBdfpAac0+D320ePzas0Ntbp7ItYu9ceKIhgfzXO3No7P3QK0rCrOxL+ABTn8Q==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.535.0", + "@aws-sdk/credential-provider-http": "3.535.0", + "@aws-sdk/credential-provider-ini": "3.549.0", + "@aws-sdk/credential-provider-process": "3.535.0", + "@aws-sdk/credential-provider-sso": "3.549.0", + "@aws-sdk/credential-provider-web-identity": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.549.0.tgz", + "integrity": "sha512-BGopRKHs7W8zkoH8qmSHrjudj263kXbhVkAUPxVUz0I28+CZNBgJC/RfVCbOpzmysIQEpwSqvOv1y0k+DQzIJQ==", + "dependencies": { + "@aws-sdk/client-sso": "3.549.0", + "@aws-sdk/token-providers": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.549.0.tgz", + "integrity": "sha512-QzclVXPxuwSI7515l34sdvliVq5leroO8P7RQFKRgfyQKO45o1psghierwG3PgV6jlMiv78FIAGJBr/n4qZ7YA==", + "dependencies": { + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/token-providers": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.549.0.tgz", + "integrity": "sha512-rJyeXkXknLukRFGuMQOgKnPBa+kLODJtOqEBf929SpQ96f1I6ytdndmWbB5B/OQN5Fu5DOOQUQqJypDQVl5ibQ==", + "dependencies": { + "@aws-sdk/client-sso-oidc": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.549.0.tgz", + "integrity": "sha512-VdLltPf6fUDBFHPJBtYsWnM+nvdau7KuRlwwGnYzmYap79ifE538JTXE6AGyGcIp3YP44BlFtWgvW26kvDbX9g==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/core": "3.549.0", + "@aws-sdk/credential-provider-node": "3.549.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.1", + "@smithy/eventstream-serde-browser": "^2.2.0", + "@smithy/eventstream-serde-config-resolver": "^2.2.0", + "@smithy/eventstream-serde-node": "^2.2.0", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.0", + "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.0", + "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-stream": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "@smithy/util-waiter": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/client-sso": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.549.0.tgz", + "integrity": "sha512-lz+yflOAj5Q263FlCsKpNqttaCb2NPh8jC76gVCqCt7TPxRDBYVaqg0OZYluDaETIDNJi4DwN2Azcck7ilwuPw==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.549.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.1", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.0", + "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.0", + "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.549.0.tgz", + "integrity": "sha512-FbB4A78ILAb8sM4TfBd+3CrQcfZIhe0gtVZNbaxpq5cJZh1K7oZ8vPfKw4do9JWkDUXPLsD9Bwz12f8/JpAb6Q==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/core": "3.549.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.1", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.0", + "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.0", + "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": "^3.549.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.535.0.tgz", + "integrity": "sha512-kdj1wCmOMZ29jSlUskRqN04S6fJ4dvt0Nq9Z32SA6wO7UG8ht6Ot9h/au/eTWJM3E1somZ7D771oK7dQt9b8yw==", + "dependencies": { + "@aws-sdk/types": "3.535.0", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/util-stream": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.549.0.tgz", + "integrity": "sha512-k6IIrluZjQpzui5Din8fW3bFFhHaJ64XrsfYx0Ks1mb7xan84dJxmYP3tdDDmLzUeJv5h95ag88taHfjY9rakA==", + "dependencies": { + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/credential-provider-env": "3.535.0", + "@aws-sdk/credential-provider-process": "3.535.0", + "@aws-sdk/credential-provider-sso": "3.549.0", + "@aws-sdk/credential-provider-web-identity": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/credential-provider-node": { "version": "3.549.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.549.0.tgz", "integrity": "sha512-f3YgalsMuywEAVX4AUm9tojqrBdfpAac0+D320ePzas0Ntbp7ItYu9ceKIhgfzXO3No7P3QK0rCrOxL+ABTn8Q==", @@ -1554,6 +2290,1533 @@ "node": ">=14.0.0" } }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.549.0.tgz", + "integrity": "sha512-BGopRKHs7W8zkoH8qmSHrjudj263kXbhVkAUPxVUz0I28+CZNBgJC/RfVCbOpzmysIQEpwSqvOv1y0k+DQzIJQ==", + "dependencies": { + "@aws-sdk/client-sso": "3.549.0", + "@aws-sdk/token-providers": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.549.0.tgz", + "integrity": "sha512-QzclVXPxuwSI7515l34sdvliVq5leroO8P7RQFKRgfyQKO45o1psghierwG3PgV6jlMiv78FIAGJBr/n4qZ7YA==", + "dependencies": { + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/token-providers": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.549.0.tgz", + "integrity": "sha512-rJyeXkXknLukRFGuMQOgKnPBa+kLODJtOqEBf929SpQ96f1I6ytdndmWbB5B/OQN5Fu5DOOQUQqJypDQVl5ibQ==", + "dependencies": { + "@aws-sdk/client-sso-oidc": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.550.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.550.0.tgz", + "integrity": "sha512-45jjDQI0Q37PIteWhywhlExxYaiUeOsTsbE62b+U/FOjYV8tirC8uBY9eHeHaP4IPVGHeQWvEYrFJHNU+qsQLQ==", + "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.549.0", + "@aws-sdk/core": "3.549.0", + "@aws-sdk/credential-provider-node": "3.549.0", + "@aws-sdk/middleware-bucket-endpoint": "3.535.0", + "@aws-sdk/middleware-expect-continue": "3.535.0", + "@aws-sdk/middleware-flexible-checksums": "3.535.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-location-constraint": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-sdk-s3": "3.535.0", + "@aws-sdk/middleware-signing": "3.535.0", + "@aws-sdk/middleware-ssec": "3.537.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/signature-v4-multi-region": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@aws-sdk/xml-builder": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.1", + "@smithy/eventstream-serde-browser": "^2.2.0", + "@smithy/eventstream-serde-config-resolver": "^2.2.0", + "@smithy/eventstream-serde-node": "^2.2.0", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-blob-browser": "^2.2.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/hash-stream-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/md5-js": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.0", + "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.0", + "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-stream": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "@smithy/util-waiter": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sso": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.549.0.tgz", + "integrity": "sha512-lz+yflOAj5Q263FlCsKpNqttaCb2NPh8jC76gVCqCt7TPxRDBYVaqg0OZYluDaETIDNJi4DwN2Azcck7ilwuPw==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.549.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.1", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.0", + "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.0", + "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.549.0.tgz", + "integrity": "sha512-FbB4A78ILAb8sM4TfBd+3CrQcfZIhe0gtVZNbaxpq5cJZh1K7oZ8vPfKw4do9JWkDUXPLsD9Bwz12f8/JpAb6Q==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/core": "3.549.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.1", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.0", + "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.0", + "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": "^3.549.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.535.0.tgz", + "integrity": "sha512-kdj1wCmOMZ29jSlUskRqN04S6fJ4dvt0Nq9Z32SA6wO7UG8ht6Ot9h/au/eTWJM3E1somZ7D771oK7dQt9b8yw==", + "dependencies": { + "@aws-sdk/types": "3.535.0", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/util-stream": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.549.0.tgz", + "integrity": "sha512-k6IIrluZjQpzui5Din8fW3bFFhHaJ64XrsfYx0Ks1mb7xan84dJxmYP3tdDDmLzUeJv5h95ag88taHfjY9rakA==", + "dependencies": { + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/credential-provider-env": "3.535.0", + "@aws-sdk/credential-provider-process": "3.535.0", + "@aws-sdk/credential-provider-sso": "3.549.0", + "@aws-sdk/credential-provider-web-identity": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.549.0.tgz", + "integrity": "sha512-f3YgalsMuywEAVX4AUm9tojqrBdfpAac0+D320ePzas0Ntbp7ItYu9ceKIhgfzXO3No7P3QK0rCrOxL+ABTn8Q==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.535.0", + "@aws-sdk/credential-provider-http": "3.535.0", + "@aws-sdk/credential-provider-ini": "3.549.0", + "@aws-sdk/credential-provider-process": "3.535.0", + "@aws-sdk/credential-provider-sso": "3.549.0", + "@aws-sdk/credential-provider-web-identity": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.549.0.tgz", + "integrity": "sha512-BGopRKHs7W8zkoH8qmSHrjudj263kXbhVkAUPxVUz0I28+CZNBgJC/RfVCbOpzmysIQEpwSqvOv1y0k+DQzIJQ==", + "dependencies": { + "@aws-sdk/client-sso": "3.549.0", + "@aws-sdk/token-providers": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.549.0.tgz", + "integrity": "sha512-QzclVXPxuwSI7515l34sdvliVq5leroO8P7RQFKRgfyQKO45o1psghierwG3PgV6jlMiv78FIAGJBr/n4qZ7YA==", + "dependencies": { + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/token-providers": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.549.0.tgz", + "integrity": "sha512-rJyeXkXknLukRFGuMQOgKnPBa+kLODJtOqEBf929SpQ96f1I6ytdndmWbB5B/OQN5Fu5DOOQUQqJypDQVl5ibQ==", + "dependencies": { + "@aws-sdk/client-sso-oidc": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-secrets-manager": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.549.0.tgz", + "integrity": "sha512-UcfU1vdghAJMWK1T6NnNe0ZhOIfSY6s0OBooXSybDgZvH47g+rYD/4VdtZRWRSjdCcF4PA+hfRa7vhfOyXdW/A==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/core": "3.549.0", + "@aws-sdk/credential-provider-node": "3.549.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.1", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.0", + "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.0", + "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/client-sso": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.549.0.tgz", + "integrity": "sha512-lz+yflOAj5Q263FlCsKpNqttaCb2NPh8jC76gVCqCt7TPxRDBYVaqg0OZYluDaETIDNJi4DwN2Azcck7ilwuPw==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.549.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.1", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.0", + "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.0", + "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.549.0.tgz", + "integrity": "sha512-FbB4A78ILAb8sM4TfBd+3CrQcfZIhe0gtVZNbaxpq5cJZh1K7oZ8vPfKw4do9JWkDUXPLsD9Bwz12f8/JpAb6Q==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/core": "3.549.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.1", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.0", + "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.0", + "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": "^3.549.0" + } + }, + "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.535.0.tgz", + "integrity": "sha512-kdj1wCmOMZ29jSlUskRqN04S6fJ4dvt0Nq9Z32SA6wO7UG8ht6Ot9h/au/eTWJM3E1somZ7D771oK7dQt9b8yw==", + "dependencies": { + "@aws-sdk/types": "3.535.0", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/util-stream": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.549.0.tgz", + "integrity": "sha512-k6IIrluZjQpzui5Din8fW3bFFhHaJ64XrsfYx0Ks1mb7xan84dJxmYP3tdDDmLzUeJv5h95ag88taHfjY9rakA==", + "dependencies": { + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/credential-provider-env": "3.535.0", + "@aws-sdk/credential-provider-process": "3.535.0", + "@aws-sdk/credential-provider-sso": "3.549.0", + "@aws-sdk/credential-provider-web-identity": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.549.0.tgz", + "integrity": "sha512-f3YgalsMuywEAVX4AUm9tojqrBdfpAac0+D320ePzas0Ntbp7ItYu9ceKIhgfzXO3No7P3QK0rCrOxL+ABTn8Q==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.535.0", + "@aws-sdk/credential-provider-http": "3.535.0", + "@aws-sdk/credential-provider-ini": "3.549.0", + "@aws-sdk/credential-provider-process": "3.535.0", + "@aws-sdk/credential-provider-sso": "3.549.0", + "@aws-sdk/credential-provider-web-identity": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.549.0.tgz", + "integrity": "sha512-BGopRKHs7W8zkoH8qmSHrjudj263kXbhVkAUPxVUz0I28+CZNBgJC/RfVCbOpzmysIQEpwSqvOv1y0k+DQzIJQ==", + "dependencies": { + "@aws-sdk/client-sso": "3.549.0", + "@aws-sdk/token-providers": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.549.0.tgz", + "integrity": "sha512-QzclVXPxuwSI7515l34sdvliVq5leroO8P7RQFKRgfyQKO45o1psghierwG3PgV6jlMiv78FIAGJBr/n4qZ7YA==", + "dependencies": { + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/token-providers": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.549.0.tgz", + "integrity": "sha512-rJyeXkXknLukRFGuMQOgKnPBa+kLODJtOqEBf929SpQ96f1I6ytdndmWbB5B/OQN5Fu5DOOQUQqJypDQVl5ibQ==", + "dependencies": { + "@aws-sdk/client-sso-oidc": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sesv2": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.549.0.tgz", + "integrity": "sha512-o+78gx6E4aKB9dqbUfpCo7SP24zQn8BKmh17hWOXB6ryDqZUHeaoeJh0gtcrE6EXEsEtqGlXIIaTu9kYdYDepA==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/core": "3.549.0", + "@aws-sdk/credential-provider-node": "3.549.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.1", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.0", + "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.0", + "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sesv2/node_modules/@aws-sdk/client-sso": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.549.0.tgz", + "integrity": "sha512-lz+yflOAj5Q263FlCsKpNqttaCb2NPh8jC76gVCqCt7TPxRDBYVaqg0OZYluDaETIDNJi4DwN2Azcck7ilwuPw==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.549.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.1", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.0", + "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.0", + "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sesv2/node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.549.0.tgz", + "integrity": "sha512-FbB4A78ILAb8sM4TfBd+3CrQcfZIhe0gtVZNbaxpq5cJZh1K7oZ8vPfKw4do9JWkDUXPLsD9Bwz12f8/JpAb6Q==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/core": "3.549.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.1", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.0", + "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.0", + "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": "^3.549.0" + } + }, + "node_modules/@aws-sdk/client-sesv2/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.535.0.tgz", + "integrity": "sha512-kdj1wCmOMZ29jSlUskRqN04S6fJ4dvt0Nq9Z32SA6wO7UG8ht6Ot9h/au/eTWJM3E1somZ7D771oK7dQt9b8yw==", + "dependencies": { + "@aws-sdk/types": "3.535.0", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/util-stream": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sesv2/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.549.0.tgz", + "integrity": "sha512-k6IIrluZjQpzui5Din8fW3bFFhHaJ64XrsfYx0Ks1mb7xan84dJxmYP3tdDDmLzUeJv5h95ag88taHfjY9rakA==", + "dependencies": { + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/credential-provider-env": "3.535.0", + "@aws-sdk/credential-provider-process": "3.535.0", + "@aws-sdk/credential-provider-sso": "3.549.0", + "@aws-sdk/credential-provider-web-identity": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sesv2/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.549.0.tgz", + "integrity": "sha512-f3YgalsMuywEAVX4AUm9tojqrBdfpAac0+D320ePzas0Ntbp7ItYu9ceKIhgfzXO3No7P3QK0rCrOxL+ABTn8Q==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.535.0", + "@aws-sdk/credential-provider-http": "3.535.0", + "@aws-sdk/credential-provider-ini": "3.549.0", + "@aws-sdk/credential-provider-process": "3.535.0", + "@aws-sdk/credential-provider-sso": "3.549.0", + "@aws-sdk/credential-provider-web-identity": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sesv2/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.549.0.tgz", + "integrity": "sha512-BGopRKHs7W8zkoH8qmSHrjudj263kXbhVkAUPxVUz0I28+CZNBgJC/RfVCbOpzmysIQEpwSqvOv1y0k+DQzIJQ==", + "dependencies": { + "@aws-sdk/client-sso": "3.549.0", + "@aws-sdk/token-providers": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sesv2/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.549.0.tgz", + "integrity": "sha512-QzclVXPxuwSI7515l34sdvliVq5leroO8P7RQFKRgfyQKO45o1psghierwG3PgV6jlMiv78FIAGJBr/n4qZ7YA==", + "dependencies": { + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sesv2/node_modules/@aws-sdk/token-providers": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.549.0.tgz", + "integrity": "sha512-rJyeXkXknLukRFGuMQOgKnPBa+kLODJtOqEBf929SpQ96f1I6ytdndmWbB5B/OQN5Fu5DOOQUQqJypDQVl5ibQ==", + "dependencies": { + "@aws-sdk/client-sso-oidc": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-ssm": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ssm/-/client-ssm-3.549.0.tgz", + "integrity": "sha512-h5EvM1e09+ybmaCFXVjibWGyBSJ7yNIgKFq1SoifnZ5O4nf6SNNAhbuea1Gq4JCcjvDfzJ4Wl3EqVHf/Jb9oJg==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/core": "3.549.0", + "@aws-sdk/credential-provider-node": "3.549.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.1", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.0", + "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.0", + "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "@smithy/util-waiter": "^2.2.0", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/client-sso": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.549.0.tgz", + "integrity": "sha512-lz+yflOAj5Q263FlCsKpNqttaCb2NPh8jC76gVCqCt7TPxRDBYVaqg0OZYluDaETIDNJi4DwN2Azcck7ilwuPw==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.549.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.1", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.0", + "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.0", + "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.549.0.tgz", + "integrity": "sha512-FbB4A78ILAb8sM4TfBd+3CrQcfZIhe0gtVZNbaxpq5cJZh1K7oZ8vPfKw4do9JWkDUXPLsD9Bwz12f8/JpAb6Q==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/core": "3.549.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.1", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.0", + "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.0", + "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": "^3.549.0" + } + }, + "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.535.0.tgz", + "integrity": "sha512-kdj1wCmOMZ29jSlUskRqN04S6fJ4dvt0Nq9Z32SA6wO7UG8ht6Ot9h/au/eTWJM3E1somZ7D771oK7dQt9b8yw==", + "dependencies": { + "@aws-sdk/types": "3.535.0", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/util-stream": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.549.0.tgz", + "integrity": "sha512-k6IIrluZjQpzui5Din8fW3bFFhHaJ64XrsfYx0Ks1mb7xan84dJxmYP3tdDDmLzUeJv5h95ag88taHfjY9rakA==", + "dependencies": { + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/credential-provider-env": "3.535.0", + "@aws-sdk/credential-provider-process": "3.535.0", + "@aws-sdk/credential-provider-sso": "3.549.0", + "@aws-sdk/credential-provider-web-identity": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.549.0.tgz", + "integrity": "sha512-f3YgalsMuywEAVX4AUm9tojqrBdfpAac0+D320ePzas0Ntbp7ItYu9ceKIhgfzXO3No7P3QK0rCrOxL+ABTn8Q==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.535.0", + "@aws-sdk/credential-provider-http": "3.535.0", + "@aws-sdk/credential-provider-ini": "3.549.0", + "@aws-sdk/credential-provider-process": "3.535.0", + "@aws-sdk/credential-provider-sso": "3.549.0", + "@aws-sdk/credential-provider-web-identity": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.549.0.tgz", + "integrity": "sha512-BGopRKHs7W8zkoH8qmSHrjudj263kXbhVkAUPxVUz0I28+CZNBgJC/RfVCbOpzmysIQEpwSqvOv1y0k+DQzIJQ==", + "dependencies": { + "@aws-sdk/client-sso": "3.549.0", + "@aws-sdk/token-providers": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.549.0.tgz", + "integrity": "sha512-QzclVXPxuwSI7515l34sdvliVq5leroO8P7RQFKRgfyQKO45o1psghierwG3PgV6jlMiv78FIAGJBr/n4qZ7YA==", + "dependencies": { + "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/token-providers": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.549.0.tgz", + "integrity": "sha512-rJyeXkXknLukRFGuMQOgKnPBa+kLODJtOqEBf929SpQ96f1I6ytdndmWbB5B/OQN5Fu5DOOQUQqJypDQVl5ibQ==", + "dependencies": { + "@aws-sdk/client-sso-oidc": "3.549.0", + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.552.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.552.0.tgz", + "integrity": "sha512-IAjRj5gcuyoPe/OhciMY/UyW8C1kyXSUJFagxvbeSv8q0mEfaPBVjGgz2xSYRFhhZr3gFlGCS9SiukwOL2/VoA==", + "peer": true, + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.552.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.2", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.552.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.552.0.tgz", + "integrity": "sha512-6JYTgN/n4xTm3Z+JhEZq06pyYsgo7heYDmR+0smmauQS02Eu8lvUc2jPs/0GDAmty7J4tq3gS6TRwvf7181C2w==", + "peer": true, + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.552.0", + "@aws-sdk/core": "3.552.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.2", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": "^3.552.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/client-sts": { + "version": "3.552.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.552.0.tgz", + "integrity": "sha512-rOZlAj8GyFgUBESyKezes67A8Kj5+KjRhfBHMXrkcM5h9UOIz5q7QdkSQOmzWwRoPDmmAqb6t+y041/76TnPEg==", + "peer": true, + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.552.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.2", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": "^3.552.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/core": { + "version": "3.552.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.552.0.tgz", + "integrity": "sha512-T7ovljf6fCvIHG9SOSZqGmbVbqZPXPywLAcU+onk/fYLZJj6kjfzKZzSAUBI0nO1OKpuP/nCHaCp51NLWNqsnw==", + "peer": true, + "dependencies": { + "@smithy/core": "^1.4.2", + "@smithy/protocol-http": "^3.3.0", + "@smithy/signature-v4": "^2.2.1", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "fast-xml-parser": "4.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/core": { + "version": "3.552.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.552.0.tgz", + "integrity": "sha512-T7ovljf6fCvIHG9SOSZqGmbVbqZPXPywLAcU+onk/fYLZJj6kjfzKZzSAUBI0nO1OKpuP/nCHaCp51NLWNqsnw==", + "peer": true, + "dependencies": { + "@smithy/core": "^1.4.2", + "@smithy/protocol-http": "^3.3.0", + "@smithy/signature-v4": "^2.2.1", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "fast-xml-parser": "4.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sts": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.549.0.tgz", + "integrity": "sha512-63IreJ598Dzvpb+6sy81KfIX5iQxnrWSEtlyeCdC2GO6gmSQVwJzc9kr5pAC83lHmlZcm/Q3KZr3XBhRQqP0og==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.549.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.1", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.0", + "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.0", + "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": "^3.549.0" + } + }, + "node_modules/@aws-sdk/cloudfront-signer": { + "version": "3.541.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/cloudfront-signer/-/cloudfront-signer-3.541.0.tgz", + "integrity": "sha512-WBC11kKMWpuvvBmoKfsdAj0JuS0Atu6whd3dbdMxQmhar+G8iEh2gBoFlExE3Ixzn991bY7w3/NY8yb9TpwA8g==", + "dependencies": { + "@smithy/url-parser": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.549.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.549.0.tgz", + "integrity": "sha512-jC61OxJn72r/BbuDRCcluiw05Xw9eVLG0CwxQpF3RocxfxyZqlrGYaGecZ8Wy+7g/3sqGRC/Ar5eUhU1YcLx7w==", + "dependencies": { + "@smithy/core": "^1.4.1", + "@smithy/protocol-http": "^3.3.0", + "@smithy/signature-v4": "^2.2.0", + "@smithy/smithy-client": "^2.5.0", + "@smithy/types": "^2.12.0", + "fast-xml-parser": "4.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.535.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.535.0.tgz", + "integrity": "sha512-XppwO8c0GCGSAvdzyJOhbtktSEaShg14VJKg8mpMa1XcgqzmcqqHQjtDWbx5rZheY1VdpXZhpEzJkB6LpQejpA==", + "dependencies": { + "@aws-sdk/types": "3.535.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.552.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.552.0.tgz", + "integrity": "sha512-vsmu7Cz1i45pFEqzVb4JcFmAmVnWFNLsGheZc8SCptlqCO5voETrZZILHYIl4cjKkSDk3pblBOf0PhyjqWW6WQ==", + "peer": true, + "dependencies": { + "@aws-sdk/types": "3.535.0", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "@smithy/util-stream": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.552.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.552.0.tgz", + "integrity": "sha512-/Z9y+P4M/eZA/5hGH3Kwm6TOIAiVtsIo7sC/x7hZPXn/IMJQ2QmxzeMozVqMWzx8+2zUA/dmgmWnHoVvH4R/jg==", + "peer": true, + "dependencies": { + "@aws-sdk/client-sts": "3.552.0", + "@aws-sdk/credential-provider-env": "3.535.0", + "@aws-sdk/credential-provider-process": "3.535.0", + "@aws-sdk/credential-provider-sso": "3.552.0", + "@aws-sdk/credential-provider-web-identity": "3.552.0", + "@aws-sdk/types": "3.535.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/client-sts": { + "version": "3.552.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.552.0.tgz", + "integrity": "sha512-rOZlAj8GyFgUBESyKezes67A8Kj5+KjRhfBHMXrkcM5h9UOIz5q7QdkSQOmzWwRoPDmmAqb6t+y041/76TnPEg==", + "peer": true, + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.552.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.2", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": "^3.552.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/core": { + "version": "3.552.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.552.0.tgz", + "integrity": "sha512-T7ovljf6fCvIHG9SOSZqGmbVbqZPXPywLAcU+onk/fYLZJj6kjfzKZzSAUBI0nO1OKpuP/nCHaCp51NLWNqsnw==", + "peer": true, + "dependencies": { + "@smithy/core": "^1.4.2", + "@smithy/protocol-http": "^3.3.0", + "@smithy/signature-v4": "^2.2.1", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "fast-xml-parser": "4.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.552.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.552.0.tgz", + "integrity": "sha512-GUH5awokiR4FcALeQxOrNZtDKJgzEza6NW9HYxAaHt0LNSHCjG21zMFDPYAXlDjlPP9AIdWmVvYrfJoPJI28AQ==", + "peer": true, + "dependencies": { + "@aws-sdk/credential-provider-env": "3.535.0", + "@aws-sdk/credential-provider-http": "3.552.0", + "@aws-sdk/credential-provider-ini": "3.552.0", + "@aws-sdk/credential-provider-process": "3.535.0", + "@aws-sdk/credential-provider-sso": "3.552.0", + "@aws-sdk/credential-provider-web-identity": "3.552.0", + "@aws-sdk/types": "3.535.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@aws-sdk/credential-provider-process": { "version": "3.535.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.535.0.tgz", @@ -1570,12 +3833,13 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.549.0.tgz", - "integrity": "sha512-BGopRKHs7W8zkoH8qmSHrjudj263kXbhVkAUPxVUz0I28+CZNBgJC/RfVCbOpzmysIQEpwSqvOv1y0k+DQzIJQ==", + "version": "3.552.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.552.0.tgz", + "integrity": "sha512-h+xyWG4HMqf4SFzilpK1u50fO2aIBRg3nwuXRy9v5E2qdpJgZS2JXibO1jNHd+JXq4qjs2YG1WK2fGcdxZJ2bQ==", + "peer": true, "dependencies": { - "@aws-sdk/client-sso": "3.549.0", - "@aws-sdk/token-providers": "3.549.0", + "@aws-sdk/client-sso": "3.552.0", + "@aws-sdk/token-providers": "3.552.0", "@aws-sdk/types": "3.535.0", "@smithy/property-provider": "^2.2.0", "@smithy/shared-ini-file-loader": "^2.4.0", @@ -1587,11 +3851,12 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.549.0.tgz", - "integrity": "sha512-QzclVXPxuwSI7515l34sdvliVq5leroO8P7RQFKRgfyQKO45o1psghierwG3PgV6jlMiv78FIAGJBr/n4qZ7YA==", + "version": "3.552.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.552.0.tgz", + "integrity": "sha512-6jXfXaLKDy3S4LHR8ZXIIZw5B80uiYjnPp4bmqmY18LGeoZxmkJ/SfkwypVruezCu+GpA+IubmIbc5TQi6BCAw==", + "peer": true, "dependencies": { - "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/client-sts": "3.552.0", "@aws-sdk/types": "3.535.0", "@smithy/property-provider": "^2.2.0", "@smithy/types": "^2.12.0", @@ -1601,6 +3866,76 @@ "node": ">=14.0.0" } }, + "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@aws-sdk/client-sts": { + "version": "3.552.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.552.0.tgz", + "integrity": "sha512-rOZlAj8GyFgUBESyKezes67A8Kj5+KjRhfBHMXrkcM5h9UOIz5q7QdkSQOmzWwRoPDmmAqb6t+y041/76TnPEg==", + "peer": true, + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.552.0", + "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-logger": "3.535.0", + "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-user-agent": "3.540.0", + "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/types": "3.535.0", + "@aws-sdk/util-endpoints": "3.540.0", + "@aws-sdk/util-user-agent-browser": "3.535.0", + "@aws-sdk/util-user-agent-node": "3.535.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.2", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": "^3.552.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@aws-sdk/core": { + "version": "3.552.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.552.0.tgz", + "integrity": "sha512-T7ovljf6fCvIHG9SOSZqGmbVbqZPXPywLAcU+onk/fYLZJj6kjfzKZzSAUBI0nO1OKpuP/nCHaCp51NLWNqsnw==", + "peer": true, + "dependencies": { + "@smithy/core": "^1.4.2", + "@smithy/protocol-http": "^3.3.0", + "@smithy/signature-v4": "^2.2.1", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "fast-xml-parser": "4.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@aws-sdk/lib-storage": { "version": "3.550.0", "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.550.0.tgz", @@ -1821,11 +4156,12 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.549.0.tgz", - "integrity": "sha512-rJyeXkXknLukRFGuMQOgKnPBa+kLODJtOqEBf929SpQ96f1I6ytdndmWbB5B/OQN5Fu5DOOQUQqJypDQVl5ibQ==", + "version": "3.552.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.552.0.tgz", + "integrity": "sha512-5dNE2KqtgkT+DQXfkSmzmVSB72LpjSIK86lLD9LeQ1T+b0gfEd74MAl/AGC15kQdKLg5I3LlN5q32f1fkmYR8g==", + "peer": true, "dependencies": { - "@aws-sdk/client-sso-oidc": "3.549.0", + "@aws-sdk/client-sso-oidc": "3.552.0", "@aws-sdk/types": "3.535.0", "@smithy/property-provider": "^2.2.0", "@smithy/shared-ini-file-loader": "^2.4.0", @@ -4122,9 +6458,9 @@ "peer": true }, "node_modules/@codemirror/view": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.26.1.tgz", - "integrity": "sha512-wLw0t3R9AwOSQThdZ5Onw8QQtem5asE7+bPlnzc57eubPqiuJKIzwjMZ+C42vQett+iva+J8VgFV4RYWDBh5FA==", + "version": "6.26.2", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.26.2.tgz", + "integrity": "sha512-j6V48PlFC/O7ERAR5vRW5QKDdchzmyyfojDdt+zPsB0YXoWgcjlC1IWjmlYfx08aQZ3HN5BtALcgGgtSKGMe7A==", "dev": true, "peer": true, "dependencies": { @@ -8064,14 +10400,14 @@ } }, "node_modules/@expo/env": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@expo/env/-/env-0.2.2.tgz", - "integrity": "sha512-m9nGuaSpzdvMzevQ1H60FWgf4PG5s4J0dfKUzdAGnDu7sMUerY/yUeDaA4+OBo3vBwGVQ+UHcQS9vPSMBNaPcg==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@expo/env/-/env-0.2.3.tgz", + "integrity": "sha512-a+uJ/e6MAVxPVVN/HbXU5qxzdqrqDwNQYxCfxtAufgmd5VZj54e5f3TJA3LEEUW3pTSZR8xK0H0EtVN297AZnw==", "dependencies": { "chalk": "^4.0.0", "debug": "^4.3.4", - "dotenv": "~16.0.3", - "dotenv-expand": "~10.0.0", + "dotenv": "~16.4.5", + "dotenv-expand": "~11.0.6", "getenv": "^1.0.0" } }, @@ -8104,12 +10440,18 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@expo/env/node_modules/dotenv": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", - "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", + "node_modules/@expo/env/node_modules/dotenv-expand": { + "version": "11.0.6", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.6.tgz", + "integrity": "sha512-8NHi73otpWsZGBSZwwknTXS5pqMOrk9+Ssrna8xCaxkzEpU9OTf9R5ArQGVw03//Zmk9MOwLPng9WwndvpAJ5g==", + "dependencies": { + "dotenv": "^16.4.4" + }, "engines": { "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" } }, "node_modules/@expo/env/node_modules/supports-color": { @@ -16054,6 +18396,195 @@ "url": "https://ko-fi.com/killymxi" } }, + "node_modules/@serialport/binding-mock": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@serialport/binding-mock/-/binding-mock-10.2.2.tgz", + "integrity": "sha512-HAFzGhk9OuFMpuor7aT5G1ChPgn5qSsklTFOTUX72Rl6p0xwcSVsRtG/xaGp6bxpN7fI9D/S8THLBWbBgS6ldw==", + "dependencies": { + "@serialport/bindings-interface": "^1.2.1", + "debug": "^4.3.3" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@serialport/bindings-cpp": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-12.0.1.tgz", + "integrity": "sha512-r2XOwY2dDvbW7dKqSPIk2gzsr6M6Qpe9+/Ngs94fNaNlcTRCV02PfaoDmRgcubpNVVcLATlxSxPTIDw12dbKOg==", + "hasInstallScript": true, + "dependencies": { + "@serialport/bindings-interface": "1.2.2", + "@serialport/parser-readline": "11.0.0", + "debug": "4.3.4", + "node-addon-api": "7.0.0", + "node-gyp-build": "4.6.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-delimiter": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-11.0.0.tgz", + "integrity": "sha512-aZLJhlRTjSmEwllLG7S4J8s8ctRAS0cbvCpO87smLvl3e4BgzbVgF6Z6zaJd3Aji2uSiYgfedCdNc4L6W+1E2g==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-readline": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-11.0.0.tgz", + "integrity": "sha512-rRAivhRkT3YO28WjmmG4FQX6L+KMb5/ikhyylRfzWPw0nSXy97+u07peS9CbHqaNvJkMhH1locp2H36aGMOEIA==", + "dependencies": { + "@serialport/parser-delimiter": "11.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/bindings-interface": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@serialport/bindings-interface/-/bindings-interface-1.2.2.tgz", + "integrity": "sha512-CJaUd5bLvtM9c5dmO9rPBHPXTa9R2UwpkJ0wdh9JCYcbrPWsKz+ErvR0hBLeo7NPeiFdjFO4sonRljiw4d2XiA==", + "engines": { + "node": "^12.22 || ^14.13 || >=16" + } + }, + "node_modules/@serialport/parser-byte-length": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-12.0.0.tgz", + "integrity": "sha512-0ei0txFAj+s6FTiCJFBJ1T2hpKkX8Md0Pu6dqMrYoirjPskDLJRgZGLqoy3/lnU1bkvHpnJO+9oJ3PB9v8rNlg==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-cctalk": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-12.0.0.tgz", + "integrity": "sha512-0PfLzO9t2X5ufKuBO34DQKLXrCCqS9xz2D0pfuaLNeTkyGUBv426zxoMf3rsMRodDOZNbFblu3Ae84MOQXjnZw==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-delimiter": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-12.0.0.tgz", + "integrity": "sha512-gu26tVt5lQoybhorLTPsH2j2LnX3AOP2x/34+DUSTNaUTzu2fBXw+isVjQJpUBFWu6aeQRZw5bJol5X9Gxjblw==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-inter-byte-timeout": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-12.0.0.tgz", + "integrity": "sha512-GnCh8K0NAESfhCuXAt+FfBRz1Cf9CzIgXfp7SdMgXwrtuUnCC/yuRTUFWRvuzhYKoAo1TL0hhUo77SFHUH1T/w==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-packet-length": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-packet-length/-/parser-packet-length-12.0.0.tgz", + "integrity": "sha512-p1hiCRqvGHHLCN/8ZiPUY/G0zrxd7gtZs251n+cfNTn+87rwcdUeu9Dps3Aadx30/sOGGFL6brIRGK4l/t7MuQ==", + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/@serialport/parser-readline": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-12.0.0.tgz", + "integrity": "sha512-O7cywCWC8PiOMvo/gglEBfAkLjp/SENEML46BXDykfKP5mTPM46XMaX1L0waWU6DXJpBgjaL7+yX6VriVPbN4w==", + "dependencies": { + "@serialport/parser-delimiter": "12.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-ready": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-12.0.0.tgz", + "integrity": "sha512-ygDwj3O4SDpZlbrRUraoXIoIqb8sM7aMKryGjYTIF0JRnKeB1ys8+wIp0RFMdFbO62YriUDextHB5Um5cKFSWg==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-regex": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-12.0.0.tgz", + "integrity": "sha512-dCAVh4P/pZrLcPv9NJ2mvPRBg64L5jXuiRxIlyxxdZGH4WubwXVXY/kBTihQmiAMPxbT3yshSX8f2+feqWsxqA==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-slip-encoder": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-slip-encoder/-/parser-slip-encoder-12.0.0.tgz", + "integrity": "sha512-0APxDGR9YvJXTRfY+uRGhzOhTpU5akSH183RUcwzN7QXh8/1jwFsFLCu0grmAUfi+fItCkR+Xr1TcNJLR13VNA==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-spacepacket": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-spacepacket/-/parser-spacepacket-12.0.0.tgz", + "integrity": "sha512-dozONxhPC/78pntuxpz/NOtVps8qIc/UZzdc/LuPvVsqCoJXiRxOg6ZtCP/W58iibJDKPZPAWPGYeZt9DJxI+Q==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/stream": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-12.0.0.tgz", + "integrity": "sha512-9On64rhzuqKdOQyiYLYv2lQOh3TZU/D3+IWCR5gk0alPel2nwpp4YwDEGiUBfrQZEdQ6xww0PWkzqth4wqwX3Q==", + "dependencies": { + "@serialport/bindings-interface": "1.2.2", + "debug": "4.3.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -17740,9 +20271,9 @@ } }, "node_modules/@storybook/core-server/node_modules/@types/node": { - "version": "18.19.30", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.30.tgz", - "integrity": "sha512-453z1zPuJLVDbyahaa1sSD5C2sht6ZpHp5rgJNs+H8YGqhluCXcuOUmBYsAo0Tos0cHySJ3lVUGbGgLlqIkpyg==", + "version": "18.19.31", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.31.tgz", + "integrity": "sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -18185,9 +20716,9 @@ } }, "node_modules/@storybook/react/node_modules/@types/node": { - "version": "18.19.30", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.30.tgz", - "integrity": "sha512-453z1zPuJLVDbyahaa1sSD5C2sht6ZpHp5rgJNs+H8YGqhluCXcuOUmBYsAo0Tos0cHySJ3lVUGbGgLlqIkpyg==", + "version": "18.19.31", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.31.tgz", + "integrity": "sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -20129,9 +22660,9 @@ } }, "node_modules/@types/ssh2/node_modules/@types/node": { - "version": "18.19.30", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.30.tgz", - "integrity": "sha512-453z1zPuJLVDbyahaa1sSD5C2sht6ZpHp5rgJNs+H8YGqhluCXcuOUmBYsAo0Tos0cHySJ3lVUGbGgLlqIkpyg==", + "version": "18.19.31", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.31.tgz", + "integrity": "sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -21573,7 +24104,6 @@ "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.5", "is-array-buffer": "^3.0.4" @@ -21723,7 +24253,6 @@ "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.1", "call-bind": "^1.0.5", @@ -26601,7 +29130,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", - "dev": true, "dependencies": { "call-bind": "^1.0.6", "es-errors": "^1.3.0", @@ -26618,7 +29146,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -26635,7 +29162,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", - "dev": true, "dependencies": { "call-bind": "^1.0.6", "es-errors": "^1.3.0", @@ -27564,6 +30090,7 @@ "version": "10.0.0", "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", + "dev": true, "engines": { "node": ">=12" } @@ -27836,7 +30363,6 @@ "version": "1.23.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", - "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.1", "arraybuffer.prototype.slice": "^1.0.3", @@ -27972,7 +30498,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", - "dev": true, "dependencies": { "es-errors": "^1.3.0" }, @@ -27984,7 +30509,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", - "dev": true, "dependencies": { "get-intrinsic": "^1.2.4", "has-tostringtag": "^1.0.2", @@ -28007,7 +30531,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, "dependencies": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -31068,7 +33591,6 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -31313,7 +33835,6 @@ "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.5", "es-errors": "^1.3.0", @@ -31557,7 +34078,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, "dependencies": { "define-properties": "^1.1.3" }, @@ -31794,7 +34314,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -32965,7 +35484,6 @@ "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": { "es-errors": "^1.3.0", "hasown": "^2.0.0", @@ -33141,7 +35659,6 @@ "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.1" @@ -33177,7 +35694,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, "dependencies": { "has-bigints": "^1.0.1" }, @@ -33201,7 +35717,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -33271,7 +35786,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", - "dev": true, "dependencies": { "is-typed-array": "^1.1.13" }, @@ -33537,7 +36051,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -33569,7 +36082,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -33699,7 +36211,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", - "dev": true, "dependencies": { "call-bind": "^1.0.7" }, @@ -33725,7 +36236,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -33740,7 +36250,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -33809,7 +36318,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2" }, @@ -36263,9 +38771,14 @@ } }, "node_modules/json-schema-deref-sync/node_modules/traverse": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.8.tgz", - "integrity": "sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.9.tgz", + "integrity": "sha512-7bBrcF+/LQzSgFmT0X5YclVqQxtv7TDJ1f8Wj7ibBu/U6BMLeOpUxuZjV7rMc44UtKxlnMFigdhFAIszSX1DMg==", + "dependencies": { + "gopd": "^1.0.1", + "typedarray.prototype.slice": "^1.0.3", + "which-typed-array": "^1.1.15" + }, "engines": { "node": ">= 0.4" }, @@ -41893,6 +44406,11 @@ "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" }, + "node_modules/node-addon-api": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.0.0.tgz", + "integrity": "sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==" + }, "node_modules/node-cleanup": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", @@ -42002,6 +44520,16 @@ "node": "^12.13 || ^14.13 || >=16" } }, + "node_modules/node-gyp-build": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-gyp-build-optional-packages": { "version": "5.0.7", "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.7.tgz", @@ -43542,7 +46070,6 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.5", "define-properties": "^1.2.1", @@ -46139,9 +48666,9 @@ "integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==" }, "node_modules/preact": { - "version": "10.20.1", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.20.1.tgz", - "integrity": "sha512-JIFjgFg9B2qnOoGiYMVBtrcFxHqn+dNXbq76bVmcaHYJFYR4lW67AOcXgAYQQTDYXDOg/kTZrKPNCdRgJ2UJmw==", + "version": "10.20.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.20.2.tgz", + "integrity": "sha512-S1d1ernz3KQ+Y2awUxKakpfOg2CEmJmwOP+6igPx6dgr6pgDvenqYviyokWso2rhHvGtTlWWnJDa7RaPbQerTg==", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -48976,7 +51503,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", - "dev": true, "dependencies": { "call-bind": "^1.0.7", "get-intrinsic": "^1.2.4", @@ -48993,8 +51519,7 @@ "node_modules/safe-array-concat/node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" }, "node_modules/safe-buffer": { "version": "5.1.2", @@ -49011,7 +51536,6 @@ "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.6", "es-errors": "^1.3.0", @@ -49273,6 +51797,33 @@ "randombytes": "^2.1.0" } }, + "node_modules/serialport": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/serialport/-/serialport-12.0.0.tgz", + "integrity": "sha512-AmH3D9hHPFmnF/oq/rvigfiAouAKyK/TjnrkwZRYSFZxNggJxwvbAbfYrLeuvq7ktUdhuHdVdSjj852Z55R+uA==", + "dependencies": { + "@serialport/binding-mock": "10.2.2", + "@serialport/bindings-cpp": "12.0.1", + "@serialport/parser-byte-length": "12.0.0", + "@serialport/parser-cctalk": "12.0.0", + "@serialport/parser-delimiter": "12.0.0", + "@serialport/parser-inter-byte-timeout": "12.0.0", + "@serialport/parser-packet-length": "12.0.0", + "@serialport/parser-readline": "12.0.0", + "@serialport/parser-ready": "12.0.0", + "@serialport/parser-regex": "12.0.0", + "@serialport/parser-slip-encoder": "12.0.0", + "@serialport/parser-spacepacket": "12.0.0", + "@serialport/stream": "12.0.0", + "debug": "4.3.4" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, "node_modules/serve-handler": { "version": "6.1.5", "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.5.tgz", @@ -49947,9 +52498,9 @@ } }, "node_modules/socks": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.1.tgz", - "integrity": "sha512-B6w7tkwNid7ToxjZ08rQMT8M9BJAf8DKx8Ft4NivzH0zBUfd6jldGcisJn/RLgxcX3FPNDdNQCUEMMT79b+oCQ==", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.2.tgz", + "integrity": "sha512-5Hvyu6Md91ooZzdrN/bSn/zlulFaRHrk2/6IY6qQNa3oVHTiG+CKxBV5PynKCGf31ar+3/hyPvlHFAYaBEOa3A==", "dev": true, "dependencies": { "ip-address": "^9.0.5", @@ -51023,7 +53574,6 @@ "version": "1.2.9", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", - "dev": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -51041,7 +53591,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -51055,7 +53604,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -52829,7 +55377,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -52843,7 +55390,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", - "dev": true, "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", @@ -52862,7 +55408,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", - "dev": true, "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.7", @@ -52882,7 +55427,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", - "dev": true, "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", @@ -52912,6 +55456,25 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/typedarray.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typedarray.prototype.slice/-/typedarray.prototype.slice-1.0.3.tgz", + "integrity": "sha512-8WbVAQAUlENo1q3c3zZYuy5k9VzBQvp8AX9WOtbvyWlLM1v5JaSRmjubLjzHF4JFtptjH/5c/i95yaElvcjC0A==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-errors": "^1.3.0", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-offset": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typescript": { "version": "5.4.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz", @@ -52976,7 +55539,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-bigints": "^1.0.2", @@ -54786,7 +57348,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, "dependencies": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", @@ -55482,6 +58043,7 @@ "@medplum/hl7": "3.1.2", "dcmjs-dimse": "0.1.27", "node-windows": "1.0.0-beta.8", + "serialport": "12.0.0", "ws": "8.16.0" }, "devDependencies": { @@ -55937,8 +58499,8 @@ "rimraf": "5.0.5", "sinon": "17.0.1", "storybook": "8.0.6", - "typescript": "5.4.4", "storybook-addon-mantine": "4.0.2", + "typescript": "5.4.4", "vite-plugin-turbosnap": "^1.0.3" }, "engines": { diff --git a/packages/agent/package.json b/packages/agent/package.json index 14ce8c8846..5f82b7d38d 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -27,6 +27,7 @@ "@medplum/hl7": "3.1.2", "dcmjs-dimse": "0.1.27", "node-windows": "1.0.0-beta.8", + "serialport": "12.0.0", "ws": "8.16.0" }, "devDependencies": { diff --git a/packages/agent/src/app.ts b/packages/agent/src/app.ts index f349523f70..51e9bcba9b 100644 --- a/packages/agent/src/app.ts +++ b/packages/agent/src/app.ts @@ -18,6 +18,7 @@ import WebSocket from 'ws'; import { Channel } from './channel'; import { AgentDicomChannel } from './dicom'; import { AgentHl7Channel } from './hl7'; +import { AgentSerialPortChannel } from './serialport'; async function execAsync(command: string, options: ExecOptions): Promise<{ stdout: string; stderr: string }> { return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => { @@ -186,6 +187,8 @@ export class App { channel = new AgentDicomChannel(this, definition, endpoint); } else if (endpoint.address.startsWith('mllp')) { channel = new AgentHl7Channel(this, definition, endpoint); + } else if (endpoint.address.startsWith('serial')) { + channel = new AgentSerialPortChannel(this, definition, endpoint); } else { this.log.error(`Unsupported endpoint type: ${endpoint.address}`); } diff --git a/packages/agent/src/serialport.test.ts b/packages/agent/src/serialport.test.ts new file mode 100644 index 0000000000..d6fed71b2c --- /dev/null +++ b/packages/agent/src/serialport.test.ts @@ -0,0 +1,173 @@ +import { allOk, ContentType, createReference, LogLevel, sleep } from '@medplum/core'; +import { Agent, Bot, Endpoint, Resource } from '@medplum/fhirtypes'; +import { MockClient } from '@medplum/mock'; +import { Client, Server } from 'mock-socket'; +import { SerialPort } from 'serialport'; +import { App } from './app'; +import { + ASCII_END_OF_TEXT, + ASCII_END_OF_TRANSMISSION, + ASCII_ENQUIRY, + ASCII_START_OF_HEADING, + ASCII_START_OF_TEXT, +} from './serialport'; + +jest.mock('node-windows'); +jest.mock('serialport'); + +describe('Serial port', () => { + const medplum = new MockClient(); + let bot: Bot; + let endpoint: Endpoint; + + beforeAll(async () => { + console.log = jest.fn(); + console.warn = jest.fn(); + + medplum.router.router.add('POST', ':resourceType/:id/$execute', async () => { + return [allOk, {} as Resource]; + }); + + bot = await medplum.createResource({ resourceType: 'Bot' }); + + const url = new URL('serial://COM1'); + url.searchParams.set('baudRate', '9600'); + url.searchParams.set('dataBits', '8'); + url.searchParams.set('stopBits', '1'); + url.searchParams.set('clearOnStartOfHeading', 'true'); + url.searchParams.set('clearOnStartOfText', 'true'); + url.searchParams.set('transmitOnEndOfText', 'true'); + url.searchParams.set('transmitOnEndOfTransmission', 'true'); + url.searchParams.set('ackOnEnquiry', 'true'); + url.searchParams.set('ackOnEndOfText', 'true'); + url.searchParams.set('ackOnEndOfTransmission', 'true'); + url.searchParams.set('ackOnNewLine', 'true'); + + endpoint = await medplum.createResource({ + resourceType: 'Endpoint', + status: 'active', + address: url.toString(), + connectionType: { code: ContentType.TEXT }, + payloadType: [{ coding: [{ code: ContentType.TEXT }] }], + }); + }); + + test('Send and receive', async () => { + const mockServer = new Server('wss://example.com/ws/agent'); + const state = { + socket: undefined as Client | undefined, + messages: [] as string[], + gotConnectRequest: false, + gotTransmitRequest: false, + }; + + mockServer.on('connection', (socket) => { + state.socket = socket; + + socket.on('message', (data) => { + const command = JSON.parse((data as Buffer).toString('utf8')); + if (command.type === 'agent:connect:request') { + state.gotConnectRequest = true; + socket.send( + Buffer.from( + JSON.stringify({ + type: 'agent:connect:response', + }) + ) + ); + } + + if (command.type === 'agent:transmit:request') { + state.gotTransmitRequest = true; + state.messages.push(command.body); + socket.send( + Buffer.from( + JSON.stringify({ + type: 'agent:transmit:response', + channel: command.channel, + remote: command.remote, + body: 'OK', + }) + ) + ); + } + }); + }); + + const agent = await medplum.createResource({ + resourceType: 'Agent', + channel: [ + { + name: 'test', + endpoint: createReference(endpoint), + targetReference: createReference(bot), + }, + ], + } as Agent); + + const app = new App(medplum, agent.id as string, LogLevel.INFO); + await app.start(); + + // Get the mocked instance of SerialPort + expect(SerialPort).toHaveBeenCalledTimes(1); + const serialPort = (SerialPort as unknown as jest.Mock).mock.instances[0]; + expect(serialPort.on).toHaveBeenCalledTimes(3); + + const onOpen = serialPort.on.mock.calls[0]; + expect(onOpen[0]).toBe('open'); + expect(onOpen[1]).toBeInstanceOf(Function); + + const onError = serialPort.on.mock.calls[1]; + expect(onError[0]).toBe('error'); + expect(onError[1]).toBeInstanceOf(Function); + + const onData = serialPort.on.mock.calls[2]; + expect(onData[0]).toBe('data'); + expect(onData[1]).toBeInstanceOf(Function); + + expect(serialPort.open).toHaveBeenCalledTimes(1); + + // Simulate an 'open' event + const onOpenCallback = onOpen[1] as () => void; + onOpenCallback(); + + // Simulate a recoverable 'error' event + const onErrorCallback = onError[1] as (err: Error) => void; + onErrorCallback(new Error('test error')); + + // Wait for the WebSocket to connect + while (!state.socket) { + await sleep(100); + } + + // At this point, we expect the websocket to be connected + expect(state.socket).toBeDefined(); + expect(state.gotConnectRequest).toBe(true); + + // Clear the messages + state.messages.length = 0; + + // Mock sending data to the serial port + const onDataCallback = onData[1] as (data: Buffer) => void; + onDataCallback(Buffer.from([ASCII_START_OF_HEADING])); + onDataCallback(Buffer.from([ASCII_START_OF_TEXT])); + onDataCallback(Buffer.from('test\n')); + onDataCallback(Buffer.from([ASCII_END_OF_TEXT])); + onDataCallback(Buffer.from([ASCII_END_OF_TRANSMISSION])); + onDataCallback(Buffer.from([ASCII_ENQUIRY])); + + // Wait for the WebSocket to transmit + while (!state.gotTransmitRequest) { + await sleep(100); + } + expect(state.gotTransmitRequest).toBe(true); + + // Wait for the WebSocket to receive a reply + while (state.messages.length === 0) { + await sleep(100); + } + + app.stop(); + mockServer.stop(); + }); +}); diff --git a/packages/agent/src/serialport.ts b/packages/agent/src/serialport.ts new file mode 100644 index 0000000000..1fd9eb2190 --- /dev/null +++ b/packages/agent/src/serialport.ts @@ -0,0 +1,184 @@ +import { AgentTransmitResponse, ContentType, normalizeErrorString } from '@medplum/core'; +import { AgentChannel, Endpoint } from '@medplum/fhirtypes'; +import { SerialPort } from 'serialport'; +import { App } from './app'; +import { Channel } from './channel'; + +export const ASCII_START_OF_HEADING = 0x01; +export const ASCII_START_OF_TEXT = 0x02; +export const ASCII_END_OF_TEXT = 0x03; +export const ASCII_END_OF_TRANSMISSION = 0x04; +export const ASCII_ENQUIRY = 0x05; +export const ASCII_ACKNOWLEDGE = 0x06; +export const ASCII_NEW_LINE = 0x0a; + +export class AgentSerialPortChannel implements Channel { + readonly port: SerialPort; + readonly url: URL; + readonly path: string; + private buffer = ''; + + constructor( + readonly app: App, + readonly definition: AgentChannel, + readonly endpoint: Endpoint + ) { + this.url = new URL(this.endpoint.address as string); + this.path = this.url.hostname + this.url.pathname; + + // Create a new port connection + this.port = new SerialPort({ + path: this.path, + baudRate: parseInt(this.url.searchParams.get('baudRate') || '9600', 10), + dataBits: parseInt(this.url.searchParams.get('dataBits') || '8', 10) as 5 | 6 | 7 | 8, + stopBits: parseFloat(this.url.searchParams.get('stopBits') || '1') as 1 | 1.5 | 2, + autoOpen: false, + }); + } + + start(): void { + this.app.log.info(`Channel starting on ${this.url}`); + + // Parse options + const clearOnStartOfHeading = this.url.searchParams.get('clearOnStartOfHeading') === 'true'; + const clearOnStartOfText = this.url.searchParams.get('clearOnStartOfText') === 'true'; + const ackOnEndOfText = this.url.searchParams.get('ackOnEndOfText') === 'true'; + const ackOnEndOfTransmission = this.url.searchParams.get('ackOnEndOfTransmission') === 'true'; + const transmitOnEndOfText = this.url.searchParams.get('transmitOnEndOfText') === 'true'; + const transmitOnEndOfTransmission = this.url.searchParams.get('transmitOnEndOfTransmission') === 'true'; + const ackOnEnquiry = this.url.searchParams.get('ackOnEnquiry') === 'true'; + const ackOnNewLine = this.url.searchParams.get('ackOnNewLine') === 'true'; + const transmitOnNewLine = this.url.searchParams.get('transmitOnNewLine') === 'true'; + + // Add event handler for the "open" event + // Just log a message + this.port.on('open', () => { + this.logInfo(`[${this.path}] Serial port open`); + this.sendToServer(`[${this.path}] Serial port open`); + }); + + // Add event handler for the "error" event + // Just log a message + this.port.on('error', (err) => { + this.logError(`[${this.path}] Serial port error: ` + err); + this.sendToServer(`[${this.path}] Serial port error: ` + err); + }); + + // Add event handler for the "data" event + // Convert the contents to ASCII + // Build up the data buffer + // When we receive the "end" code (0x03), then send to the cloud + this.port.on('data', (data) => { + const str = data.toString('ascii'); + for (let i = 0; i < str.length; i++) { + const code = str.charCodeAt(i); + switch (code) { + case ASCII_START_OF_HEADING: + if (clearOnStartOfHeading) { + this.buffer = ''; + } + break; + + case ASCII_START_OF_TEXT: + if (clearOnStartOfText) { + this.buffer = ''; + } + break; + + case ASCII_END_OF_TEXT: + if (transmitOnEndOfText) { + this.sendToServer(this.buffer); + this.buffer = ''; + } + if (ackOnEndOfText) { + this.port.write(String.fromCharCode(ASCII_ACKNOWLEDGE)); + } + break; + + case ASCII_END_OF_TRANSMISSION: + if (transmitOnEndOfTransmission) { + this.sendToServer(this.buffer); + this.buffer = ''; + } + if (ackOnEndOfTransmission) { + this.port.write(String.fromCharCode(ASCII_ACKNOWLEDGE)); + } + break; + + case ASCII_ENQUIRY: + if (ackOnEnquiry) { + this.port.write(String.fromCharCode(ASCII_ACKNOWLEDGE)); + } + break; + + case ASCII_NEW_LINE: + if (transmitOnNewLine) { + this.sendToServer(this.buffer); + this.buffer = ''; + } else { + this.buffer += '\n'; + } + if (ackOnNewLine) { + this.port.write(String.fromCharCode(ASCII_ACKNOWLEDGE)); + } + break; + + default: + // Otherwise add to the buffer + this.buffer += str.charAt(i); + break; + } + } + }); + + // Open the connection + this.port.open((err) => { + if (err) { + this.logError(`[${this.path}] Error opening serial port: ` + err); + } + }); + + this.sendToServer(`[${this.path}] Running`); + + this.app.log.info('Channel started successfully'); + } + + stop(): void { + this.app.log.info('Channel stopping...'); + this.port.close((err) => { + if (err) { + this.logError(`[${this.path}] Error closing serial port: ` + err); + } + }); + this.app.log.info('Channel stopped successfully'); + } + + sendToRemote(msg: AgentTransmitResponse): void { + console.warn(`SerialPort sendToRemote not implemented (${msg.body})`); + } + + private sendToServer(body: string): void { + try { + this.app.log.info('Received:'); + this.app.log.info(body); + this.app.addToWebSocketQueue({ + type: 'agent:transmit:request', + accessToken: 'placeholder', + channel: this.definition.name as string, + remote: this.path, + contentType: ContentType.TEXT, + body, + }); + } catch (err) { + this.app.log.error(`HL7 error: ${normalizeErrorString(err)}`); + } + } + + private logInfo(message: string): void { + this.app.log.info(message); + } + + private logError(message: string): void { + this.app.log.error(message); + } +} diff --git a/packages/server/src/fhir/operations/projectinit.test.ts b/packages/server/src/fhir/operations/projectinit.test.ts index 09a39dfcbd..05618d25be 100644 --- a/packages/server/src/fhir/operations/projectinit.test.ts +++ b/packages/server/src/fhir/operations/projectinit.test.ts @@ -1,14 +1,19 @@ -import express from 'express'; -import { loadTestConfig } from '../../config'; -import { initApp, shutdownApp } from '../../app'; -import { initTestAuth, withTestContext } from '../../test.setup'; -import request from 'supertest'; import { ContentType, createReference, isUUID } from '@medplum/core'; +import { Practitioner, Project } from '@medplum/fhirtypes'; import { randomUUID } from 'crypto'; +import express from 'express'; +import { pwnedPassword } from 'hibp'; +import fetch from 'node-fetch'; +import request from 'supertest'; +import { initApp, shutdownApp } from '../../app'; import { createUser } from '../../auth/newuser'; -import { Practitioner, Project } from '@medplum/fhirtypes'; +import { loadTestConfig } from '../../config'; +import { initTestAuth, setupPwnedPasswordMock, setupRecaptchaMock, withTestContext } from '../../test.setup'; import { getSystemRepo } from '../repo'; +jest.mock('hibp'); +jest.mock('node-fetch'); + const app = express(); describe('Project $init', () => { @@ -21,6 +26,13 @@ describe('Project $init', () => { await shutdownApp(); }); + beforeEach(() => { + (fetch as unknown as jest.Mock).mockClear(); + (pwnedPassword as unknown as jest.Mock).mockClear(); + setupPwnedPasswordMock(pwnedPassword as unknown as jest.Mock, 0); + setupRecaptchaMock(fetch as unknown as jest.Mock, true); + }); + test('Success', async () => { const superAdminAccessToken = await initTestAuth({ superAdmin: true }); expect(superAdminAccessToken).toBeDefined(); From 53578bf8fde82c601ad0a250b2cc5a2cc4cd7aa2 Mon Sep 17 00:00:00 2001 From: Derrick Farris Date: Tue, 9 Apr 2024 20:45:28 -0700 Subject: [PATCH 08/52] feat(agent): allow valid hostnames to be pinged (#4350) * feat(agent): add pinging valid hostnames * tweak(client): update UI to note hostnames are allowed --- packages/agent/src/app.ts | 25 ++++--- packages/agent/src/net-utils.test.ts | 43 +++++++++++- packages/app/src/resource/ToolsPage.test.tsx | 10 +-- packages/app/src/resource/ToolsPage.tsx | 15 ++-- packages/core/src/utils.test.ts | 19 ++++++ packages/core/src/utils.ts | 33 +++++++++ packages/mock/src/client.ts | 21 +++--- .../src/fhir/operations/agentpush.test.ts | 68 ++++++++++++++++++- .../server/src/fhir/operations/agentpush.ts | 2 +- .../server/src/fhir/operations/agentutils.ts | 8 ++- 10 files changed, 205 insertions(+), 39 deletions(-) diff --git a/packages/agent/src/app.ts b/packages/agent/src/app.ts index 51e9bcba9b..8be4a71f16 100644 --- a/packages/agent/src/app.ts +++ b/packages/agent/src/app.ts @@ -7,6 +7,7 @@ import { LogLevel, Logger, MedplumClient, + isValidHostname, normalizeErrorString, } from '@medplum/core'; import { Endpoint, Reference } from '@medplum/fhirtypes'; @@ -157,7 +158,7 @@ export class App { case 'push': case 'agent:transmit:request': if (command.contentType === ContentType.PING) { - await this.tryPingIp(command); + await this.tryPingHost(command); } else { this.pushMessage(command); } @@ -279,22 +280,26 @@ export class App { } // This covers Windows, Linux, and Mac - private getPingCommand(ip: string, count = 1): string { - return platform() === 'win32' ? `ping /n ${count} ${ip}` : `ping -c ${count} ${ip}`; + private getPingCommand(host: string, count = 1): string { + return platform() === 'win32' ? `ping /n ${count} ${host}` : `ping -c ${count} ${host}`; } - private async tryPingIp(message: AgentTransmitRequest): Promise { + private async tryPingHost(message: AgentTransmitRequest): Promise { try { if (message.body && !message.body.startsWith('PING')) { const warnMsg = 'Message body present but unused. Body for a ping request should be empty or a message formatted as `PING[ count]`.'; 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.`; - } + + if (isIPv6(message.remote)) { + const errMsg = `Attempted to ping an IPv6 address: ${message.remote}\n\nIPv6 is currently unsupported.`; + this.log.error(errMsg); + throw new Error(errMsg); + } + + if (!(isIPv4(message.remote) || isValidHostname(message.remote))) { + const errMsg = `Attempted to ping an invalid host.\n\n"${message.remote}" is not a valid IPv4 address or a resolvable hostname.`; this.log.error(errMsg); throw new Error(errMsg); } @@ -331,7 +336,7 @@ export class App { body: result, } satisfies AgentTransmitResponse); } catch (err) { - this.log.error(`Error during ping attempt to ${message.remote ?? 'NO_IP_GIVEN'}: ${normalizeErrorString(err)}`); + this.log.error(`Error during ping attempt to ${message.remote ?? 'NO_HOST_GIVEN'}: ${normalizeErrorString(err)}`); this.addToWebSocketQueue({ type: 'agent:transmit:response', channel: message.channel, diff --git a/packages/agent/src/net-utils.test.ts b/packages/agent/src/net-utils.test.ts index 8071585c69..2234f441cb 100644 --- a/packages/agent/src/net-utils.test.ts +++ b/packages/agent/src/net-utils.test.ts @@ -90,7 +90,7 @@ describe('Agent Net Utils', () => { clearTimeout(timer); }); - test('Valid ping', async () => { + test('Valid ping to IP', async () => { let resolve: (value: AgentMessage) => void; let reject: (error: Error) => void; @@ -127,7 +127,44 @@ describe('Agent Net Utils', () => { }); }); - test('Non-IP remote', async () => { + test('Valid ping to domain name', 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(); + + const callback = generateId(); + wsClient.send( + Buffer.from( + JSON.stringify({ + type: 'agent:transmit:request', + contentType: ContentType.PING, + remote: 'localhost', + callback, + body: 'PING', + } satisfies AgentTransmitRequest) + ) + ); + + timer = setTimeout(() => { + reject(new Error('Timeout')); + }, 3500); + + await expect(messageReceived).resolves.toMatchObject>({ + type: 'agent:transmit:response', + callback, + statusCode: 200, + body: expect.stringMatching(/ping statistics/), + }); + }); + + test('Invalid remote', async () => { let resolve: (value: AgentMessage) => void; let reject: (error: Error) => void; @@ -162,7 +199,7 @@ describe('Agent Net Utils', () => { contentType: ContentType.TEXT, statusCode: 500, callback, - body: expect.stringMatching(/invalid ip/i), + body: expect.stringMatching(/invalid host/i), }); }); diff --git a/packages/app/src/resource/ToolsPage.test.tsx b/packages/app/src/resource/ToolsPage.test.tsx index 7dda14d446..78905a5f24 100644 --- a/packages/app/src/resource/ToolsPage.test.tsx +++ b/packages/app/src/resource/ToolsPage.test.tsx @@ -64,7 +64,7 @@ describe('ToolsPage', () => { expect(screen.getAllByText(agent.name)[0]).toBeInTheDocument(); await act(async () => { - fireEvent.change(screen.getByLabelText('IP Address'), { target: { value: '8.8.8.8' } }); + fireEvent.change(screen.getByLabelText('IP Address / Hostname'), { target: { value: '8.8.8.8' } }); fireEvent.click(screen.getByLabelText('Ping')); }); @@ -80,7 +80,7 @@ describe('ToolsPage', () => { expect(screen.getAllByText(agent.name)[0]).toBeInTheDocument(); await act(async () => { - fireEvent.change(screen.getByLabelText('IP Address'), { target: { value: 'abc123' } }); + fireEvent.change(screen.getByLabelText('IP Address / Hostname'), { target: { value: 'abc123' } }); fireEvent.click(screen.getByLabelText('Ping')); }); @@ -98,7 +98,7 @@ describe('ToolsPage', () => { expect(screen.getAllByText(agent.name)[0]).toBeInTheDocument(); await act(async () => { - fireEvent.change(screen.getByLabelText('IP Address'), { target: { value: '8.8.8.8' } }); + fireEvent.change(screen.getByLabelText('IP Address / Hostname'), { target: { value: '8.8.8.8' } }); fireEvent.click(screen.getByLabelText('Ping')); }); @@ -125,7 +125,7 @@ describe('ToolsPage', () => { expect(screen.getAllByText(agent.name)[0]).toBeInTheDocument(); await act(async () => { - fireEvent.change(screen.getByLabelText('IP Address'), { target: { value: '8.8.8.8' } }); + fireEvent.change(screen.getByLabelText('IP Address / Hostname'), { target: { value: '8.8.8.8' } }); }); await act(async () => { @@ -144,7 +144,7 @@ describe('ToolsPage', () => { pushToAgentSpy.mockRestore(); }); - test('No IP entered for ping', async () => { + test('No host entered for ping', async () => { const pushToAgentSpy = jest.spyOn(medplum, 'pushToAgent'); // load agent page diff --git a/packages/app/src/resource/ToolsPage.tsx b/packages/app/src/resource/ToolsPage.tsx index 041104be14..6af857248c 100644 --- a/packages/app/src/resource/ToolsPage.tsx +++ b/packages/app/src/resource/ToolsPage.tsx @@ -31,14 +31,14 @@ export function ToolsPage(): JSX.Element | null { const handlePing = useCallback( (formData: Record) => { - const ip = formData.ip; + const host = formData.host; const pingCount = formData.pingCount || 1; - if (!ip) { + if (!host) { return; } setPinging(true); medplum - .pushToAgent(reference, ip, `PING ${pingCount}`, ContentType.PING, true) + .pushToAgent(reference, host, `PING ${pingCount}`, ContentType.PING, true) .then((pingResult: string) => setLastPing(pingResult)) .catch((err: unknown) => showError(normalizeErrorString(err))) .finally(() => setPinging(false)); @@ -89,15 +89,16 @@ export function ToolsPage(): JSX.Element | null { Ping from Agent

- Send a ping command from the agent to an IP address. Use this tool to troubleshoot local network connectivity. + Send a ping command from the agent to a valid IP address or hostname. Use this tool to troubleshoot local + network connectivity.

diff --git a/packages/core/src/utils.test.ts b/packages/core/src/utils.test.ts index 27ca03bc63..08c211fdc7 100644 --- a/packages/core/src/utils.test.ts +++ b/packages/core/src/utils.test.ts @@ -45,6 +45,7 @@ import { isPopulated, isProfileResource, isUUID, + isValidHostname, lazy, parseReference, preciseEquals, @@ -1342,4 +1343,22 @@ describe('Core Utils', () => { ); expect(getQueryString(undefined)).toEqual(''); }); + + test('isValidHostname', () => { + expect(isValidHostname('foo')).toEqual(true); + expect(isValidHostname('foo.com')).toEqual(true); + expect(isValidHostname('foo.bar.com')).toEqual(true); + expect(isValidHostname('foo.org')).toEqual(true); + expect(isValidHostname('foo.bar.co.uk')).toEqual(true); + expect(isValidHostname('localhost')).toEqual(true); + expect(isValidHostname('LOCALHOST')).toEqual(true); + expect(isValidHostname('foo-bar-baz')).toEqual(true); + expect(isValidHostname('foo_bar')).toEqual(true); + expect(isValidHostname('foobar123')).toEqual(true); + + expect(isValidHostname('foo.com/bar')).toEqual(false); + expect(isValidHostname('https://foo.com')).toEqual(false); + expect(isValidHostname('foo_-bar_-')).toEqual(false); + expect(isValidHostname('foo | rm -rf /')).toEqual(false); + }); }); diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 02a8685d01..0b71db545d 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -1139,3 +1139,36 @@ export function getQueryString(query: QueryTypes): string { // Source: https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams:~:text=6.2.%20URLSearchParams,)%20init%20%3D%20%22%22)%3B return new URLSearchParams(query).toString(); } + +export const VALID_HOSTNAME_REGEX = + /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-_]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-_]*[A-Za-z0-9])$/; + +/** + * Tests whether a given input is a valid hostname. + * + * __NOTE: Does not validate that the input is a valid domain name, only a valid hostname.__ + * + * @param input - The input to test. + * @returns True if `input` is a valid hostname, otherwise returns false. + * + * ### Valid matches: + * - foo + * - foo.com + * - foo.bar.com + * - foo.org + * - foo.bar.co.uk + * - localhost + * - LOCALHOST + * - foo-bar-baz + * - foo_bar + * - foobar123 + * + * ### Invalid matches: + * - foo.com/bar + * - https://foo.com + * - foo_-bar_- + * - foo | rm -rf / + */ +export function isValidHostname(input: string): boolean { + return VALID_HOSTNAME_REGEX.test(input); +} diff --git a/packages/mock/src/client.ts b/packages/mock/src/client.ts index 9976b41b6e..4348d15d90 100644 --- a/packages/mock/src/client.ts +++ b/packages/mock/src/client.ts @@ -222,20 +222,23 @@ export class MockClient extends MedplumClient { if (!this.agentAvailable) { throw new OperationOutcomeError(badRequest('Timeout')); } - if (typeof destination === 'string' && destination !== '8.8.8.8') { + if (typeof destination !== 'string' || (destination !== '8.8.8.8' && destination !== 'localhost')) { // Exception for test case if (destination !== 'abc123') { - console.warn('IPs other than 8.8.8.8 will always throw an error in MockClient'); + console.warn( + 'IPs other than 8.8.8.8 and hostnames other than `localhost` 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 --- + const ip = destination === 'localhost' ? '127.0.0.1' : destination; + return `PING ${destination} (${ip}): 56 data bytes +64 bytes from ${ip}: icmp_seq=0 ttl=115 time=10.977 ms +64 bytes from ${ip}: icmp_seq=1 ttl=115 time=13.037 ms +64 bytes from ${ip}: icmp_seq=2 ttl=115 time=23.159 ms +64 bytes from ${ip}: icmp_seq=3 ttl=115 time=12.725 ms + +--- ${destination} 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 `; diff --git a/packages/server/src/fhir/operations/agentpush.test.ts b/packages/server/src/fhir/operations/agentpush.test.ts index 086520c643..9101abe448 100644 --- a/packages/server/src/fhir/operations/agentpush.test.ts +++ b/packages/server/src/fhir/operations/agentpush.test.ts @@ -258,7 +258,7 @@ describe('Agent Push', () => { expect(res.body.issue[0].details.text).toEqual('Invalid wait timeout'); }); - test('Ping -- Successful', async () => { + test('Ping -- Successful ping to IP', async () => { const redis = getRedis(); const publishSpy = jest.spyOn(redis, 'publish'); @@ -324,6 +324,72 @@ round-trip min/avg/max/stddev = 10.316/10.316/10.316/nan ms`, publishSpy.mockRestore(); }); + test('Ping -- Successful ping to hostname', async () => { + const redis = getRedis(); + const publishSpy = jest.spyOn(redis, 'publish'); + + let resolve!: (value: request.Response) => void | PromiseLike; + let reject!: (err: Error) => void; + + const deferredResponse = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + + request(app) + .post(`/fhir/R4/Agent/${agent.id}/$push`) + .set('Content-Type', ContentType.JSON) + .set('Authorization', 'Bearer ' + accessToken) + .send({ + contentType: ContentType.PING, + body: 'PING', + destination: 'localhost', + waitForResponse: true, + } satisfies AgentPushParameters) + .then(resolve) + .catch(reject); + + let shouldThrow = false; + const timer = setTimeout(() => { + shouldThrow = true; + }, 3500); + + while (!publishSpy.mock.lastCall) { + if (shouldThrow) { + throw new Error('Timeout'); + } + await sleep(50); + } + clearTimeout(timer); + + const transmitRequestStr = publishSpy.mock.lastCall?.[1]?.toString() as string; + expect(transmitRequestStr).toBeDefined(); + const transmitRequest = JSON.parse(transmitRequestStr) as AgentTransmitRequest; + + await getRedis().publish( + transmitRequest.callback as string, + JSON.stringify({ + ...transmitRequest, + type: 'agent:transmit:response', + statusCode: 200, + contentType: ContentType.TEXT, + body: ` +PING localhost (127.0.0.1): 56 data bytes +64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.081 ms + +--- localhost ping statistics --- +1 packets transmitted, 1 packets received, 0.0% packet loss +round-trip min/avg/max/stddev = 0.081/0.081/0.081/nan ms`, + } satisfies AgentTransmitResponse) + ); + + const res = await deferredResponse; + expect(res.status).toEqual(200); + expect(res.text).toEqual(expect.stringMatching(/ping statistics/i)); + + publishSpy.mockRestore(); + }); + test('Ping -- Error', async () => { const redis = getRedis(); const publishSpy = jest.spyOn(redis, 'publish'); diff --git a/packages/server/src/fhir/operations/agentpush.ts b/packages/server/src/fhir/operations/agentpush.ts index 0bee00b899..4bdf0c7113 100644 --- a/packages/server/src/fhir/operations/agentpush.ts +++ b/packages/server/src/fhir/operations/agentpush.ts @@ -66,7 +66,7 @@ export const agentPushHandler = asyncWrap(async (req: Request, res: Response) => return; } - const device = await getDevice(repo, params.destination); + const device = await getDevice(repo, params); if (!device) { sendOutcome(res, badRequest('Destination device not found')); return; diff --git a/packages/server/src/fhir/operations/agentutils.ts b/packages/server/src/fhir/operations/agentutils.ts index ff9946f533..0495609209 100644 --- a/packages/server/src/fhir/operations/agentutils.ts +++ b/packages/server/src/fhir/operations/agentutils.ts @@ -1,9 +1,10 @@ -import { Operator, parseSearchRequest } from '@medplum/core'; +import { ContentType, Operator, isValidHostname, parseSearchRequest } from '@medplum/core'; import { FhirRequest } from '@medplum/fhir-router'; import { Agent, Device } from '@medplum/fhirtypes'; import { Request } from 'express'; import { isIPv4 } from 'node:net'; import { Repository } from '../repo'; +import { AgentPushParameters } from './agentpush'; /** * Returns the Agent for a request. @@ -40,7 +41,8 @@ export async function getAgentForRequest(req: Request | FhirRequest, repo: Repos return undefined; } -export async function getDevice(repo: Repository, destination: string): Promise { +export async function getDevice(repo: Repository, params: AgentPushParameters): Promise { + const { destination, contentType } = params; if (destination.startsWith('Device/')) { try { return await repo.readReference({ reference: destination }); @@ -51,7 +53,7 @@ export async function getDevice(repo: Repository, destination: string): Promise< if (destination.startsWith('Device?')) { return repo.searchOne(parseSearchRequest(destination)); } - if (isIPv4(destination)) { + if (contentType === ContentType.PING && (isIPv4(destination) || isValidHostname(destination))) { return { resourceType: 'Device', url: destination }; } return undefined; From 91716c713737777c37c7baca1c80a128ae3557d3 Mon Sep 17 00:00:00 2001 From: Derrick Farris Date: Tue, 9 Apr 2024 20:48:39 -0700 Subject: [PATCH 09/52] fix(chat-demo): match only thread between the two recipients (#4349) --- examples/medplum-live-chat-demo/src/pages/HomePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/medplum-live-chat-demo/src/pages/HomePage.tsx b/examples/medplum-live-chat-demo/src/pages/HomePage.tsx index b0698d631c..8363fc4e36 100644 --- a/examples/medplum-live-chat-demo/src/pages/HomePage.tsx +++ b/examples/medplum-live-chat-demo/src/pages/HomePage.tsx @@ -55,7 +55,7 @@ export function HomePage(): JSX.Element { recipient: [meReference, createReference(homerSimpson)], status: 'in-progress', }, - `part-of:missing=true&recipient=${getReferenceString(profile)},${getReferenceString(homerSimpson)}&topic:text='Demo Thread'` + `part-of:missing=true&recipient=${getReferenceString(profile)}&recipient=${getReferenceString(homerSimpson)}&topic:text='Demo Thread'` ) .then((thread) => { setThread(thread); From c1caa0e7071e547e6c0bc839291426fd09ae30eb Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Wed, 10 Apr 2024 10:31:17 -0700 Subject: [PATCH 10/52] Added server config setting for approved email senders (#4351) --- packages/server/src/config.ts | 2 ++ packages/server/src/email/email.test.ts | 28 +++++++++++++++++++++++++ packages/server/src/email/email.ts | 25 +++++++++++++++++++--- 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/packages/server/src/config.ts b/packages/server/src/config.ts index 1268844d13..0b647f7229 100644 --- a/packages/server/src/config.ts +++ b/packages/server/src/config.ts @@ -24,6 +24,7 @@ export interface MedplumServerConfig { signingKeyId: string; signingKeyPassphrase: string; supportEmail: string; + approvedSenderEmails?: string; database: MedplumDatabaseConfig; databaseProxyEndpoint?: string; redis: MedplumRedisConfig; @@ -178,6 +179,7 @@ export async function loadTestConfig(): Promise { config.database.dbname = 'medplum_test'; config.redis.db = 7; // Select logical DB `7` so we don't collide with existing dev Redis cache. config.redis.password = process.env['REDIS_PASSWORD_DISABLED_IN_TESTS'] ? undefined : config.redis.password; + config.approvedSenderEmails = 'no-reply@example.com'; return config; } diff --git a/packages/server/src/email/email.test.ts b/packages/server/src/email/email.test.ts index 498e39a317..7d122fbe2f 100644 --- a/packages/server/src/email/email.test.ts +++ b/packages/server/src/email/email.test.ts @@ -39,8 +39,10 @@ describe('Email', () => { }); test('Send text email', async () => { + const fromAddress = 'gibberish@example.com'; const toAddresses = 'alice@example.com'; await sendEmail(systemRepo, { + from: fromAddress, to: toAddresses, cc: 'bob@example.com', subject: 'Hello', @@ -52,6 +54,32 @@ describe('Email', () => { const inputArgs = mockSESv2Client.commandCalls(SendEmailCommand)[0].args[0].input; + expect(inputArgs?.FromEmailAddress).toBe(getConfig().supportEmail); + expect(inputArgs?.Destination?.ToAddresses?.[0] ?? '').toBe('alice@example.com'); + expect(inputArgs?.Destination?.CcAddresses?.[0] ?? '').toBe('bob@example.com'); + + const parsed = await simpleParser(Readable.from(inputArgs?.Content?.Raw?.Data ?? '')); + expect(parsed.subject).toBe('Hello'); + expect(parsed.text).toBe('Hello Alice\n'); + }); + + test('Send text email from approved sender', async () => { + const fromAddress = 'no-reply@example.com'; + const toAddresses = 'alice@example.com'; + await sendEmail(systemRepo, { + from: fromAddress, + to: toAddresses, + cc: 'bob@example.com', + subject: 'Hello', + text: 'Hello Alice', + }); + + expect(mockSESv2Client.send.callCount).toBe(1); + expect(mockSESv2Client).toHaveReceivedCommandTimes(SendEmailCommand, 1); + + const inputArgs = mockSESv2Client.commandCalls(SendEmailCommand)[0].args[0].input; + + expect(inputArgs?.FromEmailAddress).toBe(fromAddress); expect(inputArgs?.Destination?.ToAddresses?.[0] ?? '').toBe('alice@example.com'); expect(inputArgs?.Destination?.CcAddresses?.[0] ?? '').toBe('bob@example.com'); diff --git a/packages/server/src/email/email.ts b/packages/server/src/email/email.ts index 595cc1d2cd..b999b120be 100644 --- a/packages/server/src/email/email.ts +++ b/packages/server/src/email/email.ts @@ -18,10 +18,9 @@ import { globalLogger } from '../logger'; */ export async function sendEmail(repo: Repository, options: Mail.Options): Promise { const config = getConfig(); - const fromAddress = config.supportEmail; + const fromAddress = getFromAddress(options); const toAddresses = buildAddresses(options.to); - // Always set the from and sender to the support email address options.from = fromAddress; options.sender = fromAddress; @@ -42,6 +41,26 @@ export async function sendEmail(repo: Repository, options: Mail.Options): Promis } } +/** + * Returns the from address to use. + * If the user specified a from address, it must be an approved sender. + * Otherwise uses the support email address. + * @param options - The user specified nodemailer options. + * @returns The from address to use. + */ +function getFromAddress(options: Mail.Options): string { + const config = getConfig(); + + if (options.from) { + const fromAddress = addressToString(options.from); + if (fromAddress && config.approvedSenderEmails?.split(',')?.includes(fromAddress)) { + return fromAddress; + } + } + + return config.supportEmail; +} + /** * Converts nodemailer addresses to an array of strings. * @param input - nodemailer address input. @@ -158,7 +177,7 @@ async function sendEmailViaSmpt(smtpConfig: MedplumSmtpConfig, options: Mail.Opt */ async function sendEmailViaSes(options: Mail.Options): Promise { const config = getConfig(); - const fromAddress = config.supportEmail; + const fromAddress = addressToString(options.from); const toAddresses = buildAddresses(options.to); const ccAddresses = buildAddresses(options.cc); const bccAddresses = buildAddresses(options.bcc); From 092a25dffe38ce6083824fba5a623c8c9a27ef6d Mon Sep 17 00:00:00 2001 From: Derrick Farris Date: Thu, 11 Apr 2024 10:51:04 -0700 Subject: [PATCH 11/52] chore(actions): add timeouts to a lot of jobs (#4357) --- .github/workflows/build.yml | 4 ++++ .github/workflows/chromatic.yml | 1 + .github/workflows/deploy.yml | 1 + .github/workflows/prepare-release.yml | 1 + .github/workflows/publish.yml | 3 +++ .github/workflows/sonar.yml | 1 + .github/workflows/staging.yml | 1 + 7 files changed, 12 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index be18fa39ab..3b37d3a843 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,6 +15,7 @@ jobs: build: name: Build runs-on: ubuntu-latest + timeout-minutes: 45 permissions: pull-requests: write strategy: @@ -72,6 +73,7 @@ jobs: eslint: name: Run eslint runs-on: ubuntu-latest + timeout-minutes: 20 needs: [build] env: SECRETS_TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} @@ -149,6 +151,7 @@ jobs: test: name: Run tests runs-on: ubuntu-latest + timeout-minutes: 45 needs: [build] strategy: matrix: @@ -233,6 +236,7 @@ jobs: build-docs: name: Build the docs runs-on: ubuntu-latest + timeout-minutes: 30 permissions: pull-requests: write env: diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index 0ef710c9b0..12caab0aef 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -5,6 +5,7 @@ on: push jobs: chromatic: runs-on: ubuntu-latest + timeout-minutes: 45 steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 25e8f21310..cf2161421b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -9,6 +9,7 @@ jobs: build: name: Deploy runs-on: ubuntu-latest + timeout-minutes: 45 if: github.repository == 'medplum/medplum' env: NODE_VERSION: '20' diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 0e5895f1ae..47a085300e 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -11,6 +11,7 @@ jobs: prepare-release: name: Prepare release runs-on: ubuntu-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 831e07018f..d810a865d7 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -8,6 +8,7 @@ jobs: build_and_check: name: publish runs-on: ubuntu-latest + timeout-minutes: 30 env: NODE_VERSION: '20' SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} @@ -93,6 +94,7 @@ jobs: build_agent_win64: runs-on: windows-latest + timeout-minutes: 45 env: NODE_VERSION: '20' TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} @@ -168,6 +170,7 @@ jobs: build_agent_linux: runs-on: ubuntu-latest + timeout-minutes: 45 env: NODE_VERSION: '20' TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 90415c714b..cfdae40638 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -7,6 +7,7 @@ jobs: sonar: name: Sonar runs-on: ubuntu-latest + timeout-minutes: 30 if: github.event.workflow_run.conclusion == 'success' steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index 2567455bb4..414c7fa290 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -10,6 +10,7 @@ jobs: build: name: Staging runs-on: ubuntu-latest + timeout-minutes: 30 env: NODE_VERSION: '20' TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} From 1a0dbfb9ca0a127f7a50dfbeafb631be3958053e Mon Sep 17 00:00:00 2001 From: Pamella Bezerra Date: Fri, 12 Apr 2024 19:21:41 -0300 Subject: [PATCH 12/52] Update component handling in formatObservationValue function (#4361) --- packages/core/src/format.test.ts | 28 +++++++++++++++++++++++++++- packages/core/src/format.ts | 24 ++++++++++++------------ 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/packages/core/src/format.test.ts b/packages/core/src/format.test.ts index 99759c2df2..215c3f45cb 100644 --- a/packages/core/src/format.test.ts +++ b/packages/core/src/format.test.ts @@ -1,5 +1,5 @@ import { Observation } from '@medplum/fhirtypes'; -import { UCUM } from './constants'; +import { LOINC, UCUM } from './constants'; import { formatAddress, formatCodeableConcept, @@ -423,4 +423,30 @@ test('Format Observation value', () => { ], } as Observation) ).toBe('110 mmHg / 75 mmHg'); + expect( + formatObservationValue({ + resourceType: 'Observation', + code: { text: 'Body temperature' }, + valueQuantity: { + value: 36.7, + unit: 'C', + code: 'Cel', + system: UCUM, + }, + component: [ + { + code: { text: 'Body temperature measurement site' }, + valueCodeableConcept: { + coding: [ + { + display: 'Oral', + code: 'LA9367-9', + system: LOINC, + }, + ], + }, + }, + ], + } as Observation) + ).toBe('36.7 C / Oral'); }); diff --git a/packages/core/src/format.ts b/packages/core/src/format.ts index 70b1b6c42c..5d3a8c5d0c 100644 --- a/packages/core/src/format.ts +++ b/packages/core/src/format.ts @@ -439,24 +439,24 @@ export function formatObservationValue(obs: Observation | ObservationComponent | return ''; } - if ('component' in obs) { - return (obs.component as ObservationComponent[]).map((c) => formatObservationValue(c)).join(' / '); - } + const result = []; if (obs.valueQuantity) { - return formatQuantity(obs.valueQuantity); - } - - if (obs.valueCodeableConcept) { - return formatCodeableConcept(obs.valueCodeableConcept); + result.push(formatQuantity(obs.valueQuantity)); + } else if (obs.valueCodeableConcept) { + result.push(formatCodeableConcept(obs.valueCodeableConcept)); + } else { + const valueString = ensureString(obs.valueString); + if (valueString) { + result.push(valueString); + } } - const valueString = ensureString(obs.valueString); - if (valueString) { - return valueString; + if ('component' in obs) { + result.push((obs.component as ObservationComponent[]).map((c) => formatObservationValue(c)).join(' / ')); } - return ''; + return result.join(' / ').trim(); } /** From fff39fe74e6166603146b8f5a8c7dc8b86a476b0 Mon Sep 17 00:00:00 2001 From: Rahul Agarwal Date: Fri, 12 Apr 2024 15:28:52 -0700 Subject: [PATCH 13/52] Task Demo Sample Data (#4278) * WIP Adding Rich Sample Data to Task Demo Add Example Task data * Add PractitionerRoles to the example data * Separate buttons to upload different types of sample data * Fix business task dropdown * Update example data bundle Add PerformerType to display * Clean up resource details page Update to parseSearchRequest * Upload buttons for different types of data * Update Upload Link copy in sidebar * Added Dynamic Links for State Tasks * [autofix.ci] apply automated fixes * Button to upload qualifications * Example data with multiple patients * Add cache time * Script to generate Bot Deployment Bundle * Added required fields * In-app button to deploy bots * Fixed example report data * [autofix.ci] apply automated fixes * Add PractitionerRole * Upsert core data * WIP - Fixing example tasks * [autofix.ci] apply automated fixes * Fixed Qualifications * Clean Up Example Data and Bots * Add or replace qualifications * Added Diagnostic report display * Adding due date to tasks * [autofix.ci] apply automated fixes * Add due date and priority to example tasks * add example bots to gitignore * remove example-bots.json * remove whitespace --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- examples/medplum-task-demo/.gitignore | 3 +- .../data/core/business-status-value-sets.json | 38 - .../data/core/business-status-valueset.json | 33 + .../data/core/practitioner-role-valueset.json | 2 +- .../data/core/task-type-valueset.json | 32 + ...essage-data.json => example-messages.json} | 66 +- .../example/example-practitioner-role.json | 29 + .../data/example/example-reports.json | 297 ++++ .../data/example/example-tasks.json | 1279 +++++++++++++++++ examples/medplum-task-demo/package.json | 6 +- examples/medplum-task-demo/src/App.tsx | 257 ++-- .../bots/example/create-review-report-task.ts | 4 +- .../src/bots/medplum.config.json | 16 - .../actions/UpdateBusinessStatus.tsx | 2 +- examples/medplum-task-demo/src/main.tsx | 3 +- .../src/pages/ResourcePage.tsx | 23 +- .../src/pages/SearchPage.tsx | 32 +- .../src/pages/UploadDataPage.tsx | 273 +++- .../src/scripts/deploy-bots.ts | 131 ++ .../utils.tsx => utils/search-control.ts} | 0 examples/medplum-task-demo/tsconfig-bots.json | 33 + 21 files changed, 2352 insertions(+), 207 deletions(-) delete mode 100644 examples/medplum-task-demo/data/core/business-status-value-sets.json create mode 100644 examples/medplum-task-demo/data/core/business-status-valueset.json create mode 100644 examples/medplum-task-demo/data/core/task-type-valueset.json rename examples/medplum-task-demo/data/example/{respond-to-message-data.json => example-messages.json} (72%) create mode 100644 examples/medplum-task-demo/data/example/example-practitioner-role.json create mode 100644 examples/medplum-task-demo/data/example/example-reports.json create mode 100644 examples/medplum-task-demo/data/example/example-tasks.json delete mode 100644 examples/medplum-task-demo/src/bots/medplum.config.json create mode 100644 examples/medplum-task-demo/src/scripts/deploy-bots.ts rename examples/medplum-task-demo/src/{pages/utils.tsx => utils/search-control.ts} (100%) create mode 100644 examples/medplum-task-demo/tsconfig-bots.json diff --git a/examples/medplum-task-demo/.gitignore b/examples/medplum-task-demo/.gitignore index 81aa27a05a..231199a085 100644 --- a/examples/medplum-task-demo/.gitignore +++ b/examples/medplum-task-demo/.gitignore @@ -13,6 +13,7 @@ dist-ssr *.local package-lock.json .env +data/example/example-bots.json # Editor directories and files .vscode/* @@ -23,4 +24,4 @@ package-lock.json *.ntvs* *.njsproj *.sln -*.sw? +*.sw? \ No newline at end of file diff --git a/examples/medplum-task-demo/data/core/business-status-value-sets.json b/examples/medplum-task-demo/data/core/business-status-value-sets.json deleted file mode 100644 index daead0fcf2..0000000000 --- a/examples/medplum-task-demo/data/core/business-status-value-sets.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "resourceType": "Bundle", - "type": "batch", - "entry": [ - { - "request": { "method": "POST", "url": "ValueSet" }, - "resource": { - "resourceType": "ValueSet", - "id": "business-status", - "url": "https://medplum.com/medplum-task-example-app/task-status-valueset", - "name": "example-business-status-value-set", - "title": "Example Business Status Value Set", - "status": "active", - "compose": { - "include": [ - { - "system": "https://medplum.com/medplum-task-example-app/task-status-valueset", - "concept": [ - { - "code": "doctor-sign-off-needed", - "display": "Doctor sign-off needed." - }, - { - "code": "doctor-review-needed", - "display": "Doctor review needed." - }, - { - "code": "follow-up-needed", - "display": "Follow-up needed." - } - ] - } - ] - } - } - } - ] -} diff --git a/examples/medplum-task-demo/data/core/business-status-valueset.json b/examples/medplum-task-demo/data/core/business-status-valueset.json new file mode 100644 index 0000000000..f0e7633dd0 --- /dev/null +++ b/examples/medplum-task-demo/data/core/business-status-valueset.json @@ -0,0 +1,33 @@ +{ + "resourceType": "ValueSet", + "id": "urn:uuid:39996991-94bd-4fdd-bba6-d72ec5e3cd21", + "url": "https://medplum.com/medplum-task-example-app/task-status", + "name": "example-business-status-value-set", + "title": "Example Business Status Value Set", + "status": "active", + "compose": { + "include": [ + { + "system": "https://medplum.com/medplum-task-example-app/task-status", + "concept": [ + { + "code": "ready", + "display": "Ready" + }, + { + "code": "doctor-sign-off-needed", + "display": "Doctor sign-off needed" + }, + { + "code": "doctor-review-needed", + "display": "Doctor review needed" + }, + { + "code": "follow-up-needed", + "display": "Follow-up needed" + } + ] + } + ] + } +} diff --git a/examples/medplum-task-demo/data/core/practitioner-role-valueset.json b/examples/medplum-task-demo/data/core/practitioner-role-valueset.json index 095cf12d05..a07ec209db 100644 --- a/examples/medplum-task-demo/data/core/practitioner-role-valueset.json +++ b/examples/medplum-task-demo/data/core/practitioner-role-valueset.json @@ -1,6 +1,6 @@ { "resourceType": "ValueSet", - "id": "7869c2b5-77a3-428b-9088-0401e3632c87", + "id": "urn:uuid:7869c2b5-77a3-428b-9088-0401e3632c87", "url": "http://medplum.com/medplum-task-demo/practitioner-role-codes", "name": "Practitioner Role Codes", "title": "Practitioner Role Codes", diff --git a/examples/medplum-task-demo/data/core/task-type-valueset.json b/examples/medplum-task-demo/data/core/task-type-valueset.json new file mode 100644 index 0000000000..c6aa9d9d9c --- /dev/null +++ b/examples/medplum-task-demo/data/core/task-type-valueset.json @@ -0,0 +1,32 @@ +{ + "resourceType": "ValueSet", + "url": "https://medplum.com/medplum-task-demo/task-type", + "name": "Example Task Type Value Set", + "title": "Example Task Type Value Set", + "status": "active", + "compose": { + "include": [ + { + "system": "https://medplum.com/medplum-task-demo/task-type", + "concept": [ + { + "code": "respond-to-message", + "display": "Respond to Patient Message" + }, + { + "code": "schedule-appointment", + "display": "Schedule Appointment" + }, + { + "code": "review-diagnostic-report", + "display": "Review Diagnostic Report" + }, + { + "code": "verify-drivers-license", + "display": "Verify Drivers License" + } + ] + } + ] + } +} diff --git a/examples/medplum-task-demo/data/example/respond-to-message-data.json b/examples/medplum-task-demo/data/example/example-messages.json similarity index 72% rename from examples/medplum-task-demo/data/example/respond-to-message-data.json rename to examples/medplum-task-demo/data/example/example-messages.json index 93bc038b99..b91ce516bd 100644 --- a/examples/medplum-task-demo/data/example/respond-to-message-data.json +++ b/examples/medplum-task-demo/data/example/example-messages.json @@ -1,15 +1,55 @@ { "resourceType": "Bundle", - "type": "batch", + "type": "transaction", "entry": [ + { + "fullUrl": "urn:uuid:93e4cba2-9db5-11ee-8c90-0242ac876543", + "resource": { + "resourceType": "Patient", + "identifier": [ + { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "SS" + } + ] + }, + "system": "http://hl7.org/fhir/sid/us-ssn", + "value": "333222222" + } + ], + "name": [ + { + "given": ["John"], + "family": "Smith " + } + ], + "birthDate": "1970-01-01", + "address": [ + { + "use": "home", + "line": ["123 Main St."], + "city": "Springfiled", + "state": "IL", + "postalCode": "98732" + } + ] + }, + "request": { + "method": "POST", + "url": "Patient" + } + }, { "fullUrl": "urn:uuid:d9d3cba2-9db5-11ee-8c90-0242ac120002", "resource": { "resourceType": "Communication", "status": "in-progress", "subject": { - "reference": "Patient/8f0d3209-0ee0-487f-b186-4328a949190f", - "display": "Mr. Lucien408 Bosco882 PharmD" + "reference": "urn:uuid:93e4cba2-9db5-11ee-8c90-0242ac876543", + "display": "John Smith" }, "topic": { "coding": [{ "code": "Lab test results", "display": "Lab test results" }] } }, @@ -22,7 +62,7 @@ "payload": [{ "contentString": "Do you have the results of my lab tests yet?" }], "topic": { "text": "December 15th lab tests." }, "partOf": [{ "reference": "urn:uuid:d9d3cba2-9db5-11ee-8c90-0242ac120002" }], - "sender": { "reference": "Patient/8f0d3209-0ee0-487f-b186-4328a949190f" }, + "sender": { "reference": "urn:uuid:93e4cba2-9db5-11ee-8c90-0242ac876543" }, "sent": "2023-12-18T14:26:06.531Z" }, "request": { "method": "POST", "url": "Communication" } @@ -46,7 +86,7 @@ "payload": [{ "contentString": "Yes, it is 12345" }], "topic": { "text": "December 15th lab tests." }, "partOf": [{ "reference": "urn:uuid:d9d3cba2-9db5-11ee-8c90-0242ac120002" }], - "sender": { "reference": "Patient/8f0d3209-0ee0-487f-b186-4328a949190f" }, + "sender": { "reference": "urn:uuid:93e4cba2-9db5-11ee-8c90-0242ac876543" }, "sent": "2023-12-18T14:46:06.531Z" }, "request": { "method": "POST", "url": "Communication" } @@ -88,7 +128,8 @@ } ], "sender": { - "reference": "Patient/8f0d3209-0ee0-487f-b186-4328a949190f" + "reference": "urn:uuid:93e4cba2-9db5-11ee-8c90-0242ac876543", + "display": "John Smith" }, "sent": "2023-12-18T14:01:15.175Z" }, @@ -103,7 +144,7 @@ "resourceType": "Communication", "status": "in-progress", "subject": { - "reference": "Patient/8f0d3209-0ee0-487f-b186-4328a949190f", + "reference": "urn:uuid:93e4cba2-9db5-11ee-8c90-0242ac876543", "display": "Mr. Lucien408 Bosco882 PharmD" }, "topic": { "coding": [{ "code": "Prescription Refill", "display": "Prescription Refill" }] } @@ -117,19 +158,10 @@ "payload": [{ "contentString": "My prescription ran out, can I come in to refill it?" }], "topic": { "text": "December 15th lab tests." }, "partOf": [{ "reference": "urn:uuid:ab308536-9e07-11ee-8c90-0242ac120002" }], - "sender": { "reference": "Patient/8f0d3209-0ee0-487f-b186-4328a949190f" }, + "sender": { "reference": "urn:uuid:93e4cba2-9db5-11ee-8c90-0242ac876543" }, "sent": "2023-12-18T14:26:06.531Z" }, "request": { "method": "POST", "url": "Communication" } - }, - { - "resource": { - "resourceType": "Task", - "status": "in-progress", - "focus": { "reference": "urn:uuid:ab308536-9e07-11ee-8c90-0242ac120002" }, - "code": { "text": "Respond to Message" } - }, - "request": { "method": "POST", "url": "Task" } } ] } diff --git a/examples/medplum-task-demo/data/example/example-practitioner-role.json b/examples/medplum-task-demo/data/example/example-practitioner-role.json new file mode 100644 index 0000000000..ad34aafc60 --- /dev/null +++ b/examples/medplum-task-demo/data/example/example-practitioner-role.json @@ -0,0 +1,29 @@ +{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "fullUrl": "fa6ad189-d8ce-4a76-aafc-98aaf1acc347", + "resource": { + "resourceType": "PractitionerRole", + "code": [ + { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "158965000", + "display": "Doctor" + } + ] + } + ], + "practitioner": "$practitioner" + }, + "request": { + "method": "POST", + "url": "PractitionerRole", + "ifNoneExist": "practitioner=$practitionerReference" + } + } + ] +} diff --git a/examples/medplum-task-demo/data/example/example-reports.json b/examples/medplum-task-demo/data/example/example-reports.json new file mode 100644 index 0000000000..e9679cc6aa --- /dev/null +++ b/examples/medplum-task-demo/data/example/example-reports.json @@ -0,0 +1,297 @@ +{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "fullUrl": "urn:uuid:9812cba2-9db5-11ee-8c90-0242ac104829", + "resource": { + "resourceType": "Patient", + "identifier": [ + { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "SS" + } + ] + }, + "system": "http://hl7.org/fhir/sid/us-ssn", + "value": "5552222121" + } + ], + "name": [ + { + "given": ["Benny"], + "family": "Hill" + } + ], + "birthDate": "1970-01-01", + "address": [ + { + "use": "home", + "line": ["123 Main St."], + "city": "Springfiled", + "state": "IL", + "postalCode": "98732" + } + ] + }, + "request": { + "method": "POST", + "url": "Patient" + } + }, + { + "fullUrl": "urn:uuid:3385ee9e-f180-4e8b-8b12-cf4833144e7b", + "resource": { + "resourceType": "Observation", + "status": "final", + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "35200-5", + "display": "Cholesterol [Mass or Moles/volume] in Serum or Plasma" + } + ], + "text": "Cholesterol" + }, + "subject": { + "reference": "urn:uuid:9812cba2-9db5-11ee-8c90-0242ac104829", + "display": "Benny Hill" + }, + "performer": [ + { + "display": "Acme Laboratory, Inc" + } + ], + "valueQuantity": { + "value": 6.3, + "unit": "mmol/L", + "system": "http://unitsofmeasure.org", + "code": "mmol/L" + }, + "referenceRange": [ + { + "high": { + "value": 4.5, + "unit": "mmol/L", + "system": "http://unitsofmeasure.org", + "code": "mmol/L" + } + } + ] + }, + "request": { + "method": "POST", + "url": "Observation" + } + }, + { + "fullUrl": "urn:uuid:94e4378e-9336-4538-98f5-47dcf1ded634", + "resource": { + "resourceType": "Observation", + "status": "final", + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "35217-9", + "display": "Triglyceride [Mass or Moles/volume] in Serum or Plasma" + } + ], + "text": "Triglyceride" + }, + "subject": { + "reference": "urn:uuid:9812cba2-9db5-11ee-8c90-0242ac104829", + "display": "Benny Hill" + }, + "performer": [ + { + "display": "Acme Laboratory, Inc" + } + ], + "valueQuantity": { + "value": 1.3, + "unit": "mmol/L", + "system": "http://unitsofmeasure.org", + "code": "mmol/L" + }, + "referenceRange": [ + { + "high": { + "value": 2, + "unit": "mmol/L", + "system": "http://unitsofmeasure.org", + "code": "mmol/L" + } + } + ] + }, + "request": { + "method": "POST", + "url": "Observation" + } + }, + { + "fullUrl": "urn:uuid:3b7c71bb-1357-4cac-88d7-3e374676e988", + "resource": { + "resourceType": "Observation", + "id": "hdlcholesterol", + "text": { + "status": "generated", + "div": "

Generated Narrative: Observation

Resource Observation "hdlcholesterol"

status: final

code: Cholesterol in HDL (LOINC#2085-9 "Cholesterol in HDL [Mass/volume] in Serum or Plasma")

subject: Patient/pat2 "Duck DONALD"

performer: Organization/1832473e-2fe0-452d-abe9-3cdb9879522f: Acme Laboratory, Inc "Clinical Lab"

value: 1.3 mmol/L (Details: UCUM code mmol/L = 'mmol/L')

ReferenceRanges

-Low
*1.5 mmol/L (Details: UCUM code mmol/L = 'mmol/L')
" + }, + "status": "final", + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "2085-9", + "display": "Cholesterol in HDL [Mass/volume] in Serum or Plasma" + } + ], + "text": "Cholesterol in HDL" + }, + "subject": { + "reference": "urn:uuid:9812cba2-9db5-11ee-8c90-0242ac104829", + "display": "Benny Hill" + }, + "performer": [ + { + "display": "Acme Laboratory, Inc" + } + ], + "valueQuantity": { + "value": 1.3, + "unit": "mmol/L", + "system": "http://unitsofmeasure.org", + "code": "mmol/L" + }, + "referenceRange": [ + { + "low": { + "value": 1.5, + "unit": "mmol/L", + "system": "http://unitsofmeasure.org", + "code": "mmol/L" + } + } + ] + }, + "request": { + "method": "POST", + "url": "Observation" + } + }, + { + "fullUrl": "urn:uuid:f2aedf86-166c-4f14-995e-9320ebab06e7", + "resource": { + "resourceType": "Observation", + "status": "final", + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "13457-7", + "display": "Cholesterol in LDL [Mass/volume] in Serum or Plasma by calculation" + } + ], + "text": "LDL Chol. (Calc)" + }, + "subject": { + "reference": "urn:uuid:9812cba2-9db5-11ee-8c90-0242ac104829", + "display": "Benny Hill" + }, + "performer": [ + { + "display": "Acme Laboratory, Inc" + } + ], + "valueQuantity": { + "value": 4.6, + "unit": "mmol/L", + "system": "http://unitsofmeasure.org", + "code": "mmol/L" + }, + "referenceRange": [ + { + "high": { + "value": 3, + "unit": "mmol/L", + "system": "http://unitsofmeasure.org", + "code": "mmol/L" + } + } + ] + }, + "request": { + "method": "POST", + "url": "Observation" + } + }, + { + "fullUrl": "urn:uuid:9812cba2-9db5-11ee-8c90-0242ac104829", + "resource": { + "resourceType": "DiagnosticReport", + "identifier": [ + { + "system": "http://acme.com/lab/reports", + "value": "5234342" + } + ], + "status": "preliminary", + "category": [ + { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0074", + "code": "HM" + } + ] + } + ], + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "57698-3", + "display": "Lipid panel with direct LDL - Serum or Plasma" + } + ], + "text": "Lipid Panel" + }, + "subject": { + "reference": "urn:uuid:9812cba2-9db5-11ee-8c90-0242ac104829", + "display": "Benny Hill" + }, + "effectiveDateTime": "2011-03-04T08:30:00+11:00", + "issued": "2013-01-27T11:45:33+11:00", + "performer": [ + { + "display": "Acme Laboratory, Inc" + } + ], + "result": [ + { + "reference": "urn:uuid:3385ee9e-f180-4e8b-8b12-cf4833144e7b" + }, + { + "reference": "urn:uuid:94e4378e-9336-4538-98f5-47dcf1ded634" + }, + { + "reference": "urn:uuid:3b7c71bb-1357-4cac-88d7-3e374676e988" + }, + { + "reference": "urn:uuid:f2aedf86-166c-4f14-995e-9320ebab06e7" + } + ] + }, + "request": { + "method": "POST", + "url": "DiagnosticReport" + } + } + ] +} diff --git a/examples/medplum-task-demo/data/example/example-tasks.json b/examples/medplum-task-demo/data/example/example-tasks.json new file mode 100644 index 0000000000..500f4ff0ec --- /dev/null +++ b/examples/medplum-task-demo/data/example/example-tasks.json @@ -0,0 +1,1279 @@ +{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "fullUrl": "urn:uuid:c9d3cba2-9db5-11ee-8c90-0242ac120002", + "resource": { + "resourceType": "Patient", + "identifier": [ + { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "SS" + } + ] + }, + "system": "http://hl7.org/fhir/sid/us-ssn", + "value": "444222222" + } + ], + "name": [ + { + "given": ["Jane"], + "family": "California" + } + ], + "birthDate": "1970-01-01", + "address": [ + { + "use": "home", + "line": ["123 Main St."], + "city": "San Francisco", + "state": "CA", + "postalCode": "98732" + } + ] + }, + "request": { + "method": "POST", + "url": "Patient" + } + }, + { + "fullUrl": "urn:uuid:3d9d2d9c-6648-4583-812b-d0d389facca2", + "resource": { + "resourceType": "Patient", + "identifier": [ + { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "SS" + } + ] + }, + "system": "http://hl7.org/fhir/sid/us-ssn", + "value": "923081234" + } + ], + "name": [ + { + "given": ["Walker", "Texas"], + "family": "Ranger" + } + ], + "birthDate": "1970-01-01", + "address": [ + { + "use": "home", + "line": ["123 Main St."], + "city": "Dallas", + "state": "TX", + "postalCode": "98732" + } + ] + }, + "request": { + "method": "POST", + "url": "Patient" + } + }, + { + "fullUrl": "urn:uuid:ba4a1ef7-c674-4acb-a506-6aac4a7aec53", + "resource": { + "resourceType": "Patient", + "identifier": [ + { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "SS" + } + ] + }, + "system": "http://hl7.org/fhir/sid/us-ssn", + "value": "8370413840" + } + ], + "name": [ + { + "given": ["Tony"], + "family": "Soprano" + } + ], + "birthDate": "1970-01-01", + "address": [ + { + "use": "home", + "line": ["123 Main St."], + "city": "Albany", + "state": "NY", + "postalCode": "98732" + } + ] + }, + "request": { + "method": "POST", + "url": "Patient" + } + }, + { + "fullUrl": "urn:uuid:c9d3c09b-9474-11ee-8c90-0242ac120812", + "resource": { + "resourceType": "Practitioner", + "identifier": [ + { + "system": "http://hl7.org/fhir/sid/us-npi", + "value": "1234567890" + } + ], + "name": [ + { + "family": "Smith", + "given": ["Alice"], + "prefix": ["Dr."] + } + ], + "gender": "female", + "qualification": [ + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0360", + "code": "MD" + } + ], + "text": "MD" + }, + "issuer": { + "display": "State of New York" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/us/davinci-pdex-plan-net/StructureDefinition/practitioner-qualification", + "extension": [ + { + "url": "whereValid", + "valueCodeableConcept": { + "coding": [ + { + "system": "https://www.usps.com/", + "code": "NY" + } + ] + } + } + ] + } + ] + } + ] + }, + "request": { + "method": "POST", + "url": "Practitioner" + } + }, + { + "fullUrl": "urn:uuid:c9d3c09b-1234-11ee-8c90-0242ac120002", + "resource": { + "resourceType": "Practitioner", + "active": true, + "name": [ + { + "prefix": ["Nurse"], + "given": ["Bob"], + "family": "Jones" + } + ], + "gender": "male", + "birthDate": "1973-11-02" + }, + "request": { + "method": "POST", + "url": "Practitioner" + } + }, + { + "fullUrl": "urn:uuid:d9d3cba2-9db5-11ee-8c90-0242ac120002", + "resource": { + "resourceType": "Communication", + "status": "in-progress", + "subject": { + "reference": "urn:uuid:c9d3cba2-9db5-11ee-8c90-0242ac120002", + "display": "Jane California" + }, + "topic": { + "coding": [ + { + "code": "Lab test results", + "display": "Lab test results" + } + ] + } + }, + "request": { + "method": "POST", + "url": "Communication" + } + }, + { + "resource": { + "resourceType": "Communication", + "status": "in-progress", + "payload": [ + { + "contentString": "Do you have the results of my lab tests yet?" + } + ], + "topic": { + "text": "December 15th lab tests." + }, + "partOf": [ + { + "reference": "urn:uuid:d9d3cba2-9db5-11ee-8c90-0242ac120002" + } + ], + "sender": { + "reference": "urn:uuid:c9d3cba2-9db5-11ee-8c90-0242ac120002", + "display": "Jane California" + }, + "sent": "2023-12-18T14:26:06.531Z" + }, + "request": { + "method": "POST", + "url": "Communication" + } + }, + { + "resource": { + "resourceType": "Communication", + "status": "in-progress", + "payload": [ + { + "contentString": "Do you have the test id number?" + } + ], + "topic": { + "text": "December 15th lab tests." + }, + "partOf": [ + { + "reference": "urn:uuid:d9d3cba2-9db5-11ee-8c90-0242ac120002" + } + ], + "sender": { + "reference": "Practitioner/b95651dc-448b-42c3-b427-f26d082a574d" + }, + "sent": "2023-12-18T14:28:06.531Z" + }, + "request": { + "method": "POST", + "url": "Communication" + } + }, + { + "resource": { + "resourceType": "Communication", + "status": "in-progress", + "payload": [ + { + "contentString": "Yes, it is 12345" + } + ], + "topic": { + "text": "December 15th lab tests." + }, + "partOf": [ + { + "reference": "urn:uuid:d9d3cba2-9db5-11ee-8c90-0242ac120002" + } + ], + "sender": { + "reference": "urn:uuid:c9d3cba2-9db5-11ee-8c90-0242ac120002", + "display": "Jane California" + }, + "sent": "2023-12-18T14:46:06.531Z" + }, + "request": { + "method": "POST", + "url": "Communication" + } + }, + { + "fullUrl": "urn:uuid:b0fa2a73-1b87-4121-9a04-731bc0c177ea", + "resource": { + "resourceType": "Communication", + "status": "in-progress", + "topic": { + "coding": [ + { + "code": "Schedule a Physical", + "display": "Schedule a Physical" + } + ] + } + }, + "request": { + "method": "POST", + "url": "Communication" + } + }, + { + "resource": { + "resourceType": "Communication", + "status": "in-progress", + "payload": [ + { + "contentString": "Can I schedule a physical for December 23rd?" + } + ], + "topic": { + "text": "Schedule a Physical" + }, + "partOf": [ + { + "reference": "urn:uuid:b0fa2a73-1b87-4121-9a04-731bc0c177ea" + } + ], + "sender": { + "reference": "urn:uuid:c9d3cba2-9db5-11ee-8c90-0242ac120002" + }, + "sent": "2023-12-18T14:01:15.175Z" + }, + "request": { + "method": "POST", + "url": "Communication" + } + }, + { + "fullUrl": "urn:uuid:ab308536-9e07-11ee-8c90-0242ac120002", + "resource": { + "resourceType": "Communication", + "status": "in-progress", + "subject": { + "reference": "urn:uuid:c9d3cba2-9db5-11ee-8c90-0242ac120002", + "display": "Mr. Lucien408 Bosco882 PharmD" + }, + "topic": { + "coding": [ + { + "code": "Prescription Refill", + "display": "Prescription Refill" + } + ] + } + }, + "request": { + "method": "POST", + "url": "Communication" + } + }, + { + "resource": { + "resourceType": "Communication", + "status": "in-progress", + "payload": [ + { + "contentString": "My prescription ran out, can I come in to refill it?" + } + ], + "topic": { + "text": "December 15th lab tests." + }, + "partOf": [ + { + "reference": "urn:uuid:ab308536-9e07-11ee-8c90-0242ac120002" + } + ], + "sender": { + "reference": "urn:uuid:c9d3cba2-9db5-11ee-8c90-0242ac120002", + "display": "Jane California" + }, + "sent": "2023-12-18T14:26:06.531Z" + }, + "request": { + "method": "POST", + "url": "Communication" + } + }, + { + "resource": { + "resourceType": "Task", + "intent": "order", + "status": "ready", + "focus": { + "reference": "urn:uuid:d9d3cba2-9db5-11ee-8c90-0242ac120002" + }, + "for": { + "reference": "urn:uuid:3d9d2d9c-6648-4583-812b-d0d389facca2", + "display": "Walker Texas Ranger" + }, + "performerType": [ + { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "224535009", + "display": "Registered Nurse" + } + ] + }, + { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "768820003", + "display": "Care Coordinator" + } + ] + } + ], + "businessStatus": { + "coding": [ + { + "system": "https://medplum.com/medplum-task-example-app/task-status", + "code": "ready", + "display": "Ready" + } + ] + }, + "restriction": { "period": { "end": "2024-04-01T16:00:00.000Z" } }, + "code": { + "coding": [ + { + "system": "https://medplum.com/medplum-task-demo/task-type", + "code": "respond-to-message", + "display": "Respond to Patient Message" + } + ] + } + }, + "request": { + "method": "POST", + "url": "Task" + } + }, + { + "resource": { + "resourceType": "Task", + "intent": "order", + "status": "completed", + "focus": { + "reference": "urn:uuid:d9d3cba2-9db5-11ee-8c90-0242ac120002" + }, + "for": { + "reference": "urn:uuid:c9d3cba2-9db5-11ee-8c90-0242ac120002", + "display": "Jane California" + }, + "performerType": [ + { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "224535009", + "display": "Registered Nurse" + } + ] + }, + { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "768820003", + "display": "Care Coordinator" + } + ] + } + ], + "businessStatus": { + "coding": [ + { + "system": "https://medplum.com/medplum-task-example-app/task-status", + "code": "ready", + "display": "Ready" + } + ] + }, + "restriction": { "period": { "end": "2024-04-01T16:30:00.000Z" } }, + "code": { + "coding": [ + { + "system": "https://medplum.com/medplum-task-demo/task-type", + "code": "respond-to-message", + "display": "Respond to Patient Message" + } + ] + } + }, + "request": { + "method": "POST", + "url": "Task" + } + }, + { + "resource": { + "resourceType": "Task", + "intent": "order", + "status": "completed", + "focus": { + "reference": "urn:uuid:d9d3cba2-9db5-11ee-8c90-0242ac120002" + }, + "for": { + "reference": "urn:uuid:c9d3cba2-9db5-11ee-8c90-0242ac120002", + "display": "Jane California" + }, + "performerType": [ + { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "224535009", + "display": "Registered Nurse" + } + ] + }, + { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "768820003", + "display": "Care Coordinator" + } + ] + } + ], + "businessStatus": { + "coding": [ + { + "system": "https://medplum.com/medplum-task-example-app/task-status", + "code": "ready", + "display": "Ready" + } + ] + }, + "code": { + "coding": [ + { + "system": "https://medplum.com/medplum-task-demo/task-type", + "code": "respond-to-message", + "display": "Respond to Patient Message" + } + ] + } + }, + "request": { + "method": "POST", + "url": "Task" + } + }, + { + "resource": { + "resourceType": "Task", + "intent": "order", + "status": "ready", + "focus": { + "reference": "urn:uuid:d9d3cba2-9db5-11ee-8c90-0242ac120002" + }, + "for": { + "reference": "urn:uuid:ba4a1ef7-c674-4acb-a506-6aac4a7aec53", + "display": "Tony Soprano" + }, + "performerType": [ + { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "224535009", + "display": "Registered Nurse" + } + ] + }, + { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "768820003", + "display": "Care Coordinator" + } + ] + } + ], + "businessStatus": { + "coding": [ + { + "system": "https://medplum.com/medplum-task-example-app/task-status", + "code": "ready", + "display": "Ready" + } + ] + }, + "restriction": { "period": { "end": "2024-04-01T16:30:00.000Z" } }, + "code": { + "coding": [ + { + "system": "https://medplum.com/medplum-task-demo/task-type", + "code": "respond-to-message", + "display": "Respond to Patient Message" + } + ] + } + }, + "request": { + "method": "POST", + "url": "Task" + } + }, + { + "resource": { + "resourceType": "Task", + "intent": "order", + "status": "completed", + "focus": { + "reference": "urn:uuid:d9d3cba2-9db5-11ee-8c90-0242ac120002" + }, + "for": { + "reference": "urn:uuid:ba4a1ef7-c674-4acb-a506-6aac4a7aec53", + "display": "Tony Soprano" + }, + "performerType": [ + { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "224535009", + "display": "Registered Nurse" + } + ] + }, + { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "768820003", + "display": "Care Coordinator" + } + ] + } + ], + "businessStatus": { + "coding": [ + { + "system": "https://medplum.com/medplum-task-example-app/task-status", + "code": "ready", + "display": "Ready" + } + ] + }, + "code": { + "coding": [ + { + "system": "https://medplum.com/medplum-task-demo/task-type", + "code": "respond-to-message", + "display": "Respond to Patient Message" + } + ] + } + }, + "request": { + "method": "POST", + "url": "Task" + } + }, + + { + "fullUrl": "urn:uuid:ab308536-9e07-11ee-8c90-0242ac129872", + "resource": { + "resourceType": "Schedule", + "active": true, + "actor": [ + { + "reference": "urn:uuid:c9d3c09b-9474-11ee-8c90-0242ac120812" + } + ] + }, + "request": { + "method": "POST", + "url": "Schedule" + } + }, + { + "resource": { + "resourceType": "Task", + "status": "ready", + "focus": { + "reference": "urn:uuid:ab308536-9e07-11ee-8c90-0242ac129872" + }, + "intent": "order", + "for": { + "reference": "urn:uuid:ba4a1ef7-c674-4acb-a506-6aac4a7aec53", + "display": "Tony Soprano" + }, + "performerType": [ + { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "768820003", + "display": "Care Coordinator" + } + ] + } + ], + "businessStatus": { + "coding": [ + { + "system": "https://medplum.com/medplum-task-example-app/task-status", + "code": "ready", + "display": "Ready" + } + ] + }, + "code": { + "coding": [ + { + "system": "https://medplum.com/medplum-task-demo/task-type", + "code": "schedule-appointment", + "display": "Schedule Appointment" + } + ] + } + }, + "request": { + "method": "POST", + "url": "Task" + } + }, + { + "fullUrl": "urn:uuid:3385ee9e-f180-4e8b-8b12-cf4833144e7b", + "resource": { + "resourceType": "Observation", + "status": "final", + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "35200-5", + "display": "Cholesterol [Mass or Moles/volume] in Serum or Plasma" + } + ], + "text": "Cholesterol" + }, + "subject": { + "reference": "urn:uuid:c9d3cba2-9db5-11ee-8c90-0242ac120002", + "display": "Jane California" + }, + "performer": [ + { + "display": "Acme Laboratory, Inc" + } + ], + "valueQuantity": { + "value": 6.3, + "unit": "mmol/L", + "system": "http://unitsofmeasure.org", + "code": "mmol/L" + }, + "referenceRange": [ + { + "high": { + "value": 4.5, + "unit": "mmol/L", + "system": "http://unitsofmeasure.org", + "code": "mmol/L" + } + } + ] + }, + "request": { + "method": "POST", + "url": "Observation" + } + }, + { + "fullUrl": "urn:uuid:94e4378e-9336-4538-98f5-47dcf1ded634", + "resource": { + "resourceType": "Observation", + "status": "final", + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "35217-9", + "display": "Triglyceride [Mass or Moles/volume] in Serum or Plasma" + } + ], + "text": "Triglyceride" + }, + "subject": { + "reference": "urn:uuid:c9d3cba2-9db5-11ee-8c90-0242ac120002", + "display": "Jane California" + }, + "performer": [ + { + "display": "Acme Laboratory, Inc" + } + ], + "valueQuantity": { + "value": 1.3, + "unit": "mmol/L", + "system": "http://unitsofmeasure.org", + "code": "mmol/L" + }, + "referenceRange": [ + { + "high": { + "value": 2, + "unit": "mmol/L", + "system": "http://unitsofmeasure.org", + "code": "mmol/L" + } + } + ] + }, + "request": { + "method": "POST", + "url": "Observation" + } + }, + { + "fullUrl": "urn:uuid:3b7c71bb-1357-4cac-88d7-3e374676e988", + "resource": { + "resourceType": "Observation", + "id": "hdlcholesterol", + "text": { + "status": "generated", + "div": "

Generated Narrative: Observation

Resource Observation "hdlcholesterol"

status: final

code: Cholesterol in HDL (LOINC#2085-9 "Cholesterol in HDL [Mass/volume] in Serum or Plasma")

subject: Patient/pat2 "Duck DONALD"

performer: Organization/1832473e-2fe0-452d-abe9-3cdb9879522f: Acme Laboratory, Inc "Clinical Lab"

value: 1.3 mmol/L (Details: UCUM code mmol/L = 'mmol/L')

ReferenceRanges

-Low
*1.5 mmol/L (Details: UCUM code mmol/L = 'mmol/L')
" + }, + "status": "final", + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "2085-9", + "display": "Cholesterol in HDL [Mass/volume] in Serum or Plasma" + } + ], + "text": "Cholesterol in HDL" + }, + "subject": { + "reference": "urn:uuid:c9d3cba2-9db5-11ee-8c90-0242ac120002", + "display": "Jane California" + }, + "performer": [ + { + "display": "Acme Laboratory, Inc" + } + ], + "valueQuantity": { + "value": 1.3, + "unit": "mmol/L", + "system": "http://unitsofmeasure.org", + "code": "mmol/L" + }, + "referenceRange": [ + { + "low": { + "value": 1.5, + "unit": "mmol/L", + "system": "http://unitsofmeasure.org", + "code": "mmol/L" + } + } + ] + }, + "request": { + "method": "POST", + "url": "Observation" + } + }, + { + "fullUrl": "urn:uuid:f2aedf86-166c-4f14-995e-9320ebab06e7", + "resource": { + "resourceType": "Observation", + "status": "final", + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "13457-7", + "display": "Cholesterol in LDL [Mass/volume] in Serum or Plasma by calculation" + } + ], + "text": "LDL Chol. (Calc)" + }, + "subject": { + "reference": "urn:uuid:c9d3cba2-9db5-11ee-8c90-0242ac120002", + "display": "Jane California" + }, + "performer": [ + { + "display": "Acme Laboratory, Inc" + } + ], + "valueQuantity": { + "value": 4.6, + "unit": "mmol/L", + "system": "http://unitsofmeasure.org", + "code": "mmol/L" + }, + "referenceRange": [ + { + "high": { + "value": 3, + "unit": "mmol/L", + "system": "http://unitsofmeasure.org", + "code": "mmol/L" + } + } + ] + }, + "request": { + "method": "POST", + "url": "Observation" + } + }, + { + "fullUrl": "urn:uuid:e2b02def-3968-476c-b7be-d404b4a459ed", + "resource": { + "resourceType": "DiagnosticReport", + "identifier": [ + { + "system": "http://acme.com/lab/reports", + "value": "5234342" + } + ], + "status": "final", + "category": [ + { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0074", + "code": "HM" + } + ] + } + ], + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "57698-3", + "display": "Lipid panel with direct LDL - Serum or Plasma" + } + ], + "text": "Lipid Panel" + }, + "subject": { + "reference": "urn:uuid:c9d3cba2-9db5-11ee-8c90-0242ac120002", + "display": "Jane California" + }, + "effectiveDateTime": "2011-03-04T08:30:00+11:00", + "issued": "2013-01-27T11:45:33+11:00", + "performer": [ + { + "display": "Acme Laboratory, Inc" + } + ], + "result": [ + { + "reference": "urn:uuid:3385ee9e-f180-4e8b-8b12-cf4833144e7b" + }, + { + "reference": "urn:uuid:94e4378e-9336-4538-98f5-47dcf1ded634" + }, + { + "reference": "urn:uuid:3b7c71bb-1357-4cac-88d7-3e374676e988" + }, + { + "reference": "urn:uuid:f2aedf86-166c-4f14-995e-9320ebab06e7" + } + ] + }, + "request": { + "method": "POST", + "url": "DiagnosticReport" + } + }, + { + "resource": { + "resourceType": "Task", + "status": "ready", + "focus": { + "reference": "urn:uuid:e2b02def-3968-476c-b7be-d404b4a459ed" + }, + "intent": "order", + "for": { + "reference": "urn:uuid:c9d3cba2-9db5-11ee-8c90-0242ac120002", + "display": "Jane California" + }, + "performerType": [ + { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "158965000", + "display": "Doctor" + } + ] + } + ], + "businessStatus": { + "coding": [ + { + "system": "https://medplum.com/medplum-task-example-app/task-status", + "code": "ready", + "display": "Ready" + } + ] + }, + "code": { + "coding": [ + { + "system": "https://medplum.com/medplum-task-demo/task-type", + "code": "review-diagnostic-report", + "display": "Review Diagnostic Report" + } + ] + } + }, + "request": { + "method": "POST", + "url": "Task" + } + }, + { + "fullUrl": "urn:uuid:569781a7-6ee9-42f1-88ac-ac4cacf721b8", + "resource": { + "resourceType": "DocumentReference", + "status": "current", + "docStatus": "final", + "type": { + "text": "Driver's License" + }, + "category": [ + { + "text": "Identity Document" + } + ], + "content": [ + { + "attachment": { + "contentType": "image/jpeg", + "url": "https://upload.wikimedia.org/wikipedia/commons/7/79/Californian_sample_driver%27s_license%2C_c._2019.jpg" + } + } + ] + }, + "request": { + "method": "POST", + "url": "DocumentReference" + } + }, + { + "fullUrl": "urn:uuid:569781a7-6ee9-42f1-88ac-ac4cacf721b8", + "resource": { + "resourceType": "DocumentReference", + "status": "current", + "docStatus": "final", + "type": { + "text": "Driver's License" + }, + "category": [ + { + "text": "Identity Document" + } + ], + "content": [ + { + "attachment": { + "contentType": "image/jpeg", + "url": "https://upload.wikimedia.org/wikipedia/commons/7/79/Californian_sample_driver%27s_license%2C_c._2019.jpg" + } + } + ] + }, + "request": { + "method": "POST", + "url": "DocumentReference" + } + }, + { + "fullUrl": "urn:uuid:569781a7-6ee9-42f1-88ac-ac4cacf721b8", + "resource": { + "resourceType": "DocumentReference", + "status": "current", + "docStatus": "final", + "type": { + "text": "Driver's License" + }, + "category": [ + { + "text": "Identity Document" + } + ], + "content": [ + { + "attachment": { + "contentType": "image/jpeg", + "url": "https://upload.wikimedia.org/wikipedia/commons/7/79/Californian_sample_driver%27s_license%2C_c._2019.jpg" + } + } + ] + }, + "request": { + "method": "POST", + "url": "DocumentReference" + } + }, + { + "resource": { + "resourceType": "Task", + "status": "ready", + "focus": { + "reference": "urn:uuid:569781a7-6ee9-42f1-88ac-ac4cacf721b8" + }, + "intent": "order", + "for": { + "reference": "urn:uuid:c9d3cba2-9db5-11ee-8c90-0242ac120002", + "display": "Jane California" + }, + "performerType": [ + { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "768820003", + "display": "Care Coordinator" + } + ] + } + ], + "businessStatus": { + "coding": [ + { + "system": "https://medplum.com/medplum-task-example-app/task-status", + "code": "ready", + "display": "Ready" + } + ] + }, + "code": { + "coding": [ + { + "system": "https://medplum.com/medplum-task-demo/task-type", + "code": "verify-drivers-license", + "display": "Verify Drivers License" + } + ] + } + }, + "request": { + "method": "POST", + "url": "Task" + } + }, + { + "resource": { + "resourceType": "Task", + "status": "ready", + "focus": { + "reference": "urn:uuid:569781a7-6ee9-42f1-88ac-ac4cacf721b8" + }, + "intent": "order", + "for": { + "reference": "urn:uuid:3d9d2d9c-6648-4583-812b-d0d389facca2", + "display": "Walker Texas Ranger" + }, + "performerType": [ + { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "768820003", + "display": "Care Coordinator" + } + ] + } + ], + "businessStatus": { + "coding": [ + { + "system": "https://medplum.com/medplum-task-example-app/task-status", + "code": "ready", + "display": "Ready" + } + ] + }, + "code": { + "coding": [ + { + "system": "https://medplum.com/medplum-task-demo/task-type", + "code": "verify-drivers-license", + "display": "Verify Drivers License" + } + ] + } + }, + "request": { + "method": "POST", + "url": "Task" + } + }, + { + "resource": { + "resourceType": "Task", + "status": "ready", + "priority": "asap", + "focus": { + "reference": "urn:uuid:569781a7-6ee9-42f1-88ac-ac4cacf721b8" + }, + "intent": "order", + "for": { + "reference": "urn:uuid:ba4a1ef7-c674-4acb-a506-6aac4a7aec53", + "display": "Tony Soprano" + }, + "performerType": [ + { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "768820003", + "display": "Care Coordinator" + } + ] + } + ], + "businessStatus": { + "coding": [ + { + "system": "https://medplum.com/medplum-task-example-app/task-status", + "code": "ready", + "display": "Ready" + } + ] + }, + "restriction": { "period": { "end": "2024-04-01T17:30:00.000Z" } }, + "code": { + "coding": [ + { + "system": "https://medplum.com/medplum-task-demo/task-type", + "code": "verify-drivers-license", + "display": "Verify Drivers License" + } + ] + } + }, + "request": { + "method": "POST", + "url": "Task" + } + } + ] +} diff --git a/examples/medplum-task-demo/package.json b/examples/medplum-task-demo/package.json index 79b6784e89..5dd11359ea 100644 --- a/examples/medplum-task-demo/package.json +++ b/examples/medplum-task-demo/package.json @@ -4,10 +4,10 @@ "private": true, "type": "module", "scripts": { - "build": "npm run clean && tsc && vite build", - "build:bots": "npm run clean && npm run lint && tsc", + "build": "npm run clean && npm run build:bots && tsc && vite build", + "build:bots": "npm run clean && npm run lint && tsc --project tsconfig-bots.json && node --loader ts-node/esm src/scripts/deploy-bots.ts", "clean": "rimraf dist", - "dev": "vite", + "dev": "npm run build:bots && vite", "lint": "eslint src/", "preview": "vite preview" }, diff --git a/examples/medplum-task-demo/src/App.tsx b/examples/medplum-task-demo/src/App.tsx index 993d8ec666..c79aa19659 100644 --- a/examples/medplum-task-demo/src/App.tsx +++ b/examples/medplum-task-demo/src/App.tsx @@ -1,15 +1,31 @@ +import { LoadingOverlay } from '@mantine/core'; import { + MedplumClient, Operator, SearchRequest, + capitalize, formatCodeableConcept, formatSearchQuery, + getExtension, getReferenceString, normalizeErrorString, } from '@medplum/core'; +import { Practitioner } from '@medplum/fhirtypes'; import { AppShell, Loading, Logo, NavbarLink, useMedplum, useMedplumProfile } from '@medplum/react'; -import { IconCategory, IconDatabaseImport, IconFileImport, IconGridDots, IconUser } from '@tabler/icons-react'; +import { + IconCategory, + IconChecklist, + IconDatabaseImport, + IconGridDots, + IconMail, + IconNurse, + IconReportMedical, + IconRibbonHealth, + IconRobot, + IconUser, +} from '@tabler/icons-react'; import { Suspense, useEffect, useState } from 'react'; -import { Route, Routes } from 'react-router-dom'; +import { Navigate, Route, Routes } from 'react-router-dom'; import { LandingPage } from './pages/LandingPage'; import { ResourcePage } from './pages/ResourcePage'; import { SearchPage } from './pages/SearchPage'; @@ -17,98 +33,177 @@ import { SignInPage } from './pages/SignInPage'; import { TaskPage } from './pages/TaskPage'; import { UploadDataPage } from './pages/UploadDataPage'; +const SEARCH_TABLE_FIELDS = ['code', 'owner', 'for', 'priority', 'due-date', '_lastUpdated', 'performerType']; +const ALL_TASKS_LINK = { + icon: , + label: 'All Tasks', + href: `/Task?_fields=${SEARCH_TABLE_FIELDS.join(',')}`, +}; + export function App(): JSX.Element | null { const medplum = useMedplum(); const profile = useMedplumProfile(); + const [userLinks, setUserLinks] = useState([]); + const showLoadingOverlay = profile && (medplum.isLoading() || userLinks.length === 0); - const profileReference = profile && getReferenceString(profile); - const [userLinks, setUserLinks] = useState([ - { icon: , label: 'All Tasks', href: '/Task' }, - ]); - + // Update the sidebar links associated with the Medplum profiles useEffect(() => { - if (!profileReference) { + const profileReferenceString = profile && getReferenceString(profile); + + if (!profileReferenceString) { return; } - const myTasksQuery = formatSearchQuery({ - resourceType: 'Task', - fields: ['code', '_lastUpdated', 'owner', 'for', 'priority'], - sortRules: [{ code: '-priority-order,due-date' }], - filters: [ - { code: 'owner', operator: Operator.EQUALS, value: profileReference }, - { code: 'status:not', operator: Operator.EQUALS, value: 'completed' }, - ], - }); - - const myTasksLink = { icon: , label: 'My Tasks', href: `/Task${myTasksQuery}` }; - - medplum - .searchResources('PractitionerRole', { - practitioner: profileReference, - }) - .then((roles) => { - const roleLinks = []; - - for (const role of roles) { - const roleCode = role?.code?.[0]; - if (!roleCode?.coding?.[0]?.code) { - continue; - } - - const search: SearchRequest = { - resourceType: 'Task', - fields: ['code', '_lastUpdated', 'owner', 'for', 'priority'], - sortRules: [{ code: '-priority-order,due-date' }], - filters: [ - { code: 'owner:missing', operator: Operator.EQUALS, value: 'true' }, - { code: 'performer', operator: Operator.EQUALS, value: roleCode?.coding?.[0]?.code }, - ], - }; - const searchQuery = formatSearchQuery(search); - const roleDisplay = formatCodeableConcept(roleCode); - roleLinks.push({ icon: , label: `${roleDisplay} Tasks`, href: `/Task${searchQuery}` }); - } + // Construct the search for "My Tasks" + const myTasksLink = getMyTasksLink(profileReferenceString); - setUserLinks([myTasksLink, ...roleLinks, { icon: , label: 'All Tasks', href: '/Task' }]); + // Query the user's `PractitionerRole` resources to find all applicable roles + getTasksByRoleLinks(medplum, profileReferenceString) + .then((roleLinks) => { + setUserLinks([myTasksLink, ...roleLinks, ...stateLinks, ALL_TASKS_LINK]); }) .catch((error) => console.error('Failed to fetch PractitionerRoles', normalizeErrorString(error))); - }, [profileReference, medplum]); - if (medplum.isLoading()) { - return null; - } + // Construct Search links for all Tasks for patients in the current user's licensed states + const stateLinks = getTasksByState(profile as Practitioner); + }, [profile, medplum]); return ( - } - menus={[ - { - title: 'Tasks', - links: userLinks, - }, - { - title: 'Upload Data', - links: [ - { icon: , label: 'Upload Core Data', href: '/upload/core' }, - { icon: , label: 'Upload Example Data', href: '/upload/example' }, - ], - }, - ]} - resourceTypeSearchDisabled={true} - headerSearchDisabled={true} - > - }> - - : } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - + <> + } + menus={[ + { + title: 'Tasks', + links: userLinks, + }, + { + title: 'Upload Data', + links: [ + { icon: , label: 'Upload Core ValueSets', href: '/upload/core' }, + { icon: , label: 'Upload Example Tasks', href: '/upload/task' }, + { icon: , label: 'Upload Example Certifications', href: '/upload/role' }, + { + icon: , + label: 'Upload Example Licenses', + href: '/upload/qualifications', + }, + { icon: , label: 'Upload Example Bots', href: '/upload/bots' }, + { icon: , label: 'Upload Example Report', href: '/upload/report' }, + { icon: , label: 'Upload Example Messages', href: '/upload/message' }, + ], + }, + ]} + headerSearchDisabled={true} + > + + }> + + : } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + ); } + +/** + * @param profileReference - string representing the current user's profile + * @returns a NavBar link to a search for all open `Tasks` assigned to the current user + */ +function getMyTasksLink(profileReference: string): NavbarLink { + const myTasksQuery = formatSearchQuery({ + resourceType: 'Task', + fields: SEARCH_TABLE_FIELDS, + sortRules: [{ code: '-priority-order,due-date' }], + filters: [ + { code: 'owner', operator: Operator.EQUALS, value: profileReference }, + { code: 'status:not', operator: Operator.EQUALS, value: 'completed' }, + ], + }); + + const myTasksLink = { icon: , label: 'My Tasks', href: `/Task${myTasksQuery}` }; + return myTasksLink; +} + +/** + * @param medplum - the MedplumClient + * @param profileReference - string representing the current user's profile + * @returns an array of NavBarLinks to searches for all open `Tasks` assigned to the current user's roles + */ +async function getTasksByRoleLinks(medplum: MedplumClient, profileReference: string): Promise { + const roles = await medplum.searchResources('PractitionerRole', { + practitioner: profileReference, + }); + + // Query the user's `PractitionerRole` resources to find all applicable roles + return roles + .map((role) => { + // For each role, generate a link to all open Tasks + const roleCode = role?.code?.[0]; + + if (!roleCode?.coding?.[0]?.code) { + return undefined; + } + + const search: SearchRequest = { + resourceType: 'Task', + fields: SEARCH_TABLE_FIELDS, + sortRules: [{ code: '-priority-order,due-date' }], + filters: [ + { code: 'owner:missing', operator: Operator.EQUALS, value: 'true' }, + { code: 'status:not', operator: Operator.EQUALS, value: 'completed' }, + { code: 'performer', operator: Operator.EQUALS, value: roleCode?.coding?.[0]?.code }, + ], + }; + + const searchQuery = formatSearchQuery(search); + const roleDisplay = formatCodeableConcept(roleCode); + return { icon: , label: `${roleDisplay} Tasks`, href: `/Task${searchQuery}` } as NavbarLink; + }) + .filter((link): link is NavbarLink => !!link); +} + +/** + * + * Read all the states for which this practitioner is licensed. + * Refer to [Modeling Provider Qualifications](https://www.medplum.com/docs/administration/provider-directory/provider-credentials) + * for more information on how to represent a clinician's licenses + * @param profile - The resource representing the current user + * @returns an array of NavBarLinks to searches for all open `Tasks` assigned to patients' in states + * where the current user is licensed + */ +function getTasksByState(profile: Practitioner): NavbarLink[] { + const myStates = + profile.qualification + ?.map( + (qualification) => + getExtension( + qualification, + 'http://hl7.org/fhir/us/davinci-pdex-plan-net/StructureDefinition/practitioner-qualification', + 'whereValid' + )?.valueCodeableConcept?.coding?.find((coding) => coding.system === 'https://www.usps.com/')?.code + ) + .filter((state): state is string => !!state) ?? []; + + return myStates.map((state) => { + const search: SearchRequest = { + resourceType: 'Task', + fields: SEARCH_TABLE_FIELDS, + sortRules: [{ code: '-priority-order,due-date' }], + filters: [ + { code: 'owner:missing', operator: Operator.EQUALS, value: 'true' }, + { code: 'status:not', operator: Operator.EQUALS, value: 'completed' }, + { code: 'patient.address-state', operator: Operator.EQUALS, value: state }, + ], + }; + const searchQuery = formatSearchQuery(search); + return { icon: , label: `${capitalize(state)} Tasks`, href: `/Task${searchQuery}` } as NavbarLink; + }); +} diff --git a/examples/medplum-task-demo/src/bots/example/create-review-report-task.ts b/examples/medplum-task-demo/src/bots/example/create-review-report-task.ts index 7ae9a6a2e2..1ae763c33c 100644 --- a/examples/medplum-task-demo/src/bots/example/create-review-report-task.ts +++ b/examples/medplum-task-demo/src/bots/example/create-review-report-task.ts @@ -15,10 +15,12 @@ export async function handler(medplum: MedplumClient, event: BotEvent (window.location.href = '/'), + cacheTime: 3000, // baseUrl: 'http://localhost:8103/', //Uncomment this to run against the server on your localhost; also change `googleClientId` in `./pages/SignInPage.tsx` }); @@ -40,7 +41,7 @@ root.render( - + diff --git a/examples/medplum-task-demo/src/pages/ResourcePage.tsx b/examples/medplum-task-demo/src/pages/ResourcePage.tsx index 5d27dc2eea..857f4a0798 100644 --- a/examples/medplum-task-demo/src/pages/ResourcePage.tsx +++ b/examples/medplum-task-demo/src/pages/ResourcePage.tsx @@ -1,8 +1,16 @@ import { Paper, Tabs, Title } from '@mantine/core'; import { getDisplayString, getReferenceString } from '@medplum/core'; -import { DefaultResourceTimeline, Document, ResourceTable, useMedplumNavigate, useResource } from '@medplum/react'; +import { + DefaultResourceTimeline, + DiagnosticReportDisplay, + Document, + ResourceTable, + useMedplumNavigate, + useResource, +} from '@medplum/react'; import { useParams } from 'react-router-dom'; import { ResourceHistoryTab } from '../components/ResourceHistoryTab'; +import { DiagnosticReport } from '@medplum/fhirtypes'; /** * This is an example of a generic "Resource Display" page. @@ -14,7 +22,11 @@ export function ResourcePage(): JSX.Element | null { const navigate = useMedplumNavigate(); const reference = { reference: resourceType + '/' + id }; const resource = useResource(reference); - const tabs = ['Details', 'Timeline', 'History']; + let tabs = ['Details', 'Timeline', 'History']; + // Special Case for Diagnostic Reporets + if (resourceType === 'DiagnosticReport') { + tabs = ['Report', ...tabs]; + } const tab = window.location.pathname.split('/').pop(); const currentTab = tab && tabs.map((t) => t.toLowerCase()).includes(tab) ? tab : tabs[0].toLowerCase(); @@ -41,7 +53,7 @@ export function ResourcePage(): JSX.Element | null { - + @@ -50,6 +62,11 @@ export function ResourcePage(): JSX.Element | null { + + + + + ); diff --git a/examples/medplum-task-demo/src/pages/SearchPage.tsx b/examples/medplum-task-demo/src/pages/SearchPage.tsx index a1ba054821..a61c0967fd 100644 --- a/examples/medplum-task-demo/src/pages/SearchPage.tsx +++ b/examples/medplum-task-demo/src/pages/SearchPage.tsx @@ -1,10 +1,10 @@ import { Tabs } from '@mantine/core'; -import { formatSearchQuery, getReferenceString, Operator, parseSearchRequest, SearchRequest } from '@medplum/core'; +import { Operator, SearchRequest, formatSearchQuery, getReferenceString, parseSearchRequest } from '@medplum/core'; import { Document, Loading, SearchControl, useMedplum } from '@medplum/react'; import { useEffect, useState } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import { CreateTaskModal } from '../components/actions/CreateTaskModal'; -import { getPopulatedSearch } from './utils'; +import { getPopulatedSearch } from '../utils/search-control'; export function SearchPage(): JSX.Element { const medplum = useMedplum(); @@ -13,22 +13,10 @@ export function SearchPage(): JSX.Element { const [search, setSearch] = useState(); const [isNewOpen, setIsNewOpen] = useState(false); - const [showTabs, setShowTabs] = useState(() => { - const search = parseSearchRequest(window.location.pathname + window.location.search); - return shouldShowTabs(search); - }); - const tabs = ['Active', 'Completed']; - const searchQuery = window.location.search; - const currentSearch = parseSearchRequest(searchQuery); - + const currentSearch = parseSearchRequest(window.location.toString()); const currentTab = handleInitialTab(currentSearch); - useEffect(() => { - 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 = parseSearchRequest(location.pathname + location.search); @@ -67,7 +55,7 @@ export function SearchPage(): JSX.Element { return ( - {showTabs ? ( + {shouldShowTabs(search) ? ( {tabs.map((tab) => ( @@ -105,7 +93,7 @@ export function SearchPage(): JSX.Element { navigate(`/${getReferenceString(e.resource)}`)} - hideToolbar={true} + hideToolbar={false} hideFilters={true} onChange={(e) => { navigate(`/${search.resourceType}${formatSearchQuery(e.definition)}`); @@ -144,10 +132,12 @@ function shouldShowTabs(search: SearchRequest): boolean { return true; } - for (const filter of search.filters) { - if (filter.code === 'performer') { - return false; - } + if (search.filters.some((filter) => filter.code === 'performer')) { + return false; + } + + if (search.filters.some((filter) => filter.code === 'patient.address-state')) { + return false; } return true; diff --git a/examples/medplum-task-demo/src/pages/UploadDataPage.tsx b/examples/medplum-task-demo/src/pages/UploadDataPage.tsx index 57b050f8f1..3cb5a9c2d0 100644 --- a/examples/medplum-task-demo/src/pages/UploadDataPage.tsx +++ b/examples/medplum-task-demo/src/pages/UploadDataPage.tsx @@ -1,36 +1,72 @@ -import { Button } from '@mantine/core'; -import { MedplumClient, capitalize, normalizeErrorString } from '@medplum/core'; -import { Document, useMedplum } from '@medplum/react'; +import { Button, LoadingOverlay } from '@mantine/core'; +import { + MedplumClient, + capitalize, + createReference, + getReferenceString, + isOk, + normalizeErrorString, +} from '@medplum/core'; +import { Document, useMedplum, useMedplumProfile } from '@medplum/react'; import { useNavigate, useParams } from 'react-router-dom'; import { showNotification } from '@mantine/notifications'; -import { Bundle } from '@medplum/fhirtypes'; +import { Bot, Bundle, BundleEntry, Coding, Practitioner, ValueSet } from '@medplum/fhirtypes'; import { IconCircleCheck, IconCircleOff } from '@tabler/icons-react'; import { useCallback, useState } from 'react'; -import businessStatusValueSet from '../../data/core/business-status-value-sets.json'; -import exampleMessageData from '../../data/example/respond-to-message-data.json'; +import businessStatusValueSet from '../../data/core/business-status-valueset.json'; +import practitionerRoleValueSet from '../../data/core/practitioner-role-valueset.json'; +import taskTypeValueSet from '../../data/core/task-type-valueset.json'; +import exampleBotData from '../../data/example/example-bots.json'; +import exampleMessageData from '../../data/example/example-messages.json'; +import exampleRoleData from '../../data/example/example-practitioner-role.json'; +import exampleReportData from '../../data/example/example-reports.json'; +import exampleTaskData from '../../data/example/example-tasks.json'; + +type UploadFunction = + | ((medplum: MedplumClient, profile: Practitioner) => Promise) + | ((medplum: MedplumClient) => Promise); export function UploadDataPage(): JSX.Element { const medplum = useMedplum(); + const profile = useMedplumProfile(); const { dataType } = useParams(); const navigate = useNavigate(); - const [buttonDisabled, setButtonDisabled] = useState(false); + const [pageDisabled, setPageDisabled] = useState(false); const dataTypeDisplay = dataType ? capitalize(dataType) : ''; const handleUpload = useCallback(() => { - setButtonDisabled(true); - let uploadFunction: (medplum: MedplumClient) => Promise; - if (dataType === 'core') { - uploadFunction = uploadCoreData; - } else if (dataType === 'example') { - uploadFunction = uploadExampleData; - } else { - throw new Error(`Invalid upload type '${dataType}'`); + setPageDisabled(true); + let uploadFunction: UploadFunction; + switch (dataType) { + case 'core': + uploadFunction = uploadCoreData; + break; + case 'task': + uploadFunction = uploadExampleTaskData; + break; + case 'role': + uploadFunction = uploadExampleRoleData; + break; + case 'message': + uploadFunction = uploadExampleMessageData; + break; + case 'report': + uploadFunction = uploadExampleReportData; + break; + case 'qualifications': + uploadFunction = uploadExampleQualifications; + break; + case 'bots': + uploadFunction = uploadExampleBots; + break; + default: + throw new Error(`Invalid upload type '${dataType}'`); } - uploadFunction(medplum) - .then(() => navigate('/')) + uploadFunction(medplum, profile as Practitioner) + .then(() => navigate(-1)) .catch((error) => { showNotification({ color: 'red', @@ -39,30 +75,221 @@ export function UploadDataPage(): JSX.Element { message: normalizeErrorString(error), }); }) - .finally(() => setButtonDisabled(false)); - }, [medplum, dataType, navigate]); + .finally(() => setPageDisabled(false)); + }, [medplum, profile, dataType, navigate]); return ( - + + ); } async function uploadCoreData(medplum: MedplumClient): Promise { - await medplum.executeBatch(businessStatusValueSet as Bundle); + // Upload all the core ValueSets in a single batch request + const valueSets: ValueSet[] = [ + businessStatusValueSet as ValueSet, + taskTypeValueSet as ValueSet, + practitionerRoleValueSet as ValueSet, + ]; + + // Upsert the ValueSet (see: https://www.medplum.com/docs/fhir-datastore/fhir-batch-requests#performing-upserts) + const batch: Bundle = { + resourceType: 'Bundle', + type: 'transaction', + entry: valueSets.flatMap((valueSet) => { + const tempId = valueSet.id; + return [ + { + fullUrl: tempId, + request: { method: 'POST', url: valueSet.resourceType, ifNoneExist: `url=${valueSet.url}` }, + resource: valueSet, + }, + { + request: { method: 'PUT', url: tempId }, + resource: { id: tempId, ...valueSet }, + }, + ] as BundleEntry[]; + }), + }; + console.log(batch); + const result = await medplum.executeBatch(batch); + console.log(result); + showNotification({ icon: , title: 'Success', message: 'Uploaded Business Statuses', }); + + if (result.entry?.every((entry) => entry.response?.outcome && isOk(entry.response?.outcome))) { + await setTimeout( + () => + showNotification({ + icon: , + title: 'Success', + message: 'Uploaded Business Statuses', + }), + 1000 + ); + } else { + throw new Error('Error uploading core data'); + } } -async function uploadExampleData(medplum: MedplumClient): Promise { +async function uploadExampleMessageData(medplum: MedplumClient): Promise { await medplum.executeBatch(exampleMessageData as Bundle); showNotification({ icon: , title: 'Success', - message: 'Uploaded Example Message Data', + message: 'Uploaded Example Messages', + }); +} + +async function uploadExampleReportData(medplum: MedplumClient): Promise { + await medplum.executeBatch(exampleReportData as Bundle); + showNotification({ + icon: , + title: 'Success', + message: 'Uploaded Example Report', + }); +} + +async function uploadExampleTaskData(medplum: MedplumClient): Promise { + await medplum.executeBatch(exampleTaskData as Bundle); + showNotification({ + icon: , + title: 'Success', + message: 'Uploaded Example Tasks', + }); +} + +async function uploadExampleQualifications(medplum: MedplumClient, profile: Practitioner): Promise { + if (!profile) { + return; + } + + const states: Coding[] = [ + { code: 'NY', display: 'State of New York', system: 'https://www.usps.com/' }, + { code: 'CA', display: 'State of California', system: 'https://www.usps.com/' }, + { code: 'TX', display: 'State of Texas', system: 'https://www.usps.com/' }, + ]; + + await medplum.patchResource(profile.resourceType, profile.id as string, [ + { + path: '/qualification', + // JSON patch does not have an upsert operation. If the user already has qualifications, we should just replace them with these licences + op: profile.qualification ? 'replace' : 'add', + value: states.map((state) => ({ + code: { + coding: [ + { + system: 'http://terminology.hl7.org/CodeSystem/v2-0360', + code: 'MD', + }, + ], + text: 'MD', + }, + // Medical License Issuer: State of New York + issuer: { + display: state.display, + }, + // Extension: Medical License Valid in NY + extension: [ + { + url: 'http://hl7.org/fhir/us/davinci-pdex-plan-net/StructureDefinition/practitioner-qualification', + extension: [ + { + url: 'whereValid', + valueCodeableConcept: { + coding: [state], + }, + }, + ], + }, + ], + })), + }, + ]); + showNotification({ + icon: , + title: 'Success', + message: 'Uploaded Example Qualifications', + }); +} + +async function uploadExampleRoleData(medplum: MedplumClient, profile: Practitioner): Promise { + // Update the suffix of the current user to highlight the change + if (!profile?.name?.[0]?.suffix) { + await medplum.patchResource(profile.resourceType, profile.id as string, [ + { + op: 'add', + path: '/name/0/suffix', + value: ['MD'], + }, + ]); + } + + const bundleString = JSON.stringify(exampleRoleData, null, 2) + .replaceAll('$practitionerReference', getReferenceString(profile)) + .replaceAll('"$practitioner"', JSON.stringify(createReference(profile))); + + const transaction = JSON.parse(bundleString) as Bundle; + + // Create the practitioner role + await medplum.executeBatch(transaction); + + showNotification({ + icon: , + title: 'Success', + message: 'Uploaded Example Qualifications', + }); +} + +async function uploadExampleBots(medplum: MedplumClient, profile: Practitioner): Promise { + let transactionString = JSON.stringify(exampleBotData); + const botEntries: BundleEntry[] = + (exampleBotData as Bundle).entry?.filter((e) => e.resource?.resourceType === 'Bot') || []; + const botNames = botEntries.map((e) => (e.resource as Bot).name ?? ''); + const botIds: Record = {}; + + for (const botName of botNames) { + let existingBot = await medplum.searchOne('Bot', { name: botName }); + // Create a new Bot if it doesn't already exist + if (!existingBot) { + const projectId = profile.meta?.project; + const createBotUrl = new URL('admin/projects/' + (projectId as string) + '/bot', medplum.getBaseUrl()); + existingBot = (await medplum.post(createBotUrl, { + name: botName, + })) as Bot; + } + + botIds[botName] = existingBot.id as string; + + // Replace the Bot id placeholder in the bundle + transactionString = transactionString + .replaceAll(`$bot-${botName}-reference`, getReferenceString(existingBot)) + .replaceAll(`$bot-${botName}-id`, existingBot.id as string); + } + + // Execute the transaction to upload / update the bot + const transaction = JSON.parse(transactionString); + await medplum.executeBatch(transaction); + + // Deploy the new bots + for (const entry of botEntries) { + const botName = (entry?.resource as Bot)?.name as string; + const distUrl = (entry.resource as Bot).executableCode?.url; + const distBinaryEntry = exampleBotData.entry.find((e) => e.fullUrl === distUrl); + // Decode the base64 encoded code and deploy + const code = atob(distBinaryEntry?.resource.data as string); + await medplum.post(medplum.fhirUrl('Bot', botIds[botName], '$deploy'), { code }); + } + + showNotification({ + icon: , + title: 'Success', + message: 'Deployed Example Bots', }); } diff --git a/examples/medplum-task-demo/src/scripts/deploy-bots.ts b/examples/medplum-task-demo/src/scripts/deploy-bots.ts new file mode 100644 index 0000000000..25cbe1d90e --- /dev/null +++ b/examples/medplum-task-demo/src/scripts/deploy-bots.ts @@ -0,0 +1,131 @@ +import { ContentType } from '@medplum/core'; +import { Bot, Bundle, BundleEntry, Subscription } from '@medplum/fhirtypes'; +import fs from 'fs'; +import path from 'path'; + +interface BotDescription { + src: string; + dist: string; + criteria?: string; +} +const Bots: BotDescription[] = [ + { + src: 'src/bots/example/create-review-report-task.ts', + dist: 'dist/example/create-review-report-task.js', + criteria: 'DiagnosticReport', + }, + { + src: 'src/bots/example/create-respond-to-message-task.ts', + dist: 'dist/example/create-respond-to-message-task.js', + criteria: 'Communication?part-of:missing=true', + }, +]; + +async function main(): Promise { + const bundle: Bundle = { + resourceType: 'Bundle', + type: 'transaction', + entry: Bots.flatMap((botDescription): BundleEntry[] => { + const botName = path.parse(botDescription.src).name; + const botUrlPlaceholder = `$bot-${botName}-reference`; + const botIdPlaceholder = `$bot-${botName}-id`; + const results: BundleEntry[] = []; + const { srcEntry, distEntry } = readBotFiles(botDescription); + results.push(srcEntry, distEntry); + + results.push({ + request: { + url: botUrlPlaceholder, + method: 'PUT', + }, + resource: { + resourceType: 'Bot', + id: botIdPlaceholder, + name: botName, + runtimeVersion: 'awslambda', + sourceCode: { + contentType: ContentType.TYPESCRIPT, + url: srcEntry.fullUrl, + }, + executableCode: { + contentType: ContentType.JAVASCRIPT, + url: distEntry.fullUrl, + }, + } as Bot, + }); + + if (botDescription.criteria) { + results.push({ + request: { + url: 'Subscription', + method: 'POST', + ifNoneExist: `url=${botUrlPlaceholder}`, + }, + resource: { + resourceType: 'Subscription', + status: 'active', + reason: botName, + channel: { endpoint: botUrlPlaceholder, type: 'rest-hook' }, + criteria: botDescription.criteria, + } as Subscription, + }); + } + + return results; + }), + }; + + fs.writeFileSync('data/example/example-bots.json', JSON.stringify(bundle, null, 2)); +} + +function readBotFiles(description: BotDescription): Record { + const sourceFile = fs.readFileSync(description.src); + const distFile = fs.readFileSync(description.dist); + + const srcEntry: BundleEntry = { + fullUrl: 'urn:uuid:' + UUIDs.pop(), + request: { + method: 'POST', + url: 'Binary', + }, + resource: { + resourceType: 'Binary', + contentType: ContentType.TYPESCRIPT, + data: sourceFile.toString('base64'), + }, + }; + const distEntry: BundleEntry = { + fullUrl: 'urn:uuid:' + UUIDs.pop(), + request: { + method: 'POST', + url: 'Binary', + }, + resource: { + resourceType: 'Binary', + contentType: ContentType.JAVASCRIPT, + data: distFile.toString('base64'), + }, + }; + return { srcEntry, distEntry }; +} + +const UUIDs = [ + '1e816573-1e13-46d4-ae02-857ac10169e6', + 'b56f4407-800c-411f-bb7b-07f8c73730bf', + '09ba8367-1cf0-48b4-8965-59e494102af6', + '5ba14170-42b1-436d-9d46-9a566d534c8f', + '61750884-cf29-4690-84c6-1bcf5ad14b7e', + '73693d07-2ba1-4ddd-a6ee-9ea0b2d5aa9c', + '0ab3ff6c-7c38-4911-a49e-6e8e8fe379e6', + '4b1851e6-3ced-4f83-ad52-edb85408a1a6', + '2bf1d4a3-143d-4cbb-bf50-033805791b6d', + 'f3f2aeb8-43ac-49f9-a921-f7fba79348f7', + 'b5ffcef0-2f02-4c96-800b-b86eadc5423e', + '58019283-e86b-48b2-8aec-5bf0a9fe58f2', + 'a97b0a11-3e9f-42cd-af63-c33a736145b8', + '067a72c8-f24a-44c1-8145-cc6aa3049037', + 'e038a143-8c66-4b27-b69c-5430aeff6053', + '146feddc-7915-4ab3-800d-c98e312116cd', +]; + +main().catch(console.error); diff --git a/examples/medplum-task-demo/src/pages/utils.tsx b/examples/medplum-task-demo/src/utils/search-control.ts similarity index 100% rename from examples/medplum-task-demo/src/pages/utils.tsx rename to examples/medplum-task-demo/src/utils/search-control.ts diff --git a/examples/medplum-task-demo/tsconfig-bots.json b/examples/medplum-task-demo/tsconfig-bots.json new file mode 100644 index 0000000000..4eb853a197 --- /dev/null +++ b/examples/medplum-task-demo/tsconfig-bots.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "rootDir": "src/bots", + "outDir": "dist", + "lib": ["esnext"], + "types": ["vitest/globals"], + "target": "es2018", + "module": "commonjs", + "moduleResolution": "node", + "strict": true, + "sourceMap": true, + "declaration": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "incremental": false, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": true, + "allowJs": true, + "resolveJsonModule": true, + "noEmit": false, + "isolatedModules": true + }, + "include": ["src/bots/**/*.ts"] +} From 72bd2aab5599a733fc9d1a4ca98693d5786ee9fb Mon Sep 17 00:00:00 2001 From: Rahul Agarwal Date: Fri, 12 Apr 2024 15:50:55 -0700 Subject: [PATCH 14/52] FHIR Mapper: Maintain type information for sub-properties of mapping target (#4363) * Maintain type information for sub-properties of mapping target * propagate type in the non-array case --- packages/core/src/fhirmapper/transform.ts | 23 +++++++--- .../src/fhirmapper/transform.types.test.ts | 42 +++++++++++++++++++ 2 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 packages/core/src/fhirmapper/transform.types.test.ts diff --git a/packages/core/src/fhirmapper/transform.ts b/packages/core/src/fhirmapper/transform.ts index 86b29db4cb..06927e7bbd 100644 --- a/packages/core/src/fhirmapper/transform.ts +++ b/packages/core/src/fhirmapper/transform.ts @@ -13,7 +13,7 @@ import { generateId } from '../crypto'; import { evalFhirPathTyped } from '../fhirpath/parse'; import { getTypedPropertyValue, toJsBoolean, toTypedValue } from '../fhirpath/utils'; import { TypedValue } from '../types'; -import { tryGetDataType } from '../typeschema/types'; +import { InternalSchemaElement, tryGetDataType } from '../typeschema/types'; import { conceptMapTranslate } from './conceptmaptranslate'; interface TransformContext { @@ -345,10 +345,12 @@ function evalTarget(ctx: TransformContext, target: StructureMapGroupRuleTarget): const isArray = isArrayProperty(targetContext, target.element as string) || Array.isArray(originalValue); if (!target.transform) { + const elementTypes = tryGetPropertySchema(targetContext, target.element as string)?.type; + const elementType = elementTypes?.length === 1 ? elementTypes[0].code : undefined; if (isArray || originalValue === undefined) { - targetValue = [toTypedValue({})]; + targetValue = [elementType ? { type: elementType, value: {} } : toTypedValue({})]; } else { - targetValue = [toTypedValue(originalValue)]; + targetValue = [elementType ? { type: elementType, value: originalValue } : toTypedValue(originalValue)]; } } else { switch (target.transform) { @@ -408,9 +410,18 @@ function evalTarget(ctx: TransformContext, target: StructureMapGroupRuleTarget): * @internal */ function isArrayProperty(targetContext: TypedValue, element: string): boolean | undefined { - const targetContextTypeDefinition = tryGetDataType(targetContext.type); - const targetPropertyTypeDefinition = targetContextTypeDefinition?.elements?.[element]; - return targetPropertyTypeDefinition?.isArray; + return tryGetPropertySchema(targetContext, element)?.isArray; +} + +/** + * Returns the type schema + * @param targetContext - The target context. + * @param element - The element to check (i.e., the property name). + * @returns the type schema for the target element, if it is loeaded + * @internal + */ +function tryGetPropertySchema(targetContext: TypedValue, element: string): InternalSchemaElement | undefined { + return tryGetDataType(targetContext.type)?.elements?.[element]; } /** diff --git a/packages/core/src/fhirmapper/transform.types.test.ts b/packages/core/src/fhirmapper/transform.types.test.ts new file mode 100644 index 0000000000..ae5791292e --- /dev/null +++ b/packages/core/src/fhirmapper/transform.types.test.ts @@ -0,0 +1,42 @@ +import { readJson } from '@medplum/definitions'; +import { Bundle } from '@medplum/fhirtypes'; +import { toTypedValue } from '../fhirpath/utils'; +import { indexStructureDefinitionBundle } from '../typeschema/types'; +import { parseMappingLanguage } from './parse'; +import { structureMapTransform } from './transform'; +import { TypedValue } from '../types'; + +describe('FHIR Mapper transform - dependent', () => { + beforeAll(() => { + indexStructureDefinitionBundle(readJson('fhir/r4/profiles-types.json') as Bundle); + indexStructureDefinitionBundle(readJson('fhir/r4/profiles-resources.json') as Bundle); + }); + + test('Patient name', () => { + const map = ` + group PIDToPatient(source src: PID, target tgt: Patient) { + src -> tgt.resourceType = 'Patient'; + src.PID_5 as s_name -> tgt.name as t_name then xpnToName(s_name, t_name); + } + + group xpnToName(source srcName: XPN, target tgtName: HumanName) { + srcName._0 as s_family_name -> tgtName.family = s_family_name; + srcName._1 as s_given0 -> tgtName.given = s_given0; + srcName._2 as s_given1 -> tgtName.given = s_given1; + } + `; + + const input: TypedValue[] = [ + toTypedValue({ + PID_5: { _0: 'DOE', _1: 'JANE', _2: 'Q' }, + }), + { type: 'Patient', value: {} } as TypedValue, + ]; + + const structureMap = parseMappingLanguage(map); + const actual = structureMapTransform(structureMap, input); + const expected = [{ value: { resourceType: 'Patient', name: [{ family: 'DOE', given: ['JANE', 'Q'] }] } }]; + + expect(actual).toMatchObject(expected); + }); +}); From 75483e9f329ac59288a732d0e647d449a7577a39 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Mon, 15 Apr 2024 15:47:04 -0700 Subject: [PATCH 15/52] Restrict email api to project admins (#4373) --- packages/server/src/email/routes.test.ts | 20 +++++++++++++++++--- packages/server/src/email/routes.ts | 6 +++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/packages/server/src/email/routes.test.ts b/packages/server/src/email/routes.test.ts index 9615153759..b0f89c3398 100644 --- a/packages/server/src/email/routes.test.ts +++ b/packages/server/src/email/routes.test.ts @@ -10,13 +10,11 @@ import { initTestAuth } from '../test.setup'; jest.mock('@aws-sdk/client-sesv2'); const app = express(); -let accessToken: string; describe('Email API Routes', () => { beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); - accessToken = await initTestAuth(); }); beforeEach(() => { @@ -39,7 +37,22 @@ describe('Email API Routes', () => { expect(SendEmailCommand).toHaveBeenCalledTimes(0); }); + test('Forbidden for non project admin', async () => { + const accessToken = await initTestAuth({ membership: { admin: false } }); + const res = await request(app) + .post(`/email/v1/send`) + .set('Authorization', 'Bearer ' + accessToken) + .set('Content-Type', ContentType.JSON) + .send({ + to: 'alice@example.com', + subject: 'Subject', + text: 'Body', + }); + expect(res.status).toBe(403); + }); + test('Wrong content type', async () => { + const accessToken = await initTestAuth({ membership: { admin: true } }); const res = await request(app) .post(`/email/v1/send`) .set('Authorization', 'Bearer ' + accessToken) @@ -48,7 +61,8 @@ describe('Email API Routes', () => { expect(res.status).toBe(400); }); - test('Send email', async () => { + test('Send email as project admin', async () => { + const accessToken = await initTestAuth({ membership: { admin: true } }); const res = await request(app) .post(`/email/v1/send`) .set('Authorization', 'Bearer ' + accessToken) diff --git a/packages/server/src/email/routes.ts b/packages/server/src/email/routes.ts index 480ccc3c22..ab02d780d5 100644 --- a/packages/server/src/email/routes.ts +++ b/packages/server/src/email/routes.ts @@ -2,11 +2,11 @@ import { allOk, ContentType, forbidden } from '@medplum/core'; import { Request, Response, Router } from 'express'; import { body, check } from 'express-validator'; import { asyncWrap } from '../async'; +import { getAuthenticatedContext } from '../context'; import { sendOutcome } from '../fhir/outcomes'; import { authenticateRequest } from '../oauth/middleware'; -import { sendEmail } from './email'; -import { getAuthenticatedContext } from '../context'; import { makeValidationMiddleware } from '../util/validator'; +import { sendEmail } from './email'; export const emailRouter = Router(); emailRouter.use(authenticateRequest); @@ -24,7 +24,7 @@ emailRouter.post( const ctx = getAuthenticatedContext(); // Make sure the user project has the email feature enabled - if (!ctx.project.features?.includes('email')) { + if (!ctx.project.features?.includes('email') || !ctx.membership.admin) { sendOutcome(res, forbidden); return; } From a083693f526eaaa8e75d388d11dd59377c99f69f Mon Sep 17 00:00:00 2001 From: Derrick Farris Date: Mon, 15 Apr 2024 16:39:36 -0700 Subject: [PATCH 16/52] refactor($get-ws-binding-token): use backported `OperationDefinition` (#4368) * refactor($get-ws-binding-token): use backported `OperationDefinition` * test(getwsbindingtoken): tweak slightly for correctness * cleanup(getwsbindingtoken): remove text field --- .../fhir/operations/getwsbindingtoken.test.ts | 15 +- .../src/fhir/operations/getwsbindingtoken.ts | 140 +++++++++++++++--- 2 files changed, 131 insertions(+), 24 deletions(-) diff --git a/packages/server/src/fhir/operations/getwsbindingtoken.test.ts b/packages/server/src/fhir/operations/getwsbindingtoken.test.ts index 980c30bd1c..a4e5541e33 100644 --- a/packages/server/src/fhir/operations/getwsbindingtoken.test.ts +++ b/packages/server/src/fhir/operations/getwsbindingtoken.test.ts @@ -25,7 +25,7 @@ describe('Get WebSocket binding token', () => { withTestContext(async () => { // Create Subscription const res1 = await request(app) - .post(`/fhir/R4/Subscription`) + .post('/fhir/R4/Subscription') .set('Authorization', 'Bearer ' + accessToken) .set('Content-Type', ContentType.FHIR_JSON) .send({ @@ -51,7 +51,8 @@ describe('Get WebSocket binding token', () => { const params = res2.body as Parameters; expect(params.resourceType).toEqual('Parameters'); - expect(params.parameter?.length).toEqual(3); + expect(params.parameter?.length).toBeDefined(); + expect([3, 4]).toContain(params.parameter?.length); expect(params.parameter?.[0]).toBeDefined(); expect(params.parameter?.[0]?.name).toEqual('token'); @@ -69,9 +70,15 @@ describe('Get WebSocket binding token', () => { 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(); + expect(params.parameter?.[2]?.name).toEqual('subscription'); + expect(params.parameter?.[2]?.valueString).toBeDefined(); + expect(params.parameter?.[2]?.valueString).toEqual(createdSub.id); + + expect(params.parameter?.[3]).toBeDefined(); + expect(params.parameter?.[3]?.name).toEqual('websocket-url'); + expect(params.parameter?.[3]?.valueUrl).toBeDefined(); })); test('should return OperationOutcome error if Subscription no longer exists', () => diff --git a/packages/server/src/fhir/operations/getwsbindingtoken.ts b/packages/server/src/fhir/operations/getwsbindingtoken.ts index f6b5cd0ca7..ca89565b65 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 { FhirRequest, FhirResponse } from '@medplum/fhir-router'; -import { Parameters, Subscription } from '@medplum/fhirtypes'; +import { OperationDefinition, Subscription } from '@medplum/fhirtypes'; import { getConfig } from '../../config'; import { getAuthenticatedContext } from '../../context'; import { generateAccessToken } from '../../oauth/keys'; +import { buildOutputParameters } from './utils/parameters'; const ONE_HOUR = 60 * 60 * 1000; @@ -11,6 +12,117 @@ export type AdditionalWsBindingClaims = { subscription_id: string; }; +// Source (for backport version): https://build.fhir.org/ig/HL7/fhir-subscription-backport-ig/OperationDefinition-backport-subscription-get-ws-binding-token.json.html +// R5 definition: https://build.fhir.org/operation-subscription-get-ws-binding-token.json.html +const operation: OperationDefinition = { + resourceType: 'OperationDefinition', + id: 'backport-subscription-get-ws-binding-token', + extension: [ + { + url: 'http://hl7.org/fhir/StructureDefinition/structuredefinition-fmm', + valueInteger: 0, + }, + { + url: 'http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status', + valueCode: 'trial-use', + }, + { + url: 'http://hl7.org/fhir/StructureDefinition/structuredefinition-wg', + valueCode: 'fhir', + }, + ], + url: 'http://hl7.org/fhir/uv/subscriptions-backport/OperationDefinition/backport-subscription-get-ws-binding-token', + version: '1.2.0-ballot', + name: 'R5SubscriptionGetWsBindingToken', + title: 'Get WS Binding Token for Subscription Operation', + status: 'active', + kind: 'operation', + date: '2020-11-30', + publisher: 'HL7 International / FHIR Infrastructure', + contact: [ + { + name: 'HL7 International / FHIR Infrastructure', + telecom: [ + { + system: 'url', + value: 'http://www.hl7.org/Special/committees/fiwg', + }, + ], + }, + { + name: 'Gino Canessa', + telecom: [ + { + system: 'email', + value: 'mailto:gino.canessa@microsoft.com', + }, + ], + }, + ], + description: + 'This operation is used to get a token for a websocket client to use in order to bind to one or more subscriptions.', + jurisdiction: [ + { + coding: [ + { + system: 'http://unstats.un.org/unsd/methods/m49/m49.htm', + code: '001', + display: 'World', + }, + ], + }, + ], + affectsState: false, + code: 'get-ws-binding-token', + resource: ['Subscription'], + system: false, + type: true, + instance: true, + parameter: [ + { + name: 'id', + use: 'in', + min: 0, + max: '*', + documentation: + 'At the Instance level, this parameter is ignored. At the Resource level, one or more parameters containing a FHIR id for a Subscription to get a token for. In the absense of any specified ids, the server may either return a token for all Subscriptions available to the caller with a channel-type of websocket or fail the request.', + type: 'id', + }, + { + name: 'token', + use: 'out', + min: 1, + max: '1', + documentation: 'An access token that a client may use to show authorization during a websocket connection.', + type: 'string', + }, + { + name: 'expiration', + use: 'out', + min: 1, + max: '1', + documentation: 'The date and time this token is valid until.', + type: 'dateTime', + }, + { + name: 'subscription', + use: 'out', + min: 0, + max: '*', + documentation: 'The subscriptions this token is valid for.', + type: 'string', + }, + { + name: 'websocket-url', + use: 'out', + min: 1, + max: '1', + documentation: 'The URL the client should use to connect to Websockets.', + type: 'url', + }, + ], +}; + /** * Handles a GetWsBindingToken request. * @@ -59,24 +171,12 @@ export async function getWsBindingTokenHandler(req: FhirRequest): Promise Date: Tue, 16 Apr 2024 07:09:15 -0700 Subject: [PATCH 17/52] Dependency upgrades 2024-04-15 (#4369) * Dependency upgrades - step 1 * Dependency upgrades - step 2 * Fixed tar upgrade * Fixed Next.js / postcss config issue --------- Co-authored-by: Cody Ebberson --- examples/foomedical/package.json | 20 +- examples/medplum-chart-demo/package.json | 18 +- .../package.json | 2 +- examples/medplum-demo-bots/package.json | 12 +- .../medplum-eligibility-demo/package.json | 18 +- examples/medplum-fhircast-demo/package.json | 16 +- examples/medplum-hello-world/package.json | 18 +- examples/medplum-live-chat-demo/package.json | 18 +- examples/medplum-nextauth-demo/package.json | 18 +- .../medplum-nextauth-demo/postcss.config.cjs | 17 + .../medplum-nextauth-demo/postcss.config.mjs | 19 - examples/medplum-nextjs-demo/package.json | 20 +- .../medplum-nextjs-demo/postcss.config.cjs | 17 + .../medplum-nextjs-demo/postcss.config.mjs | 19 - examples/medplum-provider/package.json | 18 +- .../medplum-react-native-example/package.json | 4 +- examples/medplum-task-demo/package.json | 18 +- .../package.json | 16 +- package-lock.json | 6372 ++++++----------- package.json | 8 +- packages/app/package.json | 18 +- packages/cdk/package.json | 8 +- packages/cli/package.json | 19 +- packages/cli/src/utils.test.ts | 11 +- packages/cli/src/utils.ts | 7 +- packages/docs/package.json | 4 +- packages/eslint-config/index.cjs | 1 + packages/eslint-config/package.json | 4 +- packages/expo-polyfills/package.json | 2 +- packages/generator/package.json | 6 +- packages/graphiql/package.json | 10 +- packages/react-hooks/package.json | 10 +- packages/react/package.json | 38 +- packages/server/package.json | 22 +- 34 files changed, 2337 insertions(+), 4491 deletions(-) create mode 100644 examples/medplum-nextauth-demo/postcss.config.cjs delete mode 100644 examples/medplum-nextauth-demo/postcss.config.mjs create mode 100644 examples/medplum-nextjs-demo/postcss.config.cjs delete mode 100644 examples/medplum-nextjs-demo/postcss.config.mjs diff --git a/examples/foomedical/package.json b/examples/foomedical/package.json index ce42f06190..57f3d29e7c 100644 --- a/examples/foomedical/package.json +++ b/examples/foomedical/package.json @@ -25,21 +25,21 @@ "@babel/preset-env": "7.24.4", "@babel/preset-react": "7.24.1", "@babel/preset-typescript": "7.24.1", - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", - "@mantine/notifications": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", + "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/eslint-config": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/mock": "3.1.2", "@medplum/react": "3.1.2", - "@tabler/icons-react": "3.1.0", + "@tabler/icons-react": "3.2.0", "@testing-library/jest-dom": "6.4.2", - "@testing-library/react": "14.3.0", + "@testing-library/react": "15.0.2", "@types/jest": "29.5.12", - "@types/node": "20.12.5", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@types/node": "20.12.7", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "@vitejs/plugin-react": "4.2.1", "babel-jest": "29.7.0", "c8": "9.1.0", @@ -49,12 +49,12 @@ "jest-environment-jsdom": "29.7.0", "jest-transform-stub": "2.0.0", "postcss": "8.4.38", - "postcss-preset-mantine": "1.13.0", + "postcss-preset-mantine": "1.14.4", "react": "18.2.0", "react-chartjs-2": "5.2.0", "react-dom": "18.2.0", "react-router-dom": "6.22.3", - "typescript": "5.4.4", + "typescript": "5.4.5", "vite": "5.2.8" } } diff --git a/examples/medplum-chart-demo/package.json b/examples/medplum-chart-demo/package.json index c7aeb5cb0e..1103fa9d50 100644 --- a/examples/medplum-chart-demo/package.json +++ b/examples/medplum-chart-demo/package.json @@ -19,24 +19,24 @@ ] }, "devDependencies": { - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", - "@mantine/notifications": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", + "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/eslint-config": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/react": "3.1.2", - "@tabler/icons-react": "3.1.0", - "@types/node": "20.12.5", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@tabler/icons-react": "3.2.0", + "@types/node": "20.12.7", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.38", - "postcss-preset-mantine": "1.13.0", + "postcss-preset-mantine": "1.14.4", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.22.3", - "typescript": "5.4.4", + "typescript": "5.4.5", "vite": "5.2.8" } } diff --git a/examples/medplum-client-external-idp-demo/package.json b/examples/medplum-client-external-idp-demo/package.json index 974bbc45d0..3b36c08a58 100644 --- a/examples/medplum-client-external-idp-demo/package.json +++ b/examples/medplum-client-external-idp-demo/package.json @@ -15,7 +15,7 @@ "devDependencies": { "@medplum/core": "3.1.2", "rimraf": "5.0.5", - "typescript": "5.4.4", + "typescript": "5.4.5", "vite": "5.2.8" } } diff --git a/examples/medplum-demo-bots/package.json b/examples/medplum-demo-bots/package.json index b1be6b566b..3856e5068b 100644 --- a/examples/medplum-demo-bots/package.json +++ b/examples/medplum-demo-bots/package.json @@ -34,11 +34,11 @@ "@medplum/eslint-config": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/mock": "3.1.2", - "@types/node": "20.12.5", + "@types/node": "20.12.7", "@types/node-fetch": "2.6.11", "@types/ssh2-sftp-client": "9.0.3", - "@vitest/coverage-v8": "1.4.0", - "@vitest/ui": "1.4.0", + "@vitest/coverage-v8": "1.5.0", + "@vitest/ui": "1.5.0", "esbuild": "0.20.2", "form-data": "4.0.0", "glob": "^10.3.12", @@ -46,8 +46,8 @@ "pdfmake": "0.2.10", "rimraf": "5.0.5", "ssh2-sftp-client": "10.0.3", - "stripe": "14.24.0", - "typescript": "5.4.4", - "vitest": "1.4.0" + "stripe": "15.1.0", + "typescript": "5.4.5", + "vitest": "1.5.0" } } diff --git a/examples/medplum-eligibility-demo/package.json b/examples/medplum-eligibility-demo/package.json index 83a74b4b45..366a0e6e6e 100644 --- a/examples/medplum-eligibility-demo/package.json +++ b/examples/medplum-eligibility-demo/package.json @@ -21,26 +21,26 @@ ] }, "devDependencies": { - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", - "@mantine/notifications": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", + "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/definitions": "3.1.2", "@medplum/eslint-config": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/mock": "3.1.2", "@medplum/react": "3.1.2", - "@tabler/icons-react": "3.1.0", - "@types/node": "20.12.5", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@tabler/icons-react": "3.2.0", + "@types/node": "20.12.7", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.38", - "postcss-preset-mantine": "1.13.0", + "postcss-preset-mantine": "1.14.4", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.22.3", - "typescript": "5.4.4", + "typescript": "5.4.5", "vite": "5.2.8" } } diff --git a/examples/medplum-fhircast-demo/package.json b/examples/medplum-fhircast-demo/package.json index 5c13f9589e..6a42a3a151 100644 --- a/examples/medplum-fhircast-demo/package.json +++ b/examples/medplum-fhircast-demo/package.json @@ -15,23 +15,23 @@ ] }, "devDependencies": { - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", - "@mantine/notifications": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", + "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/eslint-config": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/react": "3.1.2", - "@tabler/icons-react": "3.1.0", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@tabler/icons-react": "3.2.0", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.38", - "postcss-preset-mantine": "1.13.0", + "postcss-preset-mantine": "1.14.4", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.22.3", - "typescript": "5.4.4", + "typescript": "5.4.5", "vite": "5.2.8" } } diff --git a/examples/medplum-hello-world/package.json b/examples/medplum-hello-world/package.json index 1574f7e969..03617d1f25 100644 --- a/examples/medplum-hello-world/package.json +++ b/examples/medplum-hello-world/package.json @@ -19,24 +19,24 @@ ] }, "devDependencies": { - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", - "@mantine/notifications": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", + "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/eslint-config": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/react": "3.1.2", - "@tabler/icons-react": "3.1.0", - "@types/node": "20.12.5", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@tabler/icons-react": "3.2.0", + "@types/node": "20.12.7", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.38", - "postcss-preset-mantine": "1.13.0", + "postcss-preset-mantine": "1.14.4", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.22.3", - "typescript": "5.4.4", + "typescript": "5.4.5", "vite": "5.2.8" } } diff --git a/examples/medplum-live-chat-demo/package.json b/examples/medplum-live-chat-demo/package.json index f632abb155..59f92d3156 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.7.1", - "@mantine/hooks": "7.7.1", - "@mantine/notifications": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", + "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/eslint-config": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/react": "3.1.2", - "@tabler/icons-react": "3.1.0", - "@types/node": "20.12.5", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@tabler/icons-react": "3.2.0", + "@types/node": "20.12.7", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.38", - "postcss-preset-mantine": "1.13.0", + "postcss-preset-mantine": "1.14.4", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.22.3", - "typescript": "5.4.4", + "typescript": "5.4.5", "vite": "5.2.8" } } diff --git a/examples/medplum-nextauth-demo/package.json b/examples/medplum-nextauth-demo/package.json index 9adf4fdecc..e19bf7644c 100644 --- a/examples/medplum-nextauth-demo/package.json +++ b/examples/medplum-nextauth-demo/package.json @@ -20,23 +20,23 @@ ] }, "dependencies": { - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", - "@mantine/notifications": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", + "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/eslint-config": "3.1.2", - "next": "14.1.4", + "next": "14.2.1", "next-auth": "4.24.7", "react": "18.2.0", "react-dom": "18.2.0" }, "devDependencies": { "@medplum/fhirtypes": "3.1.2", - "@types/node": "20.12.5", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@types/node": "20.12.7", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "eslint": "8.57.0", - "eslint-config-next": "14.1.4", - "typescript": "5.4.4" + "eslint-config-next": "14.2.1", + "typescript": "5.4.5" } } diff --git a/examples/medplum-nextauth-demo/postcss.config.cjs b/examples/medplum-nextauth-demo/postcss.config.cjs new file mode 100644 index 0000000000..cef96c213e --- /dev/null +++ b/examples/medplum-nextauth-demo/postcss.config.cjs @@ -0,0 +1,17 @@ +module.exports = { + plugins: [ + 'postcss-preset-mantine', + [ + 'postcss-simple-vars', + { + variables: { + 'mantine-breakpoint-xs': '36em', + 'mantine-breakpoint-sm': '48em', + 'mantine-breakpoint-md': '62em', + 'mantine-breakpoint-lg': '75em', + 'mantine-breakpoint-xl': '88em', + }, + }, + ], + ], +}; diff --git a/examples/medplum-nextauth-demo/postcss.config.mjs b/examples/medplum-nextauth-demo/postcss.config.mjs deleted file mode 100644 index feba649756..0000000000 --- a/examples/medplum-nextauth-demo/postcss.config.mjs +++ /dev/null @@ -1,19 +0,0 @@ -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-nextjs-demo/package.json b/examples/medplum-nextjs-demo/package.json index 450d1aefbd..fd1ae631c7 100644 --- a/examples/medplum-nextjs-demo/package.json +++ b/examples/medplum-nextjs-demo/package.json @@ -10,25 +10,25 @@ "start": "next start" }, "dependencies": { - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", - "@mantine/notifications": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", + "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/react": "3.1.2", - "next": "14.1.4", + "next": "14.2.1", "react": "18.2.0", "react-dom": "18.2.0", "rfc6902": "5.1.1" }, "devDependencies": { "@medplum/fhirtypes": "3.1.2", - "@types/node": "20.12.5", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@types/node": "20.12.7", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "eslint": "8.57.0", - "eslint-config-next": "14.1.4", + "eslint-config-next": "14.2.1", "postcss": "8.4.38", - "postcss-preset-mantine": "1.13.0", - "typescript": "5.4.4" + "postcss-preset-mantine": "1.14.4", + "typescript": "5.4.5" } } diff --git a/examples/medplum-nextjs-demo/postcss.config.cjs b/examples/medplum-nextjs-demo/postcss.config.cjs new file mode 100644 index 0000000000..cef96c213e --- /dev/null +++ b/examples/medplum-nextjs-demo/postcss.config.cjs @@ -0,0 +1,17 @@ +module.exports = { + plugins: [ + 'postcss-preset-mantine', + [ + 'postcss-simple-vars', + { + variables: { + 'mantine-breakpoint-xs': '36em', + 'mantine-breakpoint-sm': '48em', + 'mantine-breakpoint-md': '62em', + 'mantine-breakpoint-lg': '75em', + 'mantine-breakpoint-xl': '88em', + }, + }, + ], + ], +}; diff --git a/examples/medplum-nextjs-demo/postcss.config.mjs b/examples/medplum-nextjs-demo/postcss.config.mjs deleted file mode 100644 index feba649756..0000000000 --- a/examples/medplum-nextjs-demo/postcss.config.mjs +++ /dev/null @@ -1,19 +0,0 @@ -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/package.json b/examples/medplum-provider/package.json index ff7467305c..c8e3bb29b4 100644 --- a/examples/medplum-provider/package.json +++ b/examples/medplum-provider/package.json @@ -19,24 +19,24 @@ ] }, "devDependencies": { - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", - "@mantine/notifications": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", + "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/eslint-config": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/react": "3.1.2", - "@tabler/icons-react": "3.1.0", - "@types/node": "20.12.5", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@tabler/icons-react": "3.2.0", + "@types/node": "20.12.7", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.38", - "postcss-preset-mantine": "1.13.0", + "postcss-preset-mantine": "1.14.4", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.22.3", - "typescript": "5.4.4", + "typescript": "5.4.5", "vite": "5.2.8" } } diff --git a/examples/medplum-react-native-example/package.json b/examples/medplum-react-native-example/package.json index 5a46693fcc..231b14d4e7 100644 --- a/examples/medplum-react-native-example/package.json +++ b/examples/medplum-react-native-example/package.json @@ -24,7 +24,7 @@ "@medplum/expo-polyfills": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/react-hooks": "3.1.2", - "expo": "50.0.14", + "expo": "50.0.15", "expo-status-bar": "1.11.1", "react": "18.2.0", "react-dom": "18.2.0", @@ -33,6 +33,6 @@ }, "devDependencies": { "@babel/core": "7.24.4", - "typescript": "5.4.4" + "typescript": "5.4.5" } } diff --git a/examples/medplum-task-demo/package.json b/examples/medplum-task-demo/package.json index 5dd11359ea..ee72088df4 100644 --- a/examples/medplum-task-demo/package.json +++ b/examples/medplum-task-demo/package.json @@ -22,25 +22,25 @@ ] }, "devDependencies": { - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", - "@mantine/notifications": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", + "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/definitions": "3.1.2", "@medplum/eslint-config": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/react": "3.1.2", - "@tabler/icons-react": "3.1.0", - "@types/node": "20.12.5", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@tabler/icons-react": "3.2.0", + "@types/node": "20.12.7", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.38", - "postcss-preset-mantine": "1.13.0", + "postcss-preset-mantine": "1.14.4", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.22.3", - "typescript": "5.4.4", + "typescript": "5.4.5", "vite": "5.2.8" } } diff --git a/examples/medplum-websocket-subscriptions-demo/package.json b/examples/medplum-websocket-subscriptions-demo/package.json index 3e308d4fae..b0618fc8df 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.4", - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", - "@mantine/notifications": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", + "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/eslint-config": "3.1.2", "@medplum/fhir-router": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/mock": "3.1.2", "@medplum/react": "3.1.2", - "@tabler/icons-react": "3.1.0", - "@types/node": "20.12.5", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@tabler/icons-react": "3.2.0", + "@types/node": "20.12.7", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "@vitejs/plugin-react": "4.2.1", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.22.3", - "typescript": "5.4.4", + "typescript": "5.4.5", "vite": "5.2.8" } } diff --git a/package-lock.json b/package-lock.json index 34f00c0d53..c2b683d9ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,10 +17,10 @@ "@babel/preset-react": "7.24.1", "@babel/preset-typescript": "7.24.1", "@cyclonedx/cyclonedx-npm": "1.16.2", - "@microsoft/api-documenter": "7.24.1", - "@microsoft/api-extractor": "7.43.0", + "@microsoft/api-documenter": "7.24.2", + "@microsoft/api-extractor": "7.43.1", "@types/jest": "29.5.12", - "@types/node": "20.12.5", + "@types/node": "20.12.7", "babel-jest": "29.7.0", "babel-preset-vite": "1.1.3", "cross-env": "7.0.3", @@ -39,7 +39,7 @@ "ts-node": "10.9.2", "tslib": "2.6.2", "turbo": "1.13.2", - "typescript": "5.4.4" + "typescript": "5.4.5" }, "engines": { "node": ">=18.0.0" @@ -52,21 +52,21 @@ "@babel/preset-env": "7.24.4", "@babel/preset-react": "7.24.1", "@babel/preset-typescript": "7.24.1", - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", - "@mantine/notifications": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", + "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/eslint-config": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/mock": "3.1.2", "@medplum/react": "3.1.2", - "@tabler/icons-react": "3.1.0", + "@tabler/icons-react": "3.2.0", "@testing-library/jest-dom": "6.4.2", - "@testing-library/react": "14.3.0", + "@testing-library/react": "15.0.2", "@types/jest": "29.5.12", - "@types/node": "20.12.5", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@types/node": "20.12.7", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "@vitejs/plugin-react": "4.2.1", "babel-jest": "29.7.0", "c8": "9.1.0", @@ -76,36 +76,36 @@ "jest-environment-jsdom": "29.7.0", "jest-transform-stub": "2.0.0", "postcss": "8.4.38", - "postcss-preset-mantine": "1.13.0", + "postcss-preset-mantine": "1.14.4", "react": "18.2.0", "react-chartjs-2": "5.2.0", "react-dom": "18.2.0", "react-router-dom": "6.22.3", - "typescript": "5.4.4", + "typescript": "5.4.5", "vite": "5.2.8" } }, "examples/medplum-chart-demo": { "version": "3.1.2", "devDependencies": { - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", - "@mantine/notifications": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", + "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/eslint-config": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/react": "3.1.2", - "@tabler/icons-react": "3.1.0", - "@types/node": "20.12.5", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@tabler/icons-react": "3.2.0", + "@types/node": "20.12.7", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.38", - "postcss-preset-mantine": "1.13.0", + "postcss-preset-mantine": "1.14.4", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.22.3", - "typescript": "5.4.4", + "typescript": "5.4.5", "vite": "5.2.8" } }, @@ -114,7 +114,7 @@ "devDependencies": { "@medplum/core": "3.1.2", "rimraf": "5.0.5", - "typescript": "5.4.4", + "typescript": "5.4.5", "vite": "5.2.8" } }, @@ -127,11 +127,11 @@ "@medplum/eslint-config": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/mock": "3.1.2", - "@types/node": "20.12.5", + "@types/node": "20.12.7", "@types/node-fetch": "2.6.11", "@types/ssh2-sftp-client": "9.0.3", - "@vitest/coverage-v8": "1.4.0", - "@vitest/ui": "1.4.0", + "@vitest/coverage-v8": "1.5.0", + "@vitest/ui": "1.5.0", "esbuild": "0.20.2", "form-data": "4.0.0", "glob": "^10.3.12", @@ -139,177 +139,177 @@ "pdfmake": "0.2.10", "rimraf": "5.0.5", "ssh2-sftp-client": "10.0.3", - "stripe": "14.24.0", - "typescript": "5.4.4", - "vitest": "1.4.0" + "stripe": "15.1.0", + "typescript": "5.4.5", + "vitest": "1.5.0" } }, "examples/medplum-eligibility-demo": { "version": "3.1.2", "devDependencies": { - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", - "@mantine/notifications": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", + "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/definitions": "3.1.2", "@medplum/eslint-config": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/mock": "3.1.2", "@medplum/react": "3.1.2", - "@tabler/icons-react": "3.1.0", - "@types/node": "20.12.5", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@tabler/icons-react": "3.2.0", + "@types/node": "20.12.7", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.38", - "postcss-preset-mantine": "1.13.0", + "postcss-preset-mantine": "1.14.4", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.22.3", - "typescript": "5.4.4", + "typescript": "5.4.5", "vite": "5.2.8" } }, "examples/medplum-fhircast-demo": { "version": "3.1.2", "devDependencies": { - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", - "@mantine/notifications": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", + "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/eslint-config": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/react": "3.1.2", - "@tabler/icons-react": "3.1.0", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@tabler/icons-react": "3.2.0", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.38", - "postcss-preset-mantine": "1.13.0", + "postcss-preset-mantine": "1.14.4", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.22.3", - "typescript": "5.4.4", + "typescript": "5.4.5", "vite": "5.2.8" } }, "examples/medplum-hello-world": { "version": "3.1.2", "devDependencies": { - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", - "@mantine/notifications": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", + "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/eslint-config": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/react": "3.1.2", - "@tabler/icons-react": "3.1.0", - "@types/node": "20.12.5", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@tabler/icons-react": "3.2.0", + "@types/node": "20.12.7", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.38", - "postcss-preset-mantine": "1.13.0", + "postcss-preset-mantine": "1.14.4", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.22.3", - "typescript": "5.4.4", + "typescript": "5.4.5", "vite": "5.2.8" } }, "examples/medplum-live-chat-demo": { "version": "3.1.2", "devDependencies": { - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", - "@mantine/notifications": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", + "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/eslint-config": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/react": "3.1.2", - "@tabler/icons-react": "3.1.0", - "@types/node": "20.12.5", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@tabler/icons-react": "3.2.0", + "@types/node": "20.12.7", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.38", - "postcss-preset-mantine": "1.13.0", + "postcss-preset-mantine": "1.14.4", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.22.3", - "typescript": "5.4.4", + "typescript": "5.4.5", "vite": "5.2.8" } }, "examples/medplum-nextauth-demo": { "version": "3.1.2", "dependencies": { - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", - "@mantine/notifications": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", + "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/eslint-config": "3.1.2", - "next": "14.1.4", + "next": "14.2.1", "next-auth": "4.24.7", "react": "18.2.0", "react-dom": "18.2.0" }, "devDependencies": { "@medplum/fhirtypes": "3.1.2", - "@types/node": "20.12.5", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@types/node": "20.12.7", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "eslint": "8.57.0", - "eslint-config-next": "14.1.4", - "typescript": "5.4.4" + "eslint-config-next": "14.2.1", + "typescript": "5.4.5" } }, "examples/medplum-nextjs-demo": { "version": "3.1.2", "dependencies": { - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", - "@mantine/notifications": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", + "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/react": "3.1.2", - "next": "14.1.4", + "next": "14.2.1", "react": "18.2.0", "react-dom": "18.2.0", "rfc6902": "5.1.1" }, "devDependencies": { "@medplum/fhirtypes": "3.1.2", - "@types/node": "20.12.5", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@types/node": "20.12.7", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "eslint": "8.57.0", - "eslint-config-next": "14.1.4", + "eslint-config-next": "14.2.1", "postcss": "8.4.38", - "postcss-preset-mantine": "1.13.0", - "typescript": "5.4.4" + "postcss-preset-mantine": "1.14.4", + "typescript": "5.4.5" } }, "examples/medplum-provider": { "version": "3.1.2", "devDependencies": { - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", - "@mantine/notifications": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", + "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/eslint-config": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/react": "3.1.2", - "@tabler/icons-react": "3.1.0", - "@types/node": "20.12.5", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@tabler/icons-react": "3.2.0", + "@types/node": "20.12.7", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.38", - "postcss-preset-mantine": "1.13.0", + "postcss-preset-mantine": "1.14.4", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.22.3", - "typescript": "5.4.4", + "typescript": "5.4.5", "vite": "5.2.8" } }, @@ -321,7 +321,7 @@ "@medplum/expo-polyfills": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/react-hooks": "3.1.2", - "expo": "50.0.14", + "expo": "50.0.15", "expo-status-bar": "1.11.1", "react": "18.2.0", "react-dom": "18.2.0", @@ -330,31 +330,31 @@ }, "devDependencies": { "@babel/core": "7.24.4", - "typescript": "5.4.4" + "typescript": "5.4.5" } }, "examples/medplum-task-demo": { "version": "3.1.2", "devDependencies": { - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", - "@mantine/notifications": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", + "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/definitions": "3.1.2", "@medplum/eslint-config": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/react": "3.1.2", - "@tabler/icons-react": "3.1.0", - "@types/node": "20.12.5", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@tabler/icons-react": "3.2.0", + "@types/node": "20.12.7", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.38", - "postcss-preset-mantine": "1.13.0", + "postcss-preset-mantine": "1.14.4", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.22.3", - "typescript": "5.4.4", + "typescript": "5.4.5", "vite": "5.2.8" } }, @@ -362,24 +362,24 @@ "version": "3.1.2", "devDependencies": { "@emotion/react": "11.11.4", - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", - "@mantine/notifications": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", + "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/eslint-config": "3.1.2", "@medplum/fhir-router": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/mock": "3.1.2", "@medplum/react": "3.1.2", - "@tabler/icons-react": "3.1.0", - "@types/node": "20.12.5", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@tabler/icons-react": "3.2.0", + "@types/node": "20.12.7", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "@vitejs/plugin-react": "4.2.1", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.22.3", - "typescript": "5.4.4", + "typescript": "5.4.5", "vite": "5.2.8" } }, @@ -444,82 +444,82 @@ } }, "node_modules/@algolia/cache-browser-local-storage": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.23.2.tgz", - "integrity": "sha512-PvRQdCmtiU22dw9ZcTJkrVKgNBVAxKgD0/cfiqyxhA5+PHzA2WDt6jOmZ9QASkeM2BpyzClJb/Wr1yt2/t78Kw==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.23.3.tgz", + "integrity": "sha512-vRHXYCpPlTDE7i6UOy2xE03zHF2C8MEFjPN2v7fRbqVpcOvAUQK81x3Kc21xyb5aSIpYCjWCZbYZuz8Glyzyyg==", "dev": true, "dependencies": { - "@algolia/cache-common": "4.23.2" + "@algolia/cache-common": "4.23.3" } }, "node_modules/@algolia/cache-common": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.23.2.tgz", - "integrity": "sha512-OUK/6mqr6CQWxzl/QY0/mwhlGvS6fMtvEPyn/7AHUx96NjqDA4X4+Ju7aXFQKh+m3jW9VPB0B9xvEQgyAnRPNw==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.23.3.tgz", + "integrity": "sha512-h9XcNI6lxYStaw32pHpB1TMm0RuxphF+Ik4o7tcQiodEdpKK+wKufY6QXtba7t3k8eseirEMVB83uFFF3Nu54A==", "dev": true }, "node_modules/@algolia/cache-in-memory": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.23.2.tgz", - "integrity": "sha512-rfbi/SnhEa3MmlqQvgYz/9NNJ156NkU6xFxjbxBtLWnHbpj+qnlMoKd+amoiacHRITpajg6zYbLM9dnaD3Bczw==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.23.3.tgz", + "integrity": "sha512-yvpbuUXg/+0rbcagxNT7un0eo3czx2Uf0y4eiR4z4SD7SiptwYTpbuS0IHxcLHG3lq22ukx1T6Kjtk/rT+mqNg==", "dev": true, "dependencies": { - "@algolia/cache-common": "4.23.2" + "@algolia/cache-common": "4.23.3" } }, "node_modules/@algolia/client-account": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.23.2.tgz", - "integrity": "sha512-VbrOCLIN/5I7iIdskSoSw3uOUPF516k4SjDD4Qz3BFwa3of7D9A0lzBMAvQEJJEPHWdVraBJlGgdJq/ttmquJQ==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.23.3.tgz", + "integrity": "sha512-hpa6S5d7iQmretHHF40QGq6hz0anWEHGlULcTIT9tbUssWUriN9AUXIFQ8Ei4w9azD0hc1rUok9/DeQQobhQMA==", "dev": true, "dependencies": { - "@algolia/client-common": "4.23.2", - "@algolia/client-search": "4.23.2", - "@algolia/transporter": "4.23.2" + "@algolia/client-common": "4.23.3", + "@algolia/client-search": "4.23.3", + "@algolia/transporter": "4.23.3" } }, "node_modules/@algolia/client-analytics": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.23.2.tgz", - "integrity": "sha512-lLj7irsAztGhMoEx/SwKd1cwLY6Daf1Q5f2AOsZacpppSvuFvuBrmkzT7pap1OD/OePjLKxicJS8wNA0+zKtuw==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.23.3.tgz", + "integrity": "sha512-LBsEARGS9cj8VkTAVEZphjxTjMVCci+zIIiRhpFun9jGDUlS1XmhCW7CTrnaWeIuCQS/2iPyRqSy1nXPjcBLRA==", "dev": true, "dependencies": { - "@algolia/client-common": "4.23.2", - "@algolia/client-search": "4.23.2", - "@algolia/requester-common": "4.23.2", - "@algolia/transporter": "4.23.2" + "@algolia/client-common": "4.23.3", + "@algolia/client-search": "4.23.3", + "@algolia/requester-common": "4.23.3", + "@algolia/transporter": "4.23.3" } }, "node_modules/@algolia/client-common": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.23.2.tgz", - "integrity": "sha512-Q2K1FRJBern8kIfZ0EqPvUr3V29ICxCm/q42zInV+VJRjldAD9oTsMGwqUQ26GFMdFYmqkEfCbY4VGAiQhh22g==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.23.3.tgz", + "integrity": "sha512-l6EiPxdAlg8CYhroqS5ybfIczsGUIAC47slLPOMDeKSVXYG1n0qGiz4RjAHLw2aD0xzh2EXZ7aRguPfz7UKDKw==", "dev": true, "dependencies": { - "@algolia/requester-common": "4.23.2", - "@algolia/transporter": "4.23.2" + "@algolia/requester-common": "4.23.3", + "@algolia/transporter": "4.23.3" } }, "node_modules/@algolia/client-personalization": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.23.2.tgz", - "integrity": "sha512-vwPsgnCGhUcHhhQG5IM27z8q7dWrN9itjdvgA6uKf2e9r7vB+WXt4OocK0CeoYQt3OGEAExryzsB8DWqdMK5wg==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.23.3.tgz", + "integrity": "sha512-3E3yF3Ocr1tB/xOZiuC3doHQBQ2zu2MPTYZ0d4lpfWads2WTKG7ZzmGnsHmm63RflvDeLK/UVx7j2b3QuwKQ2g==", "dev": true, "dependencies": { - "@algolia/client-common": "4.23.2", - "@algolia/requester-common": "4.23.2", - "@algolia/transporter": "4.23.2" + "@algolia/client-common": "4.23.3", + "@algolia/requester-common": "4.23.3", + "@algolia/transporter": "4.23.3" } }, "node_modules/@algolia/client-search": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.23.2.tgz", - "integrity": "sha512-CxSB29OVGSE7l/iyoHvamMonzq7Ev8lnk/OkzleODZ1iBcCs3JC/XgTIKzN/4RSTrJ9QybsnlrN/bYCGufo7qw==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.23.3.tgz", + "integrity": "sha512-P4VAKFHqU0wx9O+q29Q8YVuaowaZ5EM77rxfmGnkHUJggh28useXQdopokgwMeYw2XUht49WX5RcTQ40rZIabw==", "dev": true, "dependencies": { - "@algolia/client-common": "4.23.2", - "@algolia/requester-common": "4.23.2", - "@algolia/transporter": "4.23.2" + "@algolia/client-common": "4.23.3", + "@algolia/requester-common": "4.23.3", + "@algolia/transporter": "4.23.3" } }, "node_modules/@algolia/events": { @@ -529,72 +529,72 @@ "dev": true }, "node_modules/@algolia/logger-common": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.23.2.tgz", - "integrity": "sha512-jGM49Q7626cXZ7qRAWXn0jDlzvoA1FvN4rKTi1g0hxKsTTSReyYk0i1ADWjChDPl3Q+nSDhJuosM2bBUAay7xw==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.23.3.tgz", + "integrity": "sha512-y9kBtmJwiZ9ZZ+1Ek66P0M68mHQzKRxkW5kAAXYN/rdzgDN0d2COsViEFufxJ0pb45K4FRcfC7+33YB4BLrZ+g==", "dev": true }, "node_modules/@algolia/logger-console": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.23.2.tgz", - "integrity": "sha512-oo+lnxxEmlhTBTFZ3fGz1O8PJ+G+8FiAoMY2Qo3Q4w23xocQev6KqDTA1JQAGPDxAewNA2VBwWOsVXeXFjrI/Q==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.23.3.tgz", + "integrity": "sha512-8xoiseoWDKuCVnWP8jHthgaeobDLolh00KJAdMe9XPrWPuf1by732jSpgy2BlsLTaT9m32pHI8CRfrOqQzHv3A==", "dev": true, "dependencies": { - "@algolia/logger-common": "4.23.2" + "@algolia/logger-common": "4.23.3" } }, "node_modules/@algolia/recommend": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.23.2.tgz", - "integrity": "sha512-Q75CjnzRCDzgIlgWfPnkLtrfF4t82JCirhalXkSSwe/c1GH5pWh4xUyDOR3KTMo+YxxX3zTlrL/FjHmUJEWEcg==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.23.3.tgz", + "integrity": "sha512-9fK4nXZF0bFkdcLBRDexsnGzVmu4TSYZqxdpgBW2tEyfuSSY54D4qSRkLmNkrrz4YFvdh2GM1gA8vSsnZPR73w==", "dev": true, "dependencies": { - "@algolia/cache-browser-local-storage": "4.23.2", - "@algolia/cache-common": "4.23.2", - "@algolia/cache-in-memory": "4.23.2", - "@algolia/client-common": "4.23.2", - "@algolia/client-search": "4.23.2", - "@algolia/logger-common": "4.23.2", - "@algolia/logger-console": "4.23.2", - "@algolia/requester-browser-xhr": "4.23.2", - "@algolia/requester-common": "4.23.2", - "@algolia/requester-node-http": "4.23.2", - "@algolia/transporter": "4.23.2" + "@algolia/cache-browser-local-storage": "4.23.3", + "@algolia/cache-common": "4.23.3", + "@algolia/cache-in-memory": "4.23.3", + "@algolia/client-common": "4.23.3", + "@algolia/client-search": "4.23.3", + "@algolia/logger-common": "4.23.3", + "@algolia/logger-console": "4.23.3", + "@algolia/requester-browser-xhr": "4.23.3", + "@algolia/requester-common": "4.23.3", + "@algolia/requester-node-http": "4.23.3", + "@algolia/transporter": "4.23.3" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.23.2.tgz", - "integrity": "sha512-TO9wLlp8+rvW9LnIfyHsu8mNAMYrqNdQ0oLF6eTWFxXfxG3k8F/Bh7nFYGk2rFAYty4Fw4XUtrv/YjeNDtM5og==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.23.3.tgz", + "integrity": "sha512-jDWGIQ96BhXbmONAQsasIpTYWslyjkiGu0Quydjlowe+ciqySpiDUrJHERIRfELE5+wFc7hc1Q5hqjGoV7yghw==", "dev": true, "dependencies": { - "@algolia/requester-common": "4.23.2" + "@algolia/requester-common": "4.23.3" } }, "node_modules/@algolia/requester-common": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.23.2.tgz", - "integrity": "sha512-3EfpBS0Hri0lGDB5H/BocLt7Vkop0bTTLVUBB844HH6tVycwShmsV6bDR7yXbQvFP1uNpgePRD3cdBCjeHmk6Q==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.23.3.tgz", + "integrity": "sha512-xloIdr/bedtYEGcXCiF2muajyvRhwop4cMZo+K2qzNht0CMzlRkm8YsDdj5IaBhshqfgmBb3rTg4sL4/PpvLYw==", "dev": true }, "node_modules/@algolia/requester-node-http": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.23.2.tgz", - "integrity": "sha512-SVzgkZM/malo+2SB0NWDXpnT7nO5IZwuDTaaH6SjLeOHcya1o56LSWXk+3F3rNLz2GVH+I/rpYKiqmHhSOjerw==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.23.3.tgz", + "integrity": "sha512-zgu++8Uj03IWDEJM3fuNl34s746JnZOWn1Uz5taV1dFyJhVM/kTNw9Ik7YJWiUNHJQXcaD8IXD1eCb0nq/aByA==", "dev": true, "dependencies": { - "@algolia/requester-common": "4.23.2" + "@algolia/requester-common": "4.23.3" } }, "node_modules/@algolia/transporter": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.23.2.tgz", - "integrity": "sha512-GY3aGKBy+8AK4vZh8sfkatDciDVKad5rTY2S10Aefyjh7e7UGBP4zigf42qVXwU8VOPwi7l/L7OACGMOFcjB0Q==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.23.3.tgz", + "integrity": "sha512-Wjl5gttqnf/gQKJA+dafnD0Y6Yw97yvfY8R9h0dQltX1GXTgNs1zWgvtWW0tHl1EgMdhAyw189uWiZMnL3QebQ==", "dev": true, "dependencies": { - "@algolia/cache-common": "4.23.2", - "@algolia/logger-common": "4.23.2", - "@algolia/requester-common": "4.23.2" + "@algolia/cache-common": "4.23.3", + "@algolia/logger-common": "4.23.3", + "@algolia/requester-common": "4.23.3" } }, "node_modules/@ampproject/remapping": { @@ -762,15 +762,15 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@aws-sdk/client-acm": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-acm/-/client-acm-3.549.0.tgz", - "integrity": "sha512-RFFKR1idpM8MlSqJ0/QFz063iuGl5ITdDV3/+twwvt+Szr8ARuCduFV3X7q63ElnTJINL1zAoyEFZwQmfxUH3w==", + "version": "3.554.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-acm/-/client-acm-3.554.0.tgz", + "integrity": "sha512-6/YnhngMLETn3XmkpTqj1bzC4qS0InO35KBOikEfmj3lMRqePX5AIEUAjnwdY7wppiaUN+tE1dpNI1uW1ASc7w==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/core": "3.549.0", - "@aws-sdk/credential-provider-node": "3.549.0", + "@aws-sdk/client-sts": "3.554.0", + "@aws-sdk/core": "3.554.0", + "@aws-sdk/credential-provider-node": "3.554.0", "@aws-sdk/middleware-host-header": "3.535.0", "@aws-sdk/middleware-logger": "3.535.0", "@aws-sdk/middleware-recursion-detection": "3.535.0", @@ -781,1899 +781,47 @@ "@aws-sdk/util-user-agent-browser": "3.535.0", "@aws-sdk/util-user-agent-node": "3.535.0", "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "@smithy/util-waiter": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-acm/node_modules/@aws-sdk/client-sso": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.549.0.tgz", - "integrity": "sha512-lz+yflOAj5Q263FlCsKpNqttaCb2NPh8jC76gVCqCt7TPxRDBYVaqg0OZYluDaETIDNJi4DwN2Azcck7ilwuPw==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.549.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.540.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.540.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-acm/node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.549.0.tgz", - "integrity": "sha512-FbB4A78ILAb8sM4TfBd+3CrQcfZIhe0gtVZNbaxpq5cJZh1K7oZ8vPfKw4do9JWkDUXPLsD9Bwz12f8/JpAb6Q==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/core": "3.549.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.540.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.540.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "@aws-sdk/credential-provider-node": "^3.549.0" - } - }, - "node_modules/@aws-sdk/client-acm/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.535.0.tgz", - "integrity": "sha512-kdj1wCmOMZ29jSlUskRqN04S6fJ4dvt0Nq9Z32SA6wO7UG8ht6Ot9h/au/eTWJM3E1somZ7D771oK7dQt9b8yw==", - "dependencies": { - "@aws-sdk/types": "3.535.0", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/util-stream": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-acm/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.549.0.tgz", - "integrity": "sha512-k6IIrluZjQpzui5Din8fW3bFFhHaJ64XrsfYx0Ks1mb7xan84dJxmYP3tdDDmLzUeJv5h95ag88taHfjY9rakA==", - "dependencies": { - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/credential-provider-env": "3.535.0", - "@aws-sdk/credential-provider-process": "3.535.0", - "@aws-sdk/credential-provider-sso": "3.549.0", - "@aws-sdk/credential-provider-web-identity": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/credential-provider-imds": "^2.3.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-acm/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.549.0.tgz", - "integrity": "sha512-f3YgalsMuywEAVX4AUm9tojqrBdfpAac0+D320ePzas0Ntbp7ItYu9ceKIhgfzXO3No7P3QK0rCrOxL+ABTn8Q==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.535.0", - "@aws-sdk/credential-provider-http": "3.535.0", - "@aws-sdk/credential-provider-ini": "3.549.0", - "@aws-sdk/credential-provider-process": "3.535.0", - "@aws-sdk/credential-provider-sso": "3.549.0", - "@aws-sdk/credential-provider-web-identity": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/credential-provider-imds": "^2.3.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-acm/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.549.0.tgz", - "integrity": "sha512-BGopRKHs7W8zkoH8qmSHrjudj263kXbhVkAUPxVUz0I28+CZNBgJC/RfVCbOpzmysIQEpwSqvOv1y0k+DQzIJQ==", - "dependencies": { - "@aws-sdk/client-sso": "3.549.0", - "@aws-sdk/token-providers": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-acm/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.549.0.tgz", - "integrity": "sha512-QzclVXPxuwSI7515l34sdvliVq5leroO8P7RQFKRgfyQKO45o1psghierwG3PgV6jlMiv78FIAGJBr/n4qZ7YA==", - "dependencies": { - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-acm/node_modules/@aws-sdk/token-providers": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.549.0.tgz", - "integrity": "sha512-rJyeXkXknLukRFGuMQOgKnPBa+kLODJtOqEBf929SpQ96f1I6ytdndmWbB5B/OQN5Fu5DOOQUQqJypDQVl5ibQ==", - "dependencies": { - "@aws-sdk/client-sso-oidc": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudformation": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudformation/-/client-cloudformation-3.549.0.tgz", - "integrity": "sha512-mEJUP2guSOdku5/+LIiKwAA9WE+nHKEtKPGOyOL0x72lkZCsfM3QSr35Xi6p1Aip3XB/LI+UCuoYpjiaceSpSw==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/core": "3.549.0", - "@aws-sdk/credential-provider-node": "3.549.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.540.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.540.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "@smithy/util-waiter": "^2.2.0", - "tslib": "^2.6.2", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/client-sso": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.549.0.tgz", - "integrity": "sha512-lz+yflOAj5Q263FlCsKpNqttaCb2NPh8jC76gVCqCt7TPxRDBYVaqg0OZYluDaETIDNJi4DwN2Azcck7ilwuPw==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.549.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.540.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.540.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.549.0.tgz", - "integrity": "sha512-FbB4A78ILAb8sM4TfBd+3CrQcfZIhe0gtVZNbaxpq5cJZh1K7oZ8vPfKw4do9JWkDUXPLsD9Bwz12f8/JpAb6Q==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/core": "3.549.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.540.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.540.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "@aws-sdk/credential-provider-node": "^3.549.0" - } - }, - "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.535.0.tgz", - "integrity": "sha512-kdj1wCmOMZ29jSlUskRqN04S6fJ4dvt0Nq9Z32SA6wO7UG8ht6Ot9h/au/eTWJM3E1somZ7D771oK7dQt9b8yw==", - "dependencies": { - "@aws-sdk/types": "3.535.0", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/util-stream": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.549.0.tgz", - "integrity": "sha512-k6IIrluZjQpzui5Din8fW3bFFhHaJ64XrsfYx0Ks1mb7xan84dJxmYP3tdDDmLzUeJv5h95ag88taHfjY9rakA==", - "dependencies": { - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/credential-provider-env": "3.535.0", - "@aws-sdk/credential-provider-process": "3.535.0", - "@aws-sdk/credential-provider-sso": "3.549.0", - "@aws-sdk/credential-provider-web-identity": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/credential-provider-imds": "^2.3.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.549.0.tgz", - "integrity": "sha512-f3YgalsMuywEAVX4AUm9tojqrBdfpAac0+D320ePzas0Ntbp7ItYu9ceKIhgfzXO3No7P3QK0rCrOxL+ABTn8Q==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.535.0", - "@aws-sdk/credential-provider-http": "3.535.0", - "@aws-sdk/credential-provider-ini": "3.549.0", - "@aws-sdk/credential-provider-process": "3.535.0", - "@aws-sdk/credential-provider-sso": "3.549.0", - "@aws-sdk/credential-provider-web-identity": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/credential-provider-imds": "^2.3.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.549.0.tgz", - "integrity": "sha512-BGopRKHs7W8zkoH8qmSHrjudj263kXbhVkAUPxVUz0I28+CZNBgJC/RfVCbOpzmysIQEpwSqvOv1y0k+DQzIJQ==", - "dependencies": { - "@aws-sdk/client-sso": "3.549.0", - "@aws-sdk/token-providers": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.549.0.tgz", - "integrity": "sha512-QzclVXPxuwSI7515l34sdvliVq5leroO8P7RQFKRgfyQKO45o1psghierwG3PgV6jlMiv78FIAGJBr/n4qZ7YA==", - "dependencies": { - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/token-providers": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.549.0.tgz", - "integrity": "sha512-rJyeXkXknLukRFGuMQOgKnPBa+kLODJtOqEBf929SpQ96f1I6ytdndmWbB5B/OQN5Fu5DOOQUQqJypDQVl5ibQ==", - "dependencies": { - "@aws-sdk/client-sso-oidc": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudfront": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudfront/-/client-cloudfront-3.549.0.tgz", - "integrity": "sha512-XoTKP2sttLl7azal3baptnk4r1zSVX5KjRVim9dlLFViCRrSxpWwQNXKyKI53Hk1rsy0CNxJzPYd4/ag11kOCQ==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/core": "3.549.0", - "@aws-sdk/credential-provider-node": "3.549.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.540.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.540.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@aws-sdk/xml-builder": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-stream": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "@smithy/util-waiter": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudfront/node_modules/@aws-sdk/client-sso": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.549.0.tgz", - "integrity": "sha512-lz+yflOAj5Q263FlCsKpNqttaCb2NPh8jC76gVCqCt7TPxRDBYVaqg0OZYluDaETIDNJi4DwN2Azcck7ilwuPw==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.549.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.540.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.540.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudfront/node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.549.0.tgz", - "integrity": "sha512-FbB4A78ILAb8sM4TfBd+3CrQcfZIhe0gtVZNbaxpq5cJZh1K7oZ8vPfKw4do9JWkDUXPLsD9Bwz12f8/JpAb6Q==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/core": "3.549.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.540.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.540.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "@aws-sdk/credential-provider-node": "^3.549.0" - } - }, - "node_modules/@aws-sdk/client-cloudfront/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.535.0.tgz", - "integrity": "sha512-kdj1wCmOMZ29jSlUskRqN04S6fJ4dvt0Nq9Z32SA6wO7UG8ht6Ot9h/au/eTWJM3E1somZ7D771oK7dQt9b8yw==", - "dependencies": { - "@aws-sdk/types": "3.535.0", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/util-stream": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudfront/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.549.0.tgz", - "integrity": "sha512-k6IIrluZjQpzui5Din8fW3bFFhHaJ64XrsfYx0Ks1mb7xan84dJxmYP3tdDDmLzUeJv5h95ag88taHfjY9rakA==", - "dependencies": { - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/credential-provider-env": "3.535.0", - "@aws-sdk/credential-provider-process": "3.535.0", - "@aws-sdk/credential-provider-sso": "3.549.0", - "@aws-sdk/credential-provider-web-identity": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/credential-provider-imds": "^2.3.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudfront/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.549.0.tgz", - "integrity": "sha512-f3YgalsMuywEAVX4AUm9tojqrBdfpAac0+D320ePzas0Ntbp7ItYu9ceKIhgfzXO3No7P3QK0rCrOxL+ABTn8Q==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.535.0", - "@aws-sdk/credential-provider-http": "3.535.0", - "@aws-sdk/credential-provider-ini": "3.549.0", - "@aws-sdk/credential-provider-process": "3.535.0", - "@aws-sdk/credential-provider-sso": "3.549.0", - "@aws-sdk/credential-provider-web-identity": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/credential-provider-imds": "^2.3.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudfront/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.549.0.tgz", - "integrity": "sha512-BGopRKHs7W8zkoH8qmSHrjudj263kXbhVkAUPxVUz0I28+CZNBgJC/RfVCbOpzmysIQEpwSqvOv1y0k+DQzIJQ==", - "dependencies": { - "@aws-sdk/client-sso": "3.549.0", - "@aws-sdk/token-providers": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudfront/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.549.0.tgz", - "integrity": "sha512-QzclVXPxuwSI7515l34sdvliVq5leroO8P7RQFKRgfyQKO45o1psghierwG3PgV6jlMiv78FIAGJBr/n4qZ7YA==", - "dependencies": { - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudfront/node_modules/@aws-sdk/token-providers": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.549.0.tgz", - "integrity": "sha512-rJyeXkXknLukRFGuMQOgKnPBa+kLODJtOqEBf929SpQ96f1I6ytdndmWbB5B/OQN5Fu5DOOQUQqJypDQVl5ibQ==", - "dependencies": { - "@aws-sdk/client-sso-oidc": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.549.0.tgz", - "integrity": "sha512-LAB4MWkx41wF+IImj4nucMmgAkGFD51sR5JkXsUoB5leLo0Oz84ZEFZ8RfqshF6ReoW5lmSRyB8x1663KdRcLA==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/core": "3.549.0", - "@aws-sdk/credential-provider-node": "3.549.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.540.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.540.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", - "@smithy/eventstream-serde-browser": "^2.2.0", - "@smithy/eventstream-serde-config-resolver": "^2.2.0", - "@smithy/eventstream-serde-node": "^2.2.0", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/client-sso": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.549.0.tgz", - "integrity": "sha512-lz+yflOAj5Q263FlCsKpNqttaCb2NPh8jC76gVCqCt7TPxRDBYVaqg0OZYluDaETIDNJi4DwN2Azcck7ilwuPw==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.549.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.540.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.540.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.549.0.tgz", - "integrity": "sha512-FbB4A78ILAb8sM4TfBd+3CrQcfZIhe0gtVZNbaxpq5cJZh1K7oZ8vPfKw4do9JWkDUXPLsD9Bwz12f8/JpAb6Q==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/core": "3.549.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.540.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.540.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "@aws-sdk/credential-provider-node": "^3.549.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.535.0.tgz", - "integrity": "sha512-kdj1wCmOMZ29jSlUskRqN04S6fJ4dvt0Nq9Z32SA6wO7UG8ht6Ot9h/au/eTWJM3E1somZ7D771oK7dQt9b8yw==", - "dependencies": { - "@aws-sdk/types": "3.535.0", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/util-stream": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.549.0.tgz", - "integrity": "sha512-k6IIrluZjQpzui5Din8fW3bFFhHaJ64XrsfYx0Ks1mb7xan84dJxmYP3tdDDmLzUeJv5h95ag88taHfjY9rakA==", - "dependencies": { - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/credential-provider-env": "3.535.0", - "@aws-sdk/credential-provider-process": "3.535.0", - "@aws-sdk/credential-provider-sso": "3.549.0", - "@aws-sdk/credential-provider-web-identity": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/credential-provider-imds": "^2.3.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.549.0.tgz", - "integrity": "sha512-f3YgalsMuywEAVX4AUm9tojqrBdfpAac0+D320ePzas0Ntbp7ItYu9ceKIhgfzXO3No7P3QK0rCrOxL+ABTn8Q==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.535.0", - "@aws-sdk/credential-provider-http": "3.535.0", - "@aws-sdk/credential-provider-ini": "3.549.0", - "@aws-sdk/credential-provider-process": "3.535.0", - "@aws-sdk/credential-provider-sso": "3.549.0", - "@aws-sdk/credential-provider-web-identity": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/credential-provider-imds": "^2.3.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.549.0.tgz", - "integrity": "sha512-BGopRKHs7W8zkoH8qmSHrjudj263kXbhVkAUPxVUz0I28+CZNBgJC/RfVCbOpzmysIQEpwSqvOv1y0k+DQzIJQ==", - "dependencies": { - "@aws-sdk/client-sso": "3.549.0", - "@aws-sdk/token-providers": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.549.0.tgz", - "integrity": "sha512-QzclVXPxuwSI7515l34sdvliVq5leroO8P7RQFKRgfyQKO45o1psghierwG3PgV6jlMiv78FIAGJBr/n4qZ7YA==", - "dependencies": { - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/token-providers": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.549.0.tgz", - "integrity": "sha512-rJyeXkXknLukRFGuMQOgKnPBa+kLODJtOqEBf929SpQ96f1I6ytdndmWbB5B/OQN5Fu5DOOQUQqJypDQVl5ibQ==", - "dependencies": { - "@aws-sdk/client-sso-oidc": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-ecs": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ecs/-/client-ecs-3.549.0.tgz", - "integrity": "sha512-3eiIs8O0fDKyjy6ch7Y7Xgb2GWt8/n+AUR4uQXP0jQWnmou/UAqwYKp5S8tqusxDV8ZG47maBFd/dZ+xOlyq2A==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/core": "3.549.0", - "@aws-sdk/credential-provider-node": "3.549.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.540.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.540.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "@smithy/util-waiter": "^2.2.0", - "tslib": "^2.6.2", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/client-sso": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.549.0.tgz", - "integrity": "sha512-lz+yflOAj5Q263FlCsKpNqttaCb2NPh8jC76gVCqCt7TPxRDBYVaqg0OZYluDaETIDNJi4DwN2Azcck7ilwuPw==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.549.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.540.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.540.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.549.0.tgz", - "integrity": "sha512-FbB4A78ILAb8sM4TfBd+3CrQcfZIhe0gtVZNbaxpq5cJZh1K7oZ8vPfKw4do9JWkDUXPLsD9Bwz12f8/JpAb6Q==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/core": "3.549.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.540.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.540.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "@aws-sdk/credential-provider-node": "^3.549.0" - } - }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.535.0.tgz", - "integrity": "sha512-kdj1wCmOMZ29jSlUskRqN04S6fJ4dvt0Nq9Z32SA6wO7UG8ht6Ot9h/au/eTWJM3E1somZ7D771oK7dQt9b8yw==", - "dependencies": { - "@aws-sdk/types": "3.535.0", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/util-stream": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.549.0.tgz", - "integrity": "sha512-k6IIrluZjQpzui5Din8fW3bFFhHaJ64XrsfYx0Ks1mb7xan84dJxmYP3tdDDmLzUeJv5h95ag88taHfjY9rakA==", - "dependencies": { - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/credential-provider-env": "3.535.0", - "@aws-sdk/credential-provider-process": "3.535.0", - "@aws-sdk/credential-provider-sso": "3.549.0", - "@aws-sdk/credential-provider-web-identity": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/credential-provider-imds": "^2.3.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.549.0.tgz", - "integrity": "sha512-f3YgalsMuywEAVX4AUm9tojqrBdfpAac0+D320ePzas0Ntbp7ItYu9ceKIhgfzXO3No7P3QK0rCrOxL+ABTn8Q==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.535.0", - "@aws-sdk/credential-provider-http": "3.535.0", - "@aws-sdk/credential-provider-ini": "3.549.0", - "@aws-sdk/credential-provider-process": "3.535.0", - "@aws-sdk/credential-provider-sso": "3.549.0", - "@aws-sdk/credential-provider-web-identity": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/credential-provider-imds": "^2.3.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.549.0.tgz", - "integrity": "sha512-BGopRKHs7W8zkoH8qmSHrjudj263kXbhVkAUPxVUz0I28+CZNBgJC/RfVCbOpzmysIQEpwSqvOv1y0k+DQzIJQ==", - "dependencies": { - "@aws-sdk/client-sso": "3.549.0", - "@aws-sdk/token-providers": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.549.0.tgz", - "integrity": "sha512-QzclVXPxuwSI7515l34sdvliVq5leroO8P7RQFKRgfyQKO45o1psghierwG3PgV6jlMiv78FIAGJBr/n4qZ7YA==", - "dependencies": { - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/token-providers": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.549.0.tgz", - "integrity": "sha512-rJyeXkXknLukRFGuMQOgKnPBa+kLODJtOqEBf929SpQ96f1I6ytdndmWbB5B/OQN5Fu5DOOQUQqJypDQVl5ibQ==", - "dependencies": { - "@aws-sdk/client-sso-oidc": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-lambda": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.549.0.tgz", - "integrity": "sha512-VdLltPf6fUDBFHPJBtYsWnM+nvdau7KuRlwwGnYzmYap79ifE538JTXE6AGyGcIp3YP44BlFtWgvW26kvDbX9g==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/core": "3.549.0", - "@aws-sdk/credential-provider-node": "3.549.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.540.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.540.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", - "@smithy/eventstream-serde-browser": "^2.2.0", - "@smithy/eventstream-serde-config-resolver": "^2.2.0", - "@smithy/eventstream-serde-node": "^2.2.0", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-stream": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "@smithy/util-waiter": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/client-sso": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.549.0.tgz", - "integrity": "sha512-lz+yflOAj5Q263FlCsKpNqttaCb2NPh8jC76gVCqCt7TPxRDBYVaqg0OZYluDaETIDNJi4DwN2Azcck7ilwuPw==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.549.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.540.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.540.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.549.0.tgz", - "integrity": "sha512-FbB4A78ILAb8sM4TfBd+3CrQcfZIhe0gtVZNbaxpq5cJZh1K7oZ8vPfKw4do9JWkDUXPLsD9Bwz12f8/JpAb6Q==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/core": "3.549.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.540.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.540.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "@aws-sdk/credential-provider-node": "^3.549.0" - } - }, - "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.535.0.tgz", - "integrity": "sha512-kdj1wCmOMZ29jSlUskRqN04S6fJ4dvt0Nq9Z32SA6wO7UG8ht6Ot9h/au/eTWJM3E1somZ7D771oK7dQt9b8yw==", - "dependencies": { - "@aws-sdk/types": "3.535.0", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/util-stream": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.549.0.tgz", - "integrity": "sha512-k6IIrluZjQpzui5Din8fW3bFFhHaJ64XrsfYx0Ks1mb7xan84dJxmYP3tdDDmLzUeJv5h95ag88taHfjY9rakA==", - "dependencies": { - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/credential-provider-env": "3.535.0", - "@aws-sdk/credential-provider-process": "3.535.0", - "@aws-sdk/credential-provider-sso": "3.549.0", - "@aws-sdk/credential-provider-web-identity": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/credential-provider-imds": "^2.3.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.549.0.tgz", - "integrity": "sha512-f3YgalsMuywEAVX4AUm9tojqrBdfpAac0+D320ePzas0Ntbp7ItYu9ceKIhgfzXO3No7P3QK0rCrOxL+ABTn8Q==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.535.0", - "@aws-sdk/credential-provider-http": "3.535.0", - "@aws-sdk/credential-provider-ini": "3.549.0", - "@aws-sdk/credential-provider-process": "3.535.0", - "@aws-sdk/credential-provider-sso": "3.549.0", - "@aws-sdk/credential-provider-web-identity": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/credential-provider-imds": "^2.3.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.549.0.tgz", - "integrity": "sha512-BGopRKHs7W8zkoH8qmSHrjudj263kXbhVkAUPxVUz0I28+CZNBgJC/RfVCbOpzmysIQEpwSqvOv1y0k+DQzIJQ==", - "dependencies": { - "@aws-sdk/client-sso": "3.549.0", - "@aws-sdk/token-providers": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.549.0.tgz", - "integrity": "sha512-QzclVXPxuwSI7515l34sdvliVq5leroO8P7RQFKRgfyQKO45o1psghierwG3PgV6jlMiv78FIAGJBr/n4qZ7YA==", - "dependencies": { - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/token-providers": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.549.0.tgz", - "integrity": "sha512-rJyeXkXknLukRFGuMQOgKnPBa+kLODJtOqEBf929SpQ96f1I6ytdndmWbB5B/OQN5Fu5DOOQUQqJypDQVl5ibQ==", - "dependencies": { - "@aws-sdk/client-sso-oidc": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-s3": { - "version": "3.550.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.550.0.tgz", - "integrity": "sha512-45jjDQI0Q37PIteWhywhlExxYaiUeOsTsbE62b+U/FOjYV8tirC8uBY9eHeHaP4IPVGHeQWvEYrFJHNU+qsQLQ==", - "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.549.0", - "@aws-sdk/core": "3.549.0", - "@aws-sdk/credential-provider-node": "3.549.0", - "@aws-sdk/middleware-bucket-endpoint": "3.535.0", - "@aws-sdk/middleware-expect-continue": "3.535.0", - "@aws-sdk/middleware-flexible-checksums": "3.535.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-location-constraint": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-sdk-s3": "3.535.0", - "@aws-sdk/middleware-signing": "3.535.0", - "@aws-sdk/middleware-ssec": "3.537.0", - "@aws-sdk/middleware-user-agent": "3.540.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/signature-v4-multi-region": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.540.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@aws-sdk/xml-builder": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", - "@smithy/eventstream-serde-browser": "^2.2.0", - "@smithy/eventstream-serde-config-resolver": "^2.2.0", - "@smithy/eventstream-serde-node": "^2.2.0", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-blob-browser": "^2.2.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/hash-stream-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/md5-js": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-stream": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "@smithy/util-waiter": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sso": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.549.0.tgz", - "integrity": "sha512-lz+yflOAj5Q263FlCsKpNqttaCb2NPh8jC76gVCqCt7TPxRDBYVaqg0OZYluDaETIDNJi4DwN2Azcck7ilwuPw==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.549.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.540.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.540.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.549.0.tgz", - "integrity": "sha512-FbB4A78ILAb8sM4TfBd+3CrQcfZIhe0gtVZNbaxpq5cJZh1K7oZ8vPfKw4do9JWkDUXPLsD9Bwz12f8/JpAb6Q==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/core": "3.549.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.540.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.540.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "@aws-sdk/credential-provider-node": "^3.549.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.535.0.tgz", - "integrity": "sha512-kdj1wCmOMZ29jSlUskRqN04S6fJ4dvt0Nq9Z32SA6wO7UG8ht6Ot9h/au/eTWJM3E1somZ7D771oK7dQt9b8yw==", - "dependencies": { - "@aws-sdk/types": "3.535.0", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/util-stream": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.549.0.tgz", - "integrity": "sha512-k6IIrluZjQpzui5Din8fW3bFFhHaJ64XrsfYx0Ks1mb7xan84dJxmYP3tdDDmLzUeJv5h95ag88taHfjY9rakA==", - "dependencies": { - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/credential-provider-env": "3.535.0", - "@aws-sdk/credential-provider-process": "3.535.0", - "@aws-sdk/credential-provider-sso": "3.549.0", - "@aws-sdk/credential-provider-web-identity": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/credential-provider-imds": "^2.3.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.549.0.tgz", - "integrity": "sha512-f3YgalsMuywEAVX4AUm9tojqrBdfpAac0+D320ePzas0Ntbp7ItYu9ceKIhgfzXO3No7P3QK0rCrOxL+ABTn8Q==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.535.0", - "@aws-sdk/credential-provider-http": "3.535.0", - "@aws-sdk/credential-provider-ini": "3.549.0", - "@aws-sdk/credential-provider-process": "3.535.0", - "@aws-sdk/credential-provider-sso": "3.549.0", - "@aws-sdk/credential-provider-web-identity": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/credential-provider-imds": "^2.3.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.549.0.tgz", - "integrity": "sha512-BGopRKHs7W8zkoH8qmSHrjudj263kXbhVkAUPxVUz0I28+CZNBgJC/RfVCbOpzmysIQEpwSqvOv1y0k+DQzIJQ==", - "dependencies": { - "@aws-sdk/client-sso": "3.549.0", - "@aws-sdk/token-providers": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.549.0.tgz", - "integrity": "sha512-QzclVXPxuwSI7515l34sdvliVq5leroO8P7RQFKRgfyQKO45o1psghierwG3PgV6jlMiv78FIAGJBr/n4qZ7YA==", - "dependencies": { - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/token-providers": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.549.0.tgz", - "integrity": "sha512-rJyeXkXknLukRFGuMQOgKnPBa+kLODJtOqEBf929SpQ96f1I6ytdndmWbB5B/OQN5Fu5DOOQUQqJypDQVl5ibQ==", - "dependencies": { - "@aws-sdk/client-sso-oidc": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-secrets-manager": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.549.0.tgz", - "integrity": "sha512-UcfU1vdghAJMWK1T6NnNe0ZhOIfSY6s0OBooXSybDgZvH47g+rYD/4VdtZRWRSjdCcF4PA+hfRa7vhfOyXdW/A==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/core": "3.549.0", - "@aws-sdk/credential-provider-node": "3.549.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.540.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.540.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", + "@smithy/core": "^1.4.2", "@smithy/fetch-http-handler": "^2.5.0", "@smithy/hash-node": "^2.2.0", "@smithy/invalid-dependency": "^2.2.0", "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", "@smithy/middleware-serde": "^2.3.0", "@smithy/middleware-stack": "^2.2.0", "@smithy/node-config-provider": "^2.3.0", "@smithy/node-http-handler": "^2.5.0", "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", + "@smithy/smithy-client": "^2.5.1", "@smithy/types": "^2.12.0", "@smithy/url-parser": "^2.2.0", "@smithy/util-base64": "^2.3.0", "@smithy/util-body-length-browser": "^2.2.0", "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", "@smithy/util-endpoints": "^1.2.0", "@smithy/util-middleware": "^2.2.0", "@smithy/util-retry": "^2.2.0", "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2", - "uuid": "^9.0.1" + "@smithy/util-waiter": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/client-sso": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.549.0.tgz", - "integrity": "sha512-lz+yflOAj5Q263FlCsKpNqttaCb2NPh8jC76gVCqCt7TPxRDBYVaqg0OZYluDaETIDNJi4DwN2Azcck7ilwuPw==", + "node_modules/@aws-sdk/client-cloudformation": { + "version": "3.555.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudformation/-/client-cloudformation-3.555.0.tgz", + "integrity": "sha512-gm+qteiSwG/Y25lrIdjiP/GQkYSCdxhTAcHHUGmC85pDTLRsZbTXUm79rhvT3SfoLX3/Hh4JHoVSiFL+wxKeww==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.549.0", + "@aws-sdk/client-sts": "3.554.0", + "@aws-sdk/core": "3.554.0", + "@aws-sdk/credential-provider-node": "3.554.0", "@aws-sdk/middleware-host-header": "3.535.0", "@aws-sdk/middleware-logger": "3.535.0", "@aws-sdk/middleware-recursion-detection": "3.535.0", @@ -2684,45 +832,48 @@ "@aws-sdk/util-user-agent-browser": "3.535.0", "@aws-sdk/util-user-agent-node": "3.535.0", "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", + "@smithy/core": "^1.4.2", "@smithy/fetch-http-handler": "^2.5.0", "@smithy/hash-node": "^2.2.0", "@smithy/invalid-dependency": "^2.2.0", "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", "@smithy/middleware-serde": "^2.3.0", "@smithy/middleware-stack": "^2.2.0", "@smithy/node-config-provider": "^2.3.0", "@smithy/node-http-handler": "^2.5.0", "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", + "@smithy/smithy-client": "^2.5.1", "@smithy/types": "^2.12.0", "@smithy/url-parser": "^2.2.0", "@smithy/util-base64": "^2.3.0", "@smithy/util-body-length-browser": "^2.2.0", "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", "@smithy/util-endpoints": "^1.2.0", "@smithy/util-middleware": "^2.2.0", "@smithy/util-retry": "^2.2.0", "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2" + "@smithy/util-waiter": "^2.2.0", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.549.0.tgz", - "integrity": "sha512-FbB4A78ILAb8sM4TfBd+3CrQcfZIhe0gtVZNbaxpq5cJZh1K7oZ8vPfKw4do9JWkDUXPLsD9Bwz12f8/JpAb6Q==", + "node_modules/@aws-sdk/client-cloudfront": { + "version": "3.554.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudfront/-/client-cloudfront-3.554.0.tgz", + "integrity": "sha512-cFKqFew8xuQWVFH1ZAI2a/Gs66bI8+OlHj/5ED1ZKoBGiBQRC+EUT/0bhbzAYJLB9jeSRPGRL/jOgcL6Sw4Hcg==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/core": "3.549.0", + "@aws-sdk/client-sts": "3.554.0", + "@aws-sdk/core": "3.554.0", + "@aws-sdk/credential-provider-node": "3.554.0", "@aws-sdk/middleware-host-header": "3.535.0", "@aws-sdk/middleware-logger": "3.535.0", "@aws-sdk/middleware-recursion-detection": "3.535.0", @@ -2732,160 +883,50 @@ "@aws-sdk/util-endpoints": "3.540.0", "@aws-sdk/util-user-agent-browser": "3.535.0", "@aws-sdk/util-user-agent-node": "3.535.0", + "@aws-sdk/xml-builder": "3.535.0", "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", + "@smithy/core": "^1.4.2", "@smithy/fetch-http-handler": "^2.5.0", "@smithy/hash-node": "^2.2.0", "@smithy/invalid-dependency": "^2.2.0", "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", "@smithy/middleware-serde": "^2.3.0", "@smithy/middleware-stack": "^2.2.0", "@smithy/node-config-provider": "^2.3.0", "@smithy/node-http-handler": "^2.5.0", "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", + "@smithy/smithy-client": "^2.5.1", "@smithy/types": "^2.12.0", "@smithy/url-parser": "^2.2.0", "@smithy/util-base64": "^2.3.0", "@smithy/util-body-length-browser": "^2.2.0", "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", "@smithy/util-endpoints": "^1.2.0", "@smithy/util-middleware": "^2.2.0", "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "@aws-sdk/credential-provider-node": "^3.549.0" - } - }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.535.0.tgz", - "integrity": "sha512-kdj1wCmOMZ29jSlUskRqN04S6fJ4dvt0Nq9Z32SA6wO7UG8ht6Ot9h/au/eTWJM3E1somZ7D771oK7dQt9b8yw==", - "dependencies": { - "@aws-sdk/types": "3.535.0", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", "@smithy/util-stream": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "@smithy/util-waiter": "^2.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.549.0.tgz", - "integrity": "sha512-k6IIrluZjQpzui5Din8fW3bFFhHaJ64XrsfYx0Ks1mb7xan84dJxmYP3tdDDmLzUeJv5h95ag88taHfjY9rakA==", - "dependencies": { - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/credential-provider-env": "3.535.0", - "@aws-sdk/credential-provider-process": "3.535.0", - "@aws-sdk/credential-provider-sso": "3.549.0", - "@aws-sdk/credential-provider-web-identity": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/credential-provider-imds": "^2.3.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.549.0.tgz", - "integrity": "sha512-f3YgalsMuywEAVX4AUm9tojqrBdfpAac0+D320ePzas0Ntbp7ItYu9ceKIhgfzXO3No7P3QK0rCrOxL+ABTn8Q==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.535.0", - "@aws-sdk/credential-provider-http": "3.535.0", - "@aws-sdk/credential-provider-ini": "3.549.0", - "@aws-sdk/credential-provider-process": "3.535.0", - "@aws-sdk/credential-provider-sso": "3.549.0", - "@aws-sdk/credential-provider-web-identity": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/credential-provider-imds": "^2.3.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.549.0.tgz", - "integrity": "sha512-BGopRKHs7W8zkoH8qmSHrjudj263kXbhVkAUPxVUz0I28+CZNBgJC/RfVCbOpzmysIQEpwSqvOv1y0k+DQzIJQ==", - "dependencies": { - "@aws-sdk/client-sso": "3.549.0", - "@aws-sdk/token-providers": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.549.0.tgz", - "integrity": "sha512-QzclVXPxuwSI7515l34sdvliVq5leroO8P7RQFKRgfyQKO45o1psghierwG3PgV6jlMiv78FIAGJBr/n4qZ7YA==", - "dependencies": { - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/token-providers": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.549.0.tgz", - "integrity": "sha512-rJyeXkXknLukRFGuMQOgKnPBa+kLODJtOqEBf929SpQ96f1I6ytdndmWbB5B/OQN5Fu5DOOQUQqJypDQVl5ibQ==", - "dependencies": { - "@aws-sdk/client-sso-oidc": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sesv2": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.549.0.tgz", - "integrity": "sha512-o+78gx6E4aKB9dqbUfpCo7SP24zQn8BKmh17hWOXB6ryDqZUHeaoeJh0gtcrE6EXEsEtqGlXIIaTu9kYdYDepA==", + "node_modules/@aws-sdk/client-cloudwatch-logs": { + "version": "3.554.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.554.0.tgz", + "integrity": "sha512-xwnCTljcrvEBVFbFoBcKfTQOhpD4ACJ17L/ISmBqqC2sZLANtxnsxGxJCpLiVwdDtoAIW43VvzFlZCZi7OEF2A==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/core": "3.549.0", - "@aws-sdk/credential-provider-node": "3.549.0", + "@aws-sdk/client-sts": "3.554.0", + "@aws-sdk/core": "3.554.0", + "@aws-sdk/credential-provider-node": "3.554.0", "@aws-sdk/middleware-host-header": "3.535.0", "@aws-sdk/middleware-logger": "3.535.0", "@aws-sdk/middleware-recursion-detection": "3.535.0", @@ -2896,44 +937,50 @@ "@aws-sdk/util-user-agent-browser": "3.535.0", "@aws-sdk/util-user-agent-node": "3.535.0", "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", + "@smithy/core": "^1.4.2", + "@smithy/eventstream-serde-browser": "^2.2.0", + "@smithy/eventstream-serde-config-resolver": "^2.2.0", + "@smithy/eventstream-serde-node": "^2.2.0", "@smithy/fetch-http-handler": "^2.5.0", "@smithy/hash-node": "^2.2.0", "@smithy/invalid-dependency": "^2.2.0", "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", "@smithy/middleware-serde": "^2.3.0", "@smithy/middleware-stack": "^2.2.0", "@smithy/node-config-provider": "^2.3.0", "@smithy/node-http-handler": "^2.5.0", "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", + "@smithy/smithy-client": "^2.5.1", "@smithy/types": "^2.12.0", "@smithy/url-parser": "^2.2.0", "@smithy/util-base64": "^2.3.0", "@smithy/util-body-length-browser": "^2.2.0", "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", "@smithy/util-endpoints": "^1.2.0", "@smithy/util-middleware": "^2.2.0", "@smithy/util-retry": "^2.2.0", "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2" + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-sesv2/node_modules/@aws-sdk/client-sso": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.549.0.tgz", - "integrity": "sha512-lz+yflOAj5Q263FlCsKpNqttaCb2NPh8jC76gVCqCt7TPxRDBYVaqg0OZYluDaETIDNJi4DwN2Azcck7ilwuPw==", + "node_modules/@aws-sdk/client-ecs": { + "version": "3.554.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ecs/-/client-ecs-3.554.0.tgz", + "integrity": "sha512-JyGbk0ulTtz8rjvVCWKqf+1Lobk6TDB+bPpfhMa3Z+ACyNsS+qU3W+XcDUXkgn+VGFd+nMfFoJOotSsT03ilFw==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.549.0", + "@aws-sdk/client-sts": "3.554.0", + "@aws-sdk/core": "3.554.0", + "@aws-sdk/credential-provider-node": "3.554.0", "@aws-sdk/middleware-host-header": "3.535.0", "@aws-sdk/middleware-logger": "3.535.0", "@aws-sdk/middleware-recursion-detection": "3.535.0", @@ -2944,45 +991,48 @@ "@aws-sdk/util-user-agent-browser": "3.535.0", "@aws-sdk/util-user-agent-node": "3.535.0", "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", + "@smithy/core": "^1.4.2", "@smithy/fetch-http-handler": "^2.5.0", "@smithy/hash-node": "^2.2.0", "@smithy/invalid-dependency": "^2.2.0", "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", "@smithy/middleware-serde": "^2.3.0", "@smithy/middleware-stack": "^2.2.0", "@smithy/node-config-provider": "^2.3.0", "@smithy/node-http-handler": "^2.5.0", "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", + "@smithy/smithy-client": "^2.5.1", "@smithy/types": "^2.12.0", "@smithy/url-parser": "^2.2.0", "@smithy/util-base64": "^2.3.0", "@smithy/util-body-length-browser": "^2.2.0", "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", "@smithy/util-endpoints": "^1.2.0", "@smithy/util-middleware": "^2.2.0", "@smithy/util-retry": "^2.2.0", "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2" + "@smithy/util-waiter": "^2.2.0", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-sesv2/node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.549.0.tgz", - "integrity": "sha512-FbB4A78ILAb8sM4TfBd+3CrQcfZIhe0gtVZNbaxpq5cJZh1K7oZ8vPfKw4do9JWkDUXPLsD9Bwz12f8/JpAb6Q==", + "node_modules/@aws-sdk/client-lambda": { + "version": "3.554.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.554.0.tgz", + "integrity": "sha512-KNUAAZKcsCdUOB2/rbWpc96jsSM/ahw3hK5/Ru4RTLfNP27GitxqF0v+mzrVk9lTuj2ChJ3JDV+UfdGsqvZgpw==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/core": "3.549.0", + "@aws-sdk/client-sts": "3.554.0", + "@aws-sdk/core": "3.554.0", + "@aws-sdk/credential-provider-node": "3.554.0", "@aws-sdk/middleware-host-header": "3.535.0", "@aws-sdk/middleware-logger": "3.535.0", "@aws-sdk/middleware-recursion-detection": "3.535.0", @@ -2993,209 +1043,118 @@ "@aws-sdk/util-user-agent-browser": "3.535.0", "@aws-sdk/util-user-agent-node": "3.535.0", "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", + "@smithy/core": "^1.4.2", + "@smithy/eventstream-serde-browser": "^2.2.0", + "@smithy/eventstream-serde-config-resolver": "^2.2.0", + "@smithy/eventstream-serde-node": "^2.2.0", "@smithy/fetch-http-handler": "^2.5.0", "@smithy/hash-node": "^2.2.0", "@smithy/invalid-dependency": "^2.2.0", "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", "@smithy/middleware-serde": "^2.3.0", "@smithy/middleware-stack": "^2.2.0", "@smithy/node-config-provider": "^2.3.0", "@smithy/node-http-handler": "^2.5.0", "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", + "@smithy/smithy-client": "^2.5.1", "@smithy/types": "^2.12.0", "@smithy/url-parser": "^2.2.0", "@smithy/util-base64": "^2.3.0", "@smithy/util-body-length-browser": "^2.2.0", "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", "@smithy/util-endpoints": "^1.2.0", "@smithy/util-middleware": "^2.2.0", "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "@aws-sdk/credential-provider-node": "^3.549.0" - } - }, - "node_modules/@aws-sdk/client-sesv2/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.535.0.tgz", - "integrity": "sha512-kdj1wCmOMZ29jSlUskRqN04S6fJ4dvt0Nq9Z32SA6wO7UG8ht6Ot9h/au/eTWJM3E1somZ7D771oK7dQt9b8yw==", - "dependencies": { - "@aws-sdk/types": "3.535.0", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", "@smithy/util-stream": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "@smithy/util-waiter": "^2.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-sesv2/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.549.0.tgz", - "integrity": "sha512-k6IIrluZjQpzui5Din8fW3bFFhHaJ64XrsfYx0Ks1mb7xan84dJxmYP3tdDDmLzUeJv5h95ag88taHfjY9rakA==", - "dependencies": { - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/credential-provider-env": "3.535.0", - "@aws-sdk/credential-provider-process": "3.535.0", - "@aws-sdk/credential-provider-sso": "3.549.0", - "@aws-sdk/credential-provider-web-identity": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/credential-provider-imds": "^2.3.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sesv2/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.549.0.tgz", - "integrity": "sha512-f3YgalsMuywEAVX4AUm9tojqrBdfpAac0+D320ePzas0Ntbp7ItYu9ceKIhgfzXO3No7P3QK0rCrOxL+ABTn8Q==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.535.0", - "@aws-sdk/credential-provider-http": "3.535.0", - "@aws-sdk/credential-provider-ini": "3.549.0", - "@aws-sdk/credential-provider-process": "3.535.0", - "@aws-sdk/credential-provider-sso": "3.549.0", - "@aws-sdk/credential-provider-web-identity": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/credential-provider-imds": "^2.3.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sesv2/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.549.0.tgz", - "integrity": "sha512-BGopRKHs7W8zkoH8qmSHrjudj263kXbhVkAUPxVUz0I28+CZNBgJC/RfVCbOpzmysIQEpwSqvOv1y0k+DQzIJQ==", - "dependencies": { - "@aws-sdk/client-sso": "3.549.0", - "@aws-sdk/token-providers": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sesv2/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.549.0.tgz", - "integrity": "sha512-QzclVXPxuwSI7515l34sdvliVq5leroO8P7RQFKRgfyQKO45o1psghierwG3PgV6jlMiv78FIAGJBr/n4qZ7YA==", - "dependencies": { - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sesv2/node_modules/@aws-sdk/token-providers": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.549.0.tgz", - "integrity": "sha512-rJyeXkXknLukRFGuMQOgKnPBa+kLODJtOqEBf929SpQ96f1I6ytdndmWbB5B/OQN5Fu5DOOQUQqJypDQVl5ibQ==", - "dependencies": { - "@aws-sdk/client-sso-oidc": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ssm/-/client-ssm-3.549.0.tgz", - "integrity": "sha512-h5EvM1e09+ybmaCFXVjibWGyBSJ7yNIgKFq1SoifnZ5O4nf6SNNAhbuea1Gq4JCcjvDfzJ4Wl3EqVHf/Jb9oJg==", + "node_modules/@aws-sdk/client-s3": { + "version": "3.554.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.554.0.tgz", + "integrity": "sha512-d5TKKtGWhN0vl9QovUFrf3UsM7jgFQkowDPx1O+E/yeQUj1FBDOoRfDCcQOKW/9ghloI6k7f0bBpNxdd+x0oKA==", "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.549.0", - "@aws-sdk/core": "3.549.0", - "@aws-sdk/credential-provider-node": "3.549.0", + "@aws-sdk/client-sts": "3.554.0", + "@aws-sdk/core": "3.554.0", + "@aws-sdk/credential-provider-node": "3.554.0", + "@aws-sdk/middleware-bucket-endpoint": "3.535.0", + "@aws-sdk/middleware-expect-continue": "3.535.0", + "@aws-sdk/middleware-flexible-checksums": "3.535.0", "@aws-sdk/middleware-host-header": "3.535.0", + "@aws-sdk/middleware-location-constraint": "3.535.0", "@aws-sdk/middleware-logger": "3.535.0", "@aws-sdk/middleware-recursion-detection": "3.535.0", + "@aws-sdk/middleware-sdk-s3": "3.552.0", + "@aws-sdk/middleware-signing": "3.552.0", + "@aws-sdk/middleware-ssec": "3.537.0", "@aws-sdk/middleware-user-agent": "3.540.0", "@aws-sdk/region-config-resolver": "3.535.0", + "@aws-sdk/signature-v4-multi-region": "3.552.0", "@aws-sdk/types": "3.535.0", "@aws-sdk/util-endpoints": "3.540.0", "@aws-sdk/util-user-agent-browser": "3.535.0", "@aws-sdk/util-user-agent-node": "3.535.0", + "@aws-sdk/xml-builder": "3.535.0", "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", + "@smithy/core": "^1.4.2", + "@smithy/eventstream-serde-browser": "^2.2.0", + "@smithy/eventstream-serde-config-resolver": "^2.2.0", + "@smithy/eventstream-serde-node": "^2.2.0", "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-blob-browser": "^2.2.0", "@smithy/hash-node": "^2.2.0", + "@smithy/hash-stream-node": "^2.2.0", "@smithy/invalid-dependency": "^2.2.0", + "@smithy/md5-js": "^2.2.0", "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", "@smithy/middleware-serde": "^2.3.0", "@smithy/middleware-stack": "^2.2.0", "@smithy/node-config-provider": "^2.3.0", "@smithy/node-http-handler": "^2.5.0", "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", + "@smithy/smithy-client": "^2.5.1", "@smithy/types": "^2.12.0", "@smithy/url-parser": "^2.2.0", "@smithy/util-base64": "^2.3.0", "@smithy/util-body-length-browser": "^2.2.0", "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", "@smithy/util-retry": "^2.2.0", + "@smithy/util-stream": "^2.2.0", "@smithy/util-utf8": "^2.3.0", "@smithy/util-waiter": "^2.2.0", - "tslib": "^2.6.2", - "uuid": "^9.0.1" + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/client-sso": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.549.0.tgz", - "integrity": "sha512-lz+yflOAj5Q263FlCsKpNqttaCb2NPh8jC76gVCqCt7TPxRDBYVaqg0OZYluDaETIDNJi4DwN2Azcck7ilwuPw==", + "node_modules/@aws-sdk/client-secrets-manager": { + "version": "3.554.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.554.0.tgz", + "integrity": "sha512-Uk9rdO6nP1Ayg6maOCD7ZI7QlRzDCGoFQtp/hxBt0uGro5C47Rpg5N6Wn3Lblk/rGnDcq+nuX24WXo83jOi/HQ==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.549.0", + "@aws-sdk/client-sts": "3.554.0", + "@aws-sdk/core": "3.554.0", + "@aws-sdk/credential-provider-node": "3.554.0", "@aws-sdk/middleware-host-header": "3.535.0", "@aws-sdk/middleware-logger": "3.535.0", "@aws-sdk/middleware-recursion-detection": "3.535.0", @@ -3206,45 +1165,47 @@ "@aws-sdk/util-user-agent-browser": "3.535.0", "@aws-sdk/util-user-agent-node": "3.535.0", "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", + "@smithy/core": "^1.4.2", "@smithy/fetch-http-handler": "^2.5.0", "@smithy/hash-node": "^2.2.0", "@smithy/invalid-dependency": "^2.2.0", "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", "@smithy/middleware-serde": "^2.3.0", "@smithy/middleware-stack": "^2.2.0", "@smithy/node-config-provider": "^2.3.0", "@smithy/node-http-handler": "^2.5.0", "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", + "@smithy/smithy-client": "^2.5.1", "@smithy/types": "^2.12.0", "@smithy/url-parser": "^2.2.0", "@smithy/util-base64": "^2.3.0", "@smithy/util-body-length-browser": "^2.2.0", "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", "@smithy/util-endpoints": "^1.2.0", "@smithy/util-middleware": "^2.2.0", "@smithy/util-retry": "^2.2.0", "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2" + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.549.0.tgz", - "integrity": "sha512-FbB4A78ILAb8sM4TfBd+3CrQcfZIhe0gtVZNbaxpq5cJZh1K7oZ8vPfKw4do9JWkDUXPLsD9Bwz12f8/JpAb6Q==", + "node_modules/@aws-sdk/client-sesv2": { + "version": "3.554.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.554.0.tgz", + "integrity": "sha512-l5x92adRLKX/PgvdsA4f/rAXyhFKoODW2KkrHdH98H9gXvWU8Tx4GRwwFw34S7ZlO+yg2j4mQZWo+4lLoJXfjw==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/core": "3.549.0", + "@aws-sdk/client-sts": "3.554.0", + "@aws-sdk/core": "3.554.0", + "@aws-sdk/credential-provider-node": "3.554.0", "@aws-sdk/middleware-host-header": "3.535.0", "@aws-sdk/middleware-logger": "3.535.0", "@aws-sdk/middleware-recursion-detection": "3.535.0", @@ -3255,158 +1216,46 @@ "@aws-sdk/util-user-agent-browser": "3.535.0", "@aws-sdk/util-user-agent-node": "3.535.0", "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", + "@smithy/core": "^1.4.2", "@smithy/fetch-http-handler": "^2.5.0", "@smithy/hash-node": "^2.2.0", "@smithy/invalid-dependency": "^2.2.0", "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", "@smithy/middleware-serde": "^2.3.0", "@smithy/middleware-stack": "^2.2.0", "@smithy/node-config-provider": "^2.3.0", "@smithy/node-http-handler": "^2.5.0", "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", + "@smithy/smithy-client": "^2.5.1", "@smithy/types": "^2.12.0", "@smithy/url-parser": "^2.2.0", "@smithy/util-base64": "^2.3.0", "@smithy/util-body-length-browser": "^2.2.0", "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", "@smithy/util-endpoints": "^1.2.0", "@smithy/util-middleware": "^2.2.0", "@smithy/util-retry": "^2.2.0", "@smithy/util-utf8": "^2.3.0", "tslib": "^2.6.2" }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "@aws-sdk/credential-provider-node": "^3.549.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.535.0.tgz", - "integrity": "sha512-kdj1wCmOMZ29jSlUskRqN04S6fJ4dvt0Nq9Z32SA6wO7UG8ht6Ot9h/au/eTWJM3E1somZ7D771oK7dQt9b8yw==", - "dependencies": { - "@aws-sdk/types": "3.535.0", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/util-stream": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.549.0.tgz", - "integrity": "sha512-k6IIrluZjQpzui5Din8fW3bFFhHaJ64XrsfYx0Ks1mb7xan84dJxmYP3tdDDmLzUeJv5h95ag88taHfjY9rakA==", - "dependencies": { - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/credential-provider-env": "3.535.0", - "@aws-sdk/credential-provider-process": "3.535.0", - "@aws-sdk/credential-provider-sso": "3.549.0", - "@aws-sdk/credential-provider-web-identity": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/credential-provider-imds": "^2.3.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.549.0.tgz", - "integrity": "sha512-f3YgalsMuywEAVX4AUm9tojqrBdfpAac0+D320ePzas0Ntbp7ItYu9ceKIhgfzXO3No7P3QK0rCrOxL+ABTn8Q==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.535.0", - "@aws-sdk/credential-provider-http": "3.535.0", - "@aws-sdk/credential-provider-ini": "3.549.0", - "@aws-sdk/credential-provider-process": "3.535.0", - "@aws-sdk/credential-provider-sso": "3.549.0", - "@aws-sdk/credential-provider-web-identity": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/credential-provider-imds": "^2.3.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.549.0.tgz", - "integrity": "sha512-BGopRKHs7W8zkoH8qmSHrjudj263kXbhVkAUPxVUz0I28+CZNBgJC/RfVCbOpzmysIQEpwSqvOv1y0k+DQzIJQ==", - "dependencies": { - "@aws-sdk/client-sso": "3.549.0", - "@aws-sdk/token-providers": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.549.0.tgz", - "integrity": "sha512-QzclVXPxuwSI7515l34sdvliVq5leroO8P7RQFKRgfyQKO45o1psghierwG3PgV6jlMiv78FIAGJBr/n4qZ7YA==", - "dependencies": { - "@aws-sdk/client-sts": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/token-providers": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.549.0.tgz", - "integrity": "sha512-rJyeXkXknLukRFGuMQOgKnPBa+kLODJtOqEBf929SpQ96f1I6ytdndmWbB5B/OQN5Fu5DOOQUQqJypDQVl5ibQ==", - "dependencies": { - "@aws-sdk/client-sso-oidc": "3.549.0", - "@aws-sdk/types": "3.535.0", - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-sso": { - "version": "3.552.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.552.0.tgz", - "integrity": "sha512-IAjRj5gcuyoPe/OhciMY/UyW8C1kyXSUJFagxvbeSv8q0mEfaPBVjGgz2xSYRFhhZr3gFlGCS9SiukwOL2/VoA==", - "peer": true, + "node_modules/@aws-sdk/client-ssm": { + "version": "3.554.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ssm/-/client-ssm-3.554.0.tgz", + "integrity": "sha512-zqc5Pyb0agJ3erp1x2ILoll7mG6atQTD2AFWA5UBFhNa7R0+w+TLvSNnX813X4bv4OySqBYYEtAokoTvV66UZw==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.552.0", + "@aws-sdk/client-sts": "3.554.0", + "@aws-sdk/core": "3.554.0", + "@aws-sdk/credential-provider-node": "3.554.0", "@aws-sdk/middleware-host-header": "3.535.0", "@aws-sdk/middleware-logger": "3.535.0", "@aws-sdk/middleware-recursion-detection": "3.535.0", @@ -3441,22 +1290,22 @@ "@smithy/util-middleware": "^2.2.0", "@smithy/util-retry": "^2.2.0", "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2" + "@smithy/util-waiter": "^2.2.0", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.552.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.552.0.tgz", - "integrity": "sha512-6JYTgN/n4xTm3Z+JhEZq06pyYsgo7heYDmR+0smmauQS02Eu8lvUc2jPs/0GDAmty7J4tq3gS6TRwvf7181C2w==", - "peer": true, + "node_modules/@aws-sdk/client-sso": { + "version": "3.554.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.554.0.tgz", + "integrity": "sha512-yj6CgIxCT3UwMumEO481KH4QvwArkAPzD7Xvwe1QKgJATc9bKNEo/FxV8LfnWIJ7nOtMDxbNxYLMXH/Fs1qGaQ==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.552.0", - "@aws-sdk/core": "3.552.0", + "@aws-sdk/core": "3.554.0", "@aws-sdk/middleware-host-header": "3.535.0", "@aws-sdk/middleware-logger": "3.535.0", "@aws-sdk/middleware-recursion-detection": "3.535.0", @@ -3495,20 +1344,17 @@ }, "engines": { "node": ">=14.0.0" - }, - "peerDependencies": { - "@aws-sdk/credential-provider-node": "^3.552.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/client-sts": { - "version": "3.552.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.552.0.tgz", - "integrity": "sha512-rOZlAj8GyFgUBESyKezes67A8Kj5+KjRhfBHMXrkcM5h9UOIz5q7QdkSQOmzWwRoPDmmAqb6t+y041/76TnPEg==", - "peer": true, + "node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.554.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.554.0.tgz", + "integrity": "sha512-M86rkiRqbZBF5VyfTQ/vttry9VSoQkZ1oCqYF+SAGlXmD0Of8587yRSj2M4rYe0Uj7nRQIfSnhDYp1UzsZeRfQ==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.552.0", + "@aws-sdk/client-sts": "3.554.0", + "@aws-sdk/core": "3.554.0", "@aws-sdk/middleware-host-header": "3.535.0", "@aws-sdk/middleware-logger": "3.535.0", "@aws-sdk/middleware-recursion-detection": "3.535.0", @@ -3549,53 +1395,17 @@ "node": ">=14.0.0" }, "peerDependencies": { - "@aws-sdk/credential-provider-node": "^3.552.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/core": { - "version": "3.552.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.552.0.tgz", - "integrity": "sha512-T7ovljf6fCvIHG9SOSZqGmbVbqZPXPywLAcU+onk/fYLZJj6kjfzKZzSAUBI0nO1OKpuP/nCHaCp51NLWNqsnw==", - "peer": true, - "dependencies": { - "@smithy/core": "^1.4.2", - "@smithy/protocol-http": "^3.3.0", - "@smithy/signature-v4": "^2.2.1", - "@smithy/smithy-client": "^2.5.1", - "@smithy/types": "^2.12.0", - "fast-xml-parser": "4.2.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/core": { - "version": "3.552.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.552.0.tgz", - "integrity": "sha512-T7ovljf6fCvIHG9SOSZqGmbVbqZPXPywLAcU+onk/fYLZJj6kjfzKZzSAUBI0nO1OKpuP/nCHaCp51NLWNqsnw==", - "peer": true, - "dependencies": { - "@smithy/core": "^1.4.2", - "@smithy/protocol-http": "^3.3.0", - "@smithy/signature-v4": "^2.2.1", - "@smithy/smithy-client": "^2.5.1", - "@smithy/types": "^2.12.0", - "fast-xml-parser": "4.2.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" + "@aws-sdk/credential-provider-node": "^3.554.0" } }, "node_modules/@aws-sdk/client-sts": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.549.0.tgz", - "integrity": "sha512-63IreJ598Dzvpb+6sy81KfIX5iQxnrWSEtlyeCdC2GO6gmSQVwJzc9kr5pAC83lHmlZcm/Q3KZr3XBhRQqP0og==", + "version": "3.554.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.554.0.tgz", + "integrity": "sha512-EhaA6T0M0DNg5M8TCF1a7XJI5D/ZxAF3dgVIchyF98iNzjYgl/7U8K6hJay2A11aFvVu70g46xYMpz3Meky4wQ==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.549.0", + "@aws-sdk/core": "3.554.0", "@aws-sdk/middleware-host-header": "3.535.0", "@aws-sdk/middleware-logger": "3.535.0", "@aws-sdk/middleware-recursion-detection": "3.535.0", @@ -3606,26 +1416,26 @@ "@aws-sdk/util-user-agent-browser": "3.535.0", "@aws-sdk/util-user-agent-node": "3.535.0", "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.1", + "@smithy/core": "^1.4.2", "@smithy/fetch-http-handler": "^2.5.0", "@smithy/hash-node": "^2.2.0", "@smithy/invalid-dependency": "^2.2.0", "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/middleware-retry": "^2.3.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", "@smithy/middleware-serde": "^2.3.0", "@smithy/middleware-stack": "^2.2.0", "@smithy/node-config-provider": "^2.3.0", "@smithy/node-http-handler": "^2.5.0", "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.0", + "@smithy/smithy-client": "^2.5.1", "@smithy/types": "^2.12.0", "@smithy/url-parser": "^2.2.0", "@smithy/util-base64": "^2.3.0", "@smithy/util-body-length-browser": "^2.2.0", "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.0", - "@smithy/util-defaults-mode-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", "@smithy/util-endpoints": "^1.2.0", "@smithy/util-middleware": "^2.2.0", "@smithy/util-retry": "^2.2.0", @@ -3636,7 +1446,7 @@ "node": ">=14.0.0" }, "peerDependencies": { - "@aws-sdk/credential-provider-node": "^3.549.0" + "@aws-sdk/credential-provider-node": "^3.554.0" } }, "node_modules/@aws-sdk/cloudfront-signer": { @@ -3652,14 +1462,14 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.549.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.549.0.tgz", - "integrity": "sha512-jC61OxJn72r/BbuDRCcluiw05Xw9eVLG0CwxQpF3RocxfxyZqlrGYaGecZ8Wy+7g/3sqGRC/Ar5eUhU1YcLx7w==", + "version": "3.554.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.554.0.tgz", + "integrity": "sha512-JrG7ToTLeNf+/S3IiCUPVw9jEDB0DXl5ho8n/HwOa946mv+QyCepCuV2U/8f/1KAX0mD8Ufm/E4/cbCbFHgbSg==", "dependencies": { - "@smithy/core": "^1.4.1", + "@smithy/core": "^1.4.2", "@smithy/protocol-http": "^3.3.0", - "@smithy/signature-v4": "^2.2.0", - "@smithy/smithy-client": "^2.5.0", + "@smithy/signature-v4": "^2.2.1", + "@smithy/smithy-client": "^2.5.1", "@smithy/types": "^2.12.0", "fast-xml-parser": "4.2.5", "tslib": "^2.6.2" @@ -3686,7 +1496,6 @@ "version": "3.552.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.552.0.tgz", "integrity": "sha512-vsmu7Cz1i45pFEqzVb4JcFmAmVnWFNLsGheZc8SCptlqCO5voETrZZILHYIl4cjKkSDk3pblBOf0PhyjqWW6WQ==", - "peer": true, "dependencies": { "@aws-sdk/types": "3.535.0", "@smithy/fetch-http-handler": "^2.5.0", @@ -3703,16 +1512,15 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.552.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.552.0.tgz", - "integrity": "sha512-/Z9y+P4M/eZA/5hGH3Kwm6TOIAiVtsIo7sC/x7hZPXn/IMJQ2QmxzeMozVqMWzx8+2zUA/dmgmWnHoVvH4R/jg==", - "peer": true, + "version": "3.554.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.554.0.tgz", + "integrity": "sha512-BQenhg43S6TMJHxrdjDVdVF+HH5tA1op9ZYLyJrvV5nn7CCO4kyAkkOuSAv1NkL+RZsIkW0/vHTXwQOQw3cUsg==", "dependencies": { - "@aws-sdk/client-sts": "3.552.0", + "@aws-sdk/client-sts": "3.554.0", "@aws-sdk/credential-provider-env": "3.535.0", "@aws-sdk/credential-provider-process": "3.535.0", - "@aws-sdk/credential-provider-sso": "3.552.0", - "@aws-sdk/credential-provider-web-identity": "3.552.0", + "@aws-sdk/credential-provider-sso": "3.554.0", + "@aws-sdk/credential-provider-web-identity": "3.554.0", "@aws-sdk/types": "3.535.0", "@smithy/credential-provider-imds": "^2.3.0", "@smithy/property-provider": "^2.2.0", @@ -3724,88 +1532,17 @@ "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/client-sts": { - "version": "3.552.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.552.0.tgz", - "integrity": "sha512-rOZlAj8GyFgUBESyKezes67A8Kj5+KjRhfBHMXrkcM5h9UOIz5q7QdkSQOmzWwRoPDmmAqb6t+y041/76TnPEg==", - "peer": true, - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.552.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.540.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.540.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.2", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.1", - "@smithy/middleware-retry": "^2.3.1", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.1", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.1", - "@smithy/util-defaults-mode-node": "^2.3.1", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "@aws-sdk/credential-provider-node": "^3.552.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/core": { - "version": "3.552.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.552.0.tgz", - "integrity": "sha512-T7ovljf6fCvIHG9SOSZqGmbVbqZPXPywLAcU+onk/fYLZJj6kjfzKZzSAUBI0nO1OKpuP/nCHaCp51NLWNqsnw==", - "peer": true, - "dependencies": { - "@smithy/core": "^1.4.2", - "@smithy/protocol-http": "^3.3.0", - "@smithy/signature-v4": "^2.2.1", - "@smithy/smithy-client": "^2.5.1", - "@smithy/types": "^2.12.0", - "fast-xml-parser": "4.2.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.552.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.552.0.tgz", - "integrity": "sha512-GUH5awokiR4FcALeQxOrNZtDKJgzEza6NW9HYxAaHt0LNSHCjG21zMFDPYAXlDjlPP9AIdWmVvYrfJoPJI28AQ==", - "peer": true, + "version": "3.554.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.554.0.tgz", + "integrity": "sha512-poX/+2OE3oxqp4f5MiaJh251p8l+bzcFwgcDBwz0e2rcpvMSYl9jw4AvGnCiG2bmf9yhNJdftBiS1A+KjxV0qA==", "dependencies": { "@aws-sdk/credential-provider-env": "3.535.0", "@aws-sdk/credential-provider-http": "3.552.0", - "@aws-sdk/credential-provider-ini": "3.552.0", + "@aws-sdk/credential-provider-ini": "3.554.0", "@aws-sdk/credential-provider-process": "3.535.0", - "@aws-sdk/credential-provider-sso": "3.552.0", - "@aws-sdk/credential-provider-web-identity": "3.552.0", + "@aws-sdk/credential-provider-sso": "3.554.0", + "@aws-sdk/credential-provider-web-identity": "3.554.0", "@aws-sdk/types": "3.535.0", "@smithy/credential-provider-imds": "^2.3.0", "@smithy/property-provider": "^2.2.0", @@ -3833,13 +1570,12 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.552.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.552.0.tgz", - "integrity": "sha512-h+xyWG4HMqf4SFzilpK1u50fO2aIBRg3nwuXRy9v5E2qdpJgZS2JXibO1jNHd+JXq4qjs2YG1WK2fGcdxZJ2bQ==", - "peer": true, + "version": "3.554.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.554.0.tgz", + "integrity": "sha512-8QPpwBA31i/fZ7lDZJC4FA9EdxLg5SJ8sPB2qLSjp5UTGTYL2HRl0Eznkb7DXyp/wImsR/HFR1NxuFCCVotLCg==", "dependencies": { - "@aws-sdk/client-sso": "3.552.0", - "@aws-sdk/token-providers": "3.552.0", + "@aws-sdk/client-sso": "3.554.0", + "@aws-sdk/token-providers": "3.554.0", "@aws-sdk/types": "3.535.0", "@smithy/property-provider": "^2.2.0", "@smithy/shared-ini-file-loader": "^2.4.0", @@ -3851,12 +1587,11 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.552.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.552.0.tgz", - "integrity": "sha512-6jXfXaLKDy3S4LHR8ZXIIZw5B80uiYjnPp4bmqmY18LGeoZxmkJ/SfkwypVruezCu+GpA+IubmIbc5TQi6BCAw==", - "peer": true, + "version": "3.554.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.554.0.tgz", + "integrity": "sha512-HN54DzLjepw5ZWSF9ycGevhFTyg6pjLuLKy5Y8t/f1jFDComzYdGEDe0cdV9YO653W3+PQwZZGz09YVygGYBLg==", "dependencies": { - "@aws-sdk/client-sts": "3.552.0", + "@aws-sdk/client-sts": "3.554.0", "@aws-sdk/types": "3.535.0", "@smithy/property-provider": "^2.2.0", "@smithy/types": "^2.12.0", @@ -3866,84 +1601,14 @@ "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@aws-sdk/client-sts": { - "version": "3.552.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.552.0.tgz", - "integrity": "sha512-rOZlAj8GyFgUBESyKezes67A8Kj5+KjRhfBHMXrkcM5h9UOIz5q7QdkSQOmzWwRoPDmmAqb6t+y041/76TnPEg==", - "peer": true, - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.552.0", - "@aws-sdk/middleware-host-header": "3.535.0", - "@aws-sdk/middleware-logger": "3.535.0", - "@aws-sdk/middleware-recursion-detection": "3.535.0", - "@aws-sdk/middleware-user-agent": "3.540.0", - "@aws-sdk/region-config-resolver": "3.535.0", - "@aws-sdk/types": "3.535.0", - "@aws-sdk/util-endpoints": "3.540.0", - "@aws-sdk/util-user-agent-browser": "3.535.0", - "@aws-sdk/util-user-agent-node": "3.535.0", - "@smithy/config-resolver": "^2.2.0", - "@smithy/core": "^1.4.2", - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/hash-node": "^2.2.0", - "@smithy/invalid-dependency": "^2.2.0", - "@smithy/middleware-content-length": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.1", - "@smithy/middleware-retry": "^2.3.1", - "@smithy/middleware-serde": "^2.3.0", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/smithy-client": "^2.5.1", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-body-length-browser": "^2.2.0", - "@smithy/util-body-length-node": "^2.3.0", - "@smithy/util-defaults-mode-browser": "^2.2.1", - "@smithy/util-defaults-mode-node": "^2.3.1", - "@smithy/util-endpoints": "^1.2.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "@aws-sdk/credential-provider-node": "^3.552.0" - } - }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@aws-sdk/core": { - "version": "3.552.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.552.0.tgz", - "integrity": "sha512-T7ovljf6fCvIHG9SOSZqGmbVbqZPXPywLAcU+onk/fYLZJj6kjfzKZzSAUBI0nO1OKpuP/nCHaCp51NLWNqsnw==", - "peer": true, - "dependencies": { - "@smithy/core": "^1.4.2", - "@smithy/protocol-http": "^3.3.0", - "@smithy/signature-v4": "^2.2.1", - "@smithy/smithy-client": "^2.5.1", - "@smithy/types": "^2.12.0", - "fast-xml-parser": "4.2.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/@aws-sdk/lib-storage": { - "version": "3.550.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.550.0.tgz", - "integrity": "sha512-zDUM4hV/t148DCXschwDusH9tzg7U1MpuUaUPJlklx9Va+NnjrjtWHwL/JeZ5sfGR/1wTZIg3sKho/4P2oAYrQ==", + "version": "3.554.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.554.0.tgz", + "integrity": "sha512-WMn2EObllRKI0ELi31SoUGPowQ23/LCAXkG1o1VEas5kqobwgVgp9D8zqs9A/MEaZYl0yDqd94uKQJd7rUM/yg==", "dependencies": { "@smithy/abort-controller": "^2.2.0", - "@smithy/middleware-endpoint": "^2.5.0", - "@smithy/smithy-client": "^2.5.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/smithy-client": "^2.5.1", "buffer": "5.6.0", "events": "3.3.0", "stream-browserify": "3.0.0", @@ -3956,6 +1621,15 @@ "@aws-sdk/client-s3": "^3.0.0" } }, + "node_modules/@aws-sdk/lib-storage/node_modules/buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { "version": "3.535.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.535.0.tgz", @@ -4060,16 +1734,16 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.535.0.tgz", - "integrity": "sha512-/dLG/E3af6ohxkQ5GBHT8tZfuPIg6eItKxCXuulvYj0Tqgf3Mb+xTsvSkxQsJF06RS4sH7Qsg/PnB8ZfrJrXpg==", + "version": "3.552.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.552.0.tgz", + "integrity": "sha512-9KzOqsbwJJuQcpmrpkkIftjPahB1bsrcWalYzcVqKCgHCylhkSHW2tX+uGHRnvAl9iobQD5D7LUrS+cv0NeQ/Q==", "dependencies": { "@aws-sdk/types": "3.535.0", "@aws-sdk/util-arn-parser": "3.535.0", "@smithy/node-config-provider": "^2.3.0", "@smithy/protocol-http": "^3.3.0", - "@smithy/signature-v4": "^2.2.0", - "@smithy/smithy-client": "^2.5.0", + "@smithy/signature-v4": "^2.2.1", + "@smithy/smithy-client": "^2.5.1", "@smithy/types": "^2.12.0", "@smithy/util-config-provider": "^2.3.0", "tslib": "^2.6.2" @@ -4079,14 +1753,14 @@ } }, "node_modules/@aws-sdk/middleware-signing": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.535.0.tgz", - "integrity": "sha512-Rb4sfus1Gc5paRl9JJgymJGsb/i3gJKK/rTuFZICdd1PBBE5osIOHP5CpzWYBtc5LlyZE1a2QoxPMCyG+QUGPw==", + "version": "3.552.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.552.0.tgz", + "integrity": "sha512-ZjOrlEmwjhbmkINa4Zx9LJh+xb/kgEiUrcfud2kq/r8ath1Nv1/4zalI9jHnou1J+R+yS+FQlXLXHSZ7vqyFbA==", "dependencies": { "@aws-sdk/types": "3.535.0", "@smithy/property-provider": "^2.2.0", "@smithy/protocol-http": "^3.3.0", - "@smithy/signature-v4": "^2.2.0", + "@smithy/signature-v4": "^2.2.1", "@smithy/types": "^2.12.0", "@smithy/util-middleware": "^2.2.0", "tslib": "^2.6.2" @@ -4140,14 +1814,14 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.535.0.tgz", - "integrity": "sha512-tqCsEsEj8icW0SAh3NvyhRUq54Gz2pu4NM2tOSrFp7SO55heUUaRLSzYteNZCTOupH//AAaZvbN/UUTO/DrOog==", + "version": "3.552.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.552.0.tgz", + "integrity": "sha512-cC11/5ahp+LaBCq7cR+51AM2ftf6m9diRd2oWkbEpjSiEKQzZRAltUPZAJM6NXGypmDODQDJphLGt45tvS+8kg==", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.535.0", + "@aws-sdk/middleware-sdk-s3": "3.552.0", "@aws-sdk/types": "3.535.0", "@smithy/protocol-http": "^3.3.0", - "@smithy/signature-v4": "^2.2.0", + "@smithy/signature-v4": "^2.2.1", "@smithy/types": "^2.12.0", "tslib": "^2.6.2" }, @@ -4156,12 +1830,11 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.552.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.552.0.tgz", - "integrity": "sha512-5dNE2KqtgkT+DQXfkSmzmVSB72LpjSIK86lLD9LeQ1T+b0gfEd74MAl/AGC15kQdKLg5I3LlN5q32f1fkmYR8g==", - "peer": true, + "version": "3.554.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.554.0.tgz", + "integrity": "sha512-KMMQ5Cw0FUPL9H8g69Lp08xtzRo7r/MK+lBV6LznWBbCP/NwtZ8awVHaPy2P31z00cWtu9MYkUTviWPqJTaBvg==", "dependencies": { - "@aws-sdk/client-sso-oidc": "3.552.0", + "@aws-sdk/client-sso-oidc": "3.554.0", "@aws-sdk/types": "3.535.0", "@smithy/property-provider": "^2.2.0", "@smithy/shared-ini-file-loader": "^2.4.0", @@ -6458,9 +4131,9 @@ "peer": true }, "node_modules/@codemirror/view": { - "version": "6.26.2", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.26.2.tgz", - "integrity": "sha512-j6V48PlFC/O7ERAR5vRW5QKDdchzmyyfojDdt+zPsB0YXoWgcjlC1IWjmlYfx08aQZ3HN5BtALcgGgtSKGMe7A==", + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.26.3.tgz", + "integrity": "sha512-gmqxkPALZjkgSxIeeweY/wGQXBfwTUaLs8h7OKtSwfbj9Ct3L11lD+u1sS7XHppxFQoMDiMDp07P9f3I2jWOHw==", "dev": true, "peer": true, "dependencies": { @@ -6502,9 +4175,9 @@ } }, "node_modules/@cyclonedx/cyclonedx-library": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/@cyclonedx/cyclonedx-library/-/cyclonedx-library-6.4.2.tgz", - "integrity": "sha512-5nnjdPARe+CiiIsdk4M3plO7QccBl1JAkWjciPi4n2PZFiF5dXgyFdTTSRwRFA2xIb1LYMZotc0Gz0PKBPv+bA==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@cyclonedx/cyclonedx-library/-/cyclonedx-library-6.5.0.tgz", + "integrity": "sha512-mPlYdNdlYbDLqlwa8xIVLhVeJjNaW3u3rGCjyKxOg+UUIaskuRnOuNkEE7csSK8QavgC/0Px1lihz9OrpWhGQg==", "dev": true, "funding": [ { @@ -7252,15 +4925,6 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/@docusaurus/core/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/@docusaurus/cssnano-preset": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.2.1.tgz", @@ -8655,15 +6319,6 @@ "node": ">= 10.0.0" } }, - "node_modules/@docusaurus/utils/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/@emotion/babel-plugin": { "version": "11.11.0", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", @@ -9568,6 +7223,14 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/@expo/cli/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/@expo/cli/node_modules/cli-cursor": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", @@ -10024,6 +7687,30 @@ "node": ">=8" } }, + "node_modules/@expo/cli/node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@expo/cli/node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, "node_modules/@expo/cli/node_modules/tempy": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.7.1.tgz", @@ -11518,9 +9205,9 @@ } }, "node_modules/@floating-ui/react": { - "version": "0.26.11", - "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.11.tgz", - "integrity": "sha512-fo01Cu+jzLDVG/AYAV2OtV6flhXvxP5rDaR1Fk8WWhtsFqwk478Dr2HGtB8s0HqQCsFWVbdHYpPjMiQiR/A9VA==", + "version": "0.26.12", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.12.tgz", + "integrity": "sha512-D09o62HrWdIkstF2kGekIKAC0/N/Dl6wo3CQsnLcOmO3LkW6Ik8uIb3kw8JYkwxNCcg+uJ2bpWUiIijTBep05w==", "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@floating-ui/utils": "^0.2.0", @@ -11563,25 +9250,6 @@ "unicode-trie": "^2.0.0" } }, - "node_modules/@foliojs-fork/fontkit/node_modules/deep-equal": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz", - "integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==", - "dependencies": { - "is-arguments": "^1.1.1", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.5.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/@foliojs-fork/linebreak": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@foliojs-fork/linebreak/-/linebreak-1.1.2.tgz", @@ -11843,9 +9511,9 @@ } }, "node_modules/@headlessui/react": { - "version": "1.7.18", - "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.18.tgz", - "integrity": "sha512-4i5DOrzwN4qSgNsL4Si61VMkUcWbcSKueUV7sFhpHzQcSShdlHENE5+QBntMSRvHt8NyoFO2AGG8si9lq+w4zQ==", + "version": "1.7.19", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.19.tgz", + "integrity": "sha512-Ll+8q3OlMJfJbAKM/+/Y2q6PPYbryqNTXDbryx7SXLIDamkF6iQFbriYHga0dY44PvDhvvBWCx1Xj4U5+G4hOw==", "dev": true, "dependencies": { "@tanstack/react-virtual": "^3.0.0-beta.60", @@ -11910,7 +9578,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -11923,6 +9590,17 @@ "node": ">=12" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.0.tgz", + "integrity": "sha512-S00nN1Qt3z3dSP6Db45fj/mksrAq5XWNIJ/SWXGP8XPT2jrzEuYRCSEx08JpJwBcG2F1xgiOtBMGDU0AZHmxew==", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@isaacs/ttlcache": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", @@ -12798,9 +10476,9 @@ } }, "node_modules/@lhncbc/ucum-lhc": { - "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==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@lhncbc/ucum-lhc/-/ucum-lhc-5.0.4.tgz", + "integrity": "sha512-khuV9GV51DF80b0wJmhZTR5Bf23fhS6SSIWnyGT9X+Uvn0FsHFl2LKViQ2TTOuvwagUOUSq8/0SyoE2ZDGwrAA==", "dev": true, "dependencies": { "coffeescript": "^2.7.0", @@ -12816,9 +10494,9 @@ } }, "node_modules/@mantine/core": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.7.1.tgz", - "integrity": "sha512-SdPzjvqvEK7uHFuVD3a8w3OZyQVoCwIXLSUfOtRNouDMQgsq6Ac7QjKXBBOk3wNweOWFVOU1vATLHobSmow0lQ==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.8.0.tgz", + "integrity": "sha512-19RKuNdJ/s8pZjy2w2rvTsl4ybi/XM6vf+Kc0WY7kpLFCvdG+/UxNi1MuJF8t2Zs0QSFeb/H5yZQNe0XPbegHw==", "dependencies": { "@floating-ui/react": "^0.26.9", "clsx": "2.1.0", @@ -12828,53 +10506,53 @@ "type-fest": "^4.12.0" }, "peerDependencies": { - "@mantine/hooks": "7.7.1", + "@mantine/hooks": "7.8.0", "react": "^18.2.0", "react-dom": "^18.2.0" } }, "node_modules/@mantine/dropzone": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@mantine/dropzone/-/dropzone-7.7.1.tgz", - "integrity": "sha512-B5DmWGX0F7yDem+Y+fQy4ZIBy/DkTd9TmlqPhCy8MYdC33I3GSv9o/MAnqbeG0tEnS0X1Aw6h5mnqnsVFYpf2w==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@mantine/dropzone/-/dropzone-7.8.0.tgz", + "integrity": "sha512-rpNTR3NASvI3BnqhY5wg3BDhxkABT9UoZEGRrOGnS3YU7SYXg5rT9ch5Cm4iPwMNdwsyAIU6K2ii4wWk40dRpg==", "dev": true, "dependencies": { "react-dropzone-esm": "15.0.1" }, "peerDependencies": { - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", "react": "^18.2.0", "react-dom": "^18.2.0" } }, "node_modules/@mantine/hooks": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.7.1.tgz", - "integrity": "sha512-3YH2FzKMlg840tb04PBDcDXyBCi9puFOxEBVgc6Y/pN6KFqfOoAnQE/YvgOtwSNXZlbTWyDlQoYj+3je7pA7og==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.8.0.tgz", + "integrity": "sha512-+70fkgjhVJeJ+nJqnburIM3UAsfvxat1Low9HMPobLbv64FIdB4Nzu5ct3qojNQ58r5sK01tg5UoFIJYslaVrg==", "peerDependencies": { "react": "^18.2.0" } }, "node_modules/@mantine/notifications": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-7.7.1.tgz", - "integrity": "sha512-UGs3r4CU2hy1Vt0TVtdorDufpI7LWCd4P7qZP0US+mXVeeZqHkNTCiwRTwlledhfaIdqERmmQn9OD2lJu8Wblg==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-7.8.0.tgz", + "integrity": "sha512-O7BnaCcwVg38fh+gSZ6GEsTFPPgJAiOTrRkOMXG+7pNqJT9YNa9KDZhiPZzn3WV4wexncjyK32a8gGSVtf+kdg==", "dependencies": { - "@mantine/store": "7.7.1", + "@mantine/store": "7.8.0", "react-transition-group": "4.4.5" }, "peerDependencies": { - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", "react": "^18.2.0", "react-dom": "^18.2.0" } }, "node_modules/@mantine/store": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@mantine/store/-/store-7.7.1.tgz", - "integrity": "sha512-dmuCOLCFlVHYhZARFsi5YckFQR2Vr4giOgs0X1hczqCVUnRDRIgRusAO5GjUhQrtNxfN0EWwFywjLdcrLkA6Lg==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@mantine/store/-/store-7.8.0.tgz", + "integrity": "sha512-oN/BXGYdUywRi0zj9ppaShv2sw5QON2DaRisB4ewJ5tDDz8qyeckgdE0NMaaU2TwpoScs8ibSnOVWV5y+vYkMA==", "peerDependencies": { "react": "^18.2.0" } @@ -12900,6 +10578,42 @@ "node-pre-gyp": "bin/node-pre-gyp" } }, + "node_modules/@mapbox/node-pre-gyp/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@mapbox/node-pre-gyp/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -12947,6 +10661,29 @@ "node": "*" } }, + "node_modules/@mapbox/node-pre-gyp/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@mapbox/node-pre-gyp/node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -12979,6 +10716,24 @@ "node": ">=10" } }, + "node_modules/@mapbox/node-pre-gyp/node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "optional": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@mapbox/node-pre-gyp/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -13147,16 +10902,16 @@ "link": true }, "node_modules/@microsoft/api-documenter": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.24.1.tgz", - "integrity": "sha512-DX332aznb5SWpOLGuymvzg2OZsQ5+dCbSm8yvVYqTylDgSAiPouvKrdlWPoEeicuLU8wzxSl3xv7DMb6xgYwPw==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.24.2.tgz", + "integrity": "sha512-q03DXLBj7nzAzLyLRAVklBynqKgSFI/JBmrhF/mEEIpg8orNo4qKXWO1RSkD2IYrqvZV63b13mcUPYgcFdifQA==", "dev": true, "dependencies": { - "@microsoft/api-extractor-model": "7.28.13", + "@microsoft/api-extractor-model": "7.28.14", "@microsoft/tsdoc": "0.14.2", - "@rushstack/node-core-library": "4.0.2", - "@rushstack/terminal": "0.10.0", - "@rushstack/ts-command-line": "4.19.1", + "@rushstack/node-core-library": "4.1.0", + "@rushstack/terminal": "0.10.1", + "@rushstack/ts-command-line": "4.19.2", "js-yaml": "~3.13.1", "resolve": "~1.22.1" }, @@ -13165,18 +10920,18 @@ } }, "node_modules/@microsoft/api-extractor": { - "version": "7.43.0", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.43.0.tgz", - "integrity": "sha512-GFhTcJpB+MI6FhvXEI9b2K0snulNLWHqC/BbcJtyNYcKUiw7l3Lgis5ApsYncJ0leALX7/of4XfmXk+maT111w==", + "version": "7.43.1", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.43.1.tgz", + "integrity": "sha512-ohg40SsvFFgzHFAtYq5wKJc8ZDyY46bphjtnSvhSSlXpPTG7GHwyyXkn48UZiUCBwr2WC7TRC1Jfwz7nreuiyQ==", "dev": true, "dependencies": { - "@microsoft/api-extractor-model": "7.28.13", + "@microsoft/api-extractor-model": "7.28.14", "@microsoft/tsdoc": "0.14.2", "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "4.0.2", + "@rushstack/node-core-library": "4.1.0", "@rushstack/rig-package": "0.5.2", - "@rushstack/terminal": "0.10.0", - "@rushstack/ts-command-line": "4.19.1", + "@rushstack/terminal": "0.10.1", + "@rushstack/ts-command-line": "4.19.2", "lodash": "~4.17.15", "minimatch": "~3.0.3", "resolve": "~1.22.1", @@ -13189,14 +10944,14 @@ } }, "node_modules/@microsoft/api-extractor-model": { - "version": "7.28.13", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.28.13.tgz", - "integrity": "sha512-39v/JyldX4MS9uzHcdfmjjfS6cYGAoXV+io8B5a338pkHiSt+gy2eXQ0Q7cGFJ7quSa1VqqlMdlPrB6sLR/cAw==", + "version": "7.28.14", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.28.14.tgz", + "integrity": "sha512-Bery/c8A8SsKPSvA82cTTuy/+OcxZbLRmKhPkk91/AJOQzxZsShcrmHFAGeiEqSIrv1nPZ3tKq9kfMLdCHmsqg==", "dev": true, "dependencies": { "@microsoft/tsdoc": "0.14.2", "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "4.0.2" + "@rushstack/node-core-library": "4.1.0" } }, "node_modules/@microsoft/api-extractor/node_modules/lru-cache": { @@ -13455,14 +11210,14 @@ } }, "node_modules/@next/env": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.1.4.tgz", - "integrity": "sha512-e7X7bbn3Z6DWnDi75UWn+REgAbLEqxI8Tq2pkFOFAMpWAWApz/YCUhtWMWn410h8Q2fYiYL7Yg5OlxMOCfFjJQ==" + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.1.tgz", + "integrity": "sha512-qsHJle3GU3CmVx7pUoXcghX4sRN+vINkbLdH611T8ZlsP//grzqVW87BSUgOZeSAD4q7ZdZicdwNe/20U2janA==" }, "node_modules/@next/eslint-plugin-next": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.1.4.tgz", - "integrity": "sha512-n4zYNLSyCo0Ln5b7qxqQeQ34OZKXwgbdcx6kmkQbywr+0k6M3Vinft0T72R6CDAcDrne2IAgSud4uWCzFgc5HA==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.1.tgz", + "integrity": "sha512-Fp+mthEBjkn8r9qd6o4JgxKp0IDEzW0VYHD8ZC05xS5/lFNwHKuOdr2kVhWG7BQCO9L6eeepshM1Wbs2T+LgSg==", "dev": true, "dependencies": { "glob": "10.3.10" @@ -13514,19 +11269,10 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@next/eslint-plugin-next/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.4.tgz", - "integrity": "sha512-ubmUkbmW65nIAOmoxT1IROZdmmJMmdYvXIe8211send9ZYJu+SqxSnJM4TrPj9wmL6g9Atvj0S/2cFmMSS99jg==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.1.tgz", + "integrity": "sha512-kGjnjcIJehEcd3rT/3NAATJQndAEELk0J9GmGMXHSC75TMnvpOhONcjNHbjtcWE5HUQnIHy5JVkatrnYm1QhVw==", "cpu": [ "arm64" ], @@ -13539,9 +11285,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.4.tgz", - "integrity": "sha512-b0Xo1ELj3u7IkZWAKcJPJEhBop117U78l70nfoQGo4xUSvv0PJSTaV4U9xQBLvZlnjsYkc8RwQN1HoH/oQmLlQ==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.1.tgz", + "integrity": "sha512-dAdWndgdQi7BK2WSXrx4lae7mYcOYjbHJUhvOUnJjMNYrmYhxbbvJ2xElZpxNxdfA6zkqagIB9He2tQk+l16ew==", "cpu": [ "x64" ], @@ -13554,9 +11300,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.4.tgz", - "integrity": "sha512-457G0hcLrdYA/u1O2XkRMsDKId5VKe3uKPvrKVOyuARa6nXrdhJOOYU9hkKKyQTMru1B8qEP78IAhf/1XnVqKA==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.1.tgz", + "integrity": "sha512-2ZctfnyFOGvTkoD6L+DtQtO3BfFz4CapoHnyLTXkOxbZkVRgg3TQBUjTD/xKrO1QWeydeo8AWfZRg8539qNKrg==", "cpu": [ "arm64" ], @@ -13569,9 +11315,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.4.tgz", - "integrity": "sha512-l/kMG+z6MB+fKA9KdtyprkTQ1ihlJcBh66cf0HvqGP+rXBbOXX0dpJatjZbHeunvEHoBBS69GYQG5ry78JMy3g==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.1.tgz", + "integrity": "sha512-jazZXctiaanemy4r+TPIpFP36t1mMwWCKMsmrTRVChRqE6putyAxZA4PDujx0SnfvZHosjdkx9xIq9BzBB5tWg==", "cpu": [ "arm64" ], @@ -13584,9 +11330,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.4.tgz", - "integrity": "sha512-BapIFZ3ZRnvQ1uWbmqEGJuPT9cgLwvKtxhK/L2t4QYO7l+/DxXuIGjvp1x8rvfa/x1FFSsipERZK70pewbtJtw==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.1.tgz", + "integrity": "sha512-VjCHWCjsAzQAAo8lkBOLEIkBZFdfW+Z18qcQ056kL4KpUYc8o59JhLDCBlhg+hINQRgzQ2UPGma2AURGOH0+Qg==", "cpu": [ "x64" ], @@ -13599,9 +11345,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.4.tgz", - "integrity": "sha512-mqVxTwk4XuBl49qn2A5UmzFImoL1iLm0KQQwtdRJRKl21ylQwwGCxJtIYo2rbfkZHoSKlh/YgztY0qH3wG1xIg==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.1.tgz", + "integrity": "sha512-7HZKYKvAp4nAHiHIbY04finRqjeYvkITOGOurP1aLMexIFG/1+oCnqhGogBdc4lao/lkMW1c+AkwWSzSlLasqw==", "cpu": [ "x64" ], @@ -13614,9 +11360,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.4.tgz", - "integrity": "sha512-xzxF4ErcumXjO2Pvg/wVGrtr9QQJLk3IyQX1ddAC/fi6/5jZCZ9xpuL9Tzc4KPWMFq8GGWFVDMshZOdHGdkvag==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.1.tgz", + "integrity": "sha512-YGHklaJ/Cj/F0Xd8jxgj2p8po4JTCi6H7Z3Yics3xJhm9CPIqtl8erlpK1CLv+HInDqEWfXilqatF8YsLxxA2Q==", "cpu": [ "arm64" ], @@ -13629,9 +11375,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.4.tgz", - "integrity": "sha512-WZiz8OdbkpRw6/IU/lredZWKKZopUMhcI2F+XiMAcPja0uZYdMTZQRoQ0WZcvinn9xZAidimE7tN9W5v9Yyfyw==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.1.tgz", + "integrity": "sha512-o+ISKOlvU/L43ZhtAAfCjwIfcwuZstiHVXq/BDsZwGqQE0h/81td95MPHliWCnFoikzWcYqh+hz54ZB2FIT8RA==", "cpu": [ "ia32" ], @@ -13644,9 +11390,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.4.tgz", - "integrity": "sha512-4Rto21sPfw555sZ/XNLqfxDUNeLhNYGO2dlPqsnuCg8N8a2a9u1ltqBOPQ4vj1Gf7eJC0W2hHG2eYUHuiXgY2w==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.1.tgz", + "integrity": "sha512-GmRoTiLcvCLifujlisknv4zu9/C4i9r0ktsA8E51EMqJL4bD4CpO7lDYr7SrUxCR0tS4RVcrqKmCak24T0ohaw==", "cpu": [ "x64" ], @@ -14477,6 +12223,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.5.0", "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cucumber/-/instrumentation-cucumber-0.5.0.tgz", @@ -15516,7 +13270,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, "optional": true, "engines": { "node": ">=14" @@ -16653,6 +14406,17 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/@react-native-community/cli-doctor/node_modules/yaml": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz", + "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/@react-native-community/cli-hermes": { "version": "12.3.6", "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-12.3.6.tgz", @@ -18079,9 +15843,9 @@ "dev": true }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.1.tgz", - "integrity": "sha512-fH8/o8nSUek8ceQnT7K4EQbSiV7jgkHq81m9lWZFIXjJ7lJzpWXbQFpT/Zh6OZYnpFykvzC3fbEvEAFZu03dPA==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.3.tgz", + "integrity": "sha512-X9alQ3XM6I9IlSlmC8ddAvMSyG1WuHk5oUnXGw+yUBs3BFoTizmG1La/Gr8fVJvDWAq+zlYTZ9DBgrlKRVY06g==", "cpu": [ "arm" ], @@ -18092,9 +15856,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.1.tgz", - "integrity": "sha512-Y/9OHLjzkunF+KGEoJr3heiD5X9OLa8sbT1lm0NYeKyaM3oMhhQFvPB0bNZYJwlq93j8Z6wSxh9+cyKQaxS7PQ==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.3.tgz", + "integrity": "sha512-eQK5JIi+POhFpzk+LnjKIy4Ks+pwJ+NXmPxOCSvOKSNRPONzKuUvWE+P9JxGZVxrtzm6BAYMaL50FFuPe0oWMQ==", "cpu": [ "arm64" ], @@ -18105,9 +15869,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.1.tgz", - "integrity": "sha512-+kecg3FY84WadgcuSVm6llrABOdQAEbNdnpi5X3UwWiFVhZIZvKgGrF7kmLguvxHNQy+UuRV66cLVl3S+Rkt+Q==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.3.tgz", + "integrity": "sha512-Od4vE6f6CTT53yM1jgcLqNfItTsLt5zE46fdPaEmeFHvPs5SjZYlLpHrSiHEKR1+HdRfxuzXHjDOIxQyC3ptBA==", "cpu": [ "arm64" ], @@ -18118,9 +15882,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.1.tgz", - "integrity": "sha512-2pYRzEjVqq2TB/UNv47BV/8vQiXkFGVmPFwJb+1E0IFFZbIX8/jo1olxqqMbo6xCXf8kabANhp5bzCij2tFLUA==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.3.tgz", + "integrity": "sha512-0IMAO21axJeNIrvS9lSe/PGthc8ZUS+zC53O0VhF5gMxfmcKAP4ESkKOCwEi6u2asUrt4mQv2rjY8QseIEb1aw==", "cpu": [ "x64" ], @@ -18131,9 +15895,22 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.1.tgz", - "integrity": "sha512-mS6wQ6Do6/wmrF9aTFVpIJ3/IDXhg1EZcQFYHZLHqw6AzMBjTHWnCG35HxSqUNphh0EHqSM6wRTT8HsL1C0x5g==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.3.tgz", + "integrity": "sha512-ge2DC7tHRHa3caVEoSbPRJpq7azhG+xYsd6u2MEnJ6XzPSzQsTKyXvh6iWjXRf7Rt9ykIUWHtl0Uz3T6yXPpKw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.14.3.tgz", + "integrity": "sha512-ljcuiDI4V3ySuc7eSk4lQ9wU8J8r8KrOUvB2U+TtK0TiW6OFDmJ+DdIjjwZHIw9CNxzbmXY39wwpzYuFDwNXuw==", "cpu": [ "arm" ], @@ -18144,9 +15921,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.1.tgz", - "integrity": "sha512-p9rGKYkHdFMzhckOTFubfxgyIO1vw//7IIjBBRVzyZebWlzRLeNhqxuSaZ7kCEKVkm/kuC9fVRW9HkC/zNRG2w==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.3.tgz", + "integrity": "sha512-Eci2us9VTHm1eSyn5/eEpaC7eP/mp5n46gTRB3Aar3BgSvDQGJZuicyq6TsH4HngNBgVqC5sDYxOzTExSU+NjA==", "cpu": [ "arm64" ], @@ -18157,9 +15934,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.1.tgz", - "integrity": "sha512-nDY6Yz5xS/Y4M2i9JLQd3Rofh5OR8Bn8qe3Mv/qCVpHFlwtZSBYSPaU4mrGazWkXrdQ98GB//H0BirGR/SKFSw==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.3.tgz", + "integrity": "sha512-UrBoMLCq4E92/LCqlh+blpqMz5h1tJttPIniwUgOFJyjWI1qrtrDhhpHPuFxULlUmjFHfloWdixtDhSxJt5iKw==", "cpu": [ "arm64" ], @@ -18170,11 +15947,11 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.1.tgz", - "integrity": "sha512-im7HE4VBL+aDswvcmfx88Mp1soqL9OBsdDBU8NqDEYtkri0qV0THhQsvZtZeNNlLeCUQ16PZyv7cqutjDF35qw==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.3.tgz", + "integrity": "sha512-5aRjvsS8q1nWN8AoRfrq5+9IflC3P1leMoy4r2WjXyFqf3qcqsxRCfxtZIV58tCxd+Yv7WELPcO9mY9aeQyAmw==", "cpu": [ - "ppc64le" + "ppc64" ], "dev": true, "optional": true, @@ -18183,9 +15960,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.1.tgz", - "integrity": "sha512-RWdiHuAxWmzPJgaHJdpvUUlDz8sdQz4P2uv367T2JocdDa98iRw2UjIJ4QxSyt077mXZT2X6pKfT2iYtVEvOFw==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.3.tgz", + "integrity": "sha512-sk/Qh1j2/RJSX7FhEpJn8n0ndxy/uf0kI/9Zc4b1ELhqULVdTfN6HL31CDaTChiBAOgLcsJ1sgVZjWv8XNEsAQ==", "cpu": [ "riscv64" ], @@ -18196,9 +15973,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.1.tgz", - "integrity": "sha512-VMgaGQ5zRX6ZqV/fas65/sUGc9cPmsntq2FiGmayW9KMNfWVG/j0BAqImvU4KTeOOgYSf1F+k6at1UfNONuNjA==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.3.tgz", + "integrity": "sha512-jOO/PEaDitOmY9TgkxF/TQIjXySQe5KVYB57H/8LRP/ux0ZoO8cSHCX17asMSv3ruwslXW/TLBcxyaUzGRHcqg==", "cpu": [ "s390x" ], @@ -18209,9 +15986,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.1.tgz", - "integrity": "sha512-9Q7DGjZN+hTdJomaQ3Iub4m6VPu1r94bmK2z3UeWP3dGUecRC54tmVu9vKHTm1bOt3ASoYtEz6JSRLFzrysKlA==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.3.tgz", + "integrity": "sha512-8ybV4Xjy59xLMyWo3GCfEGqtKV5M5gCSrZlxkPGvEPCGDLNla7v48S662HSGwRd6/2cSneMQWiv+QzcttLrrOA==", "cpu": [ "x64" ], @@ -18222,9 +15999,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.1.tgz", - "integrity": "sha512-JNEG/Ti55413SsreTguSx0LOVKX902OfXIKVg+TCXO6Gjans/k9O6ww9q3oLGjNDaTLxM+IHFMeXy/0RXL5R/g==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.3.tgz", + "integrity": "sha512-s+xf1I46trOY10OqAtZ5Rm6lzHre/UiLA1J2uOhCFXWkbZrJRkYBPO6FhvGfHmdtQ3Bx793MNa7LvoWFAm93bg==", "cpu": [ "x64" ], @@ -18235,9 +16012,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.1.tgz", - "integrity": "sha512-ryS22I9y0mumlLNwDFYZRDFLwWh3aKaC72CWjFcFvxK0U6v/mOkM5Up1bTbCRAhv3kEIwW2ajROegCIQViUCeA==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.3.tgz", + "integrity": "sha512-+4h2WrGOYsOumDQ5S2sYNyhVfrue+9tc9XcLWLh+Kw3UOxAvrfOrSMFon60KspcDdytkNDh7K2Vs6eMaYImAZg==", "cpu": [ "arm64" ], @@ -18248,9 +16025,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.1.tgz", - "integrity": "sha512-TdloItiGk+T0mTxKx7Hp279xy30LspMso+GzQvV2maYePMAWdmrzqSNZhUpPj3CGw12aGj57I026PgLCTu8CGg==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.3.tgz", + "integrity": "sha512-T1l7y/bCeL/kUwh9OD4PQT4aM7Bq43vX05htPJJ46RTI4r5KNt6qJRzAfNfM+OYMNEVBWQzR2Gyk+FXLZfogGw==", "cpu": [ "ia32" ], @@ -18261,9 +16038,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.1.tgz", - "integrity": "sha512-wQGI+LY/Py20zdUPq+XCem7JcPOyzIJBm3dli+56DJsQOHbnXZFEwgmnC6el1TPAfC8lBT3m+z69RmLykNUbew==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.3.tgz", + "integrity": "sha512-/BypzV0H1y1HzgYpxqRaXGBRqfodgoBBCcsrujT6QRcakDQdfU+Lq9PENPh5jB4I44YWq+0C2eHsHya+nZY1sA==", "cpu": [ "x64" ], @@ -18274,15 +16051,15 @@ ] }, "node_modules/@rushstack/eslint-patch": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.1.tgz", - "integrity": "sha512-S3Kq8e7LqxkA9s7HKLqXGTGck1uwis5vAXan3FnU5yw1Ec5hsSGnq4s/UCaSqABPOnOTg7zASLyst7+ohgWexg==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.2.tgz", + "integrity": "sha512-hw437iINopmQuxWPSUEvqE56NCPsiU8N4AYtfHmJFckclktzK9YQJieD3XkDCDH4OjL+C7zgPUh73R/nrcHrqw==", "dev": true }, "node_modules/@rushstack/node-core-library": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-4.0.2.tgz", - "integrity": "sha512-hyES82QVpkfQMeBMteQUnrhASL/KHPhd7iJ8euduwNJG4mu2GSOKybf0rOEjOm1Wz7CwJEUm9y0yD7jg2C1bfg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-4.1.0.tgz", + "integrity": "sha512-qz4JFBZJCf1YN5cAXa1dP6Mki/HrsQxc/oYGAGx29dF2cwF2YMxHoly0FBhMw3IEnxo5fMj0boVfoHVBkpkx/w==", "dev": true, "dependencies": { "fs-extra": "~7.0.1", @@ -18345,12 +16122,12 @@ } }, "node_modules/@rushstack/terminal": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.10.0.tgz", - "integrity": "sha512-UbELbXnUdc7EKwfH2sb8ChqNgapUOdqcCIdQP4NGxBpTZV2sQyeekuK3zmfQSa/MN+/7b4kBogl2wq0vpkpYGw==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.10.1.tgz", + "integrity": "sha512-C6Vi/m/84IYJTkfzmXr1+W8Wi3MmBjVF/q3za91Gb3VYjKbpALHVxY6FgH625AnDe5Z0Kh4MHKWA3Z7bqgAezA==", "dev": true, "dependencies": { - "@rushstack/node-core-library": "4.0.2", + "@rushstack/node-core-library": "4.1.0", "supports-color": "~8.1.1" }, "peerDependencies": { @@ -18363,12 +16140,12 @@ } }, "node_modules/@rushstack/ts-command-line": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.19.1.tgz", - "integrity": "sha512-J7H768dgcpG60d7skZ5uSSwyCZs/S2HrWP1Ds8d1qYAyaaeJmpmmLr9BVw97RjFzmQPOYnoXcKA4GkqDCkduQg==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.19.2.tgz", + "integrity": "sha512-cqmXXmBEBlzo9WtyUrHtF9e6kl0LvBY7aTSVX4jfnBfXWZQWnPq9JTFPlQZ+L/ZwjZ4HrNwQsOVvhe9oOucZkw==", "dev": true, "dependencies": { - "@rushstack/terminal": "0.10.0", + "@rushstack/terminal": "0.10.1", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" @@ -19112,9 +16889,9 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-2.2.1.tgz", - "integrity": "sha512-j5fHgL1iqKTsKJ1mTcw88p0RUcidDu95AWSeZTgiYJb+QcfwWU/UpBnaqiB59FNH5MiAZuSbOBnZlwzeeY2tIw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-2.3.0.tgz", + "integrity": "sha512-ui/NlpILU+6HAQBfJX8BBsDXuKSNrjTSuOYArRblcrErwKFutjrCNb/OExfVRyj9+26F9J+ZmfWT+fKWuDrH3Q==", "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "@smithy/types": "^2.12.0", @@ -19356,12 +17133,12 @@ } }, "node_modules/@storybook/addon-actions": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-8.0.6.tgz", - "integrity": "sha512-3R/d2Td6+yeR+UnyCAeZ4tuiRGSm+6gKUQP9vB1bvEFQGuFBrV+zs3eakcYegOqZu3IXuejgaB0Knq987gUL5A==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-8.0.8.tgz", + "integrity": "sha512-F3qpN0n53d058EroW1A2IlzrsFNR5p2srLY4FmXB80nxAKV8oqoDI4jp15zYlf8ThcJoQl36plT8gx3r1BpANA==", "dev": true, "dependencies": { - "@storybook/core-events": "8.0.6", + "@storybook/core-events": "8.0.8", "@storybook/global": "^5.0.0", "@types/uuid": "^9.0.1", "dequal": "^2.0.2", @@ -19374,9 +17151,9 @@ } }, "node_modules/@storybook/addon-backgrounds": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-8.0.6.tgz", - "integrity": "sha512-NRTmSsJiqpXqJMVrRuQ+P1wt26ZCLjBNaMafcjgicfWeyUsdhNF63yYvyrHkMRuNmYPZm0hKvtjLhW3s9VohSA==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-8.0.8.tgz", + "integrity": "sha512-lrAJjVxDeXSK116rDajb56TureZiT76ygraP22/IvU3IcWCEcRiKYwlay8WgCTbJHtFmdBpelLBapoT46+IR9Q==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0", @@ -19389,12 +17166,12 @@ } }, "node_modules/@storybook/addon-controls": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-8.0.6.tgz", - "integrity": "sha512-bNXDhi1xl7eat1dUsKTrUgu5mkwXjfFWDjIYxrzatqDOW1+rdkNaPFduQRJ2mpCs4cYcHKAr5chEcMm6byuTnA==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-8.0.8.tgz", + "integrity": "sha512-7xANN18CLYsVthuSXwxKezqpelEKJlT9xaYLtw5vvD00btW5g3vxq+Z/A31OkS2OuaH2bE0GfRCoG2OLR8yQQA==", "dev": true, "dependencies": { - "@storybook/blocks": "8.0.6", + "@storybook/blocks": "8.0.8", "lodash": "^4.17.21", "ts-dedent": "^2.0.0" }, @@ -19404,24 +17181,24 @@ } }, "node_modules/@storybook/addon-docs": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-8.0.6.tgz", - "integrity": "sha512-QOlOE2XEFcUaR85YytBuf/nfKFkbIlD0Qc9CI4E65FoZPTCMhRVKAEN2CpsKI63fs/qQxM2mWkPXb6w7QXGxvg==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-8.0.8.tgz", + "integrity": "sha512-HNiY4ESH9WxGS6QpIpURzdSbyDxbRh7VIgbvUrePSKajlsL4RFN/gdnn5TnSL00tOP/w+Cy/fXcbljMUKy7Ivg==", "dev": true, "dependencies": { "@babel/core": "^7.12.3", "@mdx-js/react": "^3.0.0", - "@storybook/blocks": "8.0.6", - "@storybook/client-logger": "8.0.6", - "@storybook/components": "8.0.6", - "@storybook/csf-plugin": "8.0.6", - "@storybook/csf-tools": "8.0.6", + "@storybook/blocks": "8.0.8", + "@storybook/client-logger": "8.0.8", + "@storybook/components": "8.0.8", + "@storybook/csf-plugin": "8.0.8", + "@storybook/csf-tools": "8.0.8", "@storybook/global": "^5.0.0", - "@storybook/node-logger": "8.0.6", - "@storybook/preview-api": "8.0.6", - "@storybook/react-dom-shim": "8.0.6", - "@storybook/theming": "8.0.6", - "@storybook/types": "8.0.6", + "@storybook/node-logger": "8.0.8", + "@storybook/preview-api": "8.0.8", + "@storybook/react-dom-shim": "8.0.8", + "@storybook/theming": "8.0.8", + "@storybook/types": "8.0.8", "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", "fs-extra": "^11.1.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0", @@ -19471,24 +17248,24 @@ } }, "node_modules/@storybook/addon-essentials": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-8.0.6.tgz", - "integrity": "sha512-L9SSsdN1EG2FZ1mNT59vwf0fpseLrzO1cWPwH6hVtp0+kci3tfropch2tEwO7Vr+YLSesJihfr4uvpI/l0jCsw==", - "dev": true, - "dependencies": { - "@storybook/addon-actions": "8.0.6", - "@storybook/addon-backgrounds": "8.0.6", - "@storybook/addon-controls": "8.0.6", - "@storybook/addon-docs": "8.0.6", - "@storybook/addon-highlight": "8.0.6", - "@storybook/addon-measure": "8.0.6", - "@storybook/addon-outline": "8.0.6", - "@storybook/addon-toolbars": "8.0.6", - "@storybook/addon-viewport": "8.0.6", - "@storybook/core-common": "8.0.6", - "@storybook/manager-api": "8.0.6", - "@storybook/node-logger": "8.0.6", - "@storybook/preview-api": "8.0.6", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-8.0.8.tgz", + "integrity": "sha512-bc9KJk7SPM2I5CCJEAP8R5leP+74IYxhWPiTN8Y1YFmf3MA1lpDJbwy+RfuRZ2ZKnSKszCXCVzU/T10HKUHLZw==", + "dev": true, + "dependencies": { + "@storybook/addon-actions": "8.0.8", + "@storybook/addon-backgrounds": "8.0.8", + "@storybook/addon-controls": "8.0.8", + "@storybook/addon-docs": "8.0.8", + "@storybook/addon-highlight": "8.0.8", + "@storybook/addon-measure": "8.0.8", + "@storybook/addon-outline": "8.0.8", + "@storybook/addon-toolbars": "8.0.8", + "@storybook/addon-viewport": "8.0.8", + "@storybook/core-common": "8.0.8", + "@storybook/manager-api": "8.0.8", + "@storybook/node-logger": "8.0.8", + "@storybook/preview-api": "8.0.8", "ts-dedent": "^2.0.0" }, "funding": { @@ -19497,9 +17274,9 @@ } }, "node_modules/@storybook/addon-highlight": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-8.0.6.tgz", - "integrity": "sha512-CxXzzgIK5sXy2RNIkwU5JXZNq+PNGhUptRm/5M5ylcB7rk0pdwnE0TLXsMU+lzD0ji+cj61LWVLdeXQa+/whSw==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-8.0.8.tgz", + "integrity": "sha512-KKD7xiNhxZQM4fdDidtcla6jSzgN1f9qe1AwFSHLXwIW22+4c97Vgf+AookN7cJvB77HxRUnvQH//zV1CJEDug==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -19510,9 +17287,9 @@ } }, "node_modules/@storybook/addon-links": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-8.0.6.tgz", - "integrity": "sha512-1UBNhQdwm17fXmuUKIsgvT6YenMbaGIYdr/9ApKmIMTKKO+emQ7APlsTbvasutcOkCd57rC1KZRfAHQpgU9wDQ==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-8.0.8.tgz", + "integrity": "sha512-iRI/W9I6fOom5zfZvsu53gfJtuhBSMmhgI/u5uZbAbfEoNL5D1PqpDXD4ygM8Vvlx90AZNZ2W5slEe7gCZOMyA==", "dev": true, "dependencies": { "@storybook/csf": "^0.1.2", @@ -19533,9 +17310,9 @@ } }, "node_modules/@storybook/addon-measure": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-8.0.6.tgz", - "integrity": "sha512-2PnytDaQzCxcgykEM5Njb71Olm+Z2EFERL5X+5RhsG2EQxEqobwh1fUtXLY4aqiImdSJOrjQnkMJchzzoTRtug==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-8.0.8.tgz", + "integrity": "sha512-akyoa+1F2ripV6ELF2UbxiSHv791LWSAVK7gsD/a5eJfKZMm5yoHjcY7Icdkc/ctE+pyjAQNhkXTixUngge09w==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0", @@ -19547,9 +17324,9 @@ } }, "node_modules/@storybook/addon-outline": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-8.0.6.tgz", - "integrity": "sha512-PfTIy64kV5h7F0tXrj5rlwdPFpOQiGrn01AQudSJDVWaMsbVgjruPU+cHG4i/L1mzzERzeHYd46bNENWZiQgDw==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-8.0.8.tgz", + "integrity": "sha512-8Gxs095ekpa5YZolLSs5cWbWK94GZTevEUX8GFeLGIz9sf1KO3kmEO3eC5ogzDoB0cloqvbmVAJvYJ3FWiUx8w==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0", @@ -19561,12 +17338,12 @@ } }, "node_modules/@storybook/addon-storysource": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-storysource/-/addon-storysource-8.0.6.tgz", - "integrity": "sha512-rglSDmwb5RdNSfZCGL9qYAYn5ycrGPA0zzTY4Do8D7HluSloBWRDb1UTLszPRfR7k/wY+MYkBx3ZYTrTHuvXXQ==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-storysource/-/addon-storysource-8.0.8.tgz", + "integrity": "sha512-mhnT+Cd12DZIQvqK1O9NHeCJ9a8j/tJjKw/jxOQ222HfZBNS1UjhqSTEYkXSx3HKs1qiBSz8423+pzEybuRTGQ==", "dev": true, "dependencies": { - "@storybook/source-loader": "8.0.6", + "@storybook/source-loader": "8.0.8", "estraverse": "^5.2.0", "tiny-invariant": "^1.3.1" }, @@ -19576,9 +17353,9 @@ } }, "node_modules/@storybook/addon-toolbars": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-8.0.6.tgz", - "integrity": "sha512-g4GjrMEHKOIQVwG1DKUHBAn4B8xmdqlxFlVusOrYD9FVfakgMNllN6WBc02hg/IiuzqIDxVK5BXiY9MbXnoguQ==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-8.0.8.tgz", + "integrity": "sha512-PZxlK+/Fwk2xcrpr5kkXYjCbBaEjAWcEHWq7mhQReMFaAs5AJE8dvmeQ7rmPDOHnlg4+YsARDFKz5FJtthRIgg==", "dev": true, "funding": { "type": "opencollective", @@ -19586,9 +17363,9 @@ } }, "node_modules/@storybook/addon-viewport": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-8.0.6.tgz", - "integrity": "sha512-R6aGEPA5e05L/NPs6Nbj0u9L6oKmchnJ/x8Rr/Xuc+nqVgXC1rslI0BcjJuC571Bewz7mT8zJ+BjP/gs7T4lnQ==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-8.0.8.tgz", + "integrity": "sha512-nOuc6DquGvm24c/A0HFTgeEN/opd58ebs1KLaEEq1f6iYV0hT2Gpnk0Usg/seOiFtJnj3NyAM46HSkZz06T8Sw==", "dev": true, "dependencies": { "memoizerific": "^1.11.3" @@ -19599,23 +17376,23 @@ } }, "node_modules/@storybook/blocks": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-8.0.6.tgz", - "integrity": "sha512-ycuPJwxyngSor4YNa4kkX3rAmX+w2pXNsIo+Zs4fEdAfCvha9+GZ/3jQSdrsHxjeIm9l9guiv4Ag8QTnnllXkw==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-8.0.8.tgz", + "integrity": "sha512-kwsjhvnmFEaIl51QHJt/83G7mZ5YbzFKnWCwy8WUpi0xvVcyoFQSGGgwR3XRrzGfUEPK8P2FDHeKw1bLzyIejA==", "dev": true, "dependencies": { - "@storybook/channels": "8.0.6", - "@storybook/client-logger": "8.0.6", - "@storybook/components": "8.0.6", - "@storybook/core-events": "8.0.6", + "@storybook/channels": "8.0.8", + "@storybook/client-logger": "8.0.8", + "@storybook/components": "8.0.8", + "@storybook/core-events": "8.0.8", "@storybook/csf": "^0.1.2", - "@storybook/docs-tools": "8.0.6", + "@storybook/docs-tools": "8.0.8", "@storybook/global": "^5.0.0", "@storybook/icons": "^1.2.5", - "@storybook/manager-api": "8.0.6", - "@storybook/preview-api": "8.0.6", - "@storybook/theming": "8.0.6", - "@storybook/types": "8.0.6", + "@storybook/manager-api": "8.0.8", + "@storybook/preview-api": "8.0.8", + "@storybook/theming": "8.0.8", + "@storybook/types": "8.0.8", "@types/lodash": "^4.14.167", "color-convert": "^2.0.1", "dequal": "^2.0.2", @@ -19647,15 +17424,15 @@ } }, "node_modules/@storybook/builder-manager": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/builder-manager/-/builder-manager-8.0.6.tgz", - "integrity": "sha512-N61Gh9FKsSYvsbdBy5qFvq1anTIuUAjh2Z+ezDMlxnfMGG77nZP9heuy1NnCaYCTFzl+lq4BsmRfXXDcKtSPRA==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/builder-manager/-/builder-manager-8.0.8.tgz", + "integrity": "sha512-0uihNTpTou0RFMM6PQLlfCxDxse9nIDEb83AmWE/OUnpKDDY9+WFupVWGaZc9HfH9h4Yqre2fiuK1b7KNYe7AQ==", "dev": true, "dependencies": { "@fal-works/esbuild-plugin-global-externals": "^2.1.2", - "@storybook/core-common": "8.0.6", - "@storybook/manager": "8.0.6", - "@storybook/node-logger": "8.0.6", + "@storybook/core-common": "8.0.8", + "@storybook/manager": "8.0.8", + "@storybook/node-logger": "8.0.8", "@types/ejs": "^3.1.1", "@yarnpkg/esbuild-plugin-pnp": "^3.0.0-rc.10", "browser-assert": "^1.2.1", @@ -19708,20 +17485,20 @@ } }, "node_modules/@storybook/builder-vite": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-8.0.6.tgz", - "integrity": "sha512-uQe1tTXdWXhP1ZO7sBRLUS5WKoD/ibrBWhyG6gY0RHC8RtGIx1sYxbg7ZzUXXX8z1GH0QJlOKrlAfcHzIchscw==", - "dev": true, - "dependencies": { - "@storybook/channels": "8.0.6", - "@storybook/client-logger": "8.0.6", - "@storybook/core-common": "8.0.6", - "@storybook/core-events": "8.0.6", - "@storybook/csf-plugin": "8.0.6", - "@storybook/node-logger": "8.0.6", - "@storybook/preview": "8.0.6", - "@storybook/preview-api": "8.0.6", - "@storybook/types": "8.0.6", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-8.0.8.tgz", + "integrity": "sha512-ibWOxoHczCc6ttMQqiSXv29m/e44sKVoc1BJluApQcjCXl9g6QXyN45zV70odjCxMfNy7EQgUjCA0mgAgMHSIw==", + "dev": true, + "dependencies": { + "@storybook/channels": "8.0.8", + "@storybook/client-logger": "8.0.8", + "@storybook/core-common": "8.0.8", + "@storybook/core-events": "8.0.8", + "@storybook/csf-plugin": "8.0.8", + "@storybook/node-logger": "8.0.8", + "@storybook/preview": "8.0.8", + "@storybook/preview-api": "8.0.8", + "@storybook/types": "8.0.8", "@types/find-cache-dir": "^3.2.1", "browser-assert": "^1.2.1", "es-module-lexer": "^0.9.3", @@ -19789,13 +17566,13 @@ } }, "node_modules/@storybook/channels": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-8.0.6.tgz", - "integrity": "sha512-IbNvjxeyQKiMpb+gSpQ7yYsFqb8BM/KYgfypJM3yJV6iU/NFeevrC/DA6/R+8xWFyPc70unRNLv8fPvxhcIu8Q==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-8.0.8.tgz", + "integrity": "sha512-L3EGVkabv3fweXnykD/GlNUDO5HtwlIfSovC7BF4MmP7662j2/eqlZrJxDojGtbv11XHjWp/UJHUIfKpcHXYjQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "8.0.6", - "@storybook/core-events": "8.0.6", + "@storybook/client-logger": "8.0.8", + "@storybook/core-events": "8.0.8", "@storybook/global": "^5.0.0", "telejson": "^7.2.0", "tiny-invariant": "^1.3.1" @@ -19806,22 +17583,22 @@ } }, "node_modules/@storybook/cli": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/cli/-/cli-8.0.6.tgz", - "integrity": "sha512-gAnl9soQUu1BtB4sANaqaaeTZAt/ThBSwCdzSLut5p21fP4ovi3FeP7hcDCJbyJZ/AvnD4k6leDrqRQxMVPr0A==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/cli/-/cli-8.0.8.tgz", + "integrity": "sha512-RnSdgykh2i7es1rQ7CNGpDrKK/PN1f0xjwpkAHXCEB6T9KpHBmqDquzZp+N127a1HBHHXy018yi4wT8mSQyEoA==", "dev": true, "dependencies": { "@babel/core": "^7.23.0", "@babel/types": "^7.23.0", "@ndelangen/get-tarball": "^3.0.7", - "@storybook/codemod": "8.0.6", - "@storybook/core-common": "8.0.6", - "@storybook/core-events": "8.0.6", - "@storybook/core-server": "8.0.6", - "@storybook/csf-tools": "8.0.6", - "@storybook/node-logger": "8.0.6", - "@storybook/telemetry": "8.0.6", - "@storybook/types": "8.0.6", + "@storybook/codemod": "8.0.8", + "@storybook/core-common": "8.0.8", + "@storybook/core-events": "8.0.8", + "@storybook/core-server": "8.0.8", + "@storybook/csf-tools": "8.0.8", + "@storybook/node-logger": "8.0.8", + "@storybook/telemetry": "8.0.8", + "@storybook/types": "8.0.8", "@types/semver": "^7.3.4", "@yarnpkg/fslib": "2.10.3", "@yarnpkg/libzip": "2.3.0", @@ -19987,9 +17764,9 @@ "dev": true }, "node_modules/@storybook/client-logger": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-8.0.6.tgz", - "integrity": "sha512-et/IHPHiiOwMg93l5KSgw47NZXz5xOyIrIElRcsT1wr8OJeIB9DzopB/suoHBZ/IML+t8x91atdutzUN2BLF6A==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-8.0.8.tgz", + "integrity": "sha512-a4BKwl9NLFcuRgMyI7S4SsJeLFK0LCQxIy76V6YyrE1DigoXz4nA4eQxdjLf7JVvU0EZFmNSfbVL/bXzzWKNXA==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -20000,18 +17777,18 @@ } }, "node_modules/@storybook/codemod": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-8.0.6.tgz", - "integrity": "sha512-IMaTVI+EvmFxkz4leKWKForPC3LFxzfeTmd/QnTNF3nCeyvmIXvP01pQXRjro0+XcGDncEStuxa1d9ClMlac9Q==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-8.0.8.tgz", + "integrity": "sha512-ufEBLciLmLlAh+L6lGgBObTiny6odXMKqiJOewQ9XfIN0wdWdyRUf5QdZIPOdfgHhWF2Q2HeswiulsoHm8Z/hA==", "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": "8.0.6", - "@storybook/node-logger": "8.0.6", - "@storybook/types": "8.0.6", + "@storybook/csf-tools": "8.0.8", + "@storybook/node-logger": "8.0.8", + "@storybook/types": "8.0.8", "@types/cross-spawn": "^6.0.2", "cross-spawn": "^7.0.3", "globby": "^11.0.2", @@ -20027,18 +17804,18 @@ } }, "node_modules/@storybook/components": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/components/-/components-8.0.6.tgz", - "integrity": "sha512-6W2BAqAPJkrExk8D/ug2NPBPvMs05p6Bdt9tk3eWjiMrhG/CUKBzlBTEfNK/mzy3YVB6ijyT2DgsqzmWWYJ/Xw==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/components/-/components-8.0.8.tgz", + "integrity": "sha512-EpBExH4kHWQJSfA8QXJJ5AsLRUGi5X/zWY7ffiYW8rtnBmEnk3T9FpmnyJlY1A8sdd3b1wQ07JGBDHfL1mdELw==", "dev": true, "dependencies": { "@radix-ui/react-slot": "^1.0.2", - "@storybook/client-logger": "8.0.6", + "@storybook/client-logger": "8.0.8", "@storybook/csf": "^0.1.2", "@storybook/global": "^5.0.0", "@storybook/icons": "^1.2.5", - "@storybook/theming": "8.0.6", - "@storybook/types": "8.0.6", + "@storybook/theming": "8.0.8", + "@storybook/types": "8.0.8", "memoizerific": "^1.11.3", "util-deprecate": "^1.0.2" }, @@ -20052,15 +17829,15 @@ } }, "node_modules/@storybook/core-common": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-8.0.6.tgz", - "integrity": "sha512-Z4cA52SjcW6SAV9hayqVm5kyr362O20Zmwz7+H2nYEhcu8bY69y5p45aaoyElMxL1GDNu84GrmTp7dY4URw1fQ==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-8.0.8.tgz", + "integrity": "sha512-CL15M2oeQW+Rb1l7ciunLDI2Re+ojL2lX1ZFAiDedcOU+JHsdq43zAuXoZVzp8icUi2AUSwEjZIxGCSingj+JQ==", "dev": true, "dependencies": { - "@storybook/core-events": "8.0.6", - "@storybook/csf-tools": "8.0.6", - "@storybook/node-logger": "8.0.6", - "@storybook/types": "8.0.6", + "@storybook/core-events": "8.0.8", + "@storybook/csf-tools": "8.0.8", + "@storybook/node-logger": "8.0.8", + "@storybook/types": "8.0.8", "@yarnpkg/fslib": "2.10.3", "@yarnpkg/libzip": "2.3.0", "chalk": "^4.1.0", @@ -20203,9 +17980,9 @@ "dev": true }, "node_modules/@storybook/core-events": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-8.0.6.tgz", - "integrity": "sha512-EwGmuMm8QTUAHPhab4yftQWoSCX3OzEk6cQdpLtbNFtRRLE9aPZzxhk5Z/d3KhLNSCUAGyCiDt5I9JxTBetT9A==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-8.0.8.tgz", + "integrity": "sha512-PtuvR7vS4glDEdCfKB4f1k3Vs1C3rTWP2DNbF+IjjPhNLMBznCdzTAPcz+NUIBvpjjGnhKwWikJ0yj931YjSVg==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -20216,28 +17993,28 @@ } }, "node_modules/@storybook/core-server": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-8.0.6.tgz", - "integrity": "sha512-COmcjrry8vZXDh08ZGbfDz2bFB4of5wnwOwYf8uwlVND6HnhQzV22On1s3/p8qw+dKOpjpwDdHWtMnndnPNuqQ==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-8.0.8.tgz", + "integrity": "sha512-tSEueEBttbSohzhZVN2bFNlFx3eoqQ7p57cjQLKXXwKygS2qKxISKnFy+Y0nj20APz68Wj51kx0rN0nGALeegw==", "dev": true, "dependencies": { "@aw-web-design/x-default-browser": "1.4.126", "@babel/core": "^7.23.9", "@discoveryjs/json-ext": "^0.5.3", - "@storybook/builder-manager": "8.0.6", - "@storybook/channels": "8.0.6", - "@storybook/core-common": "8.0.6", - "@storybook/core-events": "8.0.6", + "@storybook/builder-manager": "8.0.8", + "@storybook/channels": "8.0.8", + "@storybook/core-common": "8.0.8", + "@storybook/core-events": "8.0.8", "@storybook/csf": "^0.1.2", - "@storybook/csf-tools": "8.0.6", + "@storybook/csf-tools": "8.0.8", "@storybook/docs-mdx": "3.0.0", "@storybook/global": "^5.0.0", - "@storybook/manager": "8.0.6", - "@storybook/manager-api": "8.0.6", - "@storybook/node-logger": "8.0.6", - "@storybook/preview-api": "8.0.6", - "@storybook/telemetry": "8.0.6", - "@storybook/types": "8.0.6", + "@storybook/manager": "8.0.8", + "@storybook/manager-api": "8.0.8", + "@storybook/node-logger": "8.0.8", + "@storybook/preview-api": "8.0.8", + "@storybook/telemetry": "8.0.8", + "@storybook/types": "8.0.8", "@types/detect-port": "^1.3.0", "@types/node": "^18.0.0", "@types/pretty-hrtime": "^1.0.0", @@ -20408,21 +18185,21 @@ "dev": true }, "node_modules/@storybook/csf": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.3.tgz", - "integrity": "sha512-IPZvXXo4b3G+gpmgBSBqVM81jbp2ePOKsvhgJdhyZJtkYQCII7rg9KKLQhvBQM5sLaF1eU6r0iuwmyynC9d9SA==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.4.tgz", + "integrity": "sha512-B9UI/lsQMjF+oEfZCI6YXNoeuBcGZoOP5x8yKbe2tIEmsMjSztFKkpPzi5nLCnBk/MBtl6QJeI3ksJnbsWPkOw==", "dev": true, "dependencies": { "type-fest": "^2.19.0" } }, "node_modules/@storybook/csf-plugin": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-8.0.6.tgz", - "integrity": "sha512-ULaAFGhdgDDbknGnCqxitzeBlSzYZJQvZT4HtFgxfNU2McOu+GLIzyUOx3xG5eoziLvvm+oW+lxLr5nDkSaBUg==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-8.0.8.tgz", + "integrity": "sha512-x9WspjZGcqXENj/Vn4Qmn0oTW93KN2V9wqpflWwCUJTByl2MugQsh5xRuDbs2yM7dD6zKcqRyPaTY+GFZBW+Vg==", "dev": true, "dependencies": { - "@storybook/csf-tools": "8.0.6", + "@storybook/csf-tools": "8.0.8", "unplugin": "^1.3.1" }, "funding": { @@ -20431,9 +18208,9 @@ } }, "node_modules/@storybook/csf-tools": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-8.0.6.tgz", - "integrity": "sha512-MEBVxpnzqkBPyYXdtYQrY0SQC3oflmAQdEM0qWFzPvZXTnIMk3Q2ft8JNiBht6RlrKGvKql8TodwpbOiPeJI/w==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-8.0.8.tgz", + "integrity": "sha512-Ji5fpoGym/MSyHJ6ALghVUUecwhEbN0On+jOZ2VPkrkATi9UDtryHQPdF60HKR63Iv53xRuWRzudB6zm43RTzw==", "dev": true, "dependencies": { "@babel/generator": "^7.23.0", @@ -20441,7 +18218,7 @@ "@babel/traverse": "^7.23.2", "@babel/types": "^7.23.0", "@storybook/csf": "^0.1.2", - "@storybook/types": "8.0.6", + "@storybook/types": "8.0.8", "fs-extra": "^11.1.0", "recast": "^0.23.5", "ts-dedent": "^2.0.0" @@ -20505,14 +18282,14 @@ "dev": true }, "node_modules/@storybook/docs-tools": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/docs-tools/-/docs-tools-8.0.6.tgz", - "integrity": "sha512-PsAA2b/Q1ki5IR0fa52MI+fdDkQ0W+mrZVRRj3eJzonGZYcQtXofTXQB7yi0CaX7zzI/N8JcdE4bO9sI6SrOTg==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/docs-tools/-/docs-tools-8.0.8.tgz", + "integrity": "sha512-p/MIrDshXMl/fiCRlfG9StkRYI1QlUyUSQQ/YDBFlBfWcJYARIt3TIvQyvs3Q/apnQNcDXIW663W57s7WHTO2w==", "dev": true, "dependencies": { - "@storybook/core-common": "8.0.6", - "@storybook/preview-api": "8.0.6", - "@storybook/types": "8.0.6", + "@storybook/core-common": "8.0.8", + "@storybook/preview-api": "8.0.8", + "@storybook/types": "8.0.8", "@types/doctrine": "^0.0.3", "assert": "^2.1.0", "doctrine": "^3.0.0", @@ -20543,9 +18320,9 @@ } }, "node_modules/@storybook/manager": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/manager/-/manager-8.0.6.tgz", - "integrity": "sha512-wdL3lG72qrCOLkxEUW49+hmwA4fIFXFvAEU7wVgEt4KyRRGWhHa8Dr/5Tnq54CWJrA+BTrTPHaoo/Vu4BAjgow==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/manager/-/manager-8.0.8.tgz", + "integrity": "sha512-pWYHSDmgT8p/XbQMKuDPdgB6KzjePI6dU5KQ5MERYfch1UiuGPVm1HHDlxxSfHW0IIXw9Qnwq4L0Awe4qhvJKQ==", "dev": true, "funding": { "type": "opencollective", @@ -20553,20 +18330,20 @@ } }, "node_modules/@storybook/manager-api": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-8.0.6.tgz", - "integrity": "sha512-khYA5CM+LY/B5VsqqUmt2ivNLNqyIKfcgGsXHkOs3Kr5BOz8LhEmSwZOB348ey2C2ejFJmvKlkcsE+rB9ixlww==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-8.0.8.tgz", + "integrity": "sha512-1HU4nfLRi0sD2uw229gb8EQyufNWrLvMNpg013kBsBXRd+Dj4dqF3v+KrYFNtteY7riC4mAJ6YcQ4tBUNYZDug==", "dev": true, "dependencies": { - "@storybook/channels": "8.0.6", - "@storybook/client-logger": "8.0.6", - "@storybook/core-events": "8.0.6", + "@storybook/channels": "8.0.8", + "@storybook/client-logger": "8.0.8", + "@storybook/core-events": "8.0.8", "@storybook/csf": "^0.1.2", "@storybook/global": "^5.0.0", "@storybook/icons": "^1.2.5", - "@storybook/router": "8.0.6", - "@storybook/theming": "8.0.6", - "@storybook/types": "8.0.6", + "@storybook/router": "8.0.8", + "@storybook/theming": "8.0.8", + "@storybook/types": "8.0.8", "dequal": "^2.0.2", "lodash": "^4.17.21", "memoizerific": "^1.11.3", @@ -20580,9 +18357,9 @@ } }, "node_modules/@storybook/node-logger": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-8.0.6.tgz", - "integrity": "sha512-mDRJLVAuTWauO0mnrwajfJV/6zKBJVPp/9g0ULccE3Q+cuqNfUefqfCd17cZBlJHeRsdB9jy9tod48d4qzGEkQ==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-8.0.8.tgz", + "integrity": "sha512-ymps3MMTxtMWq0eDiXk1iO7iv0Eg0PuUvOpPPohEJauGzU9THv81xx01aaHKSprFFJYD2LMQr1aFuUplItO12g==", "dev": true, "funding": { "type": "opencollective", @@ -20590,9 +18367,9 @@ } }, "node_modules/@storybook/preview": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/preview/-/preview-8.0.6.tgz", - "integrity": "sha512-NdVstxdUghv5goQJ4zFftyezfCEPKHOSNu8k02KU6u6g5IiK430jp5y71E/eiBK3m1AivtluC7tPRSch0HsidA==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/preview/-/preview-8.0.8.tgz", + "integrity": "sha512-J/ooKcvDV1s7ROH7lF/0vOyWDOgDB7bN6vS67J1WK0HLvMGaqUzU+q3ndakGzu0LU/jvUBqEFSZd1ALWyZINDQ==", "dev": true, "funding": { "type": "opencollective", @@ -20600,17 +18377,17 @@ } }, "node_modules/@storybook/preview-api": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-8.0.6.tgz", - "integrity": "sha512-O5SvBqlHIO/Cf5oGZUJV2npkp9bLqg9Sn0T0a5zXolJbRy+gP7MDyz4AnliLpTn5bT2rzVQ6VH8IDlhHBq3K6g==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-8.0.8.tgz", + "integrity": "sha512-khgw2mNiBrSZS3KNGQPzjneL3Csh3BOq0yLAtJpT7CRSrI/YjlE7jjcTkKzoxW+UCgvNTnLvsowcuzu82e69fA==", "dev": true, "dependencies": { - "@storybook/channels": "8.0.6", - "@storybook/client-logger": "8.0.6", - "@storybook/core-events": "8.0.6", + "@storybook/channels": "8.0.8", + "@storybook/client-logger": "8.0.8", + "@storybook/core-events": "8.0.8", "@storybook/csf": "^0.1.2", "@storybook/global": "^5.0.0", - "@storybook/types": "8.0.6", + "@storybook/types": "8.0.8", "@types/qs": "^6.9.5", "dequal": "^2.0.2", "lodash": "^4.17.21", @@ -20626,17 +18403,17 @@ } }, "node_modules/@storybook/react": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/react/-/react-8.0.6.tgz", - "integrity": "sha512-A1zivNti15nHkJ6EcVKpxKwlDkyMb5MlJMUb8chX/xBWxoR1f5R8eI484rhdPRYUzBY7JwvgZfy4y/murqg6hA==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-8.0.8.tgz", + "integrity": "sha512-pPTlQntl09kv7qkAFYsxUq6qCLeeZC/K3yGFBGMy2Dc+PFjBYdT6mt2I8GB3twK0Cq5gJESlLj48QnYLQ/9PbA==", "dev": true, "dependencies": { - "@storybook/client-logger": "8.0.6", - "@storybook/docs-tools": "8.0.6", + "@storybook/client-logger": "8.0.8", + "@storybook/docs-tools": "8.0.8", "@storybook/global": "^5.0.0", - "@storybook/preview-api": "8.0.6", - "@storybook/react-dom-shim": "8.0.6", - "@storybook/types": "8.0.6", + "@storybook/preview-api": "8.0.8", + "@storybook/react-dom-shim": "8.0.8", + "@storybook/types": "8.0.8", "@types/escodegen": "^0.0.6", "@types/estree": "^0.0.51", "@types/node": "^18.0.0", @@ -20672,9 +18449,9 @@ } }, "node_modules/@storybook/react-dom-shim": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-8.0.6.tgz", - "integrity": "sha512-NC4k0dBIypvVqwqnMhKDUxNc1OeL6lgspn8V26PnmCYbvY97ZqoGQ7n2a5Kw/kubN6yWX1nxNkV6HcTRgEnYTw==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-8.0.8.tgz", + "integrity": "sha512-vOMlAz2HH/xfgZmSO28fCEmp5/tPxINDEdBDVLdZeYG6R1j5jlMRyaNcXt4cPNDkyc///PkB/K767hg4goca/Q==", "dev": true, "funding": { "type": "opencollective", @@ -20686,16 +18463,16 @@ } }, "node_modules/@storybook/react-vite": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-8.0.6.tgz", - "integrity": "sha512-M6R6nl7dcXZ+wQHqFD1Qh/v4GPygqlC0pwE/cZ7FKUYA2wO3qm81OpuZYBKJoFIyHbRP/8oPKSvuzkgZvGY+/g==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-8.0.8.tgz", + "integrity": "sha512-3xN+/KgcjEAKJ0cM8yFYk8+T59kgKSMlQaavoIgQudbEErSubr9l7jDWXH44afQIEBVs++ayYWrbEN2wyMGoug==", "dev": true, "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "0.3.0", "@rollup/pluginutils": "^5.0.2", - "@storybook/builder-vite": "8.0.6", - "@storybook/node-logger": "8.0.6", - "@storybook/react": "8.0.6", + "@storybook/builder-vite": "8.0.8", + "@storybook/node-logger": "8.0.8", + "@storybook/react": "8.0.8", "find-up": "^5.0.0", "magic-string": "^0.30.0", "react-docgen": "^7.0.0", @@ -20770,12 +18547,12 @@ "dev": true }, "node_modules/@storybook/router": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/router/-/router-8.0.6.tgz", - "integrity": "sha512-ektN0+TyQPxVxcUvt9ksGizgDM1bKFEdGJeeqv0yYaOSyC4M1e4S8QZ+Iq/p/NFNt5XJWsWU+HtQ8AzQWagQfQ==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-8.0.8.tgz", + "integrity": "sha512-wdFdNsEKweigU9VkGZtpb7GhBJLWzbABcwOuEy2h0d5m7egB97hy9BxhANdqkC+PbAHrabxC99Ca3wTj50MoDg==", "dev": true, "dependencies": { - "@storybook/client-logger": "8.0.6", + "@storybook/client-logger": "8.0.8", "memoizerific": "^1.11.3", "qs": "^6.10.0" }, @@ -20785,13 +18562,13 @@ } }, "node_modules/@storybook/source-loader": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/source-loader/-/source-loader-8.0.6.tgz", - "integrity": "sha512-19R7iUNK2OVT/a/wWfGAMJwotKn51GrD5vqhGYtaUIp0DgqV7fj1rADvFWkqNrz1b1GWeRqCMcNr7p5l+ZFMEw==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/source-loader/-/source-loader-8.0.8.tgz", + "integrity": "sha512-3xYr3/ziaiOrJ33r9+XHUZkWmY4Ej6I/ZXr6kTUMdCuia7d+WYkuzULk4DhJ/15Dv0TdygYNZZJKDkgw2sqlRg==", "dev": true, "dependencies": { "@storybook/csf": "^0.1.2", - "@storybook/types": "8.0.6", + "@storybook/types": "8.0.8", "estraverse": "^5.2.0", "lodash": "^4.17.21", "prettier": "^3.1.1" @@ -20802,14 +18579,14 @@ } }, "node_modules/@storybook/telemetry": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/telemetry/-/telemetry-8.0.6.tgz", - "integrity": "sha512-kzxhhzGRSBYR4oe/Vlp/adKVxD8KWbIDMCgLWaINe14ILfEmpyrC00MXRSjS1tMF1qfrtn600Oe/xkHFQUpivQ==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/telemetry/-/telemetry-8.0.8.tgz", + "integrity": "sha512-Uvj4nN01vQgjXZYKF/GKTFE85//Qm4ZTlJxTFWid+oYWc8NpAyJvlsJkj/dsEn4cLrgnJx2e4xvnx0Umr2ck+A==", "dev": true, "dependencies": { - "@storybook/client-logger": "8.0.6", - "@storybook/core-common": "8.0.6", - "@storybook/csf-tools": "8.0.6", + "@storybook/client-logger": "8.0.8", + "@storybook/core-common": "8.0.8", + "@storybook/csf-tools": "8.0.8", "chalk": "^4.1.0", "detect-package-manager": "^2.0.1", "fetch-retry": "^5.0.2", @@ -20900,13 +18677,13 @@ } }, "node_modules/@storybook/theming": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-8.0.6.tgz", - "integrity": "sha512-o/b12+nDp8WDFlE0qQilzJ2aIeOHD48MCoc+ouFRPRH4tUS5xNaBPYxBxTgdtFbwZNuOC2my4A37Uhjn6IwkuQ==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-8.0.8.tgz", + "integrity": "sha512-43hkNz7yo8Bl97AO2WbxIGprUqMhUZyK9g8383bd30gSxy9nfND/bdSdcgmA8IokDn8qp37Q4QmxtUZdhjMzZQ==", "dev": true, "dependencies": { "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", - "@storybook/client-logger": "8.0.6", + "@storybook/client-logger": "8.0.8", "@storybook/global": "^5.0.0", "memoizerific": "^1.11.3" }, @@ -20928,12 +18705,12 @@ } }, "node_modules/@storybook/types": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-8.0.6.tgz", - "integrity": "sha512-YKq4A+3diQ7UCGuyrB/9LkB29jjGoEmPl3TfV7mO1FvdRw22BNuV3GyJCiLUHigSKiZgFo+pfQhmsNRJInHUnQ==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-8.0.8.tgz", + "integrity": "sha512-NGsgCsXnWlaZmHenHDgHGs21zhweZACkqTNsEQ7hvsiF08QeiKAdgJLQg3YeGK73h9mFDRP9djprUtJYab6vnQ==", "dev": true, "dependencies": { - "@storybook/channels": "8.0.6", + "@storybook/channels": "8.0.8", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" }, @@ -21211,11 +18988,17 @@ "url": "https://github.com/sponsors/gregberge" } }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" + }, "node_modules/@swc/helpers": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz", - "integrity": "sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", + "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", "dependencies": { + "@swc/counter": "^0.1.3", "tslib": "^2.4.0" } }, @@ -21232,9 +19015,9 @@ } }, "node_modules/@tabler/icons": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.1.0.tgz", - "integrity": "sha512-CpZGyS1IVJKFcv88yZ2sYZIpWWhQ6oy76BQKQ5SF0fGgOqgyqKdBGG/YGyyMW632on37MX7VqQIMTzN/uQqmFg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.2.0.tgz", + "integrity": "sha512-h8GQ2rtxgiSjltrVz4vcopAxTPSpUSUi5nBfJ09H3Bk4fJk6wZ/dVUjzhv/BHfDwGTkAxZBiYe/Q/T95cPeg5Q==", "dev": true, "funding": { "type": "github", @@ -21242,12 +19025,12 @@ } }, "node_modules/@tabler/icons-react": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.1.0.tgz", - "integrity": "sha512-k/WTlax2vbj/LpxvaJ+BmaLAAhVUgyLj4Ftgaczz66tUSNzqrAZXCFdOU7cRMYPNVBqyqE2IdQd2rzzhDEJvkw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.2.0.tgz", + "integrity": "sha512-b1mZT1XpZrzvbM+eFe1YbYbxkzgJ18tM4knZKqXh0gnHDZ6XVLIH3TzJZ3HZ7PTkUqZLZ7XcGae3qQVGburlBw==", "dev": true, "dependencies": { - "@tabler/icons": "3.1.0" + "@tabler/icons": "3.2.0" }, "funding": { "type": "github", @@ -21258,12 +19041,12 @@ } }, "node_modules/@tanstack/react-virtual": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.2.0.tgz", - "integrity": "sha512-OEdMByf2hEfDa6XDbGlZN8qO6bTjlNKqjM3im9JG+u3mCL8jALy0T/67oDI001raUUPh1Bdmfn4ZvPOV5knpcg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.3.0.tgz", + "integrity": "sha512-QFxmTSZBniq15S0vSZ55P4ToXquMXwJypPXyX/ux7sYo6a2FX3/zWoRLLc4eIOGWTjvzqcIVNKhcuFb+OZL3aQ==", "dev": true, "dependencies": { - "@tanstack/virtual-core": "3.2.0" + "@tanstack/virtual-core": "3.3.0" }, "funding": { "type": "github", @@ -21275,9 +19058,9 @@ } }, "node_modules/@tanstack/virtual-core": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.2.0.tgz", - "integrity": "sha512-P5XgYoAw/vfW65byBbJQCw+cagdXDT/qH6wmABiLt4v4YBT2q2vqCOhihe+D1Nt325F/S/0Tkv6C5z0Lv+VBQQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.3.0.tgz", + "integrity": "sha512-A0004OAa1FcUkPHeeGoKgBrAgjH+uHdDPrw1L7RpkwnODYqRvoilqsHPs8cyTjMg1byZBbiNpQAq2TlFLIaQag==", "dev": true, "funding": { "type": "github", @@ -21479,135 +19262,23 @@ } }, "node_modules/@testing-library/react": { - "version": "14.3.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.3.0.tgz", - "integrity": "sha512-AYJGvNFMbCa5vt1UtDCa/dcaABrXq8gph6VN+cffIx0UeA0qiGqS+sT60+sb+Gjc8tGXdECWYQgaF0khf8b+Lg==", + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-15.0.2.tgz", + "integrity": "sha512-5mzIpuytB1ctpyywvyaY2TAAUQVCZIGqwiqFQf6u9lvj/SJQepGUzNV18Xpk+NLCaCE2j7CWrZE0tEf9xLZYiQ==", "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^9.0.0", + "@testing-library/dom": "^10.0.0", "@types/react-dom": "^18.0.0" }, "engines": { - "node": ">=14" + "node": ">=18" }, "peerDependencies": { "react": "^18.0.0", "react-dom": "^18.0.0" } }, - "node_modules/@testing-library/react/node_modules/@testing-library/dom": { - "version": "9.3.4", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz", - "integrity": "sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.1.3", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@testing-library/react/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/react/node_modules/ansi-styles": { - "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" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@testing-library/react/node_modules/aria-query": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", - "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", - "dev": true, - "dependencies": { - "deep-equal": "^2.0.5" - } - }, - "node_modules/@testing-library/react/node_modules/chalk": { - "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" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@testing-library/react/node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@testing-library/react/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@testing-library/react/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "node_modules/@testing-library/react/node_modules/supports-color": { - "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" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@testing-library/user-event": { "version": "14.5.2", "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.2.tgz", @@ -21868,9 +19539,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": "*" } @@ -21995,9 +19666,9 @@ "dev": true }, "node_modules/@types/eslint": { - "version": "8.56.7", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.7.tgz", - "integrity": "sha512-SjDvI/x3zsZnOkYZ3lCt9lOZWZLB2jIlNKz+LBgCtDurK0JZcwucxYHn1w2BJkD34dgX9Tjnak0txtq4WTggEA==", + "version": "8.56.9", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.9.tgz", + "integrity": "sha512-W4W3KcqzjJ0sHg2vAq9vfml6OhsJ53TcUjUqfzzZf/EChUtwspszj/S0pzMxnfRcO55/iGq47dscXw71Fxc4Zg==", "dev": true, "dependencies": { "@types/estree": "*", @@ -22303,6 +19974,18 @@ "iconv-lite": "^0.6.3" } }, + "node_modules/@types/mailparser/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@types/mdast": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.3.tgz", @@ -22313,9 +19996,9 @@ } }, "node_modules/@types/mdx": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.12.tgz", - "integrity": "sha512-H9VZ9YqE+H28FQVchC83RCs5xQ2J7mAAv6qdDEaWmXEVl3OpdH+xfrSUzQ1lp7U7oSTRZ0RvW08ASPJsYBi7Cw==", + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", + "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", "dev": true }, "node_modules/@types/memcached": { @@ -22363,9 +20046,9 @@ } }, "node_modules/@types/node": { - "version": "20.12.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.5.tgz", - "integrity": "sha512-BD+BjQ9LS/D8ST9p5uqBxghlN+S42iuNxjsUGjeZobe/ciXzk2qb1B6IXc6AnRLS+yFJRpN2IPEHMzwspfDJNw==", + "version": "20.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", + "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", "dependencies": { "undici-types": "~5.26.4" } @@ -22439,9 +20122,9 @@ } }, "node_modules/@types/pg": { - "version": "8.11.4", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.4.tgz", - "integrity": "sha512-yw3Bwbda6vO+NvI1Ue/YKOwtl31AYvvd/e73O3V4ZkNzuGpTDndLSyc0dQRB2xrQqDePd20pEGIfqSp/GH3pRw==", + "version": "8.11.5", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.5.tgz", + "integrity": "sha512-2xMjVviMxneZHDHX5p5S6tsRRs7TpDHeeK7kTTMe/kAC/mRRNjWHjZg0rkiY+e17jXSZV3zJYDxXV8Cy72/Vuw==", "dependencies": { "@types/node": "*", "pg-protocol": "*", @@ -22485,9 +20168,9 @@ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" }, "node_modules/@types/react": { - "version": "18.2.74", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.74.tgz", - "integrity": "sha512-9AEqNZZyBx8OdZpxzQlaFEVCSFUM2YXJH46yPOiOpm078k6ZLOCcuAzGum/zK8YBwY+dbahVNbHrbgrAwIRlqw==", + "version": "18.2.78", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.78.tgz", + "integrity": "sha512-qOwdPnnitQY4xKlKayt42q5W5UQrSHjgoXNVEtxeqdITJ99k4VXJOP3vt8Rkm9HmgJpH50UNU+rlqfkfWOqp0A==", "devOptional": true, "dependencies": { "@types/prop-types": "*", @@ -22495,9 +20178,9 @@ } }, "node_modules/@types/react-dom": { - "version": "18.2.24", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.24.tgz", - "integrity": "sha512-cN6upcKd8zkGy4HU9F1+/s98Hrp6D4MOcippK4PoE8OZRngohHZpbJn1GsaDLz87MqvHNoT13nHvNqM9ocRHZg==", + "version": "18.2.25", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.25.tgz", + "integrity": "sha512-o/V48vf4MQh7juIKZU2QGDfli6p1+OOi5oXx36Hffpc9adsHeXjVp8rHuPkjd8VT8sOJ2Zp05HR7CdpGTIUFUA==", "dev": true, "dependencies": { "@types/react": "*" @@ -22706,16 +20389,6 @@ "@types/superagent": "^8.1.0" } }, - "node_modules/@types/tar": { - "version": "6.1.12", - "resolved": "https://registry.npmjs.org/@types/tar/-/tar-6.1.12.tgz", - "integrity": "sha512-FwbJPi9YuovB6ilnHrz8Y4pb0Fh6N7guFkbnlCl39ua893Qi5gkXui7LSDpTQMJCmA4z5f6SeSrTPQEWLdtFVw==", - "dev": true, - "dependencies": { - "@types/node": "*", - "minipass": "^4.0.0" - } - }, "node_modules/@types/tedious": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/@types/tedious/-/tedious-4.0.14.tgz", @@ -22806,22 +20479,22 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.5.0.tgz", - "integrity": "sha512-HpqNTH8Du34nLxbKgVMGljZMG0rJd2O9ecvr2QLYp+7512ty1j42KnsFwspPXg1Vh8an9YImf6CokUBltisZFQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.6.0.tgz", + "integrity": "sha512-gKmTNwZnblUdnTIJu3e9kmeRRzV2j1a/LUO27KNNAnIC5zjy1aSvXSRp4rVNlmAoHlQ7HzX42NbKpcSr4jF80A==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.5.0", - "@typescript-eslint/type-utils": "7.5.0", - "@typescript-eslint/utils": "7.5.0", - "@typescript-eslint/visitor-keys": "7.5.0", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/type-utils": "7.6.0", + "@typescript-eslint/utils": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "debug": "^4.3.4", "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -22874,15 +20547,15 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.5.0.tgz", - "integrity": "sha512-cj+XGhNujfD2/wzR1tabNsidnYRaFfEkcULdcIyVBYcXjBvBKOes+mpMBP7hMpOyk+gBcfXsrg4NBGAStQyxjQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.6.0.tgz", + "integrity": "sha512-usPMPHcwX3ZoPWnBnhhorc14NJw9J4HpSXQX4urF2TPKG0au0XhJoZyX62fmvdHONUkmyUe74Hzm1//XA+BoYg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.5.0", - "@typescript-eslint/types": "7.5.0", - "@typescript-eslint/typescript-estree": "7.5.0", - "@typescript-eslint/visitor-keys": "7.5.0", + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/typescript-estree": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "debug": "^4.3.4" }, "engines": { @@ -22902,13 +20575,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.5.0.tgz", - "integrity": "sha512-Z1r7uJY0MDeUlql9XJ6kRVgk/sP11sr3HKXn268HZyqL7i4cEfrdFuSSY/0tUqT37l5zT0tJOsuDP16kio85iA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.6.0.tgz", + "integrity": "sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.5.0", - "@typescript-eslint/visitor-keys": "7.5.0" + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -22919,15 +20592,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.5.0.tgz", - "integrity": "sha512-A021Rj33+G8mx2Dqh0nMO9GyjjIBK3MqgVgZ2qlKf6CJy51wY/lkkFqq3TqqnH34XyAHUkq27IjlUkWlQRpLHw==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.6.0.tgz", + "integrity": "sha512-NxAfqAPNLG6LTmy7uZgpK8KcuiS2NZD/HlThPXQRGwz6u7MDBWRVliEEl1Gj6U7++kVJTpehkhZzCJLMK66Scw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.5.0", - "@typescript-eslint/utils": "7.5.0", + "@typescript-eslint/typescript-estree": "7.6.0", + "@typescript-eslint/utils": "7.6.0", "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -22946,9 +20619,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.5.0.tgz", - "integrity": "sha512-tv5B4IHeAdhR7uS4+bf8Ov3k793VEVHd45viRRkehIUZxm0WF82VPiLgHzA/Xl4TGPg1ZD49vfxBKFPecD5/mg==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz", + "integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==", "dev": true, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -22959,19 +20632,19 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.5.0.tgz", - "integrity": "sha512-YklQQfe0Rv2PZEueLTUffiQGKQneiIEKKnfIqPIOxgM9lKSZFCjT5Ad4VqRKj/U4+kQE3fa8YQpskViL7WjdPQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.6.0.tgz", + "integrity": "sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.5.0", - "@typescript-eslint/visitor-keys": "7.5.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.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" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -23008,9 +20681,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -23044,18 +20717,18 @@ "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.5.0.tgz", - "integrity": "sha512-3vZl9u0R+/FLQcpy2EHyRGNqAS/ofJ3Ji8aebilfJe+fobK8+LbIFmrHciLVDxjDoONmufDcnVSF38KwMEOjzw==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.6.0.tgz", + "integrity": "sha512-x54gaSsRRI+Nwz59TXpCsr6harB98qjXYzsRxGqvA5Ue3kQH+FxS7FYU81g/omn22ML2pZJkisy6Q+ElK8pBCA==", "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": "7.5.0", - "@typescript-eslint/types": "7.5.0", - "@typescript-eslint/typescript-estree": "7.5.0", - "semver": "^7.5.4" + "@types/json-schema": "^7.0.15", + "@types/semver": "^7.5.8", + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/typescript-estree": "7.6.0", + "semver": "^7.6.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -23102,13 +20775,13 @@ "dev": true }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.5.0.tgz", - "integrity": "sha512-mcuHM/QircmA6O7fy6nn2w/3ditQkj+SgtOc8DW3uQ10Yfj42amm2i+6F2K4YAOPNNTmE6iM1ynM6lrSwdendA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.6.0.tgz", + "integrity": "sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.5.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "7.6.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -23156,9 +20829,9 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.4.0.tgz", - "integrity": "sha512-4hDGyH1SvKpgZnIByr9LhGgCEuF9DKM34IBLCC/fVfy24Z3+PZ+Ii9hsVBsHvY1umM1aGPEjceRkzxCfcQ10wg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.5.0.tgz", + "integrity": "sha512-1igVwlcqw1QUMdfcMlzzY4coikSIBN944pkueGi0pawrX5I5Z+9hxdTR+w3Sg6Q3eZhvdMAs8ZaF9JuTG1uYOQ==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.1", @@ -23173,14 +20846,13 @@ "picocolors": "^1.0.0", "std-env": "^3.5.0", "strip-literal": "^2.0.0", - "test-exclude": "^6.0.0", - "v8-to-istanbul": "^9.2.0" + "test-exclude": "^6.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "1.4.0" + "vitest": "1.5.0" } }, "node_modules/@vitest/coverage-v8/node_modules/istanbul-lib-source-maps": { @@ -23198,13 +20870,13 @@ } }, "node_modules/@vitest/expect": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.4.0.tgz", - "integrity": "sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.5.0.tgz", + "integrity": "sha512-0pzuCI6KYi2SIC3LQezmxujU9RK/vwC1U9R0rLuGlNGcOuDWxqWKu6nUdFsX9tH1WU0SXtAxToOsEjeUn1s3hA==", "dev": true, "dependencies": { - "@vitest/spy": "1.4.0", - "@vitest/utils": "1.4.0", + "@vitest/spy": "1.5.0", + "@vitest/utils": "1.5.0", "chai": "^4.3.10" }, "funding": { @@ -23212,12 +20884,12 @@ } }, "node_modules/@vitest/runner": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.4.0.tgz", - "integrity": "sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.5.0.tgz", + "integrity": "sha512-7HWwdxXP5yDoe7DTpbif9l6ZmDwCzcSIK38kTSIt6CFEpMjX4EpCgT6wUmS0xTXqMI6E/ONmfgRKmaujpabjZQ==", "dev": true, "dependencies": { - "@vitest/utils": "1.4.0", + "@vitest/utils": "1.5.0", "p-limit": "^5.0.0", "pathe": "^1.1.1" }, @@ -23253,9 +20925,9 @@ } }, "node_modules/@vitest/snapshot": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.4.0.tgz", - "integrity": "sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.5.0.tgz", + "integrity": "sha512-qpv3fSEuNrhAO3FpH6YYRdaECnnRjg9VxbhdtPwPRnzSfHVXnNzzrpX4cJxqiwgRMo7uRMWDFBlsBq4Cr+rO3A==", "dev": true, "dependencies": { "magic-string": "^0.30.5", @@ -23267,9 +20939,9 @@ } }, "node_modules/@vitest/spy": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.4.0.tgz", - "integrity": "sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.5.0.tgz", + "integrity": "sha512-vu6vi6ew5N5MMHJjD5PoakMRKYdmIrNJmyfkhRpQt5d9Ewhw9nZ5Aqynbi3N61bvk9UvZ5UysMT6ayIrZ8GA9w==", "dev": true, "dependencies": { "tinyspy": "^2.2.0" @@ -23279,12 +20951,12 @@ } }, "node_modules/@vitest/ui": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-1.4.0.tgz", - "integrity": "sha512-XC6CMhN1gzYcGbpn6/Oanj4Au2EXwQEX6vpcOeLlZv8dy7g11Ukx8zwtYQbwxs9duK2s9j2o5rbQiCP5DPAcmw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-1.5.0.tgz", + "integrity": "sha512-ETcToK2TzICf/Oartvt19IH7yR4oCs8GrQk5hRhZ5oZFaSdDHTh6o3EdzyxOaY24NZ20cXYYNGjj1se/5vHfFg==", "dev": true, "dependencies": { - "@vitest/utils": "1.4.0", + "@vitest/utils": "1.5.0", "fast-glob": "^3.3.2", "fflate": "^0.8.1", "flatted": "^3.2.9", @@ -23296,13 +20968,13 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "1.4.0" + "vitest": "1.5.0" } }, "node_modules/@vitest/utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.4.0.tgz", - "integrity": "sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.5.0.tgz", + "integrity": "sha512-BDU0GNL8MWkRkSRdNFvCUCAVOeHaUlVJ9Tx0TYBZyXaaOTmGtUFObzchCivIBrIwKzvZA7A9sCejVhXM2aY98A==", "dev": true, "dependencies": { "diff-sequences": "^29.6.3", @@ -23770,26 +21442,26 @@ } }, "node_modules/algoliasearch": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.23.2.tgz", - "integrity": "sha512-8aCl055IsokLuPU8BzLjwzXjb7ty9TPcUFFOk0pYOwsE5DMVhE3kwCMFtsCFKcnoPZK7oObm+H5mbnSO/9ioxQ==", - "dev": true, - "dependencies": { - "@algolia/cache-browser-local-storage": "4.23.2", - "@algolia/cache-common": "4.23.2", - "@algolia/cache-in-memory": "4.23.2", - "@algolia/client-account": "4.23.2", - "@algolia/client-analytics": "4.23.2", - "@algolia/client-common": "4.23.2", - "@algolia/client-personalization": "4.23.2", - "@algolia/client-search": "4.23.2", - "@algolia/logger-common": "4.23.2", - "@algolia/logger-console": "4.23.2", - "@algolia/recommend": "4.23.2", - "@algolia/requester-browser-xhr": "4.23.2", - "@algolia/requester-common": "4.23.2", - "@algolia/requester-node-http": "4.23.2", - "@algolia/transporter": "4.23.2" + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.23.3.tgz", + "integrity": "sha512-Le/3YgNvjW9zxIQMRhUHuhiUjAlKY/zsdZpfq4dlLqg6mEm0nL6yk+7f2hDOtLpxsgE4jSzDmvHL7nXdBp5feg==", + "dev": true, + "dependencies": { + "@algolia/cache-browser-local-storage": "4.23.3", + "@algolia/cache-common": "4.23.3", + "@algolia/cache-in-memory": "4.23.3", + "@algolia/client-account": "4.23.3", + "@algolia/client-analytics": "4.23.3", + "@algolia/client-common": "4.23.3", + "@algolia/client-personalization": "4.23.3", + "@algolia/client-search": "4.23.3", + "@algolia/logger-common": "4.23.3", + "@algolia/logger-console": "4.23.3", + "@algolia/recommend": "4.23.3", + "@algolia/requester-browser-xhr": "4.23.3", + "@algolia/requester-common": "4.23.3", + "@algolia/requester-node-http": "4.23.3", + "@algolia/transporter": "4.23.3" } }, "node_modules/algoliasearch-helper": { @@ -23929,7 +21601,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, "engines": { "node": ">=12" }, @@ -24050,21 +21721,6 @@ "node": ">=10" } }, - "node_modules/are-we-there-yet/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "optional": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -24440,9 +22096,9 @@ } }, "node_modules/aws-cdk": { - "version": "2.136.0", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.136.0.tgz", - "integrity": "sha512-MVSE+AERoP0D1qXlkhKQOzs22QVulGleX1yJTkWzoYhEyseEmR8EiFJcmyEhJku/swmY0KDpVlT9R62dRG5+JQ==", + "version": "2.137.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.137.0.tgz", + "integrity": "sha512-3pf3SVDwNZvo3EfhO3yl1B+KbRHz7T4UmPifUEKfOwk7ABAFLRSNddZuUlF560XSBTFLkrZoeBDa0/MLJT6F4g==", "bin": { "cdk": "bin/cdk" }, @@ -24454,9 +22110,9 @@ } }, "node_modules/aws-cdk-lib": { - "version": "2.136.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.136.0.tgz", - "integrity": "sha512-zdkWNe91mvZH6ESghUoIxB8ORoreExg2wowTLEVfy3vWY1a6n69crxk8mkCG+vn6GhXEnEPpovoG1QV8BpXTpA==", + "version": "2.137.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.137.0.tgz", + "integrity": "sha512-pD3AGdKBa8q1+vVWRabiDHuecVMlP8ERGPHc9Pb0dVlpbC/ODC6XXC1S0TAMsr0JI5Lh6pk4vL5cC+spsMeotw==", "bundleDependencies": [ "@balena/dockerignore", "case", @@ -25141,15 +22797,6 @@ "node": ">=10" } }, - "node_modules/babel-plugin-macros/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/babel-plugin-polyfill-corejs2": { "version": "0.4.10", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz", @@ -25782,19 +23429,6 @@ "node": "*" } }, - "node_modules/binary": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", - "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", - "dev": true, - "dependencies": { - "buffers": "~0.1.1", - "chainsaw": "~0.1.0" - }, - "engines": { - "node": "*" - } - }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -25827,19 +23461,6 @@ "readable-stream": "^3.4.0" } }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/bluebird": { "version": "3.4.7", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", @@ -25882,17 +23503,6 @@ "ms": "2.0.0" } }, - "node_modules/body-parser/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/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -26054,12 +23664,6 @@ "pako": "~0.2.0" } }, - "node_modules/browserify-zlib/node_modules/pako": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", - "dev": true - }, "node_modules/browserslist": { "version": "4.23.0", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", @@ -26124,12 +23728,26 @@ } }, "node_modules/buffer": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", - "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, "node_modules/buffer-alloc": { @@ -26162,24 +23780,6 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, - "node_modules/buffer-indexof-polyfill": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", - "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/buffers": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", - "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", - "dev": true, - "engines": { - "node": ">=0.2.0" - } - }, "node_modules/buildcheck": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz", @@ -26244,9 +23844,9 @@ "dev": true }, "node_modules/bullmq": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.5.4.tgz", - "integrity": "sha512-qqZ8lZNfCzqV46zlIq0/4hzgCklhkXEeREeHPBJD2FAoNa7/Gwa9V4ySFX0rJv7B7U2buPrawkBQ1/7ATAu39A==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.7.1.tgz", + "integrity": "sha512-t7FhF2mCGgmjZ1rHuBYIcLwzONm4QFGrO1+9mF7hpjWtXalGfy+nGciVcb69L7aPcdJMR2XTe6bNMWHGbKy8mQ==", "dependencies": { "cron-parser": "^4.6.0", "ioredis": "^5.3.2", @@ -26363,6 +23963,15 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/cacache/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/cacache/node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -26372,15 +23981,74 @@ "node": ">=12" } }, - "node_modules/cacache/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "node_modules/cacache/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=10" + } + }, + "node_modules/cacache/node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" } }, + "node_modules/cacache/node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cacache/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacache/node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacache/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/cacheable-lookup": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", @@ -26547,9 +24215,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001607", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001607.tgz", - "integrity": "sha512-WcvhVRjXLKFB/kmOFVwELtMxyhq3iM/MvmXcyCe2PNf166c39mptscOc/45TTS96n2gpNV2z7+NakArTWZCQ3w==", + "version": "1.0.30001610", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001610.tgz", + "integrity": "sha512-QFutAY4NgaelojVMjY63o6XlZyORPaLfyMnsl3HgnWdJUcX6K0oaJymHjH8PT5Gk7sTm8rvC/c5COUQKXqmOMA==", "funding": [ { "type": "opencollective", @@ -26576,11 +24244,11 @@ } }, "node_modules/cdk": { - "version": "2.136.0", - "resolved": "https://registry.npmjs.org/cdk/-/cdk-2.136.0.tgz", - "integrity": "sha512-vcGhctRGJnsVH1LaVRjknbb0kaBk0LMqNUXkyrRhAkNRZyqDPfukqIlNaO513eNCSCO2oMAEfjl616xsaWBnaQ==", + "version": "2.137.0", + "resolved": "https://registry.npmjs.org/cdk/-/cdk-2.137.0.tgz", + "integrity": "sha512-VOGIVuyOqJ6bU3ZhJjow2w89CIPGdYMWLLUrPFR/xujYuOF/+6lKmNR+VPqTpwY8f96XtUGIessXrbaSvWph6Q==", "dependencies": { - "aws-cdk": "2.136.0" + "aws-cdk": "2.137.0" }, "bin": { "cdk": "bin/cdk" @@ -26590,18 +24258,18 @@ } }, "node_modules/cdk-nag": { - "version": "2.28.84", - "resolved": "https://registry.npmjs.org/cdk-nag/-/cdk-nag-2.28.84.tgz", - "integrity": "sha512-GRj7tjoomctFozzH0tFWOXRNaTf7mht01xu+bZzZ3794leJlZ8stl+Aw1D2RMSQy9CNGDsIxrUns5M4lQ1Hjjw==", + "version": "2.28.89", + "resolved": "https://registry.npmjs.org/cdk-nag/-/cdk-nag-2.28.89.tgz", + "integrity": "sha512-0K9roRBQNItmeGvwfZlP8DnETZaytmRFXxIva9hiKpdQOVN8HAfbNrVRQkqG/l30m9RFySf/pJHhOU0p/S5H7Q==", "peerDependencies": { "aws-cdk-lib": "^2.116.0", "constructs": "^10.0.5" } }, "node_modules/cdk-serverless-clamscan": { - "version": "2.6.145", - "resolved": "https://registry.npmjs.org/cdk-serverless-clamscan/-/cdk-serverless-clamscan-2.6.145.tgz", - "integrity": "sha512-OkvYRglr1Zv2M44A27Fvw7xvlLcURX4ZCiwWx90l1FYolM31mdyn3lqZxV5s0nnKWBr4WOLp25QGDW/wLalxSA==", + "version": "2.6.150", + "resolved": "https://registry.npmjs.org/cdk-serverless-clamscan/-/cdk-serverless-clamscan-2.6.150.tgz", + "integrity": "sha512-hberjqIDNqNMRUEmpYPg/cJrvAzMMQKvZCO9Nmi/AlJe+XsN58XE4TGep63WvAH9Nk/w9kz1PxCdrMR+fCvxlg==", "bin": { "0": "assets" }, @@ -26628,18 +24296,6 @@ "node": ">=4" } }, - "node_modules/chainsaw": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", - "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", - "dev": true, - "dependencies": { - "traverse": ">=0.3.0 <0.4" - }, - "engines": { - "node": "*" - } - }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -26816,11 +24472,11 @@ } }, "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/chromatic": { @@ -27529,6 +25185,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", @@ -27548,19 +25209,6 @@ "typedarray": "^0.0.6" } }, - "node_modules/concat-stream/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/config-chain": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", @@ -27756,25 +25404,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", @@ -27789,9 +25418,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.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -27808,6 +25437,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", @@ -28457,15 +26094,6 @@ "postcss": "^8.2.15" } }, - "node_modules/cssnano/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/csso": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", @@ -28741,6 +26369,18 @@ "node": ">= 10" } }, + "node_modules/d3-dsv/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/d3-ease": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", @@ -29310,9 +26950,9 @@ } }, "node_modules/dedent": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", - "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", "dev": true, "peerDependencies": { "babel-plugin-macros": "^3.1.0" @@ -29336,29 +26976,16 @@ } }, "node_modules/deep-equal": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", - "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", - "dev": true, + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz", + "integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==", "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.5", - "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.2", "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.2", "is-date-object": "^1.0.5", "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", "object-is": "^1.1.5", "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.13" + "regexp.prototype.flags": "^1.5.1" }, "engines": { "node": ">= 0.4" @@ -29367,12 +26994,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/deep-equal/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -30110,6 +27731,36 @@ "readable-stream": "^2.0.2" } }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexer2/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==", + "dev": true + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -30122,6 +27773,36 @@ "stream-shift": "^1.0.0" } }, + "node_modules/duplexify/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexify/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==", + "dev": true + }, + "node_modules/duplexify/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/dynamic-dedupe": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", @@ -30134,8 +27815,7 @@ "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", @@ -30152,9 +27832,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/ejs": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", - "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dev": true, "dependencies": { "jake": "^10.8.5" @@ -30167,9 +27847,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.730", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.730.tgz", - "integrity": "sha512-oJRPo82XEqtQAobHpJIR3zW5YO3sSRRkPz2an4yxi1UvqhsGm54vR/wzTFV74a3soDOJ8CKW7ajOOX5ESzddwg==" + "version": "1.4.736", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.736.tgz", + "integrity": "sha512-Rer6wc3ynLelKNM4lOCg7/zPQj8tPOCB2hzD32PX9wd3hgRRi9MxEbmkFCokzcEhRVMiOVLjnL9ig9cefJ+6+Q==" }, "node_modules/elkjs": { "version": "0.9.2", @@ -30201,8 +27881,7 @@ "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, "node_modules/emojilib": { "version": "2.4.0", @@ -30260,6 +27939,18 @@ "node": ">=8.10.0" } }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -30437,32 +28128,6 @@ "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", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-get-iterator/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, "node_modules/es-iterator-helpers": { "version": "1.0.18", "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.18.tgz", @@ -30731,14 +28396,14 @@ } }, "node_modules/eslint-config-next": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.1.4.tgz", - "integrity": "sha512-cihIahbhYAWwXJwZkAaRPpUi5t9aOi/HdfWXOjZeUOqNWXHD8X22kd1KG58Dc3MVaRx3HoR/oMGk2ltcrqDn8g==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.1.tgz", + "integrity": "sha512-BgD0kPCWMlqoItRf3xe9fG0MqwObKfVch+f2ccwDpZiCJA8ghkz2wrASH+bI6nLZzGcOJOpMm1v1Q1euhfpt4Q==", "dev": true, "dependencies": { - "@next/eslint-plugin-next": "14.1.4", + "@next/eslint-plugin-next": "14.2.1", "@rushstack/eslint-patch": "^1.3.3", - "@typescript-eslint/parser": "^5.4.2 || ^6.0.0", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.28.1", @@ -30757,15 +28422,15 @@ } }, "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==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", + "integrity": "sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==", "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.2.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", "debug": "^4.3.4" }, "engines": { @@ -30776,7 +28441,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -30785,13 +28450,13 @@ } }, "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==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz", + "integrity": "sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -30802,9 +28467,9 @@ } }, "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==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz", + "integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -30815,13 +28480,13 @@ } }, "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==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz", + "integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -30843,12 +28508,12 @@ } }, "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==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz", + "integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/types": "7.2.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -31964,9 +29629,9 @@ } }, "node_modules/expo": { - "version": "50.0.14", - "resolved": "https://registry.npmjs.org/expo/-/expo-50.0.14.tgz", - "integrity": "sha512-yLPdxCMVAbmeEIpzzyAuJ79wvr6ToDDtQmuLDMAgWtjqP8x3CGddXxUe07PpKEQgzwJabdHvCLP5Bv94wMFIjQ==", + "version": "50.0.15", + "resolved": "https://registry.npmjs.org/expo/-/expo-50.0.15.tgz", + "integrity": "sha512-tsyRmMHjA8lPlM7AsqH1smSH8hzmn1+x/vsP+xgbKYJTGtYccdY/wsm6P84VJWeK5peWSVqrWNos+YuPqXKLSQ==", "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "0.17.8", @@ -31980,7 +29645,7 @@ "expo-font": "~11.10.3", "expo-keep-awake": "~12.8.2", "expo-modules-autolinking": "1.10.3", - "expo-modules-core": "1.11.12", + "expo-modules-core": "1.11.13", "fbemitter": "^3.0.0", "whatwg-url-without-unicode": "8.0.0-3" }, @@ -32149,9 +29814,9 @@ } }, "node_modules/expo-modules-core": { - "version": "1.11.12", - "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-1.11.12.tgz", - "integrity": "sha512-/e8g4kis0pFLer7C0PLyx98AfmztIM6gU9jLkYnB1pU9JAfQf904XEi3bmszO7uoteBQwSL6FLp1m3TePKhDaA==", + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-1.11.13.tgz", + "integrity": "sha512-2H5qrGUvmLzmJNPDOnovH1Pfk5H/S/V0BifBmOQyDc9aUh9LaDwkqnChZGIXv8ZHDW8JRlUW0QqyWxTggkbw1A==", "dependencies": { "invariant": "^2.2.4" } @@ -32257,14 +29922,6 @@ "node": ">= 8.0.0" } }, - "node_modules/express/node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "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", @@ -32292,25 +29949,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -32994,7 +30632,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", - "dev": true, "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -33010,7 +30647,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "engines": { "node": ">=14" }, @@ -33275,15 +30911,6 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -33407,6 +31034,36 @@ "readable-stream": "^2.0.0" } }, + "node_modules/from2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/from2/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==", + "dev": true + }, + "node_modules/from2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/fromentries": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", @@ -33475,15 +31132,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/fs-monkey": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz", @@ -33679,9 +31327,9 @@ } }, "node_modules/gaxios": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.4.0.tgz", - "integrity": "sha512-apAloYrY4dlBGlhauDAYSZveafb5U6+L9titing1wox6BvWM0TSXBp603zTrLpyLMGkrcFgohnUN150dFN/zOA==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.5.0.tgz", + "integrity": "sha512-R9QGdv8j4/dlNoQbX3hSaK/S0rkMijqjVvW3YM06CoBdbU/VdKd159j4hePpng0KuE6Lh6JJ7UdmVGJZFcAG1w==", "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", @@ -33886,6 +31534,83 @@ "giget": "dist/cli.mjs" } }, + "node_modules/giget/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/giget/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/giget/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/giget/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/giget/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/giget/node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/giget/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/git-config-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/git-config-path/-/git-config-path-1.0.1.tgz", @@ -33930,7 +31655,6 @@ "version": "10.3.12", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", - "dev": true, "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^2.3.6", @@ -33969,7 +31693,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -33978,7 +31701,6 @@ "version": "9.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -33989,15 +31711,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/glob/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/global-dirs": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", @@ -34803,6 +32516,36 @@ "wbuf": "^1.1.0" } }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/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==", + "dev": true + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", @@ -35142,11 +32885,11 @@ "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" }, "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "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.0.0" + "safer-buffer": ">= 2.1.2 < 3" }, "engines": { "node": ">=0.10.0" @@ -36628,7 +34371,6 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", - "dev": true, "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -37263,6 +35005,18 @@ "node": ">=12" } }, + "node_modules/jest-environment-jsdom/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/jest-environment-jsdom/node_modules/jsdom": { "version": "20.0.3", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", @@ -38770,22 +36524,6 @@ "is-buffer": "~1.1.1" } }, - "node_modules/json-schema-deref-sync/node_modules/traverse": { - "version": "0.6.9", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.9.tgz", - "integrity": "sha512-7bBrcF+/LQzSgFmT0X5YclVqQxtv7TDJ1f8Wj7ibBu/U6BMLeOpUxuZjV7rMc44UtKxlnMFigdhFAIszSX1DMg==", - "dependencies": { - "gopd": "^1.0.1", - "typedarray.prototype.slice": "^1.0.3", - "which-typed-array": "^1.1.15" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -38937,6 +36675,38 @@ "setimmediate": "^1.0.5" } }, + "node_modules/jszip/node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/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/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/just-extend": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", @@ -39141,6 +36911,18 @@ "libqp": "2.1.0" } }, + "node_modules/libmime/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/libqp": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/libqp/-/libqp-2.1.0.tgz", @@ -39404,12 +37186,6 @@ "uc.micro": "^2.0.0" } }, - "node_modules/listenercount": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", - "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", - "dev": true - }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -39941,20 +37717,20 @@ } }, "node_modules/magicast": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.3.tgz", - "integrity": "sha512-ZbrP1Qxnpoes8sz47AM0z08U+jW6TyRgZzcWy3Ma3vDhJttwMwAFDMMQFobwdBxByBD46JYmxRzeF7w2+wJEuw==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.4.tgz", + "integrity": "sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==", "dev": true, "dependencies": { - "@babel/parser": "^7.23.6", - "@babel/types": "^7.23.6", - "source-map-js": "^1.0.2" + "@babel/parser": "^7.24.4", + "@babel/types": "^7.24.0", + "source-map-js": "^1.2.0" } }, "node_modules/mailparser": { - "version": "3.6.9", - "resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.6.9.tgz", - "integrity": "sha512-1fIDZlgN1NnuzmTSEUxkaViquXYkw5NbQehVc+kz55QRy98QgLdTtRSKv289Jy4NrCiDchRx6zAijB4HrPsvkA==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.7.0.tgz", + "integrity": "sha512-yFo1+4r3gHhTcazVGCv3D/uX5VJyyrx4iWkzKtJh8mujYIUm92kpG5BjKpZIJgEoKcKxbJVd4CPs+IImE1sKlQ==", "dev": true, "dependencies": { "encoding-japanese": "2.0.0", @@ -39969,6 +37745,18 @@ "tlds": "1.250.0" } }, + "node_modules/mailparser/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/mailparser/node_modules/nodemailer": { "version": "6.9.11", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.11.tgz", @@ -39989,6 +37777,18 @@ "libqp": "2.0.1" } }, + "node_modules/mailsplit/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/mailsplit/node_modules/libbase64": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/libbase64/-/libbase64-1.2.1.tgz", @@ -43652,12 +41452,11 @@ } }, "node_modules/minipass": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", - "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", - "dev": true, + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/minipass-collect": { @@ -43704,15 +41503,6 @@ "encoding": "^0.1.13" } }, - "node_modules/minipass-fetch/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/minipass-flush": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", @@ -43857,7 +41647,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "dev": true, "bin": { "mkdirp": "dist/cjs/src/bin.js" }, @@ -44006,20 +41795,6 @@ "readable-stream": "^3.6.0" } }, - "node_modules/multistream/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/mv": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", @@ -44186,12 +41961,12 @@ "integrity": "sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==" }, "node_modules/next": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/next/-/next-14.1.4.tgz", - "integrity": "sha512-1WTaXeSrUwlz/XcnhGTY7+8eiaFvdet5z9u3V2jb+Ek1vFo0VhHKSAIJvDWfQpttWjnyw14kBeq28TPq7bTeEQ==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.1.tgz", + "integrity": "sha512-SF3TJnKdH43PMkCcErLPv+x/DY1YCklslk3ZmwaVoyUfDgHKexuKlf9sEfBQ69w+ue8jQ3msLb+hSj1T19hGag==", "dependencies": { - "@next/env": "14.1.4", - "@swc/helpers": "0.5.2", + "@next/env": "14.2.1", + "@swc/helpers": "0.5.5", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "graceful-fs": "^4.2.11", @@ -44205,18 +41980,19 @@ "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.1.4", - "@next/swc-darwin-x64": "14.1.4", - "@next/swc-linux-arm64-gnu": "14.1.4", - "@next/swc-linux-arm64-musl": "14.1.4", - "@next/swc-linux-x64-gnu": "14.1.4", - "@next/swc-linux-x64-musl": "14.1.4", - "@next/swc-win32-arm64-msvc": "14.1.4", - "@next/swc-win32-ia32-msvc": "14.1.4", - "@next/swc-win32-x64-msvc": "14.1.4" + "@next/swc-darwin-arm64": "14.2.1", + "@next/swc-darwin-x64": "14.2.1", + "@next/swc-linux-arm64-gnu": "14.2.1", + "@next/swc-linux-arm64-musl": "14.2.1", + "@next/swc-linux-x64-gnu": "14.2.1", + "@next/swc-linux-x64-musl": "14.2.1", + "@next/swc-win32-arm64-msvc": "14.2.1", + "@next/swc-win32-ia32-msvc": "14.2.1", + "@next/swc-win32-x64-msvc": "14.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.3.0" @@ -44225,6 +42001,9 @@ "@opentelemetry/api": { "optional": true }, + "@playwright/test": { + "optional": true + }, "sass": { "optional": true } @@ -44645,6 +42424,15 @@ "node": ">=10" } }, + "node_modules/node-gyp/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/node-gyp/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -44821,20 +42609,6 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/node-gyp/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/node-gyp/node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -44915,6 +42689,32 @@ "node": ">=8" } }, + "node_modules/node-gyp/node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp/node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/node-gyp/node_modules/unique-filename": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", @@ -46255,6 +44055,18 @@ "yaml": "^2.4.1" } }, + "node_modules/openapi3-ts/node_modules/yaml": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz", + "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/opener": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", @@ -46657,6 +44469,15 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/pacote/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/pacote/node_modules/minipass": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", @@ -46666,10 +44487,69 @@ "node": ">=8" } }, + "node_modules/pacote/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pacote/node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pacote/node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/pacote/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pacote/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/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==" }, "node_modules/param-case": { "version": "3.0.4", @@ -46914,7 +44794,6 @@ "version": "1.10.2", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", - "dev": true, "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -46930,20 +44809,10 @@ "version": "10.2.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", - "dev": true, "engines": { "node": "14 || >=16.14" } }, - "node_modules/path-scurry/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -46986,6 +44855,17 @@ "node": ">=12" } }, + "node_modules/pdfmake/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/pdfmake/node_modules/sax": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", @@ -48346,9 +46226,9 @@ } }, "node_modules/postcss-preset-mantine": { - "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==", + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/postcss-preset-mantine/-/postcss-preset-mantine-1.14.4.tgz", + "integrity": "sha512-T1K3MVhU1hA9mJWfqoGvMcK5WKcHpVi4JUX6AYTbESvp78WneB/KFONUi+eXDG9Lpw62W/KNxEYl1ic3Dpm88w==", "dev": true, "dependencies": { "postcss-mixins": "^9.0.4", @@ -49099,9 +46979,9 @@ } }, "node_modules/qs": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.0.tgz", - "integrity": "sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg==", + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", + "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", "dev": true, "dependencies": { "side-channel": "^1.0.6" @@ -49238,17 +47118,6 @@ "node": ">= 0.8" } }, - "node_modules/raw-body/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/raw-loader": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz", @@ -49686,9 +47555,9 @@ } }, "node_modules/react-intersection-observer": { - "version": "9.8.1", - "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.8.1.tgz", - "integrity": "sha512-QzOFdROX8D8MH3wE3OVKH0f3mLjKTtEN1VX/rkNuECCff+aKky0pIjulDhr3Ewqj5el/L+MhBkM3ef0Tbt+qUQ==", + "version": "9.8.2", + "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.8.2.tgz", + "integrity": "sha512-901naEiiZmse3p+AmtbQ3NL9xx+gQ8TXLiGDc+8GiE3JKJkNV3vP737aGuWTAXBA+1QqxPrDDE+fIEgYpGDlrQ==", "dev": true, "peerDependencies": { "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", @@ -50573,17 +48442,16 @@ } }, "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" } }, "node_modules/readdirp": { @@ -51369,7 +49237,6 @@ "version": "5.0.5", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==", - "dev": true, "dependencies": { "glob": "^10.3.7" }, @@ -51390,9 +49257,9 @@ "dev": true }, "node_modules/rollup": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.1.tgz", - "integrity": "sha512-4LnHSdd3QK2pa1J6dFbfm1HN0D7vSK/ZuZTsdyUAlA6Rr1yTouUTL13HaDOGJVgby461AhrNGBS7sCGXXtT+SA==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.3.tgz", + "integrity": "sha512-ag5tTQKYsj1bhrFC9+OEWqb5O6VYgtQDO9hPDBMmIbePwhfSr+ExlcU741t8Dhw5DkPCQf6noz0jb36D6W9/hw==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -51405,21 +49272,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.14.1", - "@rollup/rollup-android-arm64": "4.14.1", - "@rollup/rollup-darwin-arm64": "4.14.1", - "@rollup/rollup-darwin-x64": "4.14.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.14.1", - "@rollup/rollup-linux-arm64-gnu": "4.14.1", - "@rollup/rollup-linux-arm64-musl": "4.14.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.14.1", - "@rollup/rollup-linux-riscv64-gnu": "4.14.1", - "@rollup/rollup-linux-s390x-gnu": "4.14.1", - "@rollup/rollup-linux-x64-gnu": "4.14.1", - "@rollup/rollup-linux-x64-musl": "4.14.1", - "@rollup/rollup-win32-arm64-msvc": "4.14.1", - "@rollup/rollup-win32-ia32-msvc": "4.14.1", - "@rollup/rollup-win32-x64-msvc": "4.14.1", + "@rollup/rollup-android-arm-eabi": "4.14.3", + "@rollup/rollup-android-arm64": "4.14.3", + "@rollup/rollup-darwin-arm64": "4.14.3", + "@rollup/rollup-darwin-x64": "4.14.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.14.3", + "@rollup/rollup-linux-arm-musleabihf": "4.14.3", + "@rollup/rollup-linux-arm64-gnu": "4.14.3", + "@rollup/rollup-linux-arm64-musl": "4.14.3", + "@rollup/rollup-linux-powerpc64le-gnu": "4.14.3", + "@rollup/rollup-linux-riscv64-gnu": "4.14.3", + "@rollup/rollup-linux-s390x-gnu": "4.14.3", + "@rollup/rollup-linux-x64-gnu": "4.14.3", + "@rollup/rollup-linux-x64-musl": "4.14.3", + "@rollup/rollup-win32-arm64-msvc": "4.14.3", + "@rollup/rollup-win32-ia32-msvc": "4.14.3", + "@rollup/rollup-win32-x64-msvc": "4.14.3", "fsevents": "~2.3.2" } }, @@ -51522,9 +49390,23 @@ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" }, "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", @@ -52498,9 +50380,9 @@ } }, "node_modules/socks": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.2.tgz", - "integrity": "sha512-5Hvyu6Md91ooZzdrN/bSn/zlulFaRHrk2/6IY6qQNa3oVHTiG+CKxBV5PynKCGf31ar+3/hyPvlHFAYaBEOa3A==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", "dev": true, "dependencies": { "ip-address": "^9.0.5", @@ -53057,20 +50939,6 @@ "wbuf": "^1.7.3" } }, - "node_modules/spdy-transport/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/split": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", @@ -53162,15 +51030,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/ssri/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/stable": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", @@ -53293,18 +51152,6 @@ "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", "dev": true }, - "node_modules/stop-iteration-iterator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", - "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", - "dev": true, - "dependencies": { - "internal-slot": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/store2": { "version": "2.14.3", "resolved": "https://registry.npmjs.org/store2/-/store2-2.14.3.tgz", @@ -53312,12 +51159,12 @@ "dev": true }, "node_modules/storybook": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-8.0.6.tgz", - "integrity": "sha512-QcQl8Sj77scGl0s9pw+cSPFmXK9DPogEkOceG12B2PqdS23oGkaBt24292Y3W5TTMVNyHtRTRB/FqPwK3FOdmA==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-8.0.8.tgz", + "integrity": "sha512-9gTnnAakJBtMCg8oPGqnpy7g/C3Tj2IWiVflHiFg1SDD9zXBoc4mZhaYPTne4LRBUhXk7XuFagKfiRN2V/MuKA==", "dev": true, "dependencies": { - "@storybook/cli": "8.0.6" + "@storybook/cli": "8.0.8" }, "bin": { "sb": "index.js", @@ -53365,19 +51212,6 @@ "readable-stream": "^3.5.0" } }, - "node_modules/stream-browserify/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/stream-buffers": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz", @@ -53395,6 +51229,36 @@ "readable-stream": "^2.1.4" } }, + "node_modules/stream-meter/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/stream-meter/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==", + "dev": true + }, + "node_modules/stream-meter/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/stream-shift": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", @@ -53425,11 +51289,11 @@ } }, "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dependencies": { - "safe-buffer": "~5.1.0" + "safe-buffer": "~5.2.0" } }, "node_modules/string-argv": { @@ -53485,11 +51349,40 @@ "readable-stream": "^2.1.0" } }, + "node_modules/string-to-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/string-to-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==", + "dev": true + }, + "node_modules/string-to-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -53507,7 +51400,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -53521,7 +51413,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -53529,14 +51420,12 @@ "node_modules/string-width-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -53657,7 +51546,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -53673,7 +51561,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -53685,7 +51572,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -53770,9 +51656,9 @@ "dev": true }, "node_modules/stripe": { - "version": "14.24.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-14.24.0.tgz", - "integrity": "sha512-r0JWz2quThXsFbp1pevkAAoDk4sw3kFMQEc2qvxMFUOhw/SFGqtAGz4vQgP/fMWzO28ljBNEiz68KqRx0JS3dw==", + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-15.1.0.tgz", + "integrity": "sha512-yMsP9VWsQKkmR14E7MhtFDYKCoYCnhG/tLtiMnNiSiVisDezU2OKJ2T9myufYKl922H85nvCgaczyBLovaJTVA==", "dev": true, "dependencies": { "@types/node": ">=8.1.0", @@ -54163,19 +52049,19 @@ } }, "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.0.1.tgz", + "integrity": "sha512-IjMhdQMZFpKsHEQT3woZVxBtCQY+0wk3CVxdRkGXEgyGa0dNS/ehPvOMr2nmfC7x5Zj2N+l6yZUpmICjLGS35w==", "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/tar-fs": { @@ -54212,66 +52098,42 @@ "node": ">=6" } }, - "node_modules/tar-stream/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "engines": { - "node": ">= 6" + "node": ">=8" } }, - "node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "node_modules/tar/node_modules/minizlib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.1.tgz", + "integrity": "sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==", "dependencies": { - "minipass": "^3.0.0" + "minipass": "^7.0.4", + "rimraf": "^5.0.5" }, "engines": { - "node": ">= 8" + "node": ">= 18" } }, - "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dependencies": { - "yallist": "^4.0.0" - }, + "node_modules/tar/node_modules/minizlib/node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/tar/node_modules/minipass": { + "node_modules/tar/node_modules/yallist": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/tar/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/telejson": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/telejson/-/telejson-7.2.0.tgz", @@ -54674,6 +52536,33 @@ "xtend": "~4.0.1" } }, + "node_modules/through2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/through2/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/through2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", @@ -54698,15 +52587,15 @@ "dev": true }, "node_modules/tinybench": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.6.0.tgz", - "integrity": "sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.7.0.tgz", + "integrity": "sha512-Qgayeb106x2o4hNzNjsZEfFziw8IbKqtbXBjVh7VIZfBxfD5M4gWtpyx5+YTae2gJ6Y6Dz/KLepiv16RFeQWNA==", "dev": true }, "node_modules/tinypool": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.3.tgz", - "integrity": "sha512-Ud7uepAklqRH1bvwy22ynrliC7Dljz7Tm8M/0RBUW+YRa4YHhZ6e4PpgE+fu1zr/WqB1kbeuVrdfeuyIBpy4tw==", + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", + "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", "dev": true, "engines": { "node": ">=14.0.0" @@ -54831,12 +52720,19 @@ } }, "node_modules/traverse": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", - "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", - "dev": true, + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.9.tgz", + "integrity": "sha512-7bBrcF+/LQzSgFmT0X5YclVqQxtv7TDJ1f8Wj7ibBu/U6BMLeOpUxuZjV7rMc44UtKxlnMFigdhFAIszSX1DMg==", + "dependencies": { + "gopd": "^1.0.1", + "typedarray.prototype.slice": "^1.0.3", + "which-typed-array": "^1.1.15" + }, "engines": { - "node": "*" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/tree-kill": { @@ -55476,9 +53372,9 @@ } }, "node_modules/typescript": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz", - "integrity": "sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -55617,11 +53513,6 @@ "tiny-inflate": "^1.0.0" } }, - "node_modules/unicode-trie/node_modules/pako": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==" - }, "node_modules/unified": { "version": "11.0.4", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.4.tgz", @@ -55830,21 +53721,16 @@ } }, "node_modules/unzipper": { - "version": "0.10.14", - "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", - "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.11.2.tgz", + "integrity": "sha512-SJZV4XldfDk3CQnTU3H/EUKOic3pwYvlavh7C0qvPOxzGC4XQxNFDl+RuIqhXyNbvRe4ocLIpFnlKY+2NpJ2bg==", "dev": true, "dependencies": { "big-integer": "^1.6.17", - "binary": "~0.3.0", "bluebird": "~3.4.1", - "buffer-indexof-polyfill": "~1.0.0", "duplexer2": "~0.1.4", "fstream": "^1.0.12", - "graceful-fs": "^4.2.2", - "listenercount": "~1.0.1", - "readable-stream": "~2.3.6", - "setimmediate": "~1.0.4" + "graceful-fs": "^4.2.2" } }, "node_modules/update-browserslist-db": { @@ -56384,9 +54270,9 @@ } }, "node_modules/vite-node": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.4.0.tgz", - "integrity": "sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.5.0.tgz", + "integrity": "sha512-tV8h6gMj6vPzVCa7l+VGq9lwoJjW8Y79vst8QZZGiuRAfijU+EEWuc0kFpmndQrWhMMhet1jdSF+40KSZUqIIw==", "dev": true, "dependencies": { "cac": "^6.7.14", @@ -56412,16 +54298,16 @@ "dev": true }, "node_modules/vitest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.4.0.tgz", - "integrity": "sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.5.0.tgz", + "integrity": "sha512-d8UKgR0m2kjdxDWX6911uwxout6GHS0XaGH1cksSIVVG8kRlE7G7aBw7myKQCvDI5dT4j7ZMa+l706BIORMDLw==", "dev": true, "dependencies": { - "@vitest/expect": "1.4.0", - "@vitest/runner": "1.4.0", - "@vitest/snapshot": "1.4.0", - "@vitest/spy": "1.4.0", - "@vitest/utils": "1.4.0", + "@vitest/expect": "1.5.0", + "@vitest/runner": "1.5.0", + "@vitest/snapshot": "1.5.0", + "@vitest/spy": "1.5.0", + "@vitest/utils": "1.5.0", "acorn-walk": "^8.3.2", "chai": "^4.3.10", "debug": "^4.3.4", @@ -56433,9 +54319,9 @@ "std-env": "^3.5.0", "strip-literal": "^2.0.0", "tinybench": "^2.5.1", - "tinypool": "^0.8.2", + "tinypool": "^0.8.3", "vite": "^5.0.0", - "vite-node": "1.4.0", + "vite-node": "1.5.0", "why-is-node-running": "^2.2.2" }, "bin": { @@ -56450,8 +54336,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.4.0", - "@vitest/ui": "1.4.0", + "@vitest/browser": "1.5.0", + "@vitest/ui": "1.5.0", "happy-dom": "*", "jsdom": "*" }, @@ -56779,9 +54665,9 @@ } }, "node_modules/webpack-bundle-analyzer": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.1.tgz", - "integrity": "sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==", + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", + "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", "dev": true, "dependencies": { "@discoveryjs/json-ext": "0.5.7", @@ -56792,7 +54678,6 @@ "escape-string-regexp": "^4.0.0", "gzip-size": "^6.0.0", "html-escaper": "^2.0.2", - "is-plain-object": "^5.0.0", "opener": "^1.5.2", "picocolors": "^1.0.0", "sirv": "^2.0.3", @@ -57282,6 +55167,18 @@ "node": ">=18" } }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/whatwg-fetch": { "version": "3.6.20", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", @@ -57553,19 +55450,6 @@ "node": ">= 12.0.0" } }, - "node_modules/winston-transport/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/winston/node_modules/@colors/colors": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", @@ -57574,19 +55458,6 @@ "node": ">=0.1.90" } }, - "node_modules/winston/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/wonka": { "version": "4.0.15", "resolved": "https://registry.npmjs.org/wonka/-/wonka-4.0.15.tgz", @@ -57602,7 +55473,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -57620,7 +55490,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -57637,7 +55506,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -57646,7 +55514,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" }, @@ -57660,14 +55527,12 @@ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -57681,7 +55546,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -57693,7 +55557,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "engines": { "node": ">=12" }, @@ -57902,14 +55765,11 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "node_modules/yaml": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz", - "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==", - "bin": { - "yaml": "bin.mjs" - }, + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", "engines": { - "node": ">= 14" + "node": ">= 6" } }, "node_modules/yargs": { @@ -58064,23 +55924,23 @@ "version": "3.1.2", "license": "Apache-2.0", "devDependencies": { - "@mantine/core": "7.7.1", - "@mantine/dropzone": "7.7.1", - "@mantine/hooks": "7.7.1", - "@mantine/notifications": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/dropzone": "7.8.0", + "@mantine/hooks": "7.8.0", + "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/definitions": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/mock": "3.1.2", "@medplum/react": "3.1.2", - "@tabler/icons-react": "3.1.0", + "@tabler/icons-react": "3.2.0", "@testing-library/jest-dom": "6.4.2", - "@testing-library/react": "14.3.0", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@testing-library/react": "15.0.2", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.38", - "postcss-preset-mantine": "1.13.0", + "postcss-preset-mantine": "1.14.4", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.22.3", @@ -58127,10 +55987,10 @@ "dependencies": { "@aws-sdk/types": "3.535.0", "@medplum/core": "3.1.2", - "aws-cdk-lib": "2.136.0", - "cdk": "2.136.0", - "cdk-nag": "2.28.84", - "cdk-serverless-clamscan": "2.6.145", + "aws-cdk-lib": "2.137.0", + "cdk": "2.137.0", + "cdk-nag": "2.28.89", + "cdk-serverless-clamscan": "2.6.150", "constructs": "10.3.0" }, "engines": { @@ -58142,13 +56002,13 @@ "version": "3.1.2", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-acm": "3.549.0", - "@aws-sdk/client-cloudformation": "3.549.0", - "@aws-sdk/client-cloudfront": "3.549.0", - "@aws-sdk/client-ecs": "3.549.0", - "@aws-sdk/client-s3": "3.550.0", - "@aws-sdk/client-ssm": "3.549.0", - "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/client-acm": "3.554.0", + "@aws-sdk/client-cloudformation": "3.555.0", + "@aws-sdk/client-cloudfront": "3.554.0", + "@aws-sdk/client-ecs": "3.554.0", + "@aws-sdk/client-s3": "3.554.0", + "@aws-sdk/client-ssm": "3.554.0", + "@aws-sdk/client-sts": "3.554.0", "@aws-sdk/types": "3.535.0", "@medplum/core": "3.1.2", "@medplum/hl7": "3.1.2", @@ -58157,7 +56017,7 @@ "dotenv": "16.4.5", "fast-glob": "3.3.2", "node-fetch": "2.7.0", - "tar": "6.2.1" + "tar": "7.0.1" }, "bin": { "medplum": "dist/cjs/index.cjs" @@ -58165,8 +56025,7 @@ "devDependencies": { "@medplum/fhirtypes": "3.1.2", "@medplum/mock": "3.1.2", - "@types/node-fetch": "2.6.11", - "@types/tar": "6.1.12" + "@types/node-fetch": "2.6.11" }, "engines": { "node": ">=18.0.0" @@ -58231,9 +56090,9 @@ "raw-loader": "4.0.2", "react": "18.2.0", "react-dom": "18.2.0", - "react-intersection-observer": "9.8.1", + "react-intersection-observer": "9.8.2", "react-router-dom": "6.22.3", - "typescript": "5.4.4", + "typescript": "5.4.5", "url-loader": "4.1.1" }, "engines": { @@ -58245,8 +56104,8 @@ "version": "3.1.2", "license": "Apache-2.0", "devDependencies": { - "@typescript-eslint/eslint-plugin": "7.5.0", - "@typescript-eslint/parser": "7.5.0", + "@typescript-eslint/eslint-plugin": "7.6.0", + "@typescript-eslint/parser": "7.6.0", "eslint": "8.57.0", "eslint-plugin-jsdoc": "48.2.3", "eslint-plugin-json-files": "4.1.0", @@ -58293,7 +56152,7 @@ "devDependencies": { "@medplum/core": "3.1.2", "@types/base-64": "1.0.2", - "@types/react": "18.2.74", + "@types/react": "18.2.78", "@types/text-encoding": "0.0.39", "esbuild": "0.20.2", "esbuild-node-externals": "1.13.0", @@ -58345,15 +56204,15 @@ "@medplum/definitions": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@types/json-schema": "7.0.15", - "@types/pg": "8.11.4", + "@types/pg": "8.11.5", "@types/unzipper": "0.10.9", "fast-xml-parser": "4.3.6", "fhirpath": "3.11.0", "mkdirp": "3.0.1", "node-stream-zip": "1.15.0", "pg": "8.11.5", - "tinybench": "2.6.0", - "unzipper": "0.10.14" + "tinybench": "2.7.0", + "unzipper": "0.11.2" }, "engines": { "node": ">=18.0.0" @@ -58388,18 +56247,18 @@ "devDependencies": { "@graphiql/react": "0.21.0", "@graphiql/toolkit": "0.9.1", - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", "@medplum/core": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/react": "3.1.2", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "graphiql": "3.2.0", "graphql": "16.8.1", "graphql-ws": "5.16.0", "postcss": "8.4.38", - "postcss-preset-mantine": "1.13.0", + "postcss-preset-mantine": "1.14.4", "react": "18.2.0", "react-dom": "18.2.0", "vite": "5.2.8" @@ -58462,45 +56321,45 @@ "version": "3.1.2", "license": "Apache-2.0", "devDependencies": { - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", - "@mantine/notifications": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", + "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/definitions": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/mock": "3.1.2", "@medplum/react-hooks": "3.1.2", - "@storybook/addon-actions": "8.0.6", - "@storybook/addon-essentials": "8.0.6", - "@storybook/addon-links": "8.0.6", - "@storybook/addon-storysource": "8.0.6", - "@storybook/blocks": "^8.0.6", - "@storybook/builder-vite": "8.0.6", - "@storybook/react": "8.0.6", - "@storybook/react-vite": "8.0.6", - "@tabler/icons-react": "3.1.0", + "@storybook/addon-actions": "8.0.8", + "@storybook/addon-essentials": "8.0.8", + "@storybook/addon-links": "8.0.8", + "@storybook/addon-storysource": "8.0.8", + "@storybook/blocks": "^8.0.8", + "@storybook/builder-vite": "8.0.8", + "@storybook/react": "8.0.8", + "@storybook/react-vite": "8.0.8", + "@tabler/icons-react": "3.2.0", "@testing-library/dom": "10.0.0", "@testing-library/jest-dom": "6.4.2", - "@testing-library/react": "14.3.0", + "@testing-library/react": "15.0.2", "@testing-library/user-event": "14.5.2", "@types/jest": "29.5.12", - "@types/node": "20.12.5", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@types/node": "20.12.7", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "@vitejs/plugin-react": "4.2.1", "chromatic": "11.0.0", "jest": "29.7.0", "jest-each": "29.7.0", "postcss": "8.4.38", - "postcss-preset-mantine": "1.13.0", + "postcss-preset-mantine": "1.14.4", "react": "18.2.0", "react-dom": "18.2.0", "rfc6902": "5.1.1", "rimraf": "5.0.5", "sinon": "17.0.1", - "storybook": "8.0.6", + "storybook": "8.0.8", "storybook-addon-mantine": "4.0.2", - "typescript": "5.4.4", + "typescript": "5.4.5", "vite-plugin-turbosnap": "^1.0.3" }, "engines": { @@ -58541,18 +56400,18 @@ "@medplum/mock": "3.1.2", "@testing-library/dom": "10.0.0", "@testing-library/jest-dom": "6.4.2", - "@testing-library/react": "14.3.0", + "@testing-library/react": "15.0.2", "@types/jest": "29.5.12", - "@types/node": "20.12.5", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@types/node": "20.12.7", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "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", - "typescript": "5.4.4" + "typescript": "5.4.5" }, "engines": { "node": ">=18.0.0" @@ -58607,14 +56466,14 @@ "version": "3.1.2", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-cloudwatch-logs": "3.549.0", - "@aws-sdk/client-lambda": "3.549.0", - "@aws-sdk/client-s3": "3.550.0", - "@aws-sdk/client-secrets-manager": "3.549.0", - "@aws-sdk/client-sesv2": "3.549.0", - "@aws-sdk/client-ssm": "3.549.0", + "@aws-sdk/client-cloudwatch-logs": "3.554.0", + "@aws-sdk/client-lambda": "3.554.0", + "@aws-sdk/client-s3": "3.554.0", + "@aws-sdk/client-secrets-manager": "3.554.0", + "@aws-sdk/client-sesv2": "3.554.0", + "@aws-sdk/client-ssm": "3.554.0", "@aws-sdk/cloudfront-signer": "3.541.0", - "@aws-sdk/lib-storage": "3.550.0", + "@aws-sdk/lib-storage": "3.554.0", "@aws-sdk/types": "3.535.0", "@medplum/core": "3.1.2", "@medplum/definitions": "3.1.2", @@ -58628,7 +56487,7 @@ "@smithy/util-stream": "2.2.0", "bcryptjs": "2.4.3", "body-parser": "1.20.2", - "bullmq": "5.5.4", + "bullmq": "5.7.1", "bytes": "3.1.2", "compression": "1.7.4", "cookie-parser": "1.4.6", @@ -58668,10 +56527,10 @@ "@types/express-rate-limit": "5.1.3", "@types/json-schema": "7.0.15", "@types/mailparser": "3.4.4", - "@types/node": "20.12.5", + "@types/node": "20.12.7", "@types/node-fetch": "2.6.11", "@types/nodemailer": "6.4.14", - "@types/pg": "8.11.2", + "@types/pg": "8.11.5", "@types/set-cookie-parser": "2.4.7", "@types/supertest": "6.0.2", "@types/ua-parser-js": "0.7.39", @@ -58680,7 +56539,7 @@ "@types/ws": "8.5.10", "aws-sdk-client-mock": "4.0.0", "aws-sdk-client-mock-jest": "4.0.0", - "mailparser": "3.6.9", + "mailparser": "3.7.0", "openapi3-ts": "4.3.1", "set-cookie-parser": "2.6.0", "supertest": "6.3.4", @@ -58690,17 +56549,6 @@ "engines": { "node": ">=18.0.0" } - }, - "packages/server/node_modules/@types/pg": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.2.tgz", - "integrity": "sha512-G2Mjygf2jFMU/9hCaTYxJrwdObdcnuQde1gndooZSOHsNSaCehAuwc7EIuSA34Do8Jx2yZ19KtvW8P0j4EuUXw==", - "dev": true, - "dependencies": { - "@types/node": "*", - "pg-protocol": "*", - "pg-types": "^4.0.1" - } } } } diff --git a/package.json b/package.json index 34717586e7..9a91d87e05 100644 --- a/package.json +++ b/package.json @@ -38,10 +38,10 @@ "@babel/preset-react": "7.24.1", "@babel/preset-typescript": "7.24.1", "@cyclonedx/cyclonedx-npm": "1.16.2", - "@microsoft/api-documenter": "7.24.1", - "@microsoft/api-extractor": "7.43.0", + "@microsoft/api-documenter": "7.24.2", + "@microsoft/api-extractor": "7.43.1", "@types/jest": "29.5.12", - "@types/node": "20.12.5", + "@types/node": "20.12.7", "babel-jest": "29.7.0", "babel-preset-vite": "1.1.3", "cross-env": "7.0.3", @@ -60,7 +60,7 @@ "ts-node": "10.9.2", "tslib": "2.6.2", "turbo": "1.13.2", - "typescript": "5.4.4" + "typescript": "5.4.5" }, "packageManager": "npm@9.8.1", "engines": { diff --git a/packages/app/package.json b/packages/app/package.json index 8e3f02b674..3f8f8a8322 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -29,23 +29,23 @@ "last 1 Chrome versions" ], "devDependencies": { - "@mantine/core": "7.7.1", - "@mantine/dropzone": "7.7.1", - "@mantine/hooks": "7.7.1", - "@mantine/notifications": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/dropzone": "7.8.0", + "@mantine/hooks": "7.8.0", + "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/definitions": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/mock": "3.1.2", "@medplum/react": "3.1.2", - "@tabler/icons-react": "3.1.0", + "@tabler/icons-react": "3.2.0", "@testing-library/jest-dom": "6.4.2", - "@testing-library/react": "14.3.0", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@testing-library/react": "15.0.2", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "@vitejs/plugin-react": "4.2.1", "postcss": "8.4.38", - "postcss-preset-mantine": "1.13.0", + "postcss-preset-mantine": "1.14.4", "react": "18.2.0", "react-dom": "18.2.0", "react-router-dom": "6.22.3", diff --git a/packages/cdk/package.json b/packages/cdk/package.json index 6cccdbad4f..187c62480e 100644 --- a/packages/cdk/package.json +++ b/packages/cdk/package.json @@ -25,10 +25,10 @@ "dependencies": { "@aws-sdk/types": "3.535.0", "@medplum/core": "3.1.2", - "aws-cdk-lib": "2.136.0", - "cdk": "2.136.0", - "cdk-nag": "2.28.84", - "cdk-serverless-clamscan": "2.6.145", + "aws-cdk-lib": "2.137.0", + "cdk": "2.137.0", + "cdk-nag": "2.28.89", + "cdk-serverless-clamscan": "2.6.150", "constructs": "10.3.0" }, "engines": { diff --git a/packages/cli/package.json b/packages/cli/package.json index 3055f6b13d..3c55689793 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -41,13 +41,13 @@ "test": "jest" }, "dependencies": { - "@aws-sdk/client-acm": "3.549.0", - "@aws-sdk/client-cloudformation": "3.549.0", - "@aws-sdk/client-cloudfront": "3.549.0", - "@aws-sdk/client-ecs": "3.549.0", - "@aws-sdk/client-s3": "3.550.0", - "@aws-sdk/client-ssm": "3.549.0", - "@aws-sdk/client-sts": "3.549.0", + "@aws-sdk/client-acm": "3.554.0", + "@aws-sdk/client-cloudformation": "3.555.0", + "@aws-sdk/client-cloudfront": "3.554.0", + "@aws-sdk/client-ecs": "3.554.0", + "@aws-sdk/client-s3": "3.554.0", + "@aws-sdk/client-ssm": "3.554.0", + "@aws-sdk/client-sts": "3.554.0", "@aws-sdk/types": "3.535.0", "@medplum/core": "3.1.2", "@medplum/hl7": "3.1.2", @@ -56,13 +56,12 @@ "dotenv": "16.4.5", "fast-glob": "3.3.2", "node-fetch": "2.7.0", - "tar": "6.2.1" + "tar": "7.0.1" }, "devDependencies": { "@medplum/fhirtypes": "3.1.2", "@medplum/mock": "3.1.2", - "@types/node-fetch": "2.6.11", - "@types/tar": "6.1.12" + "@types/node-fetch": "2.6.11" }, "engines": { "node": ">=18.0.0" diff --git a/packages/cli/src/utils.test.ts b/packages/cli/src/utils.test.ts index d9161d9019..98fffed29a 100644 --- a/packages/cli/src/utils.test.ts +++ b/packages/cli/src/utils.test.ts @@ -1,6 +1,7 @@ import { ContentType } from '@medplum/core'; +import { Stats } from 'fs'; import { Writable } from 'stream'; -import tar from 'tar'; +import tar, { Unpack } from 'tar'; import { getCodeContentType, safeTarExtractor } from './utils'; jest.mock('tar', () => ({ @@ -12,10 +13,10 @@ describe('CLI utils', () => { (tar as jest.Mocked).x.mockImplementationOnce((options) => { const writable = new Writable({ write(chunk, _, callback) { - options.filter?.(chunk.toString(), { size: 1 } as tar.FileStat); + options.filter?.(chunk.toString(), { size: 1 } as Stats); callback(); }, - }); + }) as unknown as Unpack; return writable; }); @@ -35,10 +36,10 @@ describe('CLI utils', () => { (tar as jest.Mocked).x.mockImplementationOnce((options) => { const writable = new Writable({ write(chunk, _, callback) { - options.filter?.(chunk.toString(), { size: 1024 * 1024 } as tar.FileStat); + options.filter?.(chunk.toString(), { size: 1024 * 1024 } as Stats); callback(); }, - }); + }) as unknown as Unpack; return writable; }); diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index 73666853e7..5ff7adb660 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -4,7 +4,6 @@ import { createHmac, createPrivateKey, randomBytes } from 'crypto'; import { existsSync, readFileSync, writeFileSync } from 'fs'; import { SignJWT } from 'jose'; import { basename, extname, resolve } from 'path'; -import internal from 'stream'; import tar from 'tar'; import { FileSystemStorage } from './storage'; @@ -211,7 +210,7 @@ function escapeRegex(str: string): string { * @param destinationDir - The destination directory where all files will be extracted. * @returns A tar file extractor. */ -export function safeTarExtractor(destinationDir: string): internal.Writable { +export function safeTarExtractor(destinationDir: string): NodeJS.WritableStream { const MAX_FILES = 100; const MAX_SIZE = 10 * 1024 * 1024; // 10 MB @@ -233,7 +232,9 @@ export function safeTarExtractor(destinationDir: string): internal.Writable { return true; }, - }); + + // Temporary cast for tar issue: https://github.com/isaacs/node-tar/issues/409 + }) as ReturnType & NodeJS.WritableStream; } export function getUnsupportedExtension(): Extension { diff --git a/packages/docs/package.json b/packages/docs/package.json index 5d9cbb0187..845b6d5055 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -57,9 +57,9 @@ "raw-loader": "4.0.2", "react": "18.2.0", "react-dom": "18.2.0", - "react-intersection-observer": "9.8.1", + "react-intersection-observer": "9.8.2", "react-router-dom": "6.22.3", - "typescript": "5.4.4", + "typescript": "5.4.5", "url-loader": "4.1.1" }, "engines": { diff --git a/packages/eslint-config/index.cjs b/packages/eslint-config/index.cjs index da18959d45..a786453c32 100644 --- a/packages/eslint-config/index.cjs +++ b/packages/eslint-config/index.cjs @@ -158,6 +158,7 @@ module.exports = { 'babel.config.cjs', 'jest.sequencer.js', 'package-lock.json', + 'postcss.config.cjs', 'rollup.config.mjs', 'webpack.config.js', ], diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index fd19c3a9c9..5f6368c94e 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": "7.5.0", - "@typescript-eslint/parser": "7.5.0", + "@typescript-eslint/eslint-plugin": "7.6.0", + "@typescript-eslint/parser": "7.6.0", "eslint": "8.57.0", "eslint-plugin-jsdoc": "48.2.3", "eslint-plugin-json-files": "4.1.0", diff --git a/packages/expo-polyfills/package.json b/packages/expo-polyfills/package.json index f0d58c40c6..3c64b42ae7 100644 --- a/packages/expo-polyfills/package.json +++ b/packages/expo-polyfills/package.json @@ -49,7 +49,7 @@ "devDependencies": { "@medplum/core": "3.1.2", "@types/base-64": "1.0.2", - "@types/react": "18.2.74", + "@types/react": "18.2.78", "@types/text-encoding": "0.0.39", "esbuild": "0.20.2", "esbuild-node-externals": "1.13.0", diff --git a/packages/generator/package.json b/packages/generator/package.json index b40257b79e..59f318a0e7 100644 --- a/packages/generator/package.json +++ b/packages/generator/package.json @@ -28,15 +28,15 @@ "@medplum/definitions": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@types/json-schema": "7.0.15", - "@types/pg": "8.11.4", + "@types/pg": "8.11.5", "@types/unzipper": "0.10.9", "fast-xml-parser": "4.3.6", "fhirpath": "3.11.0", "mkdirp": "3.0.1", "node-stream-zip": "1.15.0", "pg": "8.11.5", - "tinybench": "2.6.0", - "unzipper": "0.10.14" + "tinybench": "2.7.0", + "unzipper": "0.11.2" }, "engines": { "node": ">=18.0.0" diff --git a/packages/graphiql/package.json b/packages/graphiql/package.json index c08acaa69b..423014a70e 100644 --- a/packages/graphiql/package.json +++ b/packages/graphiql/package.json @@ -25,18 +25,18 @@ "devDependencies": { "@graphiql/react": "0.21.0", "@graphiql/toolkit": "0.9.1", - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", "@medplum/core": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/react": "3.1.2", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "graphiql": "3.2.0", "graphql": "16.8.1", "graphql-ws": "5.16.0", "postcss": "8.4.38", - "postcss-preset-mantine": "1.13.0", + "postcss-preset-mantine": "1.14.4", "react": "18.2.0", "react-dom": "18.2.0", "vite": "5.2.8" diff --git a/packages/react-hooks/package.json b/packages/react-hooks/package.json index 00af3c31ae..43de1313b1 100644 --- a/packages/react-hooks/package.json +++ b/packages/react-hooks/package.json @@ -63,18 +63,18 @@ "@medplum/mock": "3.1.2", "@testing-library/dom": "10.0.0", "@testing-library/jest-dom": "6.4.2", - "@testing-library/react": "14.3.0", + "@testing-library/react": "15.0.2", "@types/jest": "29.5.12", - "@types/node": "20.12.5", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@types/node": "20.12.7", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "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", - "typescript": "5.4.4" + "typescript": "5.4.5" }, "peerDependencies": { "@medplum/core": "3.1.2", diff --git a/packages/react/package.json b/packages/react/package.json index ae782633e8..550b974866 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -67,44 +67,44 @@ "test": "jest" }, "devDependencies": { - "@mantine/core": "7.7.1", - "@mantine/hooks": "7.7.1", - "@mantine/notifications": "7.7.1", + "@mantine/core": "7.8.0", + "@mantine/hooks": "7.8.0", + "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/definitions": "3.1.2", "@medplum/fhirtypes": "3.1.2", "@medplum/mock": "3.1.2", "@medplum/react-hooks": "3.1.2", - "@storybook/addon-actions": "8.0.6", - "@storybook/addon-essentials": "8.0.6", - "@storybook/addon-links": "8.0.6", - "@storybook/addon-storysource": "8.0.6", - "@storybook/blocks": "^8.0.6", - "@storybook/builder-vite": "8.0.6", - "@storybook/react": "8.0.6", - "@storybook/react-vite": "8.0.6", - "@tabler/icons-react": "3.1.0", + "@storybook/addon-actions": "8.0.8", + "@storybook/addon-essentials": "8.0.8", + "@storybook/addon-links": "8.0.8", + "@storybook/addon-storysource": "8.0.8", + "@storybook/blocks": "^8.0.8", + "@storybook/builder-vite": "8.0.8", + "@storybook/react": "8.0.8", + "@storybook/react-vite": "8.0.8", + "@tabler/icons-react": "3.2.0", "@testing-library/dom": "10.0.0", "@testing-library/jest-dom": "6.4.2", - "@testing-library/react": "14.3.0", + "@testing-library/react": "15.0.2", "@testing-library/user-event": "14.5.2", "@types/jest": "29.5.12", - "@types/node": "20.12.5", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.24", + "@types/node": "20.12.7", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", "@vitejs/plugin-react": "4.2.1", "chromatic": "11.0.0", "jest": "29.7.0", "jest-each": "29.7.0", "postcss": "8.4.38", - "postcss-preset-mantine": "1.13.0", + "postcss-preset-mantine": "1.14.4", "react": "18.2.0", "react-dom": "18.2.0", "rfc6902": "5.1.1", "rimraf": "5.0.5", "sinon": "17.0.1", - "storybook": "8.0.6", - "typescript": "5.4.4", + "storybook": "8.0.8", + "typescript": "5.4.5", "storybook-addon-mantine": "4.0.2", "vite-plugin-turbosnap": "^1.0.3" }, diff --git a/packages/server/package.json b/packages/server/package.json index a9993bf9a6..00a012dbc8 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -21,14 +21,14 @@ "test": "jest --runInBand" }, "dependencies": { - "@aws-sdk/client-cloudwatch-logs": "3.549.0", - "@aws-sdk/client-lambda": "3.549.0", - "@aws-sdk/client-s3": "3.550.0", - "@aws-sdk/client-secrets-manager": "3.549.0", - "@aws-sdk/client-sesv2": "3.549.0", - "@aws-sdk/client-ssm": "3.549.0", + "@aws-sdk/client-cloudwatch-logs": "3.554.0", + "@aws-sdk/client-lambda": "3.554.0", + "@aws-sdk/client-s3": "3.554.0", + "@aws-sdk/client-secrets-manager": "3.554.0", + "@aws-sdk/client-sesv2": "3.554.0", + "@aws-sdk/client-ssm": "3.554.0", "@aws-sdk/cloudfront-signer": "3.541.0", - "@aws-sdk/lib-storage": "3.550.0", + "@aws-sdk/lib-storage": "3.554.0", "@aws-sdk/types": "3.535.0", "@medplum/core": "3.1.2", "@medplum/definitions": "3.1.2", @@ -42,7 +42,7 @@ "@smithy/util-stream": "2.2.0", "bcryptjs": "2.4.3", "body-parser": "1.20.2", - "bullmq": "5.5.4", + "bullmq": "5.7.1", "bytes": "3.1.2", "compression": "1.7.4", "cookie-parser": "1.4.6", @@ -82,10 +82,10 @@ "@types/express-rate-limit": "5.1.3", "@types/json-schema": "7.0.15", "@types/mailparser": "3.4.4", - "@types/node": "20.12.5", + "@types/node": "20.12.7", "@types/node-fetch": "2.6.11", "@types/nodemailer": "6.4.14", - "@types/pg": "8.11.2", + "@types/pg": "8.11.5", "@types/set-cookie-parser": "2.4.7", "@types/supertest": "6.0.2", "@types/ua-parser-js": "0.7.39", @@ -94,7 +94,7 @@ "@types/ws": "8.5.10", "aws-sdk-client-mock": "4.0.0", "aws-sdk-client-mock-jest": "4.0.0", - "mailparser": "3.6.9", + "mailparser": "3.7.0", "openapi3-ts": "4.3.1", "set-cookie-parser": "2.6.0", "supertest": "6.3.4", From dc86c98f61bc9739cdc53e29a0a04065ef268753 Mon Sep 17 00:00:00 2001 From: Matt Long Date: Tue, 16 Apr 2024 11:26:27 -0700 Subject: [PATCH 18/52] [Medplum Provider] Add Edit and History functionality to resource pages (#4376) * Add Edit and History tabs in provider app * Move comment --- examples/medplum-provider/src/App.tsx | 18 +++-- .../src/pages/ResourcePage.tsx | 37 ---------- .../src/pages/resource/ResourceDetailPage.tsx | 25 +++++++ .../src/pages/resource/ResourceEditPage.tsx | 51 ++++++++++++++ .../pages/resource/ResourceHistoryPage.tsx | 13 ++++ .../pages/resource/ResourcePage.module.css | 7 ++ .../src/pages/resource/ResourcePage.tsx | 70 +++++++++++++++++++ 7 files changed, 180 insertions(+), 41 deletions(-) delete mode 100644 examples/medplum-provider/src/pages/ResourcePage.tsx create mode 100644 examples/medplum-provider/src/pages/resource/ResourceDetailPage.tsx create mode 100644 examples/medplum-provider/src/pages/resource/ResourceEditPage.tsx create mode 100644 examples/medplum-provider/src/pages/resource/ResourceHistoryPage.tsx create mode 100644 examples/medplum-provider/src/pages/resource/ResourcePage.module.css create mode 100644 examples/medplum-provider/src/pages/resource/ResourcePage.tsx diff --git a/examples/medplum-provider/src/App.tsx b/examples/medplum-provider/src/App.tsx index f41f4e83c1..93db197fc1 100644 --- a/examples/medplum-provider/src/App.tsx +++ b/examples/medplum-provider/src/App.tsx @@ -22,7 +22,6 @@ import { Navigate, Route, Routes } from 'react-router-dom'; import { CreateResourcePage } from './pages/CreateResourcePage'; import { HomePage } from './pages/HomePage'; import { OnboardingPage } from './pages/OnboardingPage'; -import { ResourcePage } from './pages/ResourcePage'; import { SearchPage } from './pages/SearchPage'; import { SignInPage } from './pages/SignInPage'; import { EditTab } from './pages/patient/EditTab'; @@ -33,6 +32,10 @@ import { PatientPage } from './pages/patient/PatientPage'; import { PatientSearchPage } from './pages/patient/PatientSearchPage'; import { TasksTab } from './pages/patient/TasksTab'; import { TimelineTab } from './pages/patient/TimelineTab'; +import { ResourceDetailPage } from './pages/resource/ResourceDetailPage'; +import { ResourceEditPage } from './pages/resource/ResourceEditPage'; +import { ResourceHistoryPage } from './pages/resource/ResourceHistoryPage'; +import { ResourcePage } from './pages/resource/ResourcePage'; export function App(): JSX.Element | null { const medplum = useMedplum(); @@ -117,14 +120,21 @@ export function App(): JSX.Element | null { } /> } /> } /> - } /> + }> + } /> + } /> + } /> + } /> } /> } /> } /> - } /> - } /> + }> + } /> + } /> + } /> + } /> ) : ( diff --git a/examples/medplum-provider/src/pages/ResourcePage.tsx b/examples/medplum-provider/src/pages/ResourcePage.tsx deleted file mode 100644 index 59f9a6b759..0000000000 --- a/examples/medplum-provider/src/pages/ResourcePage.tsx +++ /dev/null @@ -1,37 +0,0 @@ -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/resource/ResourceDetailPage.tsx b/examples/medplum-provider/src/pages/resource/ResourceDetailPage.tsx new file mode 100644 index 0000000000..cd97a049a6 --- /dev/null +++ b/examples/medplum-provider/src/pages/resource/ResourceDetailPage.tsx @@ -0,0 +1,25 @@ +import { Stack, Title } from '@mantine/core'; +import { getDisplayString } from '@medplum/core'; +import { ResourceTable, useResource } from '@medplum/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 ResourceDetailPage(): JSX.Element | null { + const { resourceType, id } = useParams(); + const resource = useResource({ reference: resourceType + '/' + id }); + + if (!resource) { + return null; + } + + return ( + + {getDisplayString(resource)} + + + ); +} diff --git a/examples/medplum-provider/src/pages/resource/ResourceEditPage.tsx b/examples/medplum-provider/src/pages/resource/ResourceEditPage.tsx new file mode 100644 index 0000000000..7debf54909 --- /dev/null +++ b/examples/medplum-provider/src/pages/resource/ResourceEditPage.tsx @@ -0,0 +1,51 @@ +import { showNotification } from '@mantine/notifications'; +import { deepClone, normalizeErrorString, normalizeOperationOutcome } from '@medplum/core'; +import { OperationOutcome, Resource, ResourceType } from '@medplum/fhirtypes'; +import { ResourceForm, useMedplum } from '@medplum/react'; +import { useCallback, useEffect, useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; + +export function ResourceEditPage(): JSX.Element | null { + const medplum = useMedplum(); + const { resourceType, id } = useParams() as { resourceType: ResourceType | undefined; id: string | undefined }; + const [value, setValue] = useState(); + const navigate = useNavigate(); + const [outcome, setOutcome] = useState(); + + useEffect(() => { + if (resourceType && id) { + medplum + .readResource(resourceType as ResourceType, id) + .then((resource) => setValue(deepClone(resource))) + .catch((err) => { + setOutcome(normalizeOperationOutcome(err)); + showNotification({ color: 'red', message: normalizeErrorString(err), autoClose: false }); + }); + } + }, [medplum, resourceType, id]); + + const handleSubmit = useCallback( + (newResource: Resource): void => { + setOutcome(undefined); + medplum + .updateResource(newResource) + .then(() => { + navigate('..'); + showNotification({ color: 'green', message: 'Success' }); + }) + .catch((err) => { + setOutcome(normalizeOperationOutcome(err)); + showNotification({ color: 'red', message: normalizeErrorString(err), autoClose: false }); + }); + }, + [medplum, navigate] + ); + + const handleDelete = useCallback(() => navigate('..'), [navigate]); + + if (!value) { + return null; + } + + return ; +} diff --git a/examples/medplum-provider/src/pages/resource/ResourceHistoryPage.tsx b/examples/medplum-provider/src/pages/resource/ResourceHistoryPage.tsx new file mode 100644 index 0000000000..012b3b0322 --- /dev/null +++ b/examples/medplum-provider/src/pages/resource/ResourceHistoryPage.tsx @@ -0,0 +1,13 @@ +import { ResourceType } from '@medplum/fhirtypes'; +import { ResourceHistoryTable } from '@medplum/react'; +import { useParams } from 'react-router-dom'; + +export function ResourceHistoryPage(): JSX.Element | null { + const { resourceType, id } = useParams() as { resourceType: ResourceType | undefined; id: string | undefined }; + + if (!resourceType || !id) { + return null; + } + + return ; +} diff --git a/examples/medplum-provider/src/pages/resource/ResourcePage.module.css b/examples/medplum-provider/src/pages/resource/ResourcePage.module.css new file mode 100644 index 0000000000..e382ced24b --- /dev/null +++ b/examples/medplum-provider/src/pages/resource/ResourcePage.module.css @@ -0,0 +1,7 @@ +.tab { + color: var(--tabs-color); + + &[data-active] { + color: var(--mantine-color-white); + } +} diff --git a/examples/medplum-provider/src/pages/resource/ResourcePage.tsx b/examples/medplum-provider/src/pages/resource/ResourcePage.tsx new file mode 100644 index 0000000000..70ada7b9ae --- /dev/null +++ b/examples/medplum-provider/src/pages/resource/ResourcePage.tsx @@ -0,0 +1,70 @@ +import { Stack, Tabs } from '@mantine/core'; +import { getReferenceString } from '@medplum/core'; +import { Resource, ResourceType } from '@medplum/fhirtypes'; +import { Document, useMedplum } from '@medplum/react'; +import { useCallback, useEffect, useState } from 'react'; +import { Outlet, useNavigate, useParams } from 'react-router-dom'; +import classes from './ResourcePage.module.css'; + +const tabs = [ + { id: 'details', url: '', label: 'Details' }, + { id: 'edit', url: 'edit', label: 'Edit' }, + { id: 'history', url: 'history', label: 'History' }, +]; + +export function ResourcePage(): JSX.Element | null { + const navigate = useNavigate(); + const medplum = useMedplum(); + const { resourceType, id } = useParams(); + const [resource, setResource] = useState(undefined); + const [currentTab, setCurrentTab] = useState(() => { + const tabId = window.location.pathname.split('/').pop(); + const tab = tabId ? tabs.find((t) => t.id === tabId) : undefined; + return (tab ?? tabs[0]).id; + }); + + useEffect(() => { + if (resourceType && id) { + medplum + .readResource(resourceType as ResourceType, id) + .then(setResource) + .catch(console.error); + } + }, [medplum, resourceType, id]); + + const onTabChange = useCallback( + (newTabName: string | null): void => { + if (!newTabName) { + newTabName = tabs[0].id; + } + + const tab = tabs.find((t) => t.id === newTabName); + if (tab) { + setCurrentTab(tab.id); + navigate(tab.url); + } + }, + [navigate] + ); + + if (!resource) { + return null; + } + + return ( + + + + + {tabs.map((t) => ( + + {t.label} + + ))} + + + + + + ); +} From 202c19b2497aea6f2a9266622bc3076a3786fc46 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Tue, 16 Apr 2024 15:48:57 -0700 Subject: [PATCH 19/52] Move AWS specific code to separate directory (#4329) --- packages/server/src/admin/invite.test.ts | 2 +- packages/server/src/admin/project.test.ts | 6 +- .../server/src/auth/changepassword.test.ts | 4 - packages/server/src/auth/login.test.ts | 1 + packages/server/src/auth/profile.test.ts | 2 - .../server/src/auth/resetpassword.test.ts | 1 + packages/server/src/auth/revoke.test.ts | 2 - packages/server/src/auth/setpassword.test.ts | 1 + packages/server/src/cloud/aws/config.test.ts | 71 +++++ packages/server/src/cloud/aws/config.ts | 118 ++++++++ packages/server/src/cloud/aws/deploy.test.ts | 255 +++++++++++++++++ packages/server/src/cloud/aws/deploy.ts | 260 +++++++++++++++++ packages/server/src/cloud/aws/email.ts | 41 +++ packages/server/src/cloud/aws/execute.test.ts | 231 +++++++++++++++ packages/server/src/cloud/aws/execute.ts | 108 ++++++++ .../server/src/{fhir => cloud/aws}/signer.md | 0 .../src/{fhir => cloud/aws}/signer.test.ts | 2 +- .../server/src/{fhir => cloud/aws}/signer.ts | 2 +- packages/server/src/cloud/aws/storage.test.ts | 188 +++++++++++++ packages/server/src/cloud/aws/storage.ts | 133 +++++++++ packages/server/src/config.test.ts | 68 ----- packages/server/src/config.ts | 97 +------ packages/server/src/email/email.test.ts | 1 + packages/server/src/email/email.ts | 118 +------- packages/server/src/email/routes.test.ts | 1 + packages/server/src/email/utils.ts | 73 +++++ packages/server/src/fhir/binary.ts | 3 +- .../server/src/fhir/operations/deploy.test.ts | 233 ---------------- packages/server/src/fhir/operations/deploy.ts | 262 +----------------- .../src/fhir/operations/execute.test.ts | 114 ++------ .../server/src/fhir/operations/execute.ts | 137 ++------- packages/server/src/fhir/rewrite.ts | 5 +- packages/server/src/fhir/storage.test.ts | 178 +----------- packages/server/src/fhir/storage.ts | 128 ++------- packages/server/src/logger.test.ts | 25 -- packages/server/src/oauth/authorize.test.ts | 2 - packages/server/src/oauth/token.test.ts | 1 - packages/server/src/util/cloudwatch.test.ts | 3 +- 38 files changed, 1565 insertions(+), 1312 deletions(-) create mode 100644 packages/server/src/cloud/aws/config.test.ts create mode 100644 packages/server/src/cloud/aws/config.ts create mode 100644 packages/server/src/cloud/aws/deploy.test.ts create mode 100644 packages/server/src/cloud/aws/deploy.ts create mode 100644 packages/server/src/cloud/aws/email.ts create mode 100644 packages/server/src/cloud/aws/execute.test.ts create mode 100644 packages/server/src/cloud/aws/execute.ts rename packages/server/src/{fhir => cloud/aws}/signer.md (100%) rename packages/server/src/{fhir => cloud/aws}/signer.test.ts (93%) rename packages/server/src/{fhir => cloud/aws}/signer.ts (95%) create mode 100644 packages/server/src/cloud/aws/storage.test.ts create mode 100644 packages/server/src/cloud/aws/storage.ts create mode 100644 packages/server/src/email/utils.ts diff --git a/packages/server/src/admin/invite.test.ts b/packages/server/src/admin/invite.test.ts index 061f2bdae0..0487054490 100644 --- a/packages/server/src/admin/invite.test.ts +++ b/packages/server/src/admin/invite.test.ts @@ -10,7 +10,6 @@ import { simpleParser } from 'mailparser'; import fetch from 'node-fetch'; import { Readable } from 'stream'; import request from 'supertest'; - import { initApp, shutdownApp } from '../app'; import { registerNew } from '../auth/register'; import { loadTestConfig } from '../config'; @@ -26,6 +25,7 @@ describe('Admin Invite', () => { beforeAll(async () => { const config = await loadTestConfig(); + config.emailProvider = 'awsses'; await withTestContext(() => initApp(app, config)); }); diff --git a/packages/server/src/admin/project.test.ts b/packages/server/src/admin/project.test.ts index b44cf7d887..5f10e047c9 100644 --- a/packages/server/src/admin/project.test.ts +++ b/packages/server/src/admin/project.test.ts @@ -1,4 +1,3 @@ -import { SendEmailCommand, SESv2Client } from '@aws-sdk/client-sesv2'; import { createReference } from '@medplum/core'; import { ProjectMembership } from '@medplum/fhirtypes'; import { randomUUID } from 'crypto'; @@ -7,12 +6,11 @@ import { pwnedPassword } from 'hibp'; import fetch from 'node-fetch'; import request from 'supertest'; import { initApp, shutdownApp } from '../app'; -import { registerNew, RegisterResponse } from '../auth/register'; +import { RegisterResponse, registerNew } from '../auth/register'; import { loadTestConfig } from '../config'; import { addTestUser, setupPwnedPasswordMock, setupRecaptchaMock, withTestContext } from '../test.setup'; import { inviteUser } from './invite'; -jest.mock('@aws-sdk/client-sesv2'); jest.mock('hibp'); jest.mock('node-fetch'); @@ -43,8 +41,6 @@ describe('Project Admin routes', () => { }); beforeEach(() => { - (SESv2Client as unknown as jest.Mock).mockClear(); - (SendEmailCommand as unknown as jest.Mock).mockClear(); (fetch as unknown as jest.Mock).mockClear(); (pwnedPassword as unknown as jest.Mock).mockClear(); setupPwnedPasswordMock(pwnedPassword as unknown as jest.Mock, 0); diff --git a/packages/server/src/auth/changepassword.test.ts b/packages/server/src/auth/changepassword.test.ts index b3fda1c1b7..61d3f8f71b 100644 --- a/packages/server/src/auth/changepassword.test.ts +++ b/packages/server/src/auth/changepassword.test.ts @@ -1,4 +1,3 @@ -import { SendEmailCommand, SESv2Client } from '@aws-sdk/client-sesv2'; import { badRequest } from '@medplum/core'; import { randomUUID } from 'crypto'; import express from 'express'; @@ -10,7 +9,6 @@ import { loadTestConfig } from '../config'; import { setupPwnedPasswordMock, setupRecaptchaMock, withTestContext } from '../test.setup'; import { registerNew } from './register'; -jest.mock('@aws-sdk/client-sesv2'); jest.mock('hibp'); jest.mock('node-fetch'); @@ -27,8 +25,6 @@ describe('Change Password', () => { }); beforeEach(() => { - (SESv2Client as unknown as jest.Mock).mockClear(); - (SendEmailCommand as unknown as jest.Mock).mockClear(); (fetch as unknown as jest.Mock).mockClear(); (pwnedPassword as unknown as jest.Mock).mockClear(); setupPwnedPasswordMock(pwnedPassword as unknown as jest.Mock, 0); diff --git a/packages/server/src/auth/login.test.ts b/packages/server/src/auth/login.test.ts index c3a5f01b88..75719c9bfa 100644 --- a/packages/server/src/auth/login.test.ts +++ b/packages/server/src/auth/login.test.ts @@ -29,6 +29,7 @@ describe('Login', () => { beforeAll(() => withTestContext(async () => { const config = await loadTestConfig(); + config.emailProvider = 'awsses'; await initApp(app, config); // Create a test project diff --git a/packages/server/src/auth/profile.test.ts b/packages/server/src/auth/profile.test.ts index f85275bbcd..6787de46af 100644 --- a/packages/server/src/auth/profile.test.ts +++ b/packages/server/src/auth/profile.test.ts @@ -9,8 +9,6 @@ import { getSystemRepo } from '../fhir/repo'; import { withTestContext } from '../test.setup'; import { registerNew } from './register'; -jest.mock('@aws-sdk/client-sesv2'); - const app = express(); const email = `multi${randomUUID()}@example.com`; const password = randomUUID(); diff --git a/packages/server/src/auth/resetpassword.test.ts b/packages/server/src/auth/resetpassword.test.ts index 42490c3748..72fbd8d5b6 100644 --- a/packages/server/src/auth/resetpassword.test.ts +++ b/packages/server/src/auth/resetpassword.test.ts @@ -24,6 +24,7 @@ describe('Reset Password', () => { beforeAll(async () => { const config = await loadTestConfig(); + config.emailProvider = 'awsses'; await initApp(app, config); }); diff --git a/packages/server/src/auth/revoke.test.ts b/packages/server/src/auth/revoke.test.ts index 0686ed1317..aa91a9cc2d 100644 --- a/packages/server/src/auth/revoke.test.ts +++ b/packages/server/src/auth/revoke.test.ts @@ -9,8 +9,6 @@ import { withTestContext } from '../test.setup'; import { registerNew } from './register'; import { setPassword } from './setpassword'; -jest.mock('@aws-sdk/client-sesv2'); - const app = express(); describe('Revoke', () => { diff --git a/packages/server/src/auth/setpassword.test.ts b/packages/server/src/auth/setpassword.test.ts index d0a230bad5..ffa3ee9b2a 100644 --- a/packages/server/src/auth/setpassword.test.ts +++ b/packages/server/src/auth/setpassword.test.ts @@ -23,6 +23,7 @@ const app = express(); describe('Set Password', () => { beforeAll(async () => { const config = await loadTestConfig(); + config.emailProvider = 'awsses'; await initApp(app, config); }); diff --git a/packages/server/src/cloud/aws/config.test.ts b/packages/server/src/cloud/aws/config.test.ts new file mode 100644 index 0000000000..dd77a4a498 --- /dev/null +++ b/packages/server/src/cloud/aws/config.test.ts @@ -0,0 +1,71 @@ +import { GetSecretValueCommand, SecretsManagerClient } from '@aws-sdk/client-secrets-manager'; +import { GetParametersByPathCommand, SSMClient } from '@aws-sdk/client-ssm'; +import { AwsClientStub, mockClient } from 'aws-sdk-client-mock'; +import 'aws-sdk-client-mock-jest'; +import { getConfig, loadConfig } from '../../config'; + +describe('Config', () => { + let mockSSMClient: AwsClientStub; + let mockSecretsManagerClient: AwsClientStub; + + beforeEach(() => { + mockSSMClient = mockClient(SSMClient); + mockSecretsManagerClient = mockClient(SecretsManagerClient); + + mockSecretsManagerClient.on(GetSecretValueCommand).resolves({ + SecretString: JSON.stringify({ host: 'host', port: 123 }), + }); + + mockSSMClient.on(GetParametersByPathCommand).resolves({ + Parameters: [ + { Name: 'baseUrl', Value: 'https://www.example.com/' }, + { Name: 'database.ssl.require', Value: 'true' }, + { Name: 'database.ssl.rejectUnauthorized', Value: 'true' }, + { Name: 'database.ssl.ca', Value: 'DatabaseSslCa' }, + { Name: 'DatabaseSecrets', Value: 'DatabaseSecretsArn' }, + { Name: 'RedisSecrets', Value: 'RedisSecretsArn' }, + { Name: 'port', Value: '8080' }, + { Name: 'botCustomFunctionsEnabled', Value: 'true' }, + { Name: 'logAuditEvents', Value: 'true' }, + { Name: 'registerEnabled', Value: 'false' }, + ], + }); + }); + + afterEach(() => { + mockSSMClient.restore(); + mockSecretsManagerClient.restore(); + }); + + test('Load AWS config', async () => { + const config = await loadConfig('aws:test'); + expect(config).toBeDefined(); + expect(config.baseUrl).toBeDefined(); + expect(config.port).toEqual(8080); + expect(config.botCustomFunctionsEnabled).toEqual(true); + expect(config.logAuditEvents).toEqual(true); + expect(config.registerEnabled).toEqual(false); + expect(config.database).toBeDefined(); + expect(config.database.ssl).toBeDefined(); + expect(config.database.ssl?.require).toEqual(true); + expect(config.database.ssl?.rejectUnauthorized).toEqual(true); + expect(config.database.ssl?.ca).toEqual('DatabaseSslCa'); + expect(getConfig()).toBe(config); + expect(mockSSMClient).toReceiveCommand(GetParametersByPathCommand); + }); + + test('Load region AWS config', async () => { + const config = await loadConfig('aws:ap-southeast-2:test'); + expect(config).toBeDefined(); + expect(config.baseUrl).toBeDefined(); + expect(config.port).toEqual(8080); + expect(getConfig()).toBe(config); + expect(mockSecretsManagerClient).toReceiveCommand(GetSecretValueCommand); + expect(mockSecretsManagerClient).toReceiveCommandWith(GetSecretValueCommand, { + SecretId: 'DatabaseSecretsArn', + }); + expect(mockSecretsManagerClient).toReceiveCommandWith(GetSecretValueCommand, { + SecretId: 'RedisSecretsArn', + }); + }); +}); diff --git a/packages/server/src/cloud/aws/config.ts b/packages/server/src/cloud/aws/config.ts new file mode 100644 index 0000000000..9ca28b8550 --- /dev/null +++ b/packages/server/src/cloud/aws/config.ts @@ -0,0 +1,118 @@ +import { GetSecretValueCommand, SecretsManagerClient } from '@aws-sdk/client-secrets-manager'; +import { GetParametersByPathCommand, Parameter, SSMClient } from '@aws-sdk/client-ssm'; +import { splitN } from '@medplum/core'; +import { MedplumServerConfig } from '../../config'; + +const DEFAULT_AWS_REGION = 'us-east-1'; + +/** + * Loads configuration settings from AWS SSM Parameter Store. + * @param path - The AWS SSM Parameter Store path prefix. + * @returns The loaded configuration. + */ +export async function loadAwsConfig(path: string): Promise { + let region = DEFAULT_AWS_REGION; + if (path.includes(':')) { + [region, path] = splitN(path, ':', 2); + } + + const client = new SSMClient({ region }); + const config: Record = {}; + const parameters = [] as Parameter[]; + let nextToken: string | undefined; + do { + const response = await client.send( + new GetParametersByPathCommand({ + Path: path, + NextToken: nextToken, + WithDecryption: true, + }) + ); + if (response.Parameters) { + parameters.push(...response.Parameters); + } + nextToken = response.NextToken; + } while (nextToken); + + // Load special AWS Secrets Manager secrets first + for (const param of parameters) { + const key = (param.Name as string).replace(path, ''); + const value = param.Value as string; + if (key === 'DatabaseSecrets') { + config['database'] = await loadAwsSecrets(region, value); + } else if (key === 'RedisSecrets') { + config['redis'] = await loadAwsSecrets(region, value); + } + } + + // Then load other parameters, which may override the secrets + for (const param of parameters) { + const key = (param.Name as string).replace(path, ''); + const value = param.Value as string; + setValue(config, key, value); + } + + return config as MedplumServerConfig; +} + +/** + * Returns the AWS Database Secret data as a JSON map. + * @param region - The AWS region. + * @param secretId - Secret ARN + * @returns The secret data as a JSON map. + */ +async function loadAwsSecrets(region: string, secretId: string): Promise | undefined> { + const client = new SecretsManagerClient({ region }); + const result = await client.send(new GetSecretValueCommand({ SecretId: secretId })); + + if (!result.SecretString) { + return undefined; + } + + return JSON.parse(result.SecretString); +} + +function setValue(config: Record, key: string, value: string): void { + const keySegments = key.split('.'); + let obj = config; + + while (keySegments.length > 1) { + const segment = keySegments.shift() as string; + if (!obj[segment]) { + obj[segment] = {}; + } + obj = obj[segment] as Record; + } + + let parsedValue: any = value; + if (isIntegerConfig(key)) { + parsedValue = parseInt(value, 10); + } else if (isBooleanConfig(key)) { + parsedValue = value === 'true'; + } else if (isObjectConfig(key)) { + parsedValue = JSON.parse(value); + } + + obj[keySegments[0]] = parsedValue; +} + +function isIntegerConfig(key: string): boolean { + return key === 'port' || key === 'accurateCountThreshold'; +} + +function isBooleanConfig(key: string): boolean { + return ( + key === 'botCustomFunctionsEnabled' || + key === 'database.ssl.rejectUnauthorized' || + key === 'database.ssl.require' || + key === 'logRequests' || + key === 'logAuditEvents' || + key === 'registerEnabled' || + key === 'require' || + key === 'rejectUnauthorized' + ); +} + +function isObjectConfig(key: string): boolean { + return key === 'tls'; +} diff --git a/packages/server/src/cloud/aws/deploy.test.ts b/packages/server/src/cloud/aws/deploy.test.ts new file mode 100644 index 0000000000..2482d5f102 --- /dev/null +++ b/packages/server/src/cloud/aws/deploy.test.ts @@ -0,0 +1,255 @@ +import { + CreateFunctionCommand, + GetFunctionCommand, + GetFunctionConfigurationCommand, + LambdaClient, + ListLayerVersionsCommand, + UpdateFunctionCodeCommand, + UpdateFunctionConfigurationCommand, +} from '@aws-sdk/client-lambda'; +import { ContentType } from '@medplum/core'; +import { Bot } from '@medplum/fhirtypes'; +import { AwsClientStub, mockClient } from 'aws-sdk-client-mock'; +import 'aws-sdk-client-mock-jest'; +import express from 'express'; +import request from 'supertest'; +import { initApp, shutdownApp } from '../../app'; +import { loadTestConfig } from '../../config'; +import { initTestAuth } from '../../test.setup'; + +const app = express(); +let accessToken: string; +let mockLambdaClient: AwsClientStub; + +describe('Deploy', () => { + beforeAll(async () => { + const config = await loadTestConfig(); + await initApp(app, config); + accessToken = await initTestAuth(); + }); + + afterAll(async () => { + await shutdownApp(); + }); + + beforeEach(() => { + let created = false; + + mockLambdaClient = mockClient(LambdaClient); + + mockLambdaClient.on(CreateFunctionCommand).callsFake(({ FunctionName }) => { + created = true; + + return { + Configuration: { + FunctionName, + }, + }; + }); + + mockLambdaClient.on(GetFunctionCommand).callsFake(({ FunctionName }) => { + if (created) { + return { + Configuration: { + FunctionName, + }, + }; + } + + return { + Configuration: {}, + }; + }); + + mockLambdaClient.on(GetFunctionConfigurationCommand).callsFake(({ FunctionName }) => { + return { + FunctionName, + Runtime: 'nodejs18.x', + Handler: 'index.handler', + State: 'Active', + Layers: [ + { + Arn: 'arn:aws:lambda:us-east-1:123456789012:layer:test-layer:1', + }, + ], + }; + }); + + mockLambdaClient.on(ListLayerVersionsCommand).resolves({ + LayerVersions: [ + { + LayerVersionArn: 'arn:aws:lambda:us-east-1:123456789012:layer:test-layer:1', + }, + ], + }); + + mockLambdaClient.on(UpdateFunctionCodeCommand).callsFake(({ FunctionName }) => ({ + Configuration: { + FunctionName, + }, + })); + }); + + afterEach(() => { + mockLambdaClient.restore(); + }); + + test('Happy path', async () => { + // Step 1: Create a bot + const res1 = await request(app) + .post(`/fhir/R4/Bot`) + .set('Content-Type', ContentType.FHIR_JSON) + .set('Authorization', 'Bearer ' + accessToken) + .send({ + resourceType: 'Bot', + name: 'Test Bot', + runtimeVersion: 'awslambda', + code: ` + export async function handler() { + console.log('input', input); + return input; + } + `, + }); + expect(res1.status).toBe(201); + + const bot = res1.body as Bot; + const name = `medplum-bot-lambda-${bot.id}`; + + // Step 2: Deploy the bot + const res2 = await request(app) + .post(`/fhir/R4/Bot/${bot.id}/$deploy`) + .set('Content-Type', ContentType.FHIR_JSON) + .set('Authorization', 'Bearer ' + accessToken) + .send({ + code: ` + export async function handler() { + console.log('input', input); + return input; + } + `, + }); + expect(res2.status).toBe(200); + + expect(mockLambdaClient).toHaveReceivedCommandTimes(GetFunctionCommand, 1); + expect(mockLambdaClient).toHaveReceivedCommandTimes(ListLayerVersionsCommand, 1); + expect(mockLambdaClient).toHaveReceivedCommandTimes(CreateFunctionCommand, 1); + expect(mockLambdaClient).toHaveReceivedCommandWith(GetFunctionCommand, { + FunctionName: name, + }); + expect(mockLambdaClient).toHaveReceivedCommandWith(CreateFunctionCommand, { + FunctionName: name, + }); + mockLambdaClient.resetHistory(); + + // Step 3: Deploy again to trigger the update path + const res3 = await request(app) + .post(`/fhir/R4/Bot/${bot.id}/$deploy`) + .set('Content-Type', ContentType.FHIR_JSON) + .set('Authorization', 'Bearer ' + accessToken) + .send({ + code: ` + export async function handler() { + console.log('input', input); + return input; + } + `, + filename: 'updated.js', + }); + expect(res3.status).toBe(200); + + expect(mockLambdaClient).toHaveReceivedCommandTimes(GetFunctionCommand, 1); + expect(mockLambdaClient).toHaveReceivedCommandTimes(ListLayerVersionsCommand, 1); + expect(mockLambdaClient).toHaveReceivedCommandTimes(GetFunctionConfigurationCommand, 1); + expect(mockLambdaClient).toHaveReceivedCommandTimes(UpdateFunctionConfigurationCommand, 0); + expect(mockLambdaClient).toHaveReceivedCommandTimes(UpdateFunctionCodeCommand, 1); + expect(mockLambdaClient).toHaveReceivedCommandWith(GetFunctionCommand, { + FunctionName: name, + }); + }); + + test('Deploy bot with lambda layer update', async () => { + // When deploying a bot, we check if we need to update the bot configuration. + // This test verifies that we correctly update the bot configuration when the lambda layer changes. + // Step 1: Create a bot + const res1 = await request(app) + .post(`/fhir/R4/Bot`) + .set('Content-Type', ContentType.FHIR_JSON) + .set('Authorization', 'Bearer ' + accessToken) + .send({ + resourceType: 'Bot', + name: 'Test Bot', + runtimeVersion: 'awslambda', + code: ` + export async function handler() { + console.log('input', input); + return input; + } + `, + }); + expect(res1.status).toBe(201); + + const bot = res1.body as Bot; + const name = `medplum-bot-lambda-${bot.id}`; + + // Step 2: Deploy the bot + const res2 = await request(app) + .post(`/fhir/R4/Bot/${bot.id}/$deploy`) + .set('Content-Type', ContentType.FHIR_JSON) + .set('Authorization', 'Bearer ' + accessToken) + .send({ + code: ` + export async function handler() { + console.log('input', input); + return input; + } + `, + }); + expect(res2.status).toBe(200); + + expect(mockLambdaClient).toHaveReceivedCommandTimes(GetFunctionCommand, 1); + expect(mockLambdaClient).toHaveReceivedCommandTimes(ListLayerVersionsCommand, 1); + expect(mockLambdaClient).toHaveReceivedCommandTimes(CreateFunctionCommand, 1); + expect(mockLambdaClient).toHaveReceivedCommandWith(GetFunctionCommand, { + FunctionName: name, + }); + expect(mockLambdaClient).toHaveReceivedCommandWith(CreateFunctionCommand, { + FunctionName: name, + }); + mockLambdaClient.resetHistory(); + + // Step 3: Simulate releasing a new version of the lambda layer + mockLambdaClient.on(ListLayerVersionsCommand).resolves({ + LayerVersions: [ + { + LayerVersionArn: 'new-layer-version-arn', + }, + ], + }); + + // Step 4: Deploy again to trigger the update path + const res3 = await request(app) + .post(`/fhir/R4/Bot/${bot.id}/$deploy`) + .set('Content-Type', ContentType.FHIR_JSON) + .set('Authorization', 'Bearer ' + accessToken) + .send({ + code: ` + export async function handler() { + console.log('input', input); + return input; + } + `, + filename: 'updated.js', + }); + expect(res3.status).toBe(200); + + expect(mockLambdaClient).toHaveReceivedCommandTimes(GetFunctionCommand, 1); + expect(mockLambdaClient).toHaveReceivedCommandTimes(ListLayerVersionsCommand, 1); + expect(mockLambdaClient).toHaveReceivedCommandTimes(GetFunctionConfigurationCommand, 2); + expect(mockLambdaClient).toHaveReceivedCommandTimes(UpdateFunctionConfigurationCommand, 1); + expect(mockLambdaClient).toHaveReceivedCommandTimes(UpdateFunctionCodeCommand, 1); + expect(mockLambdaClient).toHaveReceivedCommandWith(GetFunctionCommand, { + FunctionName: name, + }); + }); +}); diff --git a/packages/server/src/cloud/aws/deploy.ts b/packages/server/src/cloud/aws/deploy.ts new file mode 100644 index 0000000000..50dc4477dd --- /dev/null +++ b/packages/server/src/cloud/aws/deploy.ts @@ -0,0 +1,260 @@ +import { + CreateFunctionCommand, + GetFunctionCommand, + GetFunctionConfigurationCommand, + GetFunctionConfigurationCommandOutput, + LambdaClient, + ListLayerVersionsCommand, + PackageType, + UpdateFunctionCodeCommand, + UpdateFunctionConfigurationCommand, +} from '@aws-sdk/client-lambda'; +import { sleep } from '@medplum/core'; +import { Bot } from '@medplum/fhirtypes'; +import { ConfiguredRetryStrategy } from '@smithy/util-retry'; +import JSZip from 'jszip'; +import { getConfig } from '../../config'; +import { getRequestContext } from '../../context'; + +const LAMBDA_RUNTIME = 'nodejs18.x'; + +const LAMBDA_HANDLER = 'index.handler'; + +const LAMBDA_MEMORY = 1024; + +const WRAPPER_CODE = `const { ContentType, Hl7Message, MedplumClient } = require("@medplum/core"); +const fetch = require("node-fetch"); +const PdfPrinter = require("pdfmake"); +const userCode = require("./user.js"); + +exports.handler = async (event, context) => { + const { bot, baseUrl, accessToken, contentType, secrets, traceId } = event; + const medplum = new MedplumClient({ + baseUrl, + fetch: function(url, options = {}) { + options.headers ||= {}; + options.headers['X-Trace-Id'] = traceId; + options.headers['traceparent'] = traceId; + return fetch(url, options); + }, + createPdf, + }); + medplum.setAccessToken(accessToken); + try { + let input = event.input; + if (contentType === ContentType.HL7_V2 && input) { + input = Hl7Message.parse(input); + } + let result = await userCode.handler(medplum, { bot, input, contentType, secrets, traceId }); + if (contentType === ContentType.HL7_V2 && result) { + result = result.toString(); + } + return result; + } catch (err) { + if (err instanceof Error) { + console.log("Unhandled error: " + err.message + "\\n" + err.stack); + } else if (typeof err === "object") { + console.log("Unhandled error: " + JSON.stringify(err, undefined, 2)); + } else { + console.log("Unhandled error: " + err); + } + throw err; + } +}; + +function createPdf(docDefinition, tableLayouts, fonts) { + if (!fonts) { + fonts = { + Helvetica: { + normal: 'Helvetica', + bold: 'Helvetica-Bold', + italics: 'Helvetica-Oblique', + bolditalics: 'Helvetica-BoldOblique', + }, + Roboto: { + normal: '/opt/fonts/Roboto/Roboto-Regular.ttf', + bold: '/opt/fonts/Roboto/Roboto-Medium.ttf', + italics: '/opt/fonts/Roboto/Roboto-Italic.ttf', + bolditalics: '/opt/fonts/Roboto/Roboto-MediumItalic.ttf' + }, + Avenir: { + normal: '/opt/fonts/Avenir/Avenir.ttf' + } + }; + } + return new Promise((resolve, reject) => { + const printer = new PdfPrinter(fonts); + const pdfDoc = printer.createPdfKitDocument(docDefinition, { tableLayouts }); + const chunks = []; + pdfDoc.on('data', (chunk) => chunks.push(chunk)); + pdfDoc.on('end', () => resolve(Buffer.concat(chunks))); + pdfDoc.on('error', reject); + pdfDoc.end(); + }); +} +`; + +export async function deployLambda(bot: Bot, code: string): Promise { + const ctx = getRequestContext(); + + // Create a new AWS Lambda client + // Use a custom retry strategy to avoid throttling errors + // This is especially important when updating lambdas which also + // involve upgrading the layer version. + const client = new LambdaClient({ + region: getConfig().awsRegion, + retryStrategy: new ConfiguredRetryStrategy( + 5, // max attempts + (attempt: number) => 500 * 2 ** attempt // Exponential backoff + ), + }); + + const name = `medplum-bot-lambda-${bot.id}`; + ctx.logger.info('Deploying lambda function for bot', { name }); + const zipFile = await createZipFile(code); + ctx.logger.debug('Lambda function zip size', { bytes: zipFile.byteLength }); + + const exists = await lambdaExists(client, name); + if (!exists) { + await createLambda(client, name, zipFile); + } else { + await updateLambda(client, name, zipFile); + } +} + +async function createZipFile(code: string): Promise { + const zip = new JSZip(); + zip.file('user.js', code); + zip.file('index.js', WRAPPER_CODE); + return zip.generateAsync({ type: 'uint8array' }); +} + +/** + * Returns true if the AWS Lambda exists for the bot name. + * @param client - The AWS Lambda client. + * @param name - The bot name. + * @returns True if the bot exists. + */ +async function lambdaExists(client: LambdaClient, name: string): Promise { + try { + const command = new GetFunctionCommand({ FunctionName: name }); + const response = await client.send(command); + return response.Configuration?.FunctionName === name; + } catch (err) { + return false; + } +} + +/** + * Creates a new AWS Lambda for the bot name. + * @param client - The AWS Lambda client. + * @param name - The bot name. + * @param zipFile - The zip file with the bot code. + */ +async function createLambda(client: LambdaClient, name: string, zipFile: Uint8Array): Promise { + const layerVersion = await getLayerVersion(client); + + await client.send( + new CreateFunctionCommand({ + FunctionName: name, + Role: getConfig().botLambdaRoleArn, + Runtime: LAMBDA_RUNTIME, + Handler: LAMBDA_HANDLER, + MemorySize: LAMBDA_MEMORY, + PackageType: PackageType.Zip, + Layers: [layerVersion], + Code: { + ZipFile: zipFile, + }, + Publish: true, + Timeout: 10, // seconds + }) + ); +} + +/** + * Updates an existing AWS Lambda for the bot name. + * @param client - The AWS Lambda client. + * @param name - The bot name. + * @param zipFile - The zip file with the bot code. + */ +async function updateLambda(client: LambdaClient, name: string, zipFile: Uint8Array): Promise { + // First, make sure the lambda configuration is up to date + await updateLambdaConfig(client, name); + + // Then update the code + await client.send( + new UpdateFunctionCodeCommand({ + FunctionName: name, + ZipFile: zipFile, + Publish: true, + }) + ); +} + +/** + * Updates the lambda configuration. + * @param client - The AWS Lambda client. + * @param name - The lambda name. + */ +async function updateLambdaConfig(client: LambdaClient, name: string): Promise { + const layerVersion = await getLayerVersion(client); + const functionConfig = await getLambdaConfig(client, name); + if ( + functionConfig.Runtime === LAMBDA_RUNTIME && + functionConfig.Handler === LAMBDA_HANDLER && + functionConfig.Layers?.[0].Arn === layerVersion + ) { + // Everything is up-to-date + return; + } + + // Need to update + await client.send( + new UpdateFunctionConfigurationCommand({ + FunctionName: name, + Role: getConfig().botLambdaRoleArn, + Runtime: LAMBDA_RUNTIME, + Handler: LAMBDA_HANDLER, + Layers: [layerVersion], + }) + ); + + // Wait for the update to complete before returning + // Wait up to 5 seconds + // See: https://github.com/aws/aws-toolkit-visual-studio/issues/197 + // See: https://aws.amazon.com/blogs/compute/coming-soon-expansion-of-aws-lambda-states-to-all-functions/ + for (let i = 0; i < 5; i++) { + const config = await getLambdaConfig(client, name); + // Valid Values: Pending | Active | Inactive | Failed + // See: https://docs.aws.amazon.com/lambda/latest/dg/API_GetFunctionConfiguration.html + if (config.State === 'Active') { + return; + } + await sleep(1000); + } +} + +async function getLambdaConfig(client: LambdaClient, name: string): Promise { + return client.send( + new GetFunctionConfigurationCommand({ + FunctionName: name, + }) + ); +} + +/** + * Returns the latest layer version for the Medplum bot layer. + * The first result is the latest version. + * See: https://stackoverflow.com/a/55752188 + * @param client - The AWS Lambda client. + * @returns The most recent layer version ARN. + */ +async function getLayerVersion(client: LambdaClient): Promise { + const command = new ListLayerVersionsCommand({ + LayerName: getConfig().botLambdaLayerName, + MaxItems: 1, + }); + const response = await client.send(command); + return response.LayerVersions?.[0].LayerVersionArn as string; +} diff --git a/packages/server/src/cloud/aws/email.ts b/packages/server/src/cloud/aws/email.ts new file mode 100644 index 0000000000..d495c0a507 --- /dev/null +++ b/packages/server/src/cloud/aws/email.ts @@ -0,0 +1,41 @@ +import { SendEmailCommand, SESv2Client } from '@aws-sdk/client-sesv2'; +import { badRequest, normalizeErrorString, OperationOutcomeError } from '@medplum/core'; +import Mail from 'nodemailer/lib/mailer'; +import { getConfig } from '../../config'; +import { addressToString, buildAddresses, buildRawMessage } from '../../email/utils'; + +/** + * Sends an email via AWS SES. + * @param options - The nodemailer options. + */ +export async function sendEmailViaSes(options: Mail.Options): Promise { + const config = getConfig(); + const fromAddress = addressToString(options.from); + const toAddresses = buildAddresses(options.to); + const ccAddresses = buildAddresses(options.cc); + const bccAddresses = buildAddresses(options.bcc); + + let msg: Uint8Array; + try { + msg = await buildRawMessage(options); + } catch (err) { + throw new OperationOutcomeError(badRequest('Invalid email options: ' + normalizeErrorString(err)), err); + } + + const sesClient = new SESv2Client({ region: config.awsRegion }); + await sesClient.send( + new SendEmailCommand({ + FromEmailAddress: fromAddress, + Destination: { + ToAddresses: toAddresses, + CcAddresses: ccAddresses, + BccAddresses: bccAddresses, + }, + Content: { + Raw: { + Data: msg, + }, + }, + }) + ); +} diff --git a/packages/server/src/cloud/aws/execute.test.ts b/packages/server/src/cloud/aws/execute.test.ts new file mode 100644 index 0000000000..912321a1f3 --- /dev/null +++ b/packages/server/src/cloud/aws/execute.test.ts @@ -0,0 +1,231 @@ +import { InvokeCommand, LambdaClient, ListLayerVersionsCommand } from '@aws-sdk/client-lambda'; +import { ContentType } from '@medplum/core'; +import { Bot } from '@medplum/fhirtypes'; +import { AwsClientStub, mockClient } from 'aws-sdk-client-mock'; +import { randomUUID } from 'crypto'; +import express from 'express'; +import request from 'supertest'; +import { initApp, shutdownApp } from '../../app'; +import { getConfig, loadTestConfig } from '../../config'; +import { getBinaryStorage } from '../../fhir/storage'; +import { initTestAuth } from '../../test.setup'; +import { getLambdaFunctionName } from './execute'; + +const app = express(); +let accessToken: string; +let bot: Bot; + +describe('Execute', () => { + let mockLambdaClient: AwsClientStub; + + beforeEach(() => { + mockLambdaClient = mockClient(LambdaClient); + + mockLambdaClient.on(ListLayerVersionsCommand).resolves({ + LayerVersions: [ + { + LayerVersionArn: 'xyz', + }, + ], + }); + + mockLambdaClient.on(InvokeCommand).callsFake(({ Payload }) => { + const decoder = new TextDecoder(); + const event = JSON.parse(decoder.decode(Payload)); + const output = JSON.stringify(event.input); + const encoder = new TextEncoder(); + + return { + LogResult: `U1RBUlQgUmVxdWVzdElkOiAxNDZmY2ZjZi1jMzJiLTQzZjUtODJhNi1lZTBmMzEzMmQ4NzMgVmVyc2lvbjogJExBVEVTVAoyMDIyLTA1LTMwVDE2OjEyOjIyLjY4NVoJMTQ2ZmNmY2YtYzMyYi00M2Y1LTgyYTYtZWUwZjMxMzJkODczCUlORk8gdGVzdApFTkQgUmVxdWVzdElkOiAxNDZmY2ZjZi1jMzJiLTQzZjUtODJhNi1lZTBmMzEzMmQ4NzMKUkVQT1JUIFJlcXVlc3RJZDogMTQ2ZmNmY2YtYzMyYi00M2Y1LTgyYTYtZWUwZjMxMzJkODcz`, + Payload: encoder.encode(output), + }; + }); + }); + + afterEach(() => { + mockLambdaClient.restore(); + }); + + beforeAll(async () => { + const config = await loadTestConfig(); + await initApp(app, config); + accessToken = await initTestAuth(); + + const res = await request(app) + .post(`/fhir/R4/Bot`) + .set('Content-Type', ContentType.FHIR_JSON) + .set('Authorization', 'Bearer ' + accessToken) + .send({ + resourceType: 'Bot', + identifier: [{ system: 'https://example.com/bot', value: randomUUID() }], + name: 'Test Bot', + runtimeVersion: 'awslambda', + code: ` + export async function handler(medplum, event) { + console.log('input', event.input); + return event.input; + } + `, + }); + expect(res.status).toBe(201); + bot = res.body as Bot; + }); + + afterAll(async () => { + await shutdownApp(); + }); + + test('Submit plain text', async () => { + const res = await request(app) + .post(`/fhir/R4/Bot/${bot.id}/$execute`) + .set('Content-Type', ContentType.TEXT) + .set('Authorization', 'Bearer ' + accessToken) + .send('input'); + expect(res.status).toBe(200); + expect(res.headers['content-type']).toBe('text/plain; charset=utf-8'); + expect(res.text).toEqual('input'); + }); + + test('Submit FHIR with content type', async () => { + const res = await request(app) + .post(`/fhir/R4/Bot/${bot.id}/$execute`) + .set('Content-Type', ContentType.FHIR_JSON) + .set('Authorization', 'Bearer ' + accessToken) + .send({ + resourceType: 'Patient', + name: [{ given: ['John'], family: ['Doe'] }], + }); + expect(res.status).toBe(200); + expect(res.headers['content-type']).toBe('application/fhir+json; charset=utf-8'); + }); + + test('Submit FHIR without content type', async () => { + const res = await request(app) + .post(`/fhir/R4/Bot/${bot.id}/$execute`) + .set('Authorization', 'Bearer ' + accessToken) + .send({ + resourceType: 'Patient', + name: [{ given: ['John'], family: ['Doe'] }], + }); + expect(res.status).toBe(200); + expect(res.headers['content-type']).toBe('application/json; charset=utf-8'); + }); + + test('Submit HL7', async () => { + const binaryStorage = getBinaryStorage(); + const writeFileSpy = jest.spyOn(binaryStorage, 'writeFile'); + + const text = + 'MSH|^~\\&|Main_HIS|XYZ_HOSPITAL|iFW|ABC_Lab|20160915003015||ACK|9B38584D|P|2.6.1|\r' + + 'MSA|AA|9B38584D|Everything was okay dokay!|'; + + const res = await request(app) + .post(`/fhir/R4/Bot/${bot.id}/$execute`) + .set('Content-Type', ContentType.HL7_V2) + .set('Authorization', 'Bearer ' + accessToken) + .send(text); + expect(res.status).toBe(200); + expect(res.headers['content-type']).toBe('x-application/hl7-v2+er7; charset=utf-8'); + expect(writeFileSpy).toHaveBeenCalledTimes(1); + + const args = writeFileSpy.mock.calls[0]; + expect(args.length).toBe(3); + expect(args[0]).toMatch(/^bot\//); + expect(args[1]).toBe(ContentType.JSON); + + const row = JSON.parse(args[2] as string); + expect(row.botId).toEqual(bot.id); + expect(row.hl7MessageType).toEqual('ACK'); + expect(row.hl7Version).toEqual('2.6.1'); + }); + + test('Execute without code', async () => { + // Create a bot with empty code + const res1 = await request(app) + .post(`/fhir/R4/Bot`) + .set('Content-Type', ContentType.FHIR_JSON) + .set('Authorization', 'Bearer ' + accessToken) + .send({ + resourceType: 'Bot', + name: 'Test Bot', + code: '', + }); + expect(res1.status).toBe(201); + const bot = res1.body as Bot; + + // Execute the bot + const res2 = await request(app) + .post(`/fhir/R4/Bot/${bot.id}/$execute`) + .set('Content-Type', ContentType.FHIR_JSON) + .set('Authorization', 'Bearer ' + accessToken) + .send({}); + expect(res2.status).toBe(400); + }); + + test('Unsupported runtime version', async () => { + const res1 = await request(app) + .post(`/fhir/R4/Bot`) + .set('Content-Type', ContentType.FHIR_JSON) + .set('Authorization', 'Bearer ' + accessToken) + .send({ + resourceType: 'Bot', + name: 'Test Bot', + runtimeVersion: 'unsupported', + }); + expect(res1.status).toBe(201); + const bot = res1.body as Bot; + + // Step 2: Publish the bot + const res2 = await request(app) + .post(`/fhir/R4/Bot/${bot.id}/$deploy`) + .set('Content-Type', ContentType.FHIR_JSON) + .set('Authorization', 'Bearer ' + accessToken) + .send({ + code: ` + export async function handler() { + console.log('input', input); + return input; + } + `, + }); + expect(res2.status).toBe(200); + + // Step 3: Execute the bot + const res3 = await request(app) + .post(`/fhir/R4/Bot/${bot.id}/$execute`) + .set('Content-Type', ContentType.FHIR_JSON) + .set('Authorization', 'Bearer ' + accessToken) + .send({}); + expect(res3.status).toBe(400); + }); + + test('Get function name', async () => { + const config = getConfig(); + const normalBot: Bot = { resourceType: 'Bot', id: '123' }; + const customBot: Bot = { + resourceType: 'Bot', + id: '456', + identifier: [{ system: 'https://medplum.com/bot-external-function-id', value: 'custom' }], + }; + + expect(getLambdaFunctionName(normalBot)).toEqual('medplum-bot-lambda-123'); + expect(getLambdaFunctionName(customBot)).toEqual('medplum-bot-lambda-456'); + + // Temporarily enable custom bot support + config.botCustomFunctionsEnabled = true; + expect(getLambdaFunctionName(normalBot)).toEqual('medplum-bot-lambda-123'); + expect(getLambdaFunctionName(customBot)).toEqual('custom'); + config.botCustomFunctionsEnabled = false; + }); + + test('Execute by identifier', async () => { + const res = await request(app) + .post(`/fhir/R4/Bot/$execute?identifier=${bot.identifier?.[0]?.system}|${bot.identifier?.[0]?.value}`) + .set('Content-Type', ContentType.TEXT) + .set('Authorization', 'Bearer ' + accessToken) + .send('input'); + expect(res.status).toBe(200); + expect(res.headers['content-type']).toBe('text/plain; charset=utf-8'); + expect(res.text).toEqual('input'); + }); +}); diff --git a/packages/server/src/cloud/aws/execute.ts b/packages/server/src/cloud/aws/execute.ts new file mode 100644 index 0000000000..50e28d0dd7 --- /dev/null +++ b/packages/server/src/cloud/aws/execute.ts @@ -0,0 +1,108 @@ +import { InvokeCommand, LambdaClient } from '@aws-sdk/client-lambda'; +import { Hl7Message, createReference, getIdentifier, normalizeErrorString } from '@medplum/core'; +import { Bot } from '@medplum/fhirtypes'; +import { TextDecoder, TextEncoder } from 'util'; +import { getConfig } from '../../config'; +import { BotExecutionContext, BotExecutionResult } from '../../fhir/operations/execute'; + +/** + * Executes a Bot in an AWS Lambda. + * @param request - The bot request. + * @returns The bot execution result. + */ +export async function runInLambda(request: BotExecutionContext): Promise { + const { bot, accessToken, secrets, input, contentType, traceId } = request; + const config = getConfig(); + const client = new LambdaClient({ region: config.awsRegion }); + const name = getLambdaFunctionName(bot); + const payload = { + bot: createReference(bot), + baseUrl: config.baseUrl, + accessToken, + input: input instanceof Hl7Message ? input.toString() : input, + contentType, + secrets, + traceId, + }; + + // Build the command + const encoder = new TextEncoder(); + const command = new InvokeCommand({ + FunctionName: name, + InvocationType: 'RequestResponse', + LogType: 'Tail', + Payload: encoder.encode(JSON.stringify(payload)), + }); + + // Execute the command + try { + const response = await client.send(command); + const responseStr = response.Payload ? new TextDecoder().decode(response.Payload) : undefined; + + // The response from AWS Lambda is always JSON, even if the function returns a string + // Therefore we always use JSON.parse to get the return value + // See: https://stackoverflow.com/a/49951946/2051724 + const returnValue = responseStr ? JSON.parse(responseStr) : undefined; + + return { + success: !response.FunctionError, + logResult: parseLambdaLog(response.LogResult as string), + returnValue, + }; + } catch (err) { + return { + success: false, + logResult: normalizeErrorString(err), + }; + } +} + +/** + * Returns the AWS Lambda function name for the given bot. + * By default, the function name is based on the bot ID. + * If the bot has a custom function, and the server allows it, then that is used instead. + * @param bot - The Bot resource. + * @returns The AWS Lambda function name. + */ +export function getLambdaFunctionName(bot: Bot): string { + if (getConfig().botCustomFunctionsEnabled) { + const customFunction = getIdentifier(bot, 'https://medplum.com/bot-external-function-id'); + if (customFunction) { + return customFunction; + } + } + + // By default, use the bot ID as the Lambda function name + return `medplum-bot-lambda-${bot.id}`; +} + +/** + * Parses the AWS Lambda log result. + * + * The raw logs include markup metadata such as timestamps and billing information. + * + * We only want to include the actual log contents in the AuditEvent, + * so we attempt to scrub away all of that extra metadata. + * + * See: https://docs.aws.amazon.com/lambda/latest/dg/nodejs-logging.html + * @param logResult - The raw log result from the AWS lambda event. + * @returns The parsed log result. + */ +function parseLambdaLog(logResult: string): string { + const logBuffer = Buffer.from(logResult, 'base64'); + const log = logBuffer.toString('ascii'); + const lines = log.split('\n'); + const result = []; + for (const line of lines) { + if (line.startsWith('START RequestId: ')) { + // Ignore start line + continue; + } + if (line.startsWith('END RequestId: ') || line.startsWith('REPORT RequestId: ')) { + // Stop at end lines + break; + } + result.push(line); + } + return result.join('\n').trim(); +} diff --git a/packages/server/src/fhir/signer.md b/packages/server/src/cloud/aws/signer.md similarity index 100% rename from packages/server/src/fhir/signer.md rename to packages/server/src/cloud/aws/signer.md diff --git a/packages/server/src/fhir/signer.test.ts b/packages/server/src/cloud/aws/signer.test.ts similarity index 93% rename from packages/server/src/fhir/signer.test.ts rename to packages/server/src/cloud/aws/signer.test.ts index f86ec8ddda..c4b4f698c4 100644 --- a/packages/server/src/fhir/signer.test.ts +++ b/packages/server/src/cloud/aws/signer.test.ts @@ -1,6 +1,6 @@ import { Binary } from '@medplum/fhirtypes'; import { randomUUID } from 'crypto'; -import { loadTestConfig } from '../config'; +import { loadTestConfig } from '../../config'; import { getPresignedUrl } from './signer'; describe('Signer', () => { diff --git a/packages/server/src/fhir/signer.ts b/packages/server/src/cloud/aws/signer.ts similarity index 95% rename from packages/server/src/fhir/signer.ts rename to packages/server/src/cloud/aws/signer.ts index 0bcb896eaf..e33001b78f 100644 --- a/packages/server/src/fhir/signer.ts +++ b/packages/server/src/cloud/aws/signer.ts @@ -1,6 +1,6 @@ import { getSignedUrl } from '@aws-sdk/cloudfront-signer'; import { Binary } from '@medplum/fhirtypes'; -import { getConfig } from '../config'; +import { getConfig } from '../../config'; /** * Returns a presigned URL for the Binary resource content. diff --git a/packages/server/src/cloud/aws/storage.test.ts b/packages/server/src/cloud/aws/storage.test.ts new file mode 100644 index 0000000000..050ebb705f --- /dev/null +++ b/packages/server/src/cloud/aws/storage.test.ts @@ -0,0 +1,188 @@ +import { CopyObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3'; +import { ContentType } from '@medplum/core'; +import { Binary } from '@medplum/fhirtypes'; +import { sdkStreamMixin } from '@smithy/util-stream'; +import { AwsClientStub, mockClient } from 'aws-sdk-client-mock'; +import 'aws-sdk-client-mock-jest'; +import { Request } from 'express'; +import internal, { Readable } from 'stream'; +import { loadTestConfig } from '../../config'; +import { getBinaryStorage, initBinaryStorage } from '../../fhir/storage'; + +describe('Storage', () => { + let mockS3Client: AwsClientStub; + + beforeAll(async () => { + await loadTestConfig(); + }); + + beforeEach(() => { + mockS3Client = mockClient(S3Client); + }); + + afterEach(() => { + mockS3Client.restore(); + }); + + test('Undefined binary storage', () => { + initBinaryStorage('binary'); + expect(() => getBinaryStorage()).toThrow(); + }); + + test('S3 storage', async () => { + initBinaryStorage('s3:foo'); + + const storage = getBinaryStorage(); + expect(storage).toBeDefined(); + + // Write a file + const binary = { + resourceType: 'Binary', + id: '123', + meta: { + versionId: '456', + }, + } as Binary; + const req = new Readable(); + req.push('foo'); + req.push(null); + (req as any).headers = {}; + + const sdkStream = sdkStreamMixin(req); + mockS3Client.on(GetObjectCommand).resolves({ Body: sdkStream }); + + await storage.writeBinary(binary, 'test.txt', ContentType.TEXT, req as Request); + + expect(mockS3Client.send.callCount).toBe(1); + expect(mockS3Client).toReceiveCommandWith(PutObjectCommand, { + Bucket: 'foo', + Key: 'binary/123/456', + ContentType: ContentType.TEXT, + }); + + // Read a file + const stream = await storage.readBinary(binary); + expect(stream).toBeDefined(); + expect(mockS3Client).toHaveReceivedCommand(GetObjectCommand); + }); + + test('Missing metadata', async () => { + initBinaryStorage('s3:foo'); + + const storage = getBinaryStorage(); + expect(storage).toBeDefined(); + + // Write a file + const binary = { + resourceType: 'Binary', + id: '123', + meta: { + versionId: '456', + }, + } as Binary; + const req = new Readable(); + req.push('foo'); + req.push(null); + (req as any).headers = {}; + + const sdkStream = sdkStreamMixin(req); + mockS3Client.on(GetObjectCommand).resolves({ Body: sdkStream }); + + await storage.writeBinary(binary, undefined, undefined, req as Request); + expect(mockS3Client.send.callCount).toBe(1); + expect(mockS3Client).toReceiveCommandWith(PutObjectCommand, { + Bucket: 'foo', + Key: 'binary/123/456', + ContentType: 'application/octet-stream', + }); + + // Read a file + const stream = await storage.readBinary(binary); + expect(stream).toBeDefined(); + expect(mockS3Client).toHaveReceivedCommand(GetObjectCommand); + }); + + test('Invalid file extension', async () => { + initBinaryStorage('s3:foo'); + + const storage = getBinaryStorage(); + expect(storage).toBeDefined(); + + const binary = null as unknown as Binary; + const stream = null as unknown as internal.Readable; + try { + await storage.writeBinary(binary, 'test.exe', ContentType.TEXT, stream); + fail('Expected error'); + } catch (err) { + expect((err as Error).message).toEqual('Invalid file extension'); + } + expect(mockS3Client).not.toHaveReceivedCommand(PutObjectCommand); + }); + + test('Invalid content type', async () => { + initBinaryStorage('s3:foo'); + + const storage = getBinaryStorage(); + expect(storage).toBeDefined(); + + const binary = null as unknown as Binary; + const stream = null as unknown as internal.Readable; + try { + await storage.writeBinary(binary, 'test.sh', 'application/x-sh', stream); + fail('Expected error'); + } catch (err) { + expect((err as Error).message).toEqual('Invalid content type'); + } + expect(mockS3Client).not.toHaveReceivedCommand(PutObjectCommand); + }); + + test('Copy S3 object', async () => { + initBinaryStorage('s3:foo'); + + const storage = getBinaryStorage(); + expect(storage).toBeDefined(); + + // Write a file + const binary = { + resourceType: 'Binary', + id: '123', + meta: { + versionId: '456', + }, + } as Binary; + const req = new Readable(); + req.push('foo'); + req.push(null); + (req as any).headers = {}; + + const sdkStream = sdkStreamMixin(req); + mockS3Client.on(GetObjectCommand).resolves({ Body: sdkStream }); + + await storage.writeBinary(binary, 'test.txt', ContentType.TEXT, req as Request); + + expect(mockS3Client.send.callCount).toBe(1); + expect(mockS3Client).toReceiveCommandWith(PutObjectCommand, { + Bucket: 'foo', + Key: 'binary/123/456', + ContentType: ContentType.TEXT, + }); + mockS3Client.reset(); + + // Copy the object + const destinationBinary = { + resourceType: 'Binary', + id: '789', + meta: { + versionId: '012', + }, + } as Binary; + await storage.copyBinary(binary, destinationBinary); + + expect(mockS3Client.send.callCount).toBe(1); + expect(mockS3Client).toReceiveCommandWith(CopyObjectCommand, { + CopySource: 'foo/binary/123/456', + Bucket: 'foo', + Key: 'binary/789/012', + }); + }); +}); diff --git a/packages/server/src/cloud/aws/storage.ts b/packages/server/src/cloud/aws/storage.ts new file mode 100644 index 0000000000..a237889ac9 --- /dev/null +++ b/packages/server/src/cloud/aws/storage.ts @@ -0,0 +1,133 @@ +import { CopyObjectCommand, GetObjectCommand, S3Client } from '@aws-sdk/client-s3'; +import { getSignedUrl } from '@aws-sdk/cloudfront-signer'; +import { Upload } from '@aws-sdk/lib-storage'; +import { Binary } from '@medplum/fhirtypes'; +import { Readable } from 'stream'; +import { getConfig } from '../../config'; +import { BinarySource, BinaryStorage, checkFileMetadata } from '../../fhir/storage'; + +/** + * The S3Storage class stores binary data in an AWS S3 bucket. + * Files are stored in bucket/binary/binary.id/binary.meta.versionId. + */ +export class S3Storage implements BinaryStorage { + private readonly client: S3Client; + private readonly bucket: string; + + constructor(bucket: string) { + this.client = new S3Client({ region: getConfig().awsRegion }); + this.bucket = bucket; + } + + /** + * Writes a binary blob to S3. + * @param binary - The binary resource destination. + * @param filename - Optional binary filename. + * @param contentType - Optional binary content type. + * @param stream - The Node.js stream of readable content. + * @returns Promise that resolves when the write is complete. + */ + writeBinary( + binary: Binary, + filename: string | undefined, + contentType: string | undefined, + stream: BinarySource + ): Promise { + checkFileMetadata(filename, contentType); + return this.writeFile(this.getKey(binary), contentType, stream); + } + + /** + * Writes a file to S3. + * + * Early implementations used the simple "PutObjectCommand" to write the blob to S3. + * However, PutObjectCommand does not support streaming. + * + * We now use the @aws-sdk/lib-storage package. + * + * Learn more: + * https://github.com/aws/aws-sdk-js-v3/blob/main/UPGRADING.md#s3-multipart-upload + * https://github.com/aws/aws-sdk-js-v3/tree/main/lib/lib-storage + * + * Be mindful of Cache-Control settings. + * + * Because we use signed URLs intended for one hour use, + * we set "max-age" to 1 hour = 3600 seconds. + * + * But we want CloudFront to cache the response for 1 day, + * so we set "s-maxage" to 1 day = 86400 seconds. + * + * Learn more: + * https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Expiration.html + * @param key - The S3 key. + * @param contentType - Optional binary content type. + * @param stream - The Node.js stream of readable content. + */ + async writeFile(key: string, contentType: string | undefined, stream: BinarySource): Promise { + const upload = new Upload({ + params: { + Bucket: this.bucket, + Key: key, + CacheControl: 'max-age=3600, s-maxage=86400', + ContentType: contentType ?? 'application/octet-stream', + Body: stream, + }, + client: this.client, + queueSize: 3, + }); + + await upload.done(); + } + + async readBinary(binary: Binary): Promise { + const output = await this.client.send( + new GetObjectCommand({ + Bucket: this.bucket, + Key: this.getKey(binary), + }) + ); + return output.Body as Readable; + } + + async copyBinary(sourceBinary: Binary, destinationBinary: Binary): Promise { + await this.copyFile(this.getKey(sourceBinary), this.getKey(destinationBinary)); + } + + async copyFile(sourceKey: string, destinationKey: string): Promise { + await this.client.send( + new CopyObjectCommand({ + CopySource: `${this.bucket}/${sourceKey}`, + Bucket: this.bucket, + Key: destinationKey, + }) + ); + } + + /** + * Returns a presigned URL for the Binary resource content. + * + * Reference: + * https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/modules/_aws_sdk_cloudfront_signer.html + * + * @param binary - Binary resource. + * @returns Presigned URL to access the binary data. + */ + getPresignedUrl(binary: Binary): string { + const config = getConfig(); + const storageBaseUrl = config.storageBaseUrl; + const unsignedUrl = `${storageBaseUrl}${binary.id}/${binary.meta?.versionId}`; + const dateLessThan = new Date(); + dateLessThan.setHours(dateLessThan.getHours() + 1); + return getSignedUrl({ + url: unsignedUrl, + keyPairId: config.signingKeyId, + dateLessThan: dateLessThan.toISOString(), + privateKey: config.signingKey, + passphrase: config.signingKeyPassphrase, + }); + } + + private getKey(binary: Binary): string { + return 'binary/' + binary.id + '/' + binary.meta?.versionId; + } +} diff --git a/packages/server/src/config.test.ts b/packages/server/src/config.test.ts index 158816b8bc..05c383f522 100644 --- a/packages/server/src/config.test.ts +++ b/packages/server/src/config.test.ts @@ -1,43 +1,7 @@ -import { GetSecretValueCommand, SecretsManagerClient } from '@aws-sdk/client-secrets-manager'; -import { GetParametersByPathCommand, SSMClient } from '@aws-sdk/client-ssm'; -import { AwsClientStub, mockClient } from 'aws-sdk-client-mock'; -import 'aws-sdk-client-mock-jest'; import fs from 'fs'; import { getConfig, loadConfig } from './config'; describe('Config', () => { - let mockSSMClient: AwsClientStub; - let mockSecretsManagerClient: AwsClientStub; - - beforeEach(() => { - mockSSMClient = mockClient(SSMClient); - mockSecretsManagerClient = mockClient(SecretsManagerClient); - - mockSecretsManagerClient.on(GetSecretValueCommand).resolves({ - SecretString: JSON.stringify({ host: 'host', port: 123 }), - }); - - mockSSMClient.on(GetParametersByPathCommand).resolves({ - Parameters: [ - { Name: 'baseUrl', Value: 'https://www.example.com/' }, - { Name: 'database.ssl.require', Value: 'true' }, - { Name: 'database.ssl.rejectUnauthorized', Value: 'true' }, - { Name: 'database.ssl.ca', Value: 'DatabaseSslCa' }, - { Name: 'DatabaseSecrets', Value: 'DatabaseSecretsArn' }, - { Name: 'RedisSecrets', Value: 'RedisSecretsArn' }, - { Name: 'port', Value: '8080' }, - { Name: 'botCustomFunctionsEnabled', Value: 'true' }, - { Name: 'logAuditEvents', Value: 'true' }, - { Name: 'registerEnabled', Value: 'false' }, - ], - }); - }); - - afterEach(() => { - mockSSMClient.restore(); - mockSecretsManagerClient.restore(); - }); - test('Unrecognized config', async () => { await expect(loadConfig('unrecognized')).rejects.toThrow(); }); @@ -53,38 +17,6 @@ describe('Config', () => { expect(getConfig()).toBe(config); }); - test('Load AWS config', async () => { - const config = await loadConfig('aws:test'); - expect(config).toBeDefined(); - expect(config.baseUrl).toBeDefined(); - expect(config.port).toEqual(8080); - expect(config.botCustomFunctionsEnabled).toEqual(true); - expect(config.logAuditEvents).toEqual(true); - expect(config.registerEnabled).toEqual(false); - expect(config.database).toBeDefined(); - expect(config.database.ssl).toBeDefined(); - expect(config.database.ssl?.require).toEqual(true); - expect(config.database.ssl?.rejectUnauthorized).toEqual(true); - expect(config.database.ssl?.ca).toEqual('DatabaseSslCa'); - expect(getConfig()).toBe(config); - expect(mockSSMClient).toReceiveCommand(GetParametersByPathCommand); - }); - - test('Load region AWS config', async () => { - const config = await loadConfig('aws:ap-southeast-2:test'); - expect(config).toBeDefined(); - expect(config.baseUrl).toBeDefined(); - expect(config.port).toEqual(8080); - expect(getConfig()).toBe(config); - expect(mockSecretsManagerClient).toReceiveCommand(GetSecretValueCommand); - expect(mockSecretsManagerClient).toReceiveCommandWith(GetSecretValueCommand, { - SecretId: 'DatabaseSecretsArn', - }); - expect(mockSecretsManagerClient).toReceiveCommandWith(GetSecretValueCommand, { - SecretId: 'RedisSecretsArn', - }); - }); - test('Load env config', async () => { process.env.MEDPLUM_BASE_URL = 'http://localhost:3000'; process.env.MEDPLUM_PORT = '3000'; diff --git a/packages/server/src/config.ts b/packages/server/src/config.ts index 0b647f7229..c2a152eaa2 100644 --- a/packages/server/src/config.ts +++ b/packages/server/src/config.ts @@ -1,10 +1,9 @@ -import { GetSecretValueCommand, SecretsManagerClient } from '@aws-sdk/client-secrets-manager'; -import { GetParametersByPathCommand, Parameter, SSMClient } from '@aws-sdk/client-ssm'; import { splitN } from '@medplum/core'; import { KeepJobs } from 'bullmq'; import { mkdtempSync, readFileSync } from 'fs'; import { tmpdir } from 'os'; import { join, resolve } from 'path'; +import { loadAwsConfig } from './cloud/aws/config'; const DEFAULT_AWS_REGION = 'us-east-1'; @@ -28,6 +27,7 @@ export interface MedplumServerConfig { database: MedplumDatabaseConfig; databaseProxyEndpoint?: string; redis: MedplumRedisConfig; + emailProvider?: 'none' | 'awsses' | 'smtp'; smtp?: MedplumSmtpConfig; bullmq?: MedplumBullmqConfig; googleClientId?: string; @@ -180,6 +180,7 @@ export async function loadTestConfig(): Promise { config.redis.db = 7; // Select logical DB `7` so we don't collide with existing dev Redis cache. config.redis.password = process.env['REDIS_PASSWORD_DISABLED_IN_TESTS'] ? undefined : config.redis.password; config.approvedSenderEmails = 'no-reply@example.com'; + config.emailProvider = 'none'; return config; } @@ -235,97 +236,6 @@ async function loadFileConfig(path: string): Promise { return JSON.parse(readFileSync(resolve(__dirname, '../', path), { encoding: 'utf8' })); } -/** - * Loads configuration settings from AWS SSM Parameter Store. - * @param path - The AWS SSM Parameter Store path prefix. - * @returns The loaded configuration. - */ -async function loadAwsConfig(path: string): Promise { - let region = DEFAULT_AWS_REGION; - if (path.includes(':')) { - [region, path] = splitN(path, ':', 2); - } - - const client = new SSMClient({ region }); - const config: Record = {}; - const parameters = [] as Parameter[]; - let nextToken: string | undefined; - do { - const response = await client.send( - new GetParametersByPathCommand({ - Path: path, - NextToken: nextToken, - WithDecryption: true, - }) - ); - if (response.Parameters) { - parameters.push(...response.Parameters); - } - nextToken = response.NextToken; - } while (nextToken); - - // Load special AWS Secrets Manager secrets first - for (const param of parameters) { - const key = (param.Name as string).replace(path, ''); - const value = param.Value as string; - if (key === 'DatabaseSecrets') { - config['database'] = await loadAwsSecrets(region, value); - } else if (key === 'RedisSecrets') { - config['redis'] = await loadAwsSecrets(region, value); - } - } - - // Then load other parameters, which may override the secrets - for (const param of parameters) { - const key = (param.Name as string).replace(path, ''); - const value = param.Value as string; - setValue(config, key, value); - } - - return config as MedplumServerConfig; -} - -/** - * Returns the AWS Database Secret data as a JSON map. - * @param region - The AWS region. - * @param secretId - Secret ARN - * @returns The secret data as a JSON map. - */ -async function loadAwsSecrets(region: string, secretId: string): Promise | undefined> { - const client = new SecretsManagerClient({ region }); - const result = await client.send(new GetSecretValueCommand({ SecretId: secretId })); - - if (!result.SecretString) { - return undefined; - } - - return JSON.parse(result.SecretString); -} - -function setValue(config: MedplumDatabaseConfig, key: string, value: string): void { - const keySegments = key.split('.'); - let obj = config as Record; - - while (keySegments.length > 1) { - const segment = keySegments.shift() as string; - if (!obj[segment]) { - obj[segment] = {}; - } - obj = obj[segment] as Record; - } - - let parsedValue: any = value; - if (isIntegerConfig(key)) { - parsedValue = parseInt(value, 10); - } else if (isBooleanConfig(key)) { - parsedValue = value === 'true'; - } else if (isObjectConfig(key)) { - parsedValue = JSON.parse(value); - } - - obj[keySegments[0]] = parsedValue; -} - /** * Adds default values to the config. * @param config - The input config as loaded from the config file. @@ -348,6 +258,7 @@ function addDefaults(config: MedplumServerConfig): MedplumServerConfig { config.accurateCountThreshold = config.accurateCountThreshold ?? 1000000; config.defaultBotRuntimeVersion = config.defaultBotRuntimeVersion ?? 'awslambda'; config.defaultProjectFeatures = config.defaultProjectFeatures ?? []; + config.emailProvider = config.emailProvider || (config.smtp ? 'smtp' : 'awsses'); return config; } diff --git a/packages/server/src/email/email.test.ts b/packages/server/src/email/email.test.ts index 7d122fbe2f..b3ab339d40 100644 --- a/packages/server/src/email/email.test.ts +++ b/packages/server/src/email/email.test.ts @@ -21,6 +21,7 @@ describe('Email', () => { beforeAll(async () => { const config = await loadTestConfig(); + config.emailProvider = 'awsses'; config.storageBaseUrl = 'https://storage.example.com/'; await initAppServices(config); }); diff --git a/packages/server/src/email/email.ts b/packages/server/src/email/email.ts index b999b120be..33b7f40d6d 100644 --- a/packages/server/src/email/email.ts +++ b/packages/server/src/email/email.ts @@ -1,13 +1,12 @@ -import { SendEmailCommand, SESv2Client } from '@aws-sdk/client-sesv2'; -import { badRequest, normalizeErrorString, OperationOutcomeError } from '@medplum/core'; import { Binary } from '@medplum/fhirtypes'; import { createTransport } from 'nodemailer'; -import MailComposer from 'nodemailer/lib/mail-composer'; -import Mail, { Address } from 'nodemailer/lib/mailer'; +import Mail from 'nodemailer/lib/mailer'; +import { sendEmailViaSes } from '../cloud/aws/email'; import { getConfig, MedplumSmtpConfig } from '../config'; import { Repository } from '../fhir/repo'; import { getBinaryStorage } from '../fhir/storage'; import { globalLogger } from '../logger'; +import { getFromAddress } from './utils'; /** * Sends an email using the AWS SES service. @@ -19,7 +18,6 @@ import { globalLogger } from '../logger'; export async function sendEmail(repo: Repository, options: Mail.Options): Promise { const config = getConfig(); const fromAddress = getFromAddress(options); - const toAddresses = buildAddresses(options.to); options.from = fromAddress; options.sender = fromAddress; @@ -32,85 +30,15 @@ export async function sendEmail(repo: Repository, options: Mail.Options): Promis // "if set to true then fails with an error when a node tries to load content from a file" options.disableFileAccess = true; - globalLogger.info('Sending email', { to: toAddresses?.join(', '), subject: options.subject }); + globalLogger.info('Sending email', { to: options.to, subject: options.subject }); if (config.smtp) { await sendEmailViaSmpt(config.smtp, options); - } else { + } else if (config.emailProvider === 'awsses') { await sendEmailViaSes(options); } } -/** - * Returns the from address to use. - * If the user specified a from address, it must be an approved sender. - * Otherwise uses the support email address. - * @param options - The user specified nodemailer options. - * @returns The from address to use. - */ -function getFromAddress(options: Mail.Options): string { - const config = getConfig(); - - if (options.from) { - const fromAddress = addressToString(options.from); - if (fromAddress && config.approvedSenderEmails?.split(',')?.includes(fromAddress)) { - return fromAddress; - } - } - - return config.supportEmail; -} - -/** - * Converts nodemailer addresses to an array of strings. - * @param input - nodemailer address input. - * @returns Array of string addresses. - */ -function buildAddresses(input: string | Address | (string | Address)[] | undefined): string[] | undefined { - if (!input) { - return undefined; - } - if (Array.isArray(input)) { - return input.map(addressToString) as string[]; - } - return [addressToString(input) as string]; -} - -/** - * Converts a nodemailer address to a string. - * @param address - nodemailer address input. - * @returns String address. - */ -function addressToString(address: Address | string | undefined): string | undefined { - if (address) { - if (typeof address === 'string') { - return address; - } - if (typeof address === 'object' && 'address' in address) { - return address.address; - } - } - return undefined; -} - -/** - * Builds a raw email message using nodemailer MailComposer. - * @param options - The nodemailer options. - * @returns The raw email message. - */ -function buildRawMessage(options: Mail.Options): Promise { - const msg = new MailComposer(options); - return new Promise((resolve, reject) => { - msg.compile().build((err, message) => { - if (err) { - reject(err); - return; - } - resolve(message); - }); - }); -} - /** * Validates an array of nodemailer attachments. * @param repo - The user repository. @@ -170,39 +98,3 @@ async function sendEmailViaSmpt(smtpConfig: MedplumSmtpConfig, options: Mail.Opt }); await transport.sendMail(options); } - -/** - * Sends an email via AWS SES. - * @param options - The nodemailer options. - */ -async function sendEmailViaSes(options: Mail.Options): Promise { - const config = getConfig(); - const fromAddress = addressToString(options.from); - const toAddresses = buildAddresses(options.to); - const ccAddresses = buildAddresses(options.cc); - const bccAddresses = buildAddresses(options.bcc); - - let msg: Uint8Array; - try { - msg = await buildRawMessage(options); - } catch (err) { - throw new OperationOutcomeError(badRequest('Invalid email options: ' + normalizeErrorString(err)), err); - } - - const sesClient = new SESv2Client({ region: config.awsRegion }); - await sesClient.send( - new SendEmailCommand({ - FromEmailAddress: fromAddress, - Destination: { - ToAddresses: toAddresses, - CcAddresses: ccAddresses, - BccAddresses: bccAddresses, - }, - Content: { - Raw: { - Data: msg, - }, - }, - }) - ); -} diff --git a/packages/server/src/email/routes.test.ts b/packages/server/src/email/routes.test.ts index b0f89c3398..e4192fa3cf 100644 --- a/packages/server/src/email/routes.test.ts +++ b/packages/server/src/email/routes.test.ts @@ -14,6 +14,7 @@ const app = express(); describe('Email API Routes', () => { beforeAll(async () => { const config = await loadTestConfig(); + config.emailProvider = 'awsses'; await initApp(app, config); }); diff --git a/packages/server/src/email/utils.ts b/packages/server/src/email/utils.ts new file mode 100644 index 0000000000..df5a96b5b2 --- /dev/null +++ b/packages/server/src/email/utils.ts @@ -0,0 +1,73 @@ +import MailComposer from 'nodemailer/lib/mail-composer'; +import Mail, { Address } from 'nodemailer/lib/mailer'; +import { getConfig } from '../config'; + +/** + * Returns the from address to use. + * If the user specified a from address, it must be an approved sender. + * Otherwise uses the support email address. + * @param options - The user specified nodemailer options. + * @returns The from address to use. + */ +export function getFromAddress(options: Mail.Options): string { + const config = getConfig(); + + if (options.from) { + const fromAddress = addressToString(options.from); + if (fromAddress && config.approvedSenderEmails?.split(',')?.includes(fromAddress)) { + return fromAddress; + } + } + + return config.supportEmail; +} + +/** + * Converts nodemailer addresses to an array of strings. + * @param input - nodemailer address input. + * @returns Array of string addresses. + */ +export function buildAddresses(input: string | Address | (string | Address)[] | undefined): string[] | undefined { + if (!input) { + return undefined; + } + if (Array.isArray(input)) { + return input.map(addressToString) as string[]; + } + return [addressToString(input) as string]; +} + +/** + * Converts a nodemailer address to a string. + * @param address - nodemailer address input. + * @returns String address. + */ +export function addressToString(address: Address | string | undefined): string | undefined { + if (address) { + if (typeof address === 'string') { + return address; + } + if (typeof address === 'object' && 'address' in address) { + return address.address; + } + } + return undefined; +} + +/** + * Builds a raw email message using nodemailer MailComposer. + * @param options - The nodemailer options. + * @returns The raw email message. + */ +export function buildRawMessage(options: Mail.Options): Promise { + const msg = new MailComposer(options); + return new Promise((resolve, reject) => { + msg.compile().build((err, message) => { + if (err) { + reject(err); + return; + } + resolve(message); + }); + }); +} diff --git a/packages/server/src/fhir/binary.ts b/packages/server/src/fhir/binary.ts index 203f0b077b..ad3f6ffdc6 100644 --- a/packages/server/src/fhir/binary.ts +++ b/packages/server/src/fhir/binary.ts @@ -8,7 +8,6 @@ import { getAuthenticatedContext, getLogger } from '../context'; import { authenticateRequest } from '../oauth/middleware'; import { sendOutcome } from './outcomes'; import { sendResponse, sendResponseHeaders } from './response'; -import { getPresignedUrl } from './signer'; import { BinarySource, getBinaryStorage } from './storage'; export const binaryRouter = Router().use(authenticateRequest); @@ -119,7 +118,7 @@ async function handleBinaryWriteRequest(req: Request, res: Response): Promise; describe('Deploy', () => { beforeAll(async () => { @@ -34,142 +22,6 @@ describe('Deploy', () => { await shutdownApp(); }); - beforeEach(() => { - let created = false; - - mockLambdaClient = mockClient(LambdaClient); - - mockLambdaClient.on(CreateFunctionCommand).callsFake(({ FunctionName }) => { - created = true; - - return { - Configuration: { - FunctionName, - }, - }; - }); - - mockLambdaClient.on(GetFunctionCommand).callsFake(({ FunctionName }) => { - if (created) { - return { - Configuration: { - FunctionName, - }, - }; - } - - return { - Configuration: {}, - }; - }); - - mockLambdaClient.on(GetFunctionConfigurationCommand).callsFake(({ FunctionName }) => { - return { - FunctionName, - Runtime: 'nodejs18.x', - Handler: 'index.handler', - State: 'Active', - Layers: [ - { - Arn: 'arn:aws:lambda:us-east-1:123456789012:layer:test-layer:1', - }, - ], - }; - }); - - mockLambdaClient.on(ListLayerVersionsCommand).resolves({ - LayerVersions: [ - { - LayerVersionArn: 'arn:aws:lambda:us-east-1:123456789012:layer:test-layer:1', - }, - ], - }); - - mockLambdaClient.on(UpdateFunctionCodeCommand).callsFake(({ FunctionName }) => ({ - Configuration: { - FunctionName, - }, - })); - }); - - afterEach(() => { - mockLambdaClient.restore(); - }); - - test('Happy path', async () => { - // Step 1: Create a bot - const res1 = await request(app) - .post(`/fhir/R4/Bot`) - .set('Content-Type', ContentType.FHIR_JSON) - .set('Authorization', 'Bearer ' + accessToken) - .send({ - resourceType: 'Bot', - name: 'Test Bot', - runtimeVersion: 'awslambda', - code: ` - export async function handler() { - console.log('input', input); - return input; - } - `, - }); - expect(res1.status).toBe(201); - - const bot = res1.body as Bot; - const name = `medplum-bot-lambda-${bot.id}`; - - // Step 2: Deploy the bot - const res2 = await request(app) - .post(`/fhir/R4/Bot/${bot.id}/$deploy`) - .set('Content-Type', ContentType.FHIR_JSON) - .set('Authorization', 'Bearer ' + accessToken) - .send({ - code: ` - export async function handler() { - console.log('input', input); - return input; - } - `, - }); - expect(res2.status).toBe(200); - - expect(mockLambdaClient).toHaveReceivedCommandTimes(GetFunctionCommand, 1); - expect(mockLambdaClient).toHaveReceivedCommandTimes(ListLayerVersionsCommand, 1); - expect(mockLambdaClient).toHaveReceivedCommandTimes(CreateFunctionCommand, 1); - expect(mockLambdaClient).toHaveReceivedCommandWith(GetFunctionCommand, { - FunctionName: name, - }); - expect(mockLambdaClient).toHaveReceivedCommandWith(CreateFunctionCommand, { - FunctionName: name, - }); - mockLambdaClient.resetHistory(); - - // Step 3: Deploy again to trigger the update path - const res3 = await request(app) - .post(`/fhir/R4/Bot/${bot.id}/$deploy`) - .set('Content-Type', ContentType.FHIR_JSON) - .set('Authorization', 'Bearer ' + accessToken) - .send({ - code: ` - export async function handler() { - console.log('input', input); - return input; - } - `, - filename: 'updated.js', - }); - expect(res3.status).toBe(200); - - expect(mockLambdaClient).toHaveReceivedCommandTimes(GetFunctionCommand, 1); - expect(mockLambdaClient).toHaveReceivedCommandTimes(ListLayerVersionsCommand, 1); - expect(mockLambdaClient).toHaveReceivedCommandTimes(GetFunctionConfigurationCommand, 1); - expect(mockLambdaClient).toHaveReceivedCommandTimes(UpdateFunctionConfigurationCommand, 0); - expect(mockLambdaClient).toHaveReceivedCommandTimes(UpdateFunctionCodeCommand, 1); - expect(mockLambdaClient).toHaveReceivedCommandWith(GetFunctionCommand, { - FunctionName: name, - }); - }); - test('Deploy bot with missing code', async () => { // Step 1: Create a bot const res1 = await request(app) @@ -201,91 +53,6 @@ describe('Deploy', () => { expect(res2.body.issue[0].details.text).toEqual('Missing code'); }); - test('Deploy bot with lambda layer update', async () => { - // When deploying a bot, we check if we need to update the bot configuration. - // This test verifies that we correctly update the bot configuration when the lambda layer changes. - // Step 1: Create a bot - const res1 = await request(app) - .post(`/fhir/R4/Bot`) - .set('Content-Type', ContentType.FHIR_JSON) - .set('Authorization', 'Bearer ' + accessToken) - .send({ - resourceType: 'Bot', - name: 'Test Bot', - runtimeVersion: 'awslambda', - code: ` - export async function handler() { - console.log('input', input); - return input; - } - `, - }); - expect(res1.status).toBe(201); - - const bot = res1.body as Bot; - const name = `medplum-bot-lambda-${bot.id}`; - - // Step 2: Deploy the bot - const res2 = await request(app) - .post(`/fhir/R4/Bot/${bot.id}/$deploy`) - .set('Content-Type', ContentType.FHIR_JSON) - .set('Authorization', 'Bearer ' + accessToken) - .send({ - code: ` - export async function handler() { - console.log('input', input); - return input; - } - `, - }); - expect(res2.status).toBe(200); - - expect(mockLambdaClient).toHaveReceivedCommandTimes(GetFunctionCommand, 1); - expect(mockLambdaClient).toHaveReceivedCommandTimes(ListLayerVersionsCommand, 1); - expect(mockLambdaClient).toHaveReceivedCommandTimes(CreateFunctionCommand, 1); - expect(mockLambdaClient).toHaveReceivedCommandWith(GetFunctionCommand, { - FunctionName: name, - }); - expect(mockLambdaClient).toHaveReceivedCommandWith(CreateFunctionCommand, { - FunctionName: name, - }); - mockLambdaClient.resetHistory(); - - // Step 3: Simulate releasing a new version of the lambda layer - mockLambdaClient.on(ListLayerVersionsCommand).resolves({ - LayerVersions: [ - { - LayerVersionArn: 'new-layer-version-arn', - }, - ], - }); - - // Step 4: Deploy again to trigger the update path - const res3 = await request(app) - .post(`/fhir/R4/Bot/${bot.id}/$deploy`) - .set('Content-Type', ContentType.FHIR_JSON) - .set('Authorization', 'Bearer ' + accessToken) - .send({ - code: ` - export async function handler() { - console.log('input', input); - return input; - } - `, - filename: 'updated.js', - }); - expect(res3.status).toBe(200); - - expect(mockLambdaClient).toHaveReceivedCommandTimes(GetFunctionCommand, 1); - expect(mockLambdaClient).toHaveReceivedCommandTimes(ListLayerVersionsCommand, 1); - expect(mockLambdaClient).toHaveReceivedCommandTimes(GetFunctionConfigurationCommand, 2); - expect(mockLambdaClient).toHaveReceivedCommandTimes(UpdateFunctionConfigurationCommand, 1); - expect(mockLambdaClient).toHaveReceivedCommandTimes(UpdateFunctionCodeCommand, 1); - expect(mockLambdaClient).toHaveReceivedCommandWith(GetFunctionCommand, { - FunctionName: name, - }); - }); - test('Bots not enabled', async () => { // First, Alice creates a project const { project, accessToken } = await withTestContext(() => diff --git a/packages/server/src/fhir/operations/deploy.ts b/packages/server/src/fhir/operations/deploy.ts index 6b8f447587..b065908cd9 100644 --- a/packages/server/src/fhir/operations/deploy.ts +++ b/packages/server/src/fhir/operations/deploy.ts @@ -1,104 +1,13 @@ -import { - CreateFunctionCommand, - GetFunctionCommand, - GetFunctionConfigurationCommand, - GetFunctionConfigurationCommandOutput, - LambdaClient, - ListLayerVersionsCommand, - PackageType, - UpdateFunctionCodeCommand, - UpdateFunctionConfigurationCommand, -} from '@aws-sdk/client-lambda'; -import { ContentType, allOk, badRequest, getReferenceString, normalizeOperationOutcome, sleep } from '@medplum/core'; +import { ContentType, allOk, badRequest, getReferenceString, normalizeOperationOutcome } from '@medplum/core'; import { FhirRequest, FhirResponse } from '@medplum/fhir-router'; import { Binary, Bot } from '@medplum/fhirtypes'; -import { ConfiguredRetryStrategy } from '@smithy/util-retry'; -import JSZip from 'jszip'; import { Readable } from 'stream'; -import { getConfig } from '../../config'; -import { getAuthenticatedContext, getRequestContext } from '../../context'; +import { deployLambda } from '../../cloud/aws/deploy'; +import { getAuthenticatedContext } from '../../context'; import { getSystemRepo } from '../repo'; import { getBinaryStorage } from '../storage'; import { isBotEnabled } from './execute'; -const LAMBDA_RUNTIME = 'nodejs18.x'; - -const LAMBDA_HANDLER = 'index.handler'; - -const LAMBDA_MEMORY = 1024; - -const WRAPPER_CODE = `const { ContentType, Hl7Message, MedplumClient } = require("@medplum/core"); -const fetch = require("node-fetch"); -const PdfPrinter = require("pdfmake"); -const userCode = require("./user.js"); - -exports.handler = async (event, context) => { - const { bot, baseUrl, accessToken, contentType, secrets, traceId } = event; - const medplum = new MedplumClient({ - baseUrl, - fetch: function(url, options = {}) { - options.headers ||= {}; - options.headers['X-Trace-Id'] = traceId; - options.headers['traceparent'] = traceId; - return fetch(url, options); - }, - createPdf, - }); - medplum.setAccessToken(accessToken); - try { - let input = event.input; - if (contentType === ContentType.HL7_V2 && input) { - input = Hl7Message.parse(input); - } - let result = await userCode.handler(medplum, { bot, input, contentType, secrets, traceId }); - if (contentType === ContentType.HL7_V2 && result) { - result = result.toString(); - } - return result; - } catch (err) { - if (err instanceof Error) { - console.log("Unhandled error: " + err.message + "\\n" + err.stack); - } else if (typeof err === "object") { - console.log("Unhandled error: " + JSON.stringify(err, undefined, 2)); - } else { - console.log("Unhandled error: " + err); - } - throw err; - } -}; - -function createPdf(docDefinition, tableLayouts, fonts) { - if (!fonts) { - fonts = { - Helvetica: { - normal: 'Helvetica', - bold: 'Helvetica-Bold', - italics: 'Helvetica-Oblique', - bolditalics: 'Helvetica-BoldOblique', - }, - Roboto: { - normal: '/opt/fonts/Roboto/Roboto-Regular.ttf', - bold: '/opt/fonts/Roboto/Roboto-Medium.ttf', - italics: '/opt/fonts/Roboto/Roboto-Italic.ttf', - bolditalics: '/opt/fonts/Roboto/Roboto-MediumItalic.ttf' - }, - Avenir: { - normal: '/opt/fonts/Avenir/Avenir.ttf' - } - }; - } - return new Promise((resolve, reject) => { - const printer = new PdfPrinter(fonts); - const pdfDoc = printer.createPdfKitDocument(docDefinition, { tableLayouts }); - const chunks = []; - pdfDoc.on('data', (chunk) => chunks.push(chunk)); - pdfDoc.on('end', () => resolve(Buffer.concat(chunks))); - pdfDoc.on('error', reject); - pdfDoc.end(); - }); -} -`; - export async function deployHandler(req: FhirRequest): Promise { const ctx = getAuthenticatedContext(); const { id } = req.params; @@ -151,168 +60,3 @@ export async function deployHandler(req: FhirRequest): Promise { return [normalizeOperationOutcome(err)]; } } - -async function deployLambda(bot: Bot, code: string): Promise { - const ctx = getRequestContext(); - - // Create a new AWS Lambda client - // Use a custom retry strategy to avoid throttling errors - // This is especially important when updating lambdas which also - // involve upgrading the layer version. - const client = new LambdaClient({ - region: getConfig().awsRegion, - retryStrategy: new ConfiguredRetryStrategy( - 5, // max attempts - (attempt: number) => 500 * 2 ** attempt // Exponential backoff - ), - }); - - const name = `medplum-bot-lambda-${bot.id}`; - ctx.logger.info('Deploying lambda function for bot', { name }); - const zipFile = await createZipFile(code); - ctx.logger.debug('Lambda function zip size', { bytes: zipFile.byteLength }); - - const exists = await lambdaExists(client, name); - if (!exists) { - await createLambda(client, name, zipFile); - } else { - await updateLambda(client, name, zipFile); - } -} - -async function createZipFile(code: string): Promise { - const zip = new JSZip(); - zip.file('user.js', code); - zip.file('index.js', WRAPPER_CODE); - return zip.generateAsync({ type: 'uint8array' }); -} - -/** - * Returns true if the AWS Lambda exists for the bot name. - * @param client - The AWS Lambda client. - * @param name - The bot name. - * @returns True if the bot exists. - */ -async function lambdaExists(client: LambdaClient, name: string): Promise { - try { - const command = new GetFunctionCommand({ FunctionName: name }); - const response = await client.send(command); - return response.Configuration?.FunctionName === name; - } catch (err) { - return false; - } -} - -/** - * Creates a new AWS Lambda for the bot name. - * @param client - The AWS Lambda client. - * @param name - The bot name. - * @param zipFile - The zip file with the bot code. - */ -async function createLambda(client: LambdaClient, name: string, zipFile: Uint8Array): Promise { - const layerVersion = await getLayerVersion(client); - - await client.send( - new CreateFunctionCommand({ - FunctionName: name, - Role: getConfig().botLambdaRoleArn, - Runtime: LAMBDA_RUNTIME, - Handler: LAMBDA_HANDLER, - MemorySize: LAMBDA_MEMORY, - PackageType: PackageType.Zip, - Layers: [layerVersion], - Code: { - ZipFile: zipFile, - }, - Publish: true, - Timeout: 10, // seconds - }) - ); -} - -/** - * Updates an existing AWS Lambda for the bot name. - * @param client - The AWS Lambda client. - * @param name - The bot name. - * @param zipFile - The zip file with the bot code. - */ -async function updateLambda(client: LambdaClient, name: string, zipFile: Uint8Array): Promise { - // First, make sure the lambda configuration is up to date - await updateLambdaConfig(client, name); - - // Then update the code - await client.send( - new UpdateFunctionCodeCommand({ - FunctionName: name, - ZipFile: zipFile, - Publish: true, - }) - ); -} - -/** - * Updates the lambda configuration. - * @param client - The AWS Lambda client. - * @param name - The lambda name. - */ -async function updateLambdaConfig(client: LambdaClient, name: string): Promise { - const layerVersion = await getLayerVersion(client); - const functionConfig = await getLambdaConfig(client, name); - if ( - functionConfig.Runtime === LAMBDA_RUNTIME && - functionConfig.Handler === LAMBDA_HANDLER && - functionConfig.Layers?.[0].Arn === layerVersion - ) { - // Everything is up-to-date - return; - } - - // Need to update - await client.send( - new UpdateFunctionConfigurationCommand({ - FunctionName: name, - Role: getConfig().botLambdaRoleArn, - Runtime: LAMBDA_RUNTIME, - Handler: LAMBDA_HANDLER, - Layers: [layerVersion], - }) - ); - - // Wait for the update to complete before returning - // Wait up to 5 seconds - // See: https://github.com/aws/aws-toolkit-visual-studio/issues/197 - // See: https://aws.amazon.com/blogs/compute/coming-soon-expansion-of-aws-lambda-states-to-all-functions/ - for (let i = 0; i < 5; i++) { - const config = await getLambdaConfig(client, name); - // Valid Values: Pending | Active | Inactive | Failed - // See: https://docs.aws.amazon.com/lambda/latest/dg/API_GetFunctionConfiguration.html - if (config.State === 'Active') { - return; - } - await sleep(1000); - } -} - -async function getLambdaConfig(client: LambdaClient, name: string): Promise { - return client.send( - new GetFunctionConfigurationCommand({ - FunctionName: name, - }) - ); -} - -/** - * Returns the latest layer version for the Medplum bot layer. - * The first result is the latest version. - * See: https://stackoverflow.com/a/55752188 - * @param client - The AWS Lambda client. - * @returns The most recent layer version ARN. - */ -async function getLayerVersion(client: LambdaClient): Promise { - const command = new ListLayerVersionsCommand({ - LayerName: getConfig().botLambdaLayerName, - MaxItems: 1, - }); - const response = await client.send(command); - return response.LayerVersions?.[0].LayerVersionArn as string; -} diff --git a/packages/server/src/fhir/operations/execute.test.ts b/packages/server/src/fhir/operations/execute.test.ts index db562824ae..977128cf7c 100644 --- a/packages/server/src/fhir/operations/execute.test.ts +++ b/packages/server/src/fhir/operations/execute.test.ts @@ -1,7 +1,5 @@ -import { InvokeCommand, LambdaClient, ListLayerVersionsCommand } from '@aws-sdk/client-lambda'; import { ContentType } from '@medplum/core'; import { Bot } from '@medplum/fhirtypes'; -import { AwsClientStub, mockClient } from 'aws-sdk-client-mock'; import { randomUUID } from 'crypto'; import express from 'express'; import request from 'supertest'; @@ -10,49 +8,19 @@ import { registerNew } from '../../auth/register'; import { getConfig, loadTestConfig } from '../../config'; import { initTestAuth, withTestContext } from '../../test.setup'; import { getBinaryStorage } from '../storage'; -import { getLambdaFunctionName } from './execute'; const app = express(); let accessToken: string; let bot: Bot; describe('Execute', () => { - let mockLambdaClient: AwsClientStub; - - beforeEach(() => { - mockLambdaClient = mockClient(LambdaClient); - - mockLambdaClient.on(ListLayerVersionsCommand).resolves({ - LayerVersions: [ - { - LayerVersionArn: 'xyz', - }, - ], - }); - - mockLambdaClient.on(InvokeCommand).callsFake(({ Payload }) => { - const decoder = new TextDecoder(); - const event = JSON.parse(decoder.decode(Payload)); - const output = JSON.stringify(event.input); - const encoder = new TextEncoder(); - - return { - LogResult: `U1RBUlQgUmVxdWVzdElkOiAxNDZmY2ZjZi1jMzJiLTQzZjUtODJhNi1lZTBmMzEzMmQ4NzMgVmVyc2lvbjogJExBVEVTVAoyMDIyLTA1LTMwVDE2OjEyOjIyLjY4NVoJMTQ2ZmNmY2YtYzMyYi00M2Y1LTgyYTYtZWUwZjMxMzJkODczCUlORk8gdGVzdApFTkQgUmVxdWVzdElkOiAxNDZmY2ZjZi1jMzJiLTQzZjUtODJhNi1lZTBmMzEzMmQ4NzMKUkVQT1JUIFJlcXVlc3RJZDogMTQ2ZmNmY2YtYzMyYi00M2Y1LTgyYTYtZWUwZjMxMzJkODcz`, - Payload: encoder.encode(output), - }; - }); - }); - - afterEach(() => { - mockLambdaClient.restore(); - }); - beforeAll(async () => { const config = await loadTestConfig(); + config.vmContextBotsEnabled = true; await initApp(app, config); accessToken = await initTestAuth(); - const res = await request(app) + const res1 = await request(app) .post(`/fhir/R4/Bot`) .set('Content-Type', ContentType.FHIR_JSON) .set('Authorization', 'Bearer ' + accessToken) @@ -60,7 +28,7 @@ describe('Execute', () => { resourceType: 'Bot', identifier: [{ system: 'https://example.com/bot', value: randomUUID() }], name: 'Test Bot', - runtimeVersion: 'awslambda', + runtimeVersion: 'vmcontext', code: ` export async function handler(medplum, event) { console.log('input', event.input); @@ -68,8 +36,22 @@ describe('Execute', () => { } `, }); - expect(res.status).toBe(201); - bot = res.body as Bot; + expect(res1.status).toBe(201); + bot = res1.body as Bot; + + const res2 = await request(app) + .post(`/fhir/R4/Bot/${bot.id}/$deploy`) + .set('Content-Type', ContentType.FHIR_JSON) + .set('Authorization', 'Bearer ' + accessToken) + .send({ + code: ` + exports.handler = async function (medplum, event) { + console.log('input', event.input); + return event.input; + }; + `, + }); + expect(res2.status).toBe(200); }); afterAll(async () => { @@ -237,64 +219,6 @@ describe('Execute', () => { expect(res3.body.issue[0].details.text).toEqual('Bots not enabled'); }); - test('Get function name', async () => { - const config = getConfig(); - const normalBot: Bot = { resourceType: 'Bot', id: '123' }; - const customBot: Bot = { - resourceType: 'Bot', - id: '456', - identifier: [{ system: 'https://medplum.com/bot-external-function-id', value: 'custom' }], - }; - - expect(getLambdaFunctionName(normalBot)).toEqual('medplum-bot-lambda-123'); - expect(getLambdaFunctionName(customBot)).toEqual('medplum-bot-lambda-456'); - - // Temporarily enable custom bot support - config.botCustomFunctionsEnabled = true; - expect(getLambdaFunctionName(normalBot)).toEqual('medplum-bot-lambda-123'); - expect(getLambdaFunctionName(customBot)).toEqual('custom'); - config.botCustomFunctionsEnabled = false; - }); - - test('Execute by identifier', async () => { - const res = await request(app) - .post(`/fhir/R4/Bot/$execute?identifier=${bot.identifier?.[0]?.system}|${bot.identifier?.[0]?.value}`) - .set('Content-Type', ContentType.TEXT) - .set('Authorization', 'Bearer ' + accessToken) - .send('input'); - expect(res.status).toBe(200); - expect(res.headers['content-type']).toBe('text/plain; charset=utf-8'); - expect(res.text).toEqual('input'); - }); - - test('Missing parameters', async () => { - const res = await request(app) - .post(`/fhir/R4/Bot/$execute`) - .set('Content-Type', ContentType.TEXT) - .set('Authorization', 'Bearer ' + accessToken) - .send('input'); - expect(res.status).toBe(400); - expect(res.body.issue[0].details.text).toEqual('Must specify bot ID or identifier.'); - }); - - test('GET request with query params', async () => { - const res = await request(app) - .get(`/fhir/R4/Bot/${bot.id}/$execute?foo=bar`) - .set('Authorization', 'Bearer ' + accessToken); - expect(res.status).toBe(200); - expect(res.body.foo).toBe('bar'); - }); - - test('POST request with extra path', async () => { - const res = await request(app) - .post(`/fhir/R4/Bot/${bot.id}/$execute/RequestGroup`) - .set('Authorization', 'Bearer ' + accessToken) - .set('Content-Type', ContentType.FHIR_JSON) - .send({ foo: 'bar' }); - expect(res.status).toBe(200); - expect(res.body.foo).toBe('bar'); - }); - test('VM context bot success', async () => { // Temporarily enable VM context bots getConfig().vmContextBotsEnabled = true; diff --git a/packages/server/src/fhir/operations/execute.ts b/packages/server/src/fhir/operations/execute.ts index cbe9069ac7..2f4fd8c079 100644 --- a/packages/server/src/fhir/operations/execute.ts +++ b/packages/server/src/fhir/operations/execute.ts @@ -1,4 +1,3 @@ -import { InvokeCommand, LambdaClient } from '@aws-sdk/client-lambda'; import { ContentType, Hl7Message, @@ -7,7 +6,6 @@ import { allOk, badRequest, createReference, - getIdentifier, normalizeErrorString, resolveId, } from '@medplum/core'; @@ -30,8 +28,8 @@ import fetch from 'node-fetch'; import { randomUUID } from 'node:crypto'; import { Readable } from 'node:stream'; import vm from 'node:vm'; -import { TextDecoder, TextEncoder } from 'util'; import { asyncWrap } from '../../async'; +import { runInLambda } from '../../cloud/aws/execute'; import { getConfig } from '../../config'; import { buildTracingExtension, getAuthenticatedContext, getLogger } from '../../context'; import { generateAccessToken } from '../../oauth/keys'; @@ -59,6 +57,11 @@ export interface BotExecutionRequest { readonly traceId?: string; } +export interface BotExecutionContext extends BotExecutionRequest { + readonly accessToken: string; + readonly secrets: Record; +} + export interface BotExecutionResult { readonly success: boolean; readonly logResult: string; @@ -161,7 +164,7 @@ async function getBotForRequest(req: Request): Promise { * @returns The bot execution result. */ export async function executeBot(request: BotExecutionRequest): Promise { - const { bot } = request; + const { bot, runAs } = request; const startTime = request.requestTime ?? new Date().toISOString(); let result: BotExecutionResult; @@ -172,10 +175,16 @@ export async function executeBot(request: BotExecutionRequest): Promise { - const { bot, runAs, input, contentType, traceId } = request; - const config = getConfig(); - const accessToken = await getBotAccessToken(runAs); - const secrets = await getBotSecrets(bot); - - const client = new LambdaClient({ region: config.awsRegion }); - const name = getLambdaFunctionName(bot); - const payload = { - bot: createReference(bot), - baseUrl: config.baseUrl, - accessToken, - input: input instanceof Hl7Message ? input.toString() : input, - contentType, - secrets, - traceId, - }; - - // Build the command - const encoder = new TextEncoder(); - const command = new InvokeCommand({ - FunctionName: name, - InvocationType: 'RequestResponse', - LogType: 'Tail', - Payload: encoder.encode(JSON.stringify(payload)), - }); - - // Execute the command - try { - const response = await client.send(command); - const responseStr = response.Payload ? new TextDecoder().decode(response.Payload) : undefined; - - // The response from AWS Lambda is always JSON, even if the function returns a string - // Therefore we always use JSON.parse to get the return value - // See: https://stackoverflow.com/a/49951946/2051724 - const returnValue = responseStr ? JSON.parse(responseStr) : undefined; - - return { - success: !response.FunctionError, - logResult: parseLambdaLog(response.LogResult as string), - returnValue, - }; - } catch (err) { - return { - success: false, - logResult: normalizeErrorString(err), - }; - } -} - -/** - * Returns the AWS Lambda function name for the given bot. - * By default, the function name is based on the bot ID. - * If the bot has a custom function, and the server allows it, then that is used instead. - * @param bot - The Bot resource. - * @returns The AWS Lambda function name. - */ -export function getLambdaFunctionName(bot: Bot): string { - if (getConfig().botCustomFunctionsEnabled) { - const customFunction = getIdentifier(bot, 'https://medplum.com/bot-external-function-id'); - if (customFunction) { - return customFunction; - } - } - - // By default, use the bot ID as the Lambda function name - return `medplum-bot-lambda-${bot.id}`; -} - -/** - * Parses the AWS Lambda log result. - * - * The raw logs include markup metadata such as timestamps and billing information. - * - * We only want to include the actual log contents in the AuditEvent, - * so we attempt to scrub away all of that extra metadata. - * - * See: https://docs.aws.amazon.com/lambda/latest/dg/nodejs-logging.html - * @param logResult - The raw log result from the AWS lambda event. - * @returns The parsed log result. - */ -function parseLambdaLog(logResult: string): string { - const logBuffer = Buffer.from(logResult, 'base64'); - const log = logBuffer.toString('ascii'); - const lines = log.split('\n'); - const result = []; - for (const line of lines) { - if (line.startsWith('START RequestId: ')) { - // Ignore start line - continue; - } - if (line.startsWith('END RequestId: ') || line.startsWith('REPORT RequestId: ')) { - // Stop at end lines - break; - } - result.push(line); - } - return result.join('\n').trim(); -} - /** * Executes a Bot on the server in a separate Node.js VM. * @param request - The bot request. * @returns The bot execution result. */ -async function runInVmContext(request: BotExecutionRequest): Promise { - const { bot, runAs, input, contentType, traceId } = request; +async function runInVmContext(request: BotExecutionContext): Promise { + const { bot, input, contentType, traceId } = request; const config = getConfig(); if (!config.vmContextBotsEnabled) { @@ -405,9 +309,6 @@ async function runInVmContext(request: BotExecutionRequest): Promise({ reference: codeUrl } as Reference); const stream = await getBinaryStorage().readBinary(binary); const code = await readStreamToString(stream); - - const accessToken = await getBotAccessToken(runAs); - const secrets = await getBotSecrets(bot); const botConsole = new MockConsole(); const sandbox = { @@ -420,10 +321,10 @@ async function runInVmContext(request: BotExecutionRequest): Promise { - let mockS3Client: AwsClientStub; - beforeAll(async () => { await loadTestConfig(); }); - beforeEach(() => { - mockS3Client = mockClient(S3Client); - }); - - afterEach(() => { - mockS3Client.restore(); - }); - test('Undefined binary storage', () => { initBinaryStorage('binary'); expect(() => getBinaryStorage()).toThrow(); @@ -60,118 +46,6 @@ describe('Storage', () => { // Verify that the file matches the expected contents const content = await streamToString(stream); expect(content).toEqual('foo'); - - // Make sure we didn't touch S3 at all - expect(mockS3Client.send.callCount).toBe(0); - expect(mockS3Client).not.toHaveReceivedCommand(PutObjectCommand); - expect(mockS3Client).not.toHaveReceivedCommand(GetObjectCommand); - }); - - test('S3 storage', async () => { - initBinaryStorage('s3:foo'); - - const storage = getBinaryStorage(); - expect(storage).toBeDefined(); - - // Write a file - const binary = { - resourceType: 'Binary', - id: '123', - meta: { - versionId: '456', - }, - } as Binary; - const req = new Readable(); - req.push('foo'); - req.push(null); - (req as any).headers = {}; - - const sdkStream = sdkStreamMixin(req); - mockS3Client.on(GetObjectCommand).resolves({ Body: sdkStream }); - - await storage.writeBinary(binary, 'test.txt', ContentType.TEXT, req as Request); - - expect(mockS3Client.send.callCount).toBe(1); - expect(mockS3Client).toReceiveCommandWith(PutObjectCommand, { - Bucket: 'foo', - Key: 'binary/123/456', - ContentType: ContentType.TEXT, - }); - - // Read a file - const stream = await storage.readBinary(binary); - expect(stream).toBeDefined(); - expect(mockS3Client).toHaveReceivedCommand(GetObjectCommand); - }); - - test('Missing metadata', async () => { - initBinaryStorage('s3:foo'); - - const storage = getBinaryStorage(); - expect(storage).toBeDefined(); - - // Write a file - const binary = { - resourceType: 'Binary', - id: '123', - meta: { - versionId: '456', - }, - } as Binary; - const req = new Readable(); - req.push('foo'); - req.push(null); - (req as any).headers = {}; - - const sdkStream = sdkStreamMixin(req); - mockS3Client.on(GetObjectCommand).resolves({ Body: sdkStream }); - - await storage.writeBinary(binary, undefined, undefined, req as Request); - expect(mockS3Client.send.callCount).toBe(1); - expect(mockS3Client).toReceiveCommandWith(PutObjectCommand, { - Bucket: 'foo', - Key: 'binary/123/456', - ContentType: 'application/octet-stream', - }); - - // Read a file - const stream = await storage.readBinary(binary); - expect(stream).toBeDefined(); - expect(mockS3Client).toHaveReceivedCommand(GetObjectCommand); - }); - - test('Invalid file extension', async () => { - initBinaryStorage('s3:foo'); - - const storage = getBinaryStorage(); - expect(storage).toBeDefined(); - - const binary = null as unknown as Binary; - const stream = null as unknown as internal.Readable; - try { - await storage.writeBinary(binary, 'test.exe', ContentType.TEXT, stream); - fail('Expected error'); - } catch (err) { - expect((err as Error).message).toEqual('Invalid file extension'); - } - expect(mockS3Client).not.toHaveReceivedCommand(PutObjectCommand); - }); - - test('Invalid content type', async () => { - initBinaryStorage('s3:foo'); - - const storage = getBinaryStorage(); - expect(storage).toBeDefined(); - - const binary = null as unknown as Binary; - const stream = null as unknown as internal.Readable; - try { - await storage.writeBinary(binary, 'test.sh', 'application/x-sh', stream); - fail('Expected error'); - } catch (err) { - expect((err as Error).message).toEqual('Invalid content type'); - } - expect(mockS3Client).not.toHaveReceivedCommand(PutObjectCommand); }); test('Should throw an error when file is not found in readBinary()', async () => { @@ -198,54 +72,4 @@ describe('Storage', () => { expect((err as Error).message).toEqual('File not found'); } }); - - test('Copy S3 object', async () => { - initBinaryStorage('s3:foo'); - - const storage = getBinaryStorage(); - expect(storage).toBeDefined(); - - // Write a file - const binary = { - resourceType: 'Binary', - id: '123', - meta: { - versionId: '456', - }, - } as Binary; - const req = new Readable(); - req.push('foo'); - req.push(null); - (req as any).headers = {}; - - const sdkStream = sdkStreamMixin(req); - mockS3Client.on(GetObjectCommand).resolves({ Body: sdkStream }); - - await storage.writeBinary(binary, 'test.txt', ContentType.TEXT, req as Request); - - expect(mockS3Client.send.callCount).toBe(1); - expect(mockS3Client).toReceiveCommandWith(PutObjectCommand, { - Bucket: 'foo', - Key: 'binary/123/456', - ContentType: ContentType.TEXT, - }); - mockS3Client.reset(); - - // Copy the object - const destinationBinary = { - resourceType: 'Binary', - id: '789', - meta: { - versionId: '012', - }, - } as Binary; - await storage.copyBinary(binary, destinationBinary); - - expect(mockS3Client.send.callCount).toBe(1); - expect(mockS3Client).toReceiveCommandWith(CopyObjectCommand, { - CopySource: 'foo/binary/123/456', - Bucket: 'foo', - Key: 'binary/789/012', - }); - }); }); diff --git a/packages/server/src/fhir/storage.ts b/packages/server/src/fhir/storage.ts index c6a05ed4ee..ddbc2abb30 100644 --- a/packages/server/src/fhir/storage.ts +++ b/packages/server/src/fhir/storage.ts @@ -1,9 +1,9 @@ -import { CopyObjectCommand, GetObjectCommand, S3Client } from '@aws-sdk/client-s3'; -import { Upload } from '@aws-sdk/lib-storage'; import { Binary } from '@medplum/fhirtypes'; +import { createSign } from 'crypto'; import { copyFileSync, createReadStream, createWriteStream, existsSync, mkdirSync } from 'fs'; import { resolve, sep } from 'path'; -import { pipeline, Readable } from 'stream'; +import { Readable, pipeline } from 'stream'; +import { S3Storage } from '../cloud/aws/storage'; import { getConfig } from '../config'; /** @@ -44,7 +44,7 @@ export function getBinaryStorage(): BinaryStorage { /** * The BinaryStorage interface represents a method of reading and writing binary blobs. */ -interface BinaryStorage { +export interface BinaryStorage { writeBinary( binary: Binary, filename: string | undefined, @@ -59,6 +59,8 @@ interface BinaryStorage { copyBinary(sourceBinary: Binary, destinationBinary: Binary): Promise; copyFile(sourceKey: string, destinationKey: string): Promise; + + getPresignedUrl(binary: Binary): string; } /** @@ -124,114 +126,28 @@ class FileSystemStorage implements BinaryStorage { copyFileSync(resolve(this.baseDir, sourceKey), resolve(this.baseDir, destinationKey)); } - private getKey(binary: Binary): string { - return binary.id + sep + binary.meta?.versionId; - } + getPresignedUrl(binary: Binary): string { + const config = getConfig(); + const storageBaseUrl = config.storageBaseUrl; + const result = new URL(`${storageBaseUrl}${binary.id}/${binary.meta?.versionId}`); - private getPath(binary: Binary): string { - return resolve(this.baseDir, this.getKey(binary)); - } -} + const dateLessThan = new Date(); + dateLessThan.setHours(dateLessThan.getHours() + 1); + result.searchParams.set('Expires', dateLessThan.getTime().toString()); -/** - * The S3Storage class stores binary data in an AWS S3 bucket. - * Files are stored in bucket/binary/binary.id/binary.meta.versionId. - */ -class S3Storage implements BinaryStorage { - private readonly client: S3Client; - private readonly bucket: string; + const privateKey = { key: config.signingKey, passphrase: config.signingKeyPassphrase }; + const signature = createSign('sha256').update(result.toString()).sign(privateKey, 'base64'); + result.searchParams.set('Signature', signature); - constructor(bucket: string) { - this.client = new S3Client({ region: getConfig().awsRegion }); - this.bucket = bucket; + return result.toString(); } - /** - * Writes a binary blob to S3. - * @param binary - The binary resource destination. - * @param filename - Optional binary filename. - * @param contentType - Optional binary content type. - * @param stream - The Node.js stream of readable content. - * @returns Promise that resolves when the write is complete. - */ - writeBinary( - binary: Binary, - filename: string | undefined, - contentType: string | undefined, - stream: BinarySource - ): Promise { - checkFileMetadata(filename, contentType); - return this.writeFile(this.getKey(binary), contentType, stream); - } - - /** - * Writes a file to S3. - * - * Early implementations used the simple "PutObjectCommand" to write the blob to S3. - * However, PutObjectCommand does not support streaming. - * - * We now use the @aws-sdk/lib-storage package. - * - * Learn more: - * https://github.com/aws/aws-sdk-js-v3/blob/main/UPGRADING.md#s3-multipart-upload - * https://github.com/aws/aws-sdk-js-v3/tree/main/lib/lib-storage - * - * Be mindful of Cache-Control settings. - * - * Because we use signed URLs intended for one hour use, - * we set "max-age" to 1 hour = 3600 seconds. - * - * But we want CloudFront to cache the response for 1 day, - * so we set "s-maxage" to 1 day = 86400 seconds. - * - * Learn more: - * https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Expiration.html - * @param key - The S3 key. - * @param contentType - Optional binary content type. - * @param stream - The Node.js stream of readable content. - */ - async writeFile(key: string, contentType: string | undefined, stream: BinarySource): Promise { - const upload = new Upload({ - params: { - Bucket: this.bucket, - Key: key, - CacheControl: 'max-age=3600, s-maxage=86400', - ContentType: contentType ?? 'application/octet-stream', - Body: stream, - }, - client: this.client, - queueSize: 3, - }); - - await upload.done(); - } - - async readBinary(binary: Binary): Promise { - const output = await this.client.send( - new GetObjectCommand({ - Bucket: this.bucket, - Key: this.getKey(binary), - }) - ); - return output.Body as Readable; - } - - async copyBinary(sourceBinary: Binary, destinationBinary: Binary): Promise { - await this.copyFile(this.getKey(sourceBinary), this.getKey(destinationBinary)); - } - - async copyFile(sourceKey: string, destinationKey: string): Promise { - await this.client.send( - new CopyObjectCommand({ - CopySource: `${this.bucket}/${sourceKey}`, - Bucket: this.bucket, - Key: destinationKey, - }) - ); + private getKey(binary: Binary): string { + return binary.id + sep + binary.meta?.versionId; } - private getKey(binary: Binary): string { - return 'binary/' + binary.id + '/' + binary.meta?.versionId; + private getPath(binary: Binary): string { + return resolve(this.baseDir, this.getKey(binary)); } } @@ -313,7 +229,7 @@ const BLOCKED_CONTENT_TYPES = [ * @param filename - The input filename. * @param contentType - The input content type. */ -function checkFileMetadata(filename: string | undefined, contentType: string | undefined): void { +export function checkFileMetadata(filename: string | undefined, contentType: string | undefined): void { if (checkFileExtension(filename)) { throw new Error('Invalid file extension'); } diff --git a/packages/server/src/logger.test.ts b/packages/server/src/logger.test.ts index 60a31d5f7c..e17363696d 100644 --- a/packages/server/src/logger.test.ts +++ b/packages/server/src/logger.test.ts @@ -1,32 +1,7 @@ -import { - CloudWatchLogsClient, - CreateLogGroupCommand, - CreateLogStreamCommand, - PutLogEventsCommand, -} from '@aws-sdk/client-cloudwatch-logs'; import { LogLevel } from '@medplum/core'; -import { AwsClientStub, mockClient } from 'aws-sdk-client-mock'; -import 'aws-sdk-client-mock-jest'; import { globalLogger } from './logger'; describe('Global Logger', () => { - let mockCloudWatchLogsClient: AwsClientStub; - - beforeEach(() => { - mockCloudWatchLogsClient = mockClient(CloudWatchLogsClient); - - mockCloudWatchLogsClient.on(CreateLogGroupCommand).resolves({}); - mockCloudWatchLogsClient.on(CreateLogStreamCommand).resolves({}); - mockCloudWatchLogsClient.on(PutLogEventsCommand).resolves({ - nextSequenceToken: '', - rejectedLogEventsInfo: {}, - }); - }); - - afterEach(() => { - mockCloudWatchLogsClient.restore(); - }); - test('Debug', () => { console.log = jest.fn(); diff --git a/packages/server/src/oauth/authorize.test.ts b/packages/server/src/oauth/authorize.test.ts index 2c93c11ac5..2ff44ae0a8 100644 --- a/packages/server/src/oauth/authorize.test.ts +++ b/packages/server/src/oauth/authorize.test.ts @@ -13,8 +13,6 @@ import { getSystemRepo } from '../fhir/repo'; import { createTestProject, withTestContext } from '../test.setup'; import { revokeLogin } from './utils'; -jest.mock('@aws-sdk/client-sesv2'); - describe('OAuth Authorize', () => { const app = express(); const systemRepo = getSystemRepo(); diff --git a/packages/server/src/oauth/token.test.ts b/packages/server/src/oauth/token.test.ts index 5ec290707e..2725515b30 100644 --- a/packages/server/src/oauth/token.test.ts +++ b/packages/server/src/oauth/token.test.ts @@ -22,7 +22,6 @@ import { createTestProject, withTestContext } from '../test.setup'; import { generateSecret } from './keys'; import { hashCode } from './token'; -jest.mock('@aws-sdk/client-sesv2'); jest.mock('jose', () => { const core = jest.requireActual('@medplum/core'); const original = jest.requireActual('jose'); diff --git a/packages/server/src/util/cloudwatch.test.ts b/packages/server/src/util/cloudwatch.test.ts index a902d86208..4c0ed10381 100644 --- a/packages/server/src/util/cloudwatch.test.ts +++ b/packages/server/src/util/cloudwatch.test.ts @@ -4,9 +4,8 @@ import { CreateLogStreamCommand, PutLogEventsCommand, } from '@aws-sdk/client-cloudwatch-logs'; -import { mockClient, AwsClientStub } from 'aws-sdk-client-mock'; +import { AwsClientStub, mockClient } from 'aws-sdk-client-mock'; import 'aws-sdk-client-mock-jest'; - import { loadTestConfig } from '../config'; import { waitFor } from '../test.setup'; import { CloudWatchLogger } from './cloudwatch'; From a88b2dd57b5c9d5c7a0efe0c46cea0284d174b00 Mon Sep 17 00:00:00 2001 From: ksmith94 <102421938+ksmith94@users.noreply.github.com> Date: Tue, 16 Apr 2024 18:52:11 -0400 Subject: [PATCH 20/52] Document how to revert changes to a resource (#4382) --- .../docs/fhir-datastore/resource-history.md | 15 +++++++++++++ .../src/fhir-datastore/resource-history.ts | 21 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/packages/docs/docs/fhir-datastore/resource-history.md b/packages/docs/docs/fhir-datastore/resource-history.md index 86595f84e0..bd564dd59f 100644 --- a/packages/docs/docs/fhir-datastore/resource-history.md +++ b/packages/docs/docs/fhir-datastore/resource-history.md @@ -65,3 +65,18 @@ These requests return a `Bundle` resource with the different versions stored as :::note Resource Creation Time There is currently no support for directly accessing the time and date that a resource was initially created. To do this use the `/_history` endpoint to retrieve all versions and view the `lastUpdated` field of the original version. Note that the GraphQL endpoint does not currently have a spec for the history API. ::: + +## Reverting Changes to a Resource + +While there is no direct method to revert changes made to a resource, it can be easily done using the `readHistory` and `readVersion` helper functions provided by Medplum. + +The `readHistory` function is used to get the entire history of the resource. You can then choose the version and use `readVersion` to return the complete details of that version of the resource. The current resource can then be updated to the historic details. + +
+ Example: Revert resource to a previous version + + {ExampleCode} + +
+ +This method does not actually revert the resources to the previous version, but it creates a new entry in the resource's history with all of the same details as the historic version. diff --git a/packages/examples/src/fhir-datastore/resource-history.ts b/packages/examples/src/fhir-datastore/resource-history.ts index 1f25b34f9e..a483047006 100644 --- a/packages/examples/src/fhir-datastore/resource-history.ts +++ b/packages/examples/src/fhir-datastore/resource-history.ts @@ -1,5 +1,6 @@ // start-block imports import { MedplumClient } from '@medplum/core'; +import { Bundle } from '@medplum/fhirtypes'; // end-block imports const medplum = new MedplumClient(); @@ -19,3 +20,23 @@ curl 'https://api.medplum.com/fhir/R4/Patient/homer-simpson/_history' \ -H 'content-type: application/fhir+json' \ // end-block accessHistoryCurl */ + +// start-block revertChanges +// Read the history, returning a bundle of history entries +const history = await medplum.readHistory('Patient', 'homer-simpson'); + +// Implement your own logic to get the historic version of the resource you want. +// You will need the versionId to use the readVersion function. +const versionId = getVersionId(history); + +// readVersion will return the historic Patient resource +const version = await medplum.readVersion('Patient', 'homer-simpson', versionId); + +// Pass the historic version to updateResource to revert to that version +await medplum.updateResource(version); +// end-block revertChanges + +function getVersionId(history: Bundle): string { + console.log(history); + return 'versionId'; +} From 48a420244f81603a6009f42366c0599337659cad Mon Sep 17 00:00:00 2001 From: ksmith94 <102421938+ksmith94@users.noreply.github.com> Date: Tue, 16 Apr 2024 18:52:31 -0400 Subject: [PATCH 21/52] Document binary security context (#4370) * Document binary security context * Remove line about access policies applying to binary --- .../docs/docs/access/binary-security-context.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 packages/docs/docs/access/binary-security-context.md diff --git a/packages/docs/docs/access/binary-security-context.md b/packages/docs/docs/access/binary-security-context.md new file mode 100644 index 0000000000..6c1c786763 --- /dev/null +++ b/packages/docs/docs/access/binary-security-context.md @@ -0,0 +1,16 @@ +# Binary Security Context + +When managing access, the FHIR [`Binary`](/docs/api/fhir/resources/binary) resource is unique case. Access controls cannot be applied to [`Binary`](/docs/api/fhir/resources/binary) resources in the same way as other resources, so you must use the `Binary.securityContext` element to add access policies. + +The `securityContext` element is a reference to another resource that acts as a proxy for the access controls of that [`Binary`](/docs/api/fhir/resources/binary). For example, if the `securityContext` references a [`Patient`](/docs/api/fhir/resources/patient), then the [`Binary`](/docs/api/fhir/resources/binary) will only be viewable by users and resources that have read access to that [`Patient`](/docs/api/fhir/resources/patient). + +Below is an example of a simiple [`Binary`](/docs/api/fhir/resources/binary) resource with a `securityContext` that references a [`Patient`](/docs/api/fhir/resources/patient). + +```json +{ + "resourceType": "Binary", + "securityContext": { "reference": "Patient/homer-simpson" } +} +``` + +For more details on how [`Binary`](/docs/api/fhir/resources/binary) resources are used in FHIR, see the [Binary Data docs](/docs/fhir-datastore/binary-data). From e1a98ca4865004449a4d830ed719d7e94633c6d3 Mon Sep 17 00:00:00 2001 From: ksmith94 <102421938+ksmith94@users.noreply.github.com> Date: Tue, 16 Apr 2024 19:21:23 -0400 Subject: [PATCH 22/52] Create a Chat Demo App (#4220) * Create chat directory and implement basic search page * Update search page and add Communication page * Add thread actions * Fix search page urls and add mantine notifications * Implement tabs on search page * Implement create new thread * Implement filter search page by patient and add create thread button * Implement patient page * Update to get all participants in a new thread * Communication and Search page improvements * Create patient page and move helper functions to utils * Make distinct resource pages for threads and individual messages * Add upload data links and page without funtionality * Add comments and remove unused pages and components * Add functionality to add multiple participants to existing thread at once * Add check for correct recipient type when creating thread or adding participants * Update dependencies for build * Fix eslint errors * Fix eslint errors and update create encounter functionality * remove unused declaration * Update readme * Improve display on thread page * Update create thread functionality * Add example data * Add example valueset and implement data upload * Make edits per feedback * Update medplum deps and move thread and message to pages * Update imports * Fix eslint errors * Update questionnaires with specific resource types for referenes * Move ThreadChat component to tabs * Add more example data * remove core data upload functionality * Clean up thread page * Remove unused import * Update example data to display names and have condition * Update patient data details * Add enhancements to thread page * rebase for build * Remove duped import * Update questionnaires and debug encounter creation * Remove unused vars * Changes per review * Improve encounter creation and viewing functionality * Fix eslint errors * AddSubject button * fix eslint * Add notifications to AppShell * improve app page * Update medplum version and app improvements * Update function declarations * Update to fix build --- examples/medplum-chat-demo/.gitattributes | 25 + .../medplum-chat-demo/.github/dependabot.yml | 13 + examples/medplum-chat-demo/.gitignore | 25 + examples/medplum-chat-demo/README.md | 63 + .../data/example/example-data.json | 2864 +++++++++++++++++ examples/medplum-chat-demo/index.html | 14 + .../medplum-chat-demo-screenshot.png | Bin 0 -> 697068 bytes examples/medplum-chat-demo/package.json | 42 + examples/medplum-chat-demo/postcss.config.mjs | 19 + examples/medplum-chat-demo/public/favicon.ico | Bin 0 -> 22382 bytes examples/medplum-chat-demo/src/App.tsx | 119 + .../src/components/CommunicationDetails.tsx | 45 + .../src/components/PatientDetails.tsx | 104 + .../src/components/actions/AddParticipant.tsx | 129 + .../src/components/actions/AddSubject.tsx | 93 + .../components/actions/CloseOpenThread.tsx | 70 + .../actions/CommunicationActions.tsx | 45 + .../components/actions/CreateEncounter.tsx | 148 + .../src/components/actions/CreateThread.tsx | 187 ++ .../components/actions/EditThreadTopic.tsx | 88 + examples/medplum-chat-demo/src/main.tsx | 49 + .../src/pages/CommunicationPage.tsx | 49 + .../src/pages/LandingPage.tsx | 21 + .../src/pages/MessagePage.tsx | 55 + .../src/pages/PatientHeader.module.css | 26 + .../src/pages/PatientHeader.tsx | 60 + .../src/pages/PatientPage.tsx | 43 + .../src/pages/ResourcePage.tsx | 103 + .../src/pages/SearchPage.tsx | 164 + .../src/pages/SignInPage.tsx | 18 + .../src/pages/ThreadPage.tsx | 92 + .../src/pages/UploadDataPage.tsx | 55 + examples/medplum-chat-demo/src/utils.tsx | 186 ++ examples/medplum-chat-demo/src/vite-env.d.ts | 1 + examples/medplum-chat-demo/tsconfig.json | 19 + examples/medplum-chat-demo/vercel.json | 5 + examples/medplum-chat-demo/vite.config.ts | 14 + package-lock.json | 892 +++++ 38 files changed, 5945 insertions(+) create mode 100644 examples/medplum-chat-demo/.gitattributes create mode 100644 examples/medplum-chat-demo/.github/dependabot.yml create mode 100644 examples/medplum-chat-demo/.gitignore create mode 100644 examples/medplum-chat-demo/README.md create mode 100644 examples/medplum-chat-demo/data/example/example-data.json create mode 100644 examples/medplum-chat-demo/index.html create mode 100644 examples/medplum-chat-demo/medplum-chat-demo-screenshot.png create mode 100644 examples/medplum-chat-demo/package.json create mode 100644 examples/medplum-chat-demo/postcss.config.mjs create mode 100644 examples/medplum-chat-demo/public/favicon.ico create mode 100644 examples/medplum-chat-demo/src/App.tsx create mode 100644 examples/medplum-chat-demo/src/components/CommunicationDetails.tsx create mode 100644 examples/medplum-chat-demo/src/components/PatientDetails.tsx create mode 100644 examples/medplum-chat-demo/src/components/actions/AddParticipant.tsx create mode 100644 examples/medplum-chat-demo/src/components/actions/AddSubject.tsx create mode 100644 examples/medplum-chat-demo/src/components/actions/CloseOpenThread.tsx create mode 100644 examples/medplum-chat-demo/src/components/actions/CommunicationActions.tsx create mode 100644 examples/medplum-chat-demo/src/components/actions/CreateEncounter.tsx create mode 100644 examples/medplum-chat-demo/src/components/actions/CreateThread.tsx create mode 100644 examples/medplum-chat-demo/src/components/actions/EditThreadTopic.tsx create mode 100644 examples/medplum-chat-demo/src/main.tsx create mode 100644 examples/medplum-chat-demo/src/pages/CommunicationPage.tsx create mode 100644 examples/medplum-chat-demo/src/pages/LandingPage.tsx create mode 100644 examples/medplum-chat-demo/src/pages/MessagePage.tsx create mode 100644 examples/medplum-chat-demo/src/pages/PatientHeader.module.css create mode 100644 examples/medplum-chat-demo/src/pages/PatientHeader.tsx create mode 100644 examples/medplum-chat-demo/src/pages/PatientPage.tsx create mode 100644 examples/medplum-chat-demo/src/pages/ResourcePage.tsx create mode 100644 examples/medplum-chat-demo/src/pages/SearchPage.tsx create mode 100644 examples/medplum-chat-demo/src/pages/SignInPage.tsx create mode 100644 examples/medplum-chat-demo/src/pages/ThreadPage.tsx create mode 100644 examples/medplum-chat-demo/src/pages/UploadDataPage.tsx create mode 100644 examples/medplum-chat-demo/src/utils.tsx create mode 100644 examples/medplum-chat-demo/src/vite-env.d.ts create mode 100644 examples/medplum-chat-demo/tsconfig.json create mode 100644 examples/medplum-chat-demo/vercel.json create mode 100644 examples/medplum-chat-demo/vite.config.ts diff --git a/examples/medplum-chat-demo/.gitattributes b/examples/medplum-chat-demo/.gitattributes new file mode 100644 index 0000000000..9758f1aa1f --- /dev/null +++ b/examples/medplum-chat-demo/.gitattributes @@ -0,0 +1,25 @@ +# Auto detect text files and perform LF normalization +* text=auto + +*.java text eol=lf diff=java +*.html text eol=lf diff=html +*.css text eol=lf +*.js text eol=lf +*.ts text eol=lf +*.sh text eol=lf +*.sql text eol=lf +*.xml text eol=lf + +*.cur binary +*.gif binary +*.ico binary +*.jar binary +*.jpg binary +*.jpeg binary +*.png binary +*.xcf binary +*.zip binary + +package-lock.json -diff +package-lock.json linguist-generated=true + diff --git a/examples/medplum-chat-demo/.github/dependabot.yml b/examples/medplum-chat-demo/.github/dependabot.yml new file mode 100644 index 0000000000..4d7656afdc --- /dev/null +++ b/examples/medplum-chat-demo/.github/dependabot.yml @@ -0,0 +1,13 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: 'npm' # See documentation for possible values + directory: '/' # Location of package manifests + schedule: + interval: 'weekly' + allow: + - dependency-name: '@medplum/*' diff --git a/examples/medplum-chat-demo/.gitignore b/examples/medplum-chat-demo/.gitignore new file mode 100644 index 0000000000..b02a1ff770 --- /dev/null +++ b/examples/medplum-chat-demo/.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-chat-demo/README.md b/examples/medplum-chat-demo/README.md new file mode 100644 index 0000000000..e054a78075 --- /dev/null +++ b/examples/medplum-chat-demo/README.md @@ -0,0 +1,63 @@ +

Medplum Chat Demo

+

A starter application for using the Medplum platform.

+

+ + + +

+ +This example app demonstrates the following: + +- Creating a new React app with Vite and TypeScript to demonstrate [`Communication`](/packages/docs/api/fhir/resources/communication)-based workflows. +- A threaded [`Communiaction`](/docs/api/fhir/resources/communication) model. +- Creating thread-level and message-level [`Communication`](/packages/docs/api/fhir/resources/communication) resources. +- Sending and replying to messages. +- Realtime communication via WebSockets. +- Adding new participants to existing threads. +- Editing thread topics and categories. + +![Chat Demo Screenshot](medplum-chat-demo-screenshot.png) + +### Code Organization + +This repo is organized into two main directories: `src` and `data`. + +The `src` directory contains the app, including a `pages` and `components` directory. In addition, it contains a `bots` directory, which has [Medplum Bots](/packages/docs/docs/bots/bot-basics.md) for use. + +The `data` directory contains data that can be uploaded for use in the demo. The `example` directory contains data that is meant to be used for testing and learning, while the `core` directory contains resources, terminologies, and more that are necessary to use the demo. + +### Getting Started + +If you haven't already done so, follow the instructions in [this tutorial](https://www.medplum.com/docs/tutorials/register) to register a Medplum project to store your data. + +[Fork](https://github.com/medplum/medplum/fork) and clone the main Medplum repo. + +Move into the `medplum-chat-demo` directory. + +```bash +cd examples/medplum-chat-demo +``` + +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-chat-demo/data/example/example-data.json b/examples/medplum-chat-demo/data/example/example-data.json new file mode 100644 index 0000000000..e3f7f04a25 --- /dev/null +++ b/examples/medplum-chat-demo/data/example/example-data.json @@ -0,0 +1,2864 @@ +{ + "resourceType": "Bundle", + "type": "batch", + "entry": [ + { + "fullUrl": "urn:uuid:ec42014b-63af-407e-ba31-c5b080c527a4", + "request": { "method": "POST", "url": "Patient" }, + "resource": { + "resourceType": "Patient", + "active": true, + "name": [{ "family": "Simpson", "given": ["Homer"] }], + "gender": "male", + "birthDate": "1956-05-12", + "address": [ + { + "use": "home", + "line": ["742 Evergreen Terrace"], + "city": "Springfield" + } + ] + } + }, + { + "fullUrl": "urn:uuid:6ac7de0c-4f35-4024-8942-639d761caa44", + "request": { "method": "POST", "url": "Condition" }, + "resource": { + "resourceType": "Condition", + "clinicalStatus": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/condition-clinical", + "code": "active", + "display": "Active" + } + ] + }, + "verificationStatus": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/condition-ver-status", + "code": "confirmed", + "display": "Confirmed" + } + ] + }, + "category": [ + { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/condition-category", + "code": "problem-list-item", + "display": "Problem List Item" + } + ] + } + ], + "code": { + "coding": [ + { + "code": "Obesity", + "display": "Obesity" + } + ] + }, + "subject": { + "reference": "urn:uuid:ec42014b-63af-407e-ba31-c5b080c527a4", + "display": "Homer Simpson" + } + } + }, + { + "fullUrl": "urn:uuid:3bf50fa0-9c13-48ca-9cd7-1379dbf5f111", + "request": { "method": "POST", "url": "Practitioner" }, + "resource": { + "resourceType": "Practitioner", + "active": true, + "name": [{ "family": "Smith", "given": ["Alice"], "prefix": ["Dr."] }], + "gender": "female", + "qualification": [ + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0360", + "code": "MD", + "display": "Doctor of Medicine" + } + ] + } + } + ] + } + }, + { + "fullUrl": "urn:uuid:5e18d560-9223-4f43-a133-76a30200315d", + "request": { "method": "POST", "url": "Communication" }, + "resource": { + "resourceType": "Communication", + "status": "in-progress", + "recipient": [ + { "reference": "urn:uuid:ec42014b-63af-407e-ba31-c5b080c527a4", "display": "Homer Simpson" }, + { "reference": "urn:uuid:3bf50fa0-9c13-48ca-9cd7-1379dbf5f111", "display": "Dr. Alice Smith" } + ], + "sender": { "reference": "urn:uuid:3bf50fa0-9c13-48ca-9cd7-1379dbf5f111", "display": "Dr. Alice Smith" }, + "topic": { "text": "Blood Test for Peanut Allergy" }, + "category": [{ "coding": [{ "display": "Lab Tests" }] }], + "subject": { "reference": "urn:uuid:ec42014b-63af-407e-ba31-c5b080c527a4", "display": "Homer Simpson" } + } + }, + { + "fullUrl": "urn:uuid:8f5c2717-7f5c-4c5c-a4a7-cf1047e4443e", + "request": { "method": "POST", "url": "Communication" }, + "resource": { + "resourceType": "Communication", + "status": "in-progress", + "partOf": [{ "reference": "urn:uuid:5e18d560-9223-4f43-a133-76a30200315d" }], + "sender": { "reference": "urn:uuid:3bf50fa0-9c13-48ca-9cd7-1379dbf5f111", "display": "Dr. Alice Smith" }, + "recipient": [{ "reference": "urn:uuid:ec42014b-63af-407e-ba31-c5b080c527a4", "display": "Homer Simpson" }], + "topic": { "coding": [{ "display": "Blood Test for Peanut Allergy" }] }, + "category": [{ "coding": [{ "display": "Lab Tests" }] }], + "sent": "2024-03-21T20:28:42.148Z", + "payload": [ + { "contentString": "Hi Homer, your blood work just came back. Would you like to come in to talk about it?" } + ] + } + }, + { + "fullUrl": "urn:uuid:09ab29bd-934f-4f99-b3c5-8a35f85e312b", + "request": { "method": "POST", "url": "Communication" }, + "resource": { + "resourceType": "Communication", + "status": "in-progress", + "partOf": [{ "reference": "urn:uuid:5e18d560-9223-4f43-a133-76a30200315d" }], + "recipient": [{ "reference": "urn:uuid:3bf50fa0-9c13-48ca-9cd7-1379dbf5f111", "display": "Dr. Alice Smith" }], + "sender": { "reference": "urn:uuid:ec42014b-63af-407e-ba31-c5b080c527a4", "display": "Homer Simpson" }, + "topic": { "coding": [{ "display": "Blood Test for Peanut Allergy" }] }, + "category": [{ "coding": [{ "display": "Lab Tests" }] }], + "sent": "2024-03-21T20:38:42.148Z", + "inResponseTo": [{ "reference": "urn:uuid:8f5c2717-7f5c-4c5c-a4a7-cf1047e4443e" }], + "payload": [{ "contentString": "Just tell me doc, I can't bear to wait any longer." }] + } + }, + { + "fullUrl": "urn:uuid:70546170-7f0a-4f95-88f4-1f908bd0a708", + "request": { "method": "POST", "url": "Communication" }, + "resource": { + "resourceType": "Communication", + "status": "in-progress", + "partOf": [{ "reference": "urn:uuid:5e18d560-9223-4f43-a133-76a30200315d" }], + "sender": { "reference": "urn:uuid:3bf50fa0-9c13-48ca-9cd7-1379dbf5f111", "display": "Dr. Alice Smith" }, + "recipient": [{ "reference": "urn:uuid:ec42014b-63af-407e-ba31-c5b080c527a4", "display": "Homer Simpson" }], + "topic": { "coding": [{ "display": "Blood Test for Peanut Allergy" }] }, + "category": [{ "coding": [{ "display": "Lab Tests" }] }], + "sent": "2024-03-21T20:40:42.148Z", + "inResponseTo": [{ "reference": "urn:uuid:09ab29bd-934f-4f99-b3c5-8a35f85e312b" }], + "payload": [ + { + "contentString": "If you insist... Unfortunately, the results came back positive. You're allergic to peanuts." + } + ] + } + }, + { + "fullUrl": "urn:uuid:6bcf025a-c8b0-483e-8399-e3e9f43bd944", + "request": { "method": "POST", "url": "Practitioner" }, + "resource": { + "resourceType": "Practitioner", + "active": true, + "name": [{ "family": "House", "given": ["Gregory"], "prefix": ["Dr."] }], + "gender": "male", + "qualification": [ + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0360", + "code": "MD", + "display": "Doctor of Medicine" + } + ] + } + } + ] + } + }, + { + "fullUrl": "urn:uuid:5be59c7f-7b82-49d3-9183-46d1a9c385c6", + "request": { "method": "POST", "url": "Practitioner" }, + "resource": { + "resourceType": "Practitioner", + "active": true, + "name": [{ "family": "Peyton", "given": ["Jackie"] }], + "gender": "female", + "qualification": [ + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0360", + "code": "RN", + "display": "Registered Nurse" + } + ] + } + } + ] + } + }, + { + "fullUrl": "urn:uuid:d2fe5b7d-98f5-4126-bd25-0dbb2ade1ff2", + "request": { "method": "POST", "url": "Communication" }, + "resource": { + "resourceType": "Communication", + "status": "in-progress", + "sender": { "reference": "urn:uuid:6bcf025a-c8b0-483e-8399-e3e9f43bd944", "display": "Dr. Gregory House" }, + "recipient": [ + { "reference": "urn:uuid:3bf50fa0-9c13-48ca-9cd7-1379dbf5f111", "display": "Dr. Alice Smith" }, + { "reference": "urn:uuid:5be59c7f-7b82-49d3-9183-46d1a9c385c6", "display": "Nurse Jackie" }, + { "reference": "urn:uuid:6bcf025a-c8b0-483e-8399-e3e9f43bd944", "display": "Dr. Gregory House" } + ], + "topic": { "coding": [{ "display": "Capturing Homer's vitals" }] }, + "category": [{ "coding": [{ "display": "Vital Signs" }] }], + "subject": { "reference": "urn:uuid:ec42014b-63af-407e-ba31-c5b080c527a4", "display": "Homer Simpson" } + } + }, + { + "fullUrl": "urn:uuid:b82f3090-82bb-4102-9081-f9113fe81ff9", + "request": { "method": "POST", "url": "Communication" }, + "resource": { + "resourceType": "Communication", + "status": "in-progress", + "sender": { "reference": "urn:uuid:6bcf025a-c8b0-483e-8399-e3e9f43bd944", "display": "Dr. Gregory House" }, + "sent": "2024-03-22T20:28:42.148Z", + "recipient": [ + { "reference": "urn:uuid:3bf50fa0-9c13-48ca-9cd7-1379dbf5f111", "display": "Dr. Alice Smith" }, + { "reference": "urn:uuid:5be59c7f-7b82-49d3-9183-46d1a9c385c6", "display": "Nurse Jackie" }, + { "reference": "urn:uuid:6bcf025a-c8b0-483e-8399-e3e9f43bd944", "display": "Dr. Gregory House" } + ], + "partOf": [{ "reference": "urn:uuid:d2fe5b7d-98f5-4126-bd25-0dbb2ade1ff2" }], + "topic": { "coding": [{ "display": "Capturing Homer's vitals" }] }, + "category": [{ "coding": [{ "display": "Vital Signs" }] }], + "subject": { "reference": "urn:uuid:ec42014b-63af-407e-ba31-c5b080c527a4", "display": "Homer Simpson" }, + "payload": [ + { "contentString": "Are either of available to take Homer's vital signs before his physical tomorrow?" } + ] + } + }, + { + "fullUrl": "urn:uuid:f7c6f976-edf9-4081-b179-f1dbb5347f13", + "request": { "method": "POST", "url": "Communication" }, + "resource": { + "resourceType": "Communication", + "status": "in-progress", + "sender": { "reference": "urn:uuid:3bf50fa0-9c13-48ca-9cd7-1379dbf5f111" }, + "sent": "2024-03-22T20:55:18.148Z", + "recipient": [ + { "reference": "urn:uuid:3bf50fa0-9c13-48ca-9cd7-1379dbf5f111", "display": "Dr. Alice Smith" }, + { "reference": "urn:uuid:5be59c7f-7b82-49d3-9183-46d1a9c385c6", "display": "Nurse Jackie" }, + { "reference": "urn:uuid:6bcf025a-c8b0-483e-8399-e3e9f43bd944", "display": "Dr. Gregory House" } + ], + "partOf": [{ "reference": "urn:uuid:d2fe5b7d-98f5-4126-bd25-0dbb2ade1ff2" }], + "inResponseTo": [{ "reference": "urn:uuid:b82f3090-82bb-4102-9081-f9113fe81ff9" }], + "topic": { "coding": [{ "display": "Capturing Homer's vitals" }] }, + "category": [{ "coding": [{ "display": "Vital Signs" }] }], + "subject": { "reference": "urn:uuid:ec42014b-63af-407e-ba31-c5b080c527a4", "display": "Homer Simpson" }, + "payload": [{ "contentString": "I have the day off tomorrow, so I cannot." }] + } + }, + { + "fullUrl": "urn:uuid:35bf5aa3-6a85-400e-bce4-a114fb60f8b9", + "request": { "method": "POST", "url": "Communication" }, + "resource": { + "resourceType": "Communication", + "status": "in-progress", + "sender": { "reference": "urn:uuid:5be59c7f-7b82-49d3-9183-46d1a9c385c6" }, + "sent": "2024-03-22T21:13:33.148Z", + "recipient": [ + { "reference": "urn:uuid:3bf50fa0-9c13-48ca-9cd7-1379dbf5f111", "display": "Dr. Alice Smith" }, + { "reference": "urn:uuid:5be59c7f-7b82-49d3-9183-46d1a9c385c6", "display": "Nurse Jackie" }, + { "reference": "urn:uuid:6bcf025a-c8b0-483e-8399-e3e9f43bd944", "display": "Dr. Gregory House" } + ], + "partOf": [{ "reference": "urn:uuid:d2fe5b7d-98f5-4126-bd25-0dbb2ade1ff2" }], + "inResponseTo": [{ "reference": "urn:uuid:f7c6f976-edf9-4081-b179-f1dbb5347f13" }], + "topic": { "coding": [{ "display": "Capturing Homer's vitals" }] }, + "category": [{ "coding": [{ "display": "Vital Signs" }] }], + "subject": { "reference": "urn:uuid:ec42014b-63af-407e-ba31-c5b080c527a4", "display": "Homer Simpson" }, + "payload": [{ "contentString": "I can do it, but it will have to be quick and he must be on time." }] + } + }, + { + "fullUrl": "urn:uuid:23b65226-f1fd-424b-90c7-af2914dd6eba", + "request": { "method": "POST", "url": "ValueSet" }, + "resource": { + "resourceType": "ValueSet", + "status": "active", + "url": "https://example.org/thread-categories", + "name": "thread-category-codes", + "title": "Thread Category Codes", + "description": "These codes identify categories to be help organize threads.", + "compose": { + "include": [ + { + "system": "http://snomed.info/sct", + "concept": [ + { "code": "19388002", "display": "Physical" }, + { "code": "33879002", "display": "Vaccination" }, + { "code": "363680008", "display": "Diagnostic imaging procedure using X-rays" }, + { "code": "396550006", "display": "Blood test (procedure)" }, + { "code": "16076005", "display": "Prescription (procedure)" }, + { "code": "225362009", "display": "Dental care" }, + { "code": "386243005", "display": "Vision care" }, + { "code": "390808007", "display": "Mental health care" }, + { "code": "722138006", "display": "Physical therapy" }, + { "code": "394582007", "display": "Dermatology" }, + { "code": "408439002", "display": "Allergy" }, + { "code": "394579002", "display": "Cardiology" }, + { "code": "394801008", "display": "Trauma and orthopedics" }, + { "code": "408470005", "display": "Obstetrics" }, + { "code": "394586005", "display": "Gynecology" }, + { "code": "394592004", "display": "Clinical oncology" }, + { "code": "726527001", "display": "Weight" }, + { "code": "52052004", "display": "Rehabilitation therapy" }, + { "code": "408450004", "display": "Sleep studies" }, + { "code": "722164000", "display": "Dietetics and nutrition" }, + { "code": "710081004", "display": "Smoking cessation therapy" }, + { "code": "91193004", "display": "Hearing therapy" }, + { "code": "722166003", "display": "Podiatry" }, + { + "code": "106289002", + "display": "Dentist (occupation)" + }, + { + "code": "106292003", + "display": "Professional nurse (occupation)" + }, + { + "code": "106293008", + "display": "Nursing personnel (occupation)" + }, + { + "code": "106294002", + "display": "Midwifery personnel (occupation)" + }, + { + "code": "106296000", + "display": "Physiotherapist/occupational therapist (occupation)" + }, + { + "code": "106310008", + "display": "Worker in religion (occupation)" + }, + { + "code": "106311007", + "display": "Minister of religion/related member of religious order (occupation)" + }, + { + "code": "106330007", + "display": "Philologist, translator/interpreter (occupation)" + }, + { + "code": "11015003", + "display": "Minister of religion (occupation)" + }, + { + "code": "116154003", + "display": "Patient (person)" + }, + { + "code": "11661002", + "display": "Neuropathologist (occupation)" + }, + { + "code": "1172950003", + "display": "Massage therapist (occupation)" + }, + { + "code": "1186716007", + "display": "Intellectual disability psychiatrist (occupation)" + }, + { + "code": "1186914001", + "display": "Intellectual disability nurse (occupation)" + }, + { + "code": "11911009", + "display": "Nephrologist (occupation)" + }, + { + "code": "119246008", + "display": "Imam (occupation)" + }, + { + "code": "11935004", + "display": "Obstetrician (occupation)" + }, + { + "code": "1251537007", + "display": "Sport medicine specialist (occupation)" + }, + { + "code": "1251542004", + "display": "Medical coder (occupation)" + }, + { + "code": "1251548000", + "display": "Neuroradiologist (occupation)" + }, + { + "code": "1254982001", + "display": "Medical surgical nurse (occupation)" + }, + { + "code": "1254983006", + "display": "Chronic care nurse (occupation)" + }, + { + "code": "1254984000", + "display": "Rehabilitation nurse (occupation)" + }, + { + "code": "1255370008", + "display": "Specialist in naturopathy (occupation)" + }, + { + "code": "1255371007", + "display": "Specialist in homeopathy (occupation)" + }, + { + "code": "1255372000", + "display": "Phytotherapist (occupation)" + }, + { + "code": "1255373005", + "display": "Specialist in traditional Chinese medicine (occupation)" + }, + { + "code": "1255374004", + "display": "Clinical nutritionist (occupation)" + }, + { + "code": "1255514008", + "display": "Regulatory affairs pharmacist (occupation)" + }, + { + "code": "1255515009", + "display": "Pharmacogenomics pharmacist (occupation)" + }, + { + "code": "1255517001", + "display": "Intern in healthcare (occupation)" + }, + { + "code": "1255518006", + "display": "Organizational and social psychologist (occupation)" + }, + { + "code": "1255519003", + "display": "Cardiopulmonary technician (occupation)" + }, + { + "code": "1255719001", + "display": "Neurophysiology technician (occupation)" + }, + { + "code": "1256114007", + "display": "Nuclear medicine technologist (occupation)" + }, + { + "code": "1259214004", + "display": "Immunohemotherapy specialist (occupation)" + }, + { + "code": "1259964002", + "display": "Oral medicine specialist (occupation)" + }, + { + "code": "1268923002", + "display": "Obstetric nurse (occupation)" + }, + { + "code": "1271000175101", + "display": "Primary obstetrician (occupation)" + }, + { + "code": "1276561000168102", + "display": "Prosthetist (occupation)" + }, + { + "code": "1276571000168108", + "display": "Orthotist and prosthetist (occupation)" + }, + { + "code": "133932002", + "display": "Caregiver (person)" + }, + { + "code": "13580004", + "display": "School dental assistant (occupation)" + }, + { + "code": "1421009", + "display": "Specialized surgeon (occupation)" + }, + { + "code": "14613005", + "display": "Ordained rabbi (occupation)" + }, + { + "code": "14698002", + "display": "Medical microbiologist (occupation)" + }, + { + "code": "158939004", + "display": "Child care officer (occupation)" + }, + { + "code": "158942005", + "display": "Residential child care worker (occupation)" + }, + { + "code": "158943000", + "display": "Residential youth care worker (occupation)" + }, + { + "code": "158965000", + "display": "Medical practitioner (occupation)" + }, + { + "code": "158966004", + "display": "Medical administrator - national (occupation)" + }, + { + "code": "158967008", + "display": "Consultant physician (occupation)" + }, + { + "code": "158968003", + "display": "Consultant surgeon (occupation)" + }, + { + "code": "158969006", + "display": "Consultant gynecology/obstetrics (occupation)" + }, + { + "code": "158971006", + "display": "Hospital registrar (occupation)" + }, + { + "code": "158972004", + "display": "House officer (occupation)" + }, + { + "code": "158973009", + "display": "Occupational physician (occupation)" + }, + { + "code": "158974003", + "display": "Clinical medical officer (occupation)" + }, + { + "code": "158975002", + "display": "Medical practitioner - teaching (occupation)" + }, + { + "code": "158977005", + "display": "Dental administrator (occupation)" + }, + { + "code": "158978000", + "display": "Dental consultant (occupation)" + }, + { + "code": "158979008", + "display": "Dental general practitioner (occupation)" + }, + { + "code": "158980006", + "display": "Dental practitioner - teaching (occupation)" + }, + { + "code": "158983008", + "display": "Nurse administrator - national (occupation)" + }, + { + "code": "158984002", + "display": "Nursing officer - region (occupation)" + }, + { + "code": "158985001", + "display": "Nursing officer - district (occupation)" + }, + { + "code": "158986000", + "display": "Nursing administrator - professional body (occupation)" + }, + { + "code": "158987009", + "display": "Nursing officer - division (occupation)" + }, + { + "code": "158988004", + "display": "Nurse education director (occupation)" + }, + { + "code": "158989007", + "display": "Occupational health nursing officer (occupation)" + }, + { + "code": "158990003", + "display": "Nursing officer (occupation)" + }, + { + "code": "158992006", + "display": "Midwifery sister (occupation)" + }, + { + "code": "158993001", + "display": "Nursing sister (theater) (occupation)" + }, + { + "code": "158994007", + "display": "Staff nurse (occupation)" + }, + { + "code": "158995008", + "display": "Staff midwife (occupation)" + }, + { + "code": "158996009", + "display": "State enrolled nurse (occupation)" + }, + { + "code": "158997000", + "display": "District nurse (occupation)" + }, + { + "code": "158998005", + "display": "Private nurse (occupation)" + }, + { + "code": "158999002", + "display": "Community midwife (occupation)" + }, + { + "code": "159001001", + "display": "Clinic nurse (occupation)" + }, + { + "code": "159002008", + "display": "Practice nurse (occupation)" + }, + { + "code": "159003003", + "display": "School nurse (occupation)" + }, + { + "code": "159004009", + "display": "Nurse teacher (occupation)" + }, + { + "code": "159005005", + "display": "Student nurse (occupation)" + }, + { + "code": "159006006", + "display": "Dental nurse (occupation)" + }, + { + "code": "159007002", + "display": "Community pediatric nurse (occupation)" + }, + { + "code": "159010009", + "display": "Hospital pharmacist (occupation)" + }, + { + "code": "159011008", + "display": "Retail pharmacist (occupation)" + }, + { + "code": "159012001", + "display": "Industrial pharmacist (occupation)" + }, + { + "code": "159014000", + "display": "Trainee pharmacist (occupation)" + }, + { + "code": "159016003", + "display": "Medical radiographer (occupation)" + }, + { + "code": "159017007", + "display": "Diagnostic radiographer (occupation)" + }, + { + "code": "159018002", + "display": "Therapeutic radiographer (occupation)" + }, + { + "code": "159019005", + "display": "Trainee radiographer (occupation)" + }, + { + "code": "159021000", + "display": "Ophthalmic optician (occupation)" + }, + { + "code": "159022007", + "display": "Trainee optician (occupation)" + }, + { + "code": "159025009", + "display": "Remedial gymnast (occupation)" + }, + { + "code": "159026005", + "display": "Speech/language therapist (occupation)" + }, + { + "code": "159027001", + "display": "Orthoptist (occupation)" + }, + { + "code": "159028006", + "display": "Trainee remedial therapist (occupation)" + }, + { + "code": "159033005", + "display": "Dietitian (occupation)" + }, + { + "code": "159034004", + "display": "Podiatrist (occupation)" + }, + { + "code": "159035003", + "display": "Dental auxiliary (occupation)" + }, + { + "code": "159036002", + "display": "Electrocardiogram technician (occupation)" + }, + { + "code": "159037006", + "display": "Electroencephalogram technician (occupation)" + }, + { + "code": "159038001", + "display": "Artificial limb fitter (occupation)" + }, + { + "code": "159039009", + "display": "Audiology technician (occupation)" + }, + { + "code": "159040006", + "display": "Pharmacy technician (occupation)" + }, + { + "code": "159041005", + "display": "Trainee medical technician (occupation)" + }, + { + "code": "159141008", + "display": "Geneticist (occupation)" + }, + { + "code": "159148002", + "display": "Research chemist (occupation)" + }, + { + "code": "159174008", + "display": "Civil engineer - research (occupation)" + }, + { + "code": "159972006", + "display": "Surgical corset fitter (occupation)" + }, + { + "code": "160008000", + "display": "Dental technician (occupation)" + }, + { + "code": "17561000", + "display": "Cardiologist (occupation)" + }, + { + "code": "184152007", + "display": "Care assistant (occupation)" + }, + { + "code": "184154008", + "display": "Care manager (occupation)" + }, + { + "code": "18803008", + "display": "Dermatologist (occupation)" + }, + { + "code": "18850004", + "display": "Laboratory hematologist (occupation)" + }, + { + "code": "19244007", + "display": "Gerodontist (occupation)" + }, + { + "code": "20145008", + "display": "Removable prosthodontist (occupation)" + }, + { + "code": "21365001", + "display": "Specialized dentist (occupation)" + }, + { + "code": "21450003", + "display": "Neuropsychiatrist (occupation)" + }, + { + "code": "224529009", + "display": "Clinical assistant (occupation)" + }, + { + "code": "224530004", + "display": "Senior registrar (occupation)" + }, + { + "code": "224531000", + "display": "Registrar (occupation)" + }, + { + "code": "224532007", + "display": "Senior house officer (occupation)" + }, + { + "code": "224533002", + "display": "Medical officer (occupation)" + }, + { + "code": "224534008", + "display": "Health visitor, nurse/midwife (occupation)" + }, + { + "code": "224535009", + "display": "Registered nurse (occupation)" + }, + { + "code": "224536005", + "display": "Midwifery tutor (occupation)" + }, + { + "code": "224537001", + "display": "Accident and Emergency nurse (occupation)" + }, + { + "code": "224538006", + "display": "Triage nurse (occupation)" + }, + { + "code": "224540001", + "display": "Community nurse (occupation)" + }, + { + "code": "224541002", + "display": "Nursing continence advisor (occupation)" + }, + { + "code": "224542009", + "display": "Coronary care nurse (occupation)" + }, + { + "code": "224543004", + "display": "Diabetic nurse (occupation)" + }, + { + "code": "224544005", + "display": "Family planning nurse (occupation)" + }, + { + "code": "224545006", + "display": "Care of the elderly nurse (occupation)" + }, + { + "code": "224546007", + "display": "Infection control nurse (occupation)" + }, + { + "code": "224547003", + "display": "Intensive therapy nurse (occupation)" + }, + { + "code": "224548008", + "display": "Learning disabilities nurse (occupation)" + }, + { + "code": "224549000", + "display": "Neonatal nurse (occupation)" + }, + { + "code": "224550000", + "display": "Neurology nurse (occupation)" + }, + { + "code": "224551001", + "display": "Industrial nurse (occupation)" + }, + { + "code": "224552008", + "display": "Oncology nurse (occupation)" + }, + { + "code": "224554009", + "display": "Marie Curie nurse (occupation)" + }, + { + "code": "224555005", + "display": "Pain control nurse (occupation)" + }, + { + "code": "224556006", + "display": "Palliative care nurse (occupation)" + }, + { + "code": "224557002", + "display": "Chemotherapy nurse (occupation)" + }, + { + "code": "224558007", + "display": "Radiotherapy nurse (occupation)" + }, + { + "code": "224559004", + "display": "Recovery nurse (occupation)" + }, + { + "code": "224560009", + "display": "Stoma care nurse (occupation)" + }, + { + "code": "224562001", + "display": "Pediatric nurse (occupation)" + }, + { + "code": "224563006", + "display": "Mental health nurse (occupation)" + }, + { + "code": "224564000", + "display": "Community mental health nurse (occupation)" + }, + { + "code": "224565004", + "display": "Renal nurse (occupation)" + }, + { + "code": "224566003", + "display": "Hemodialysis nurse (occupation)" + }, + { + "code": "224567007", + "display": "Tissue viability nurse (occupation)" + }, + { + "code": "224569005", + "display": "Nurse grade (occupation)" + }, + { + "code": "224570006", + "display": "Clinical nurse specialist (occupation)" + }, + { + "code": "224571005", + "display": "Nurse practitioner (occupation)" + }, + { + "code": "224572003", + "display": "Nursing sister (occupation)" + }, + { + "code": "224573008", + "display": "Charge nurse (occupation)" + }, + { + "code": "224574002", + "display": "Ward manager (occupation)" + }, + { + "code": "224575001", + "display": "Nursing team leader (occupation)" + }, + { + "code": "224576000", + "display": "Nursing assistant (occupation)" + }, + { + "code": "224577009", + "display": "Healthcare assistant (occupation)" + }, + { + "code": "224578004", + "display": "Nursery nurse (occupation)" + }, + { + "code": "224579007", + "display": "Healthcare service manager (occupation)" + }, + { + "code": "224580005", + "display": "Occupational health service manager (occupation)" + }, + { + "code": "224581009", + "display": "Community nurse manager (occupation)" + }, + { + "code": "224583007", + "display": "Behavior therapist (occupation)" + }, + { + "code": "224584001", + "display": "Behavior therapy assistant (occupation)" + }, + { + "code": "224585000", + "display": "Drama therapist (occupation)" + }, + { + "code": "224586004", + "display": "Domiciliary occupational therapist (occupation)" + }, + { + "code": "224587008", + "display": "Occupational therapy helper (occupation)" + }, + { + "code": "224588003", + "display": "Psychotherapist (occupation)" + }, + { + "code": "224589006", + "display": "Community-based physiotherapist (occupation)" + }, + { + "code": "224590002", + "display": "Play therapist (occupation)" + }, + { + "code": "224591003", + "display": "Play specialist (occupation)" + }, + { + "code": "224592005", + "display": "Play leader (occupation)" + }, + { + "code": "224593000", + "display": "Community-based speech/language therapist (occupation)" + }, + { + "code": "224594006", + "display": "Speech/language assistant (occupation)" + }, + { + "code": "224595007", + "display": "Professional counselor (occupation)" + }, + { + "code": "224596008", + "display": "Marriage guidance counselor (occupation)" + }, + { + "code": "224597004", + "display": "Trained nurse counselor (occupation)" + }, + { + "code": "224598009", + "display": "Trained social worker counselor (occupation)" + }, + { + "code": "224599001", + "display": "Trained personnel counselor (occupation)" + }, + { + "code": "224600003", + "display": "Psychoanalyst (occupation)" + }, + { + "code": "224601004", + "display": "Assistant psychologist (occupation)" + }, + { + "code": "224602006", + "display": "Community-based podiatrist (occupation)" + }, + { + "code": "224603001", + "display": "Foot care worker (occupation)" + }, + { + "code": "224604007", + "display": "Audiometrician (occupation)" + }, + { + "code": "224606009", + "display": "Technical healthcare occupation (occupation)" + }, + { + "code": "224607000", + "display": "Occupational therapy technical instructor (occupation)" + }, + { + "code": "224608005", + "display": "Administrative healthcare staff (occupation)" + }, + { + "code": "224609002", + "display": "Complementary health worker (occupation)" + }, + { + "code": "224610007", + "display": "Supporting services personnel (occupation)" + }, + { + "code": "224614003", + "display": "Research associate (occupation)" + }, + { + "code": "224615002", + "display": "Research nurse (occupation)" + }, + { + "code": "224620002", + "display": "Human aid to communication (occupation)" + }, + { + "code": "224621003", + "display": "Palantypist (occupation)" + }, + { + "code": "224622005", + "display": "Note taker (occupation)" + }, + { + "code": "224623000", + "display": "Cuer (occupation)" + }, + { + "code": "224624006", + "display": "Lipspeaker (occupation)" + }, + { + "code": "224625007", + "display": "Interpreter for British sign language (occupation)" + }, + { + "code": "224626008", + "display": "Interpreter for Signs supporting English (occupation)" + }, + { + "code": "224936003", + "display": "General practitioner locum (occupation)" + }, + { + "code": "22515006", + "display": "Medical assistant (occupation)" + }, + { + "code": "225725005", + "display": "Chaplain (occupation)" + }, + { + "code": "225726006", + "display": "Lactation consultant (occupation)" + }, + { + "code": "225727002", + "display": "Midwife counselor (occupation)" + }, + { + "code": "22731001", + "display": "Orthopedic surgeon (occupation)" + }, + { + "code": "229774002", + "display": "Caregiver (occupation)" + }, + { + "code": "22983004", + "display": "Thoracic surgeon (occupation)" + }, + { + "code": "23278007", + "display": "Community health physician (occupation)" + }, + { + "code": "24430003", + "display": "Physical medicine specialist (occupation)" + }, + { + "code": "24590004", + "display": "Urologist (occupation)" + }, + { + "code": "25941000087102", + "display": "Adult gerontology primary care nurse practitioner (occupation)" + }, + { + "code": "25961008", + "display": "Electroencephalography specialist (occupation)" + }, + { + "code": "26031000087100", + "display": "Pediatric nurse practitioner (occupation)" + }, + { + "code": "26042002", + "display": "Dental hygienist (occupation)" + }, + { + "code": "26071000087103", + "display": "Primary health care nurse practitioner (occupation)" + }, + { + "code": "26091000087104", + "display": "Public health nurse practitioner (occupation)" + }, + { + "code": "26369006", + "display": "Public health nurse (occupation)" + }, + { + "code": "265937000", + "display": "Nursing occupation (occupation)" + }, + { + "code": "265939002", + "display": "Medical/dental technicians (occupation)" + }, + { + "code": "28229004", + "display": "Optometrist (occupation)" + }, + { + "code": "283875005", + "display": "Parkinson's disease nurse (occupation)" + }, + { + "code": "28411006", + "display": "Neonatologist (occupation)" + }, + { + "code": "28544002", + "display": "Medical biochemist (occupation)" + }, + { + "code": "302211009", + "display": "Specialist registrar (occupation)" + }, + { + "code": "303124005", + "display": "Member of mental health review tribunal (occupation)" + }, + { + "code": "303129000", + "display": "Hospital manager (occupation)" + }, + { + "code": "303133007", + "display": "Responsible medical officer (occupation)" + }, + { + "code": "303134001", + "display": "Independent doctor (occupation)" + }, + { + "code": "304291006", + "display": "Bereavement counselor (occupation)" + }, + { + "code": "304292004", + "display": "Surgeon (occupation)" + }, + { + "code": "307988006", + "display": "Medical technician (occupation)" + }, + { + "code": "308002005", + "display": "Remedial therapist (occupation)" + }, + { + "code": "309294001", + "display": "Emergency department physician (occupation)" + }, + { + "code": "309295000", + "display": "Clinical oncologist (occupation)" + }, + { + "code": "309296004", + "display": "Family planning doctor (occupation)" + }, + { + "code": "309322005", + "display": "Associate general practitioner (occupation)" + }, + { + "code": "309323000", + "display": "Partner of general practitioner (occupation)" + }, + { + "code": "309324006", + "display": "General practitioner assistant (occupation)" + }, + { + "code": "309326008", + "display": "Deputizing general practitioner (occupation)" + }, + { + "code": "309327004", + "display": "General practitioner registrar (occupation)" + }, + { + "code": "309328009", + "display": "Ambulatory pediatrician (occupation)" + }, + { + "code": "309329001", + "display": "Community pediatrician (occupation)" + }, + { + "code": "309330006", + "display": "Pediatric cardiologist (occupation)" + }, + { + "code": "309331005", + "display": "Pediatric endocrinologist (occupation)" + }, + { + "code": "309332003", + "display": "Pediatric gastroenterologist (occupation)" + }, + { + "code": "309333008", + "display": "Pediatric nephrologist (occupation)" + }, + { + "code": "309334002", + "display": "Pediatric neurologist (occupation)" + }, + { + "code": "309335001", + "display": "Pediatric rheumatologist (occupation)" + }, + { + "code": "309336000", + "display": "Pediatric oncologist (occupation)" + }, + { + "code": "309337009", + "display": "Pain management specialist (occupation)" + }, + { + "code": "309338004", + "display": "Intensive care specialist (occupation)" + }, + { + "code": "309339007", + "display": "Adult intensive care specialist (occupation)" + }, + { + "code": "309340009", + "display": "Pediatric intensive care specialist (occupation)" + }, + { + "code": "309341008", + "display": "Blood transfusion doctor (occupation)" + }, + { + "code": "309342001", + "display": "Histopathologist (occupation)" + }, + { + "code": "309343006", + "display": "Physician (occupation)" + }, + { + "code": "309345004", + "display": "Chest physician (occupation)" + }, + { + "code": "309346003", + "display": "Thoracic physician (occupation)" + }, + { + "code": "309347007", + "display": "Clinical hematologist (occupation)" + }, + { + "code": "309348002", + "display": "Clinical neurophysiologist (occupation)" + }, + { + "code": "309349005", + "display": "Clinical physiologist (occupation)" + }, + { + "code": "309350005", + "display": "Diabetologist (occupation)" + }, + { + "code": "309351009", + "display": "Andrologist (occupation)" + }, + { + "code": "309352002", + "display": "Neuroendocrinologist (occupation)" + }, + { + "code": "309353007", + "display": "Reproductive endocrinologist (occupation)" + }, + { + "code": "309354001", + "display": "Thyroidologist (occupation)" + }, + { + "code": "309355000", + "display": "Clinical geneticist (occupation)" + }, + { + "code": "309356004", + "display": "Clinical cytogeneticist (occupation)" + }, + { + "code": "309357008", + "display": "Clinical molecular geneticist (occupation)" + }, + { + "code": "309358003", + "display": "Genitourinary medicine physician (occupation)" + }, + { + "code": "309359006", + "display": "Palliative care physician (occupation)" + }, + { + "code": "309360001", + "display": "Rehabilitation physician (occupation)" + }, + { + "code": "309361002", + "display": "Child and adolescent psychiatrist (occupation)" + }, + { + "code": "309362009", + "display": "Forensic psychiatrist (occupation)" + }, + { + "code": "309363004", + "display": "Liaison psychiatrist (occupation)" + }, + { + "code": "309364005", + "display": "Psychogeriatrician (occupation)" + }, + { + "code": "309366007", + "display": "Rehabilitation psychiatrist (occupation)" + }, + { + "code": "309367003", + "display": "Obstetrician and gynecologist (occupation)" + }, + { + "code": "309368008", + "display": "Breast surgeon (occupation)" + }, + { + "code": "309369000", + "display": "Cardiothoracic surgeon (occupation)" + }, + { + "code": "309371000", + "display": "Cardiac surgeon (occupation)" + }, + { + "code": "309372007", + "display": "Ear, nose and throat surgeon (occupation)" + }, + { + "code": "309373002", + "display": "Endocrine surgeon (occupation)" + }, + { + "code": "309374008", + "display": "Thyroid surgeon (occupation)" + }, + { + "code": "309375009", + "display": "Pituitary surgeon (occupation)" + }, + { + "code": "309376005", + "display": "Gastrointestinal surgeon (occupation)" + }, + { + "code": "309377001", + "display": "General gastrointestinal surgeon (occupation)" + }, + { + "code": "309378006", + "display": "Upper gastrointestinal surgeon (occupation)" + }, + { + "code": "309379003", + "display": "Colorectal surgeon (occupation)" + }, + { + "code": "309380000", + "display": "Hand surgeon (occupation)" + }, + { + "code": "309381001", + "display": "Hepatobiliary surgeon (occupation)" + }, + { + "code": "309382008", + "display": "Ophthalmic surgeon (occupation)" + }, + { + "code": "309383003", + "display": "Pediatric surgeon (occupation)" + }, + { + "code": "309384009", + "display": "Pancreatic surgeon (occupation)" + }, + { + "code": "309385005", + "display": "Transplant surgeon (occupation)" + }, + { + "code": "309386006", + "display": "Trauma surgeon (occupation)" + }, + { + "code": "309388007", + "display": "Vascular surgeon (occupation)" + }, + { + "code": "309389004", + "display": "Medical practitioner grade (occupation)" + }, + { + "code": "309390008", + "display": "Hospital consultant (occupation)" + }, + { + "code": "309391007", + "display": "Visiting specialist registrar (occupation)" + }, + { + "code": "309392000", + "display": "Research registrar (occupation)" + }, + { + "code": "309393005", + "display": "General practitioner grade (occupation)" + }, + { + "code": "309394004", + "display": "General practitioner principal (occupation)" + }, + { + "code": "309395003", + "display": "Hospital specialist (occupation)" + }, + { + "code": "309396002", + "display": "Associate specialist (occupation)" + }, + { + "code": "309397006", + "display": "Research fellow (occupation)" + }, + { + "code": "309398001", + "display": "Profession allied to medicine (occupation)" + }, + { + "code": "309399009", + "display": "Hospital-based dietitian (occupation)" + }, + { + "code": "309400002", + "display": "Domiciliary physiotherapist (occupation)" + }, + { + "code": "309401003", + "display": "General practitioner-based physiotherapist (occupation)" + }, + { + "code": "309402005", + "display": "Hospital-based physiotherapist (occupation)" + }, + { + "code": "309403000", + "display": "Private physiotherapist (occupation)" + }, + { + "code": "309404006", + "display": "Physiotherapy helper (occupation)" + }, + { + "code": "309409001", + "display": "Hospital-based speech and language therapist (occupation)" + }, + { + "code": "309410006", + "display": "Arts therapist (occupation)" + }, + { + "code": "309411005", + "display": "Dance therapist (occupation)" + }, + { + "code": "309412003", + "display": "Music therapist (occupation)" + }, + { + "code": "309413008", + "display": "Renal dietitian (occupation)" + }, + { + "code": "309414002", + "display": "Liver dietitian (occupation)" + }, + { + "code": "309415001", + "display": "Oncology dietitian (occupation)" + }, + { + "code": "309416000", + "display": "Pediatric dietitian (occupation)" + }, + { + "code": "309417009", + "display": "Diabetes dietitian (occupation)" + }, + { + "code": "309418004", + "display": "Audiologist (occupation)" + }, + { + "code": "309419007", + "display": "Hearing therapist (occupation)" + }, + { + "code": "309420001", + "display": "Audiological scientist (occupation)" + }, + { + "code": "309421002", + "display": "Hearing aid dispenser (occupation)" + }, + { + "code": "309422009", + "display": "Community-based occupational therapist (occupation)" + }, + { + "code": "309423004", + "display": "Hospital-based occupational therapist (occupation)" + }, + { + "code": "309427003", + "display": "Social services occupational therapist (occupation)" + }, + { + "code": "309428008", + "display": "Orthotist (occupation)" + }, + { + "code": "309429000", + "display": "Surgical fitter (occupation)" + }, + { + "code": "309434001", + "display": "Hospital-based podiatrist (occupation)" + }, + { + "code": "309435000", + "display": "Podiatry assistant (occupation)" + }, + { + "code": "309436004", + "display": "Lymphedema nurse (occupation)" + }, + { + "code": "309437008", + "display": "Community learning disabilities nurse (occupation)" + }, + { + "code": "309439006", + "display": "Clinical nurse teacher (occupation)" + }, + { + "code": "309440008", + "display": "Community practice nurse teacher (occupation)" + }, + { + "code": "309441007", + "display": "Nurse tutor (occupation)" + }, + { + "code": "309442000", + "display": "Nurse teacher practitioner (occupation)" + }, + { + "code": "309443005", + "display": "Nurse lecturer practitioner (occupation)" + }, + { + "code": "309444004", + "display": "Outreach nurse (occupation)" + }, + { + "code": "309445003", + "display": "Anesthetic nurse (occupation)" + }, + { + "code": "309446002", + "display": "Nurse manager (occupation)" + }, + { + "code": "309450009", + "display": "Nurse administrator (occupation)" + }, + { + "code": "309452001", + "display": "Midwifery grade (occupation)" + }, + { + "code": "309453006", + "display": "Registered midwife (occupation)" + }, + { + "code": "309454000", + "display": "Student midwife (occupation)" + }, + { + "code": "309455004", + "display": "Parentcraft sister (occupation)" + }, + { + "code": "309457007", + "display": "Vicar (occupation)" + }, + { + "code": "309459005", + "display": "Healthcare professional grade (occupation)" + }, + { + "code": "309460000", + "display": "Restorative dentist (occupation)" + }, + { + "code": "310170009", + "display": "Pediatric audiologist (occupation)" + }, + { + "code": "310171008", + "display": "Immunopathologist (occupation)" + }, + { + "code": "310172001", + "display": "Audiological physician (occupation)" + }, + { + "code": "310173006", + "display": "Clinical pharmacologist (occupation)" + }, + { + "code": "310174000", + "display": "Private doctor (occupation)" + }, + { + "code": "310175004", + "display": "Agency nurse (occupation)" + }, + { + "code": "310176003", + "display": "Behavioral therapist nurse (occupation)" + }, + { + "code": "310177007", + "display": "Cardiac rehabilitation nurse (occupation)" + }, + { + "code": "310178002", + "display": "Genitourinary nurse (occupation)" + }, + { + "code": "310179005", + "display": "Rheumatology nurse specialist (occupation)" + }, + { + "code": "310180008", + "display": "Continence nurse (occupation)" + }, + { + "code": "310181007", + "display": "Contact tracing nurse (occupation)" + }, + { + "code": "310182000", + "display": "General nurse (occupation)" + }, + { + "code": "310184004", + "display": "Liaison nurse (occupation)" + }, + { + "code": "310185003", + "display": "Diabetic liaison nurse (occupation)" + }, + { + "code": "310186002", + "display": "Nurse psychotherapist (occupation)" + }, + { + "code": "310187006", + "display": "Company nurse (occupation)" + }, + { + "code": "310188001", + "display": "Hospital midwife (occupation)" + }, + { + "code": "310189009", + "display": "Genetic counselor (occupation)" + }, + { + "code": "310190000", + "display": "Mental health counselor (occupation)" + }, + { + "code": "310191001", + "display": "Clinical psychologist (occupation)" + }, + { + "code": "310192008", + "display": "Educational psychologist (occupation)" + }, + { + "code": "310193003", + "display": "Coroner (occupation)" + }, + { + "code": "310194009", + "display": "Appliance officer (occupation)" + }, + { + "code": "310512001", + "display": "Medical oncologist (occupation)" + }, + { + "code": "311441001", + "display": "School medical officer (occupation)" + }, + { + "code": "312485001", + "display": "Integrated midwife (occupation)" + }, + { + "code": "3430008", + "display": "Radiation therapist (occupation)" + }, + { + "code": "36682004", + "display": "Physiotherapist (occupation)" + }, + { + "code": "37154003", + "display": "Periodontist (occupation)" + }, + { + "code": "372102007", + "display": "Registered nurse first assist (occupation)" + }, + { + "code": "373864002", + "display": "Outpatient (person)" + }, + { + "code": "37504001", + "display": "Orthodontist (occupation)" + }, + { + "code": "3842006", + "display": "Chiropractor (occupation)" + }, + { + "code": "387619007", + "display": "Optician (occupation)" + }, + { + "code": "394572006", + "display": "Medical secretary (occupation)" + }, + { + "code": "394618009", + "display": "Hospital nurse (occupation)" + }, + { + "code": "39677007", + "display": "Internal medicine specialist (occupation)" + }, + { + "code": "397897005", + "display": "Paramedic (occupation)" + }, + { + "code": "397903001", + "display": "Staff grade obstetrician (occupation)" + }, + { + "code": "397908005", + "display": "Staff grade practitioner (occupation)" + }, + { + "code": "3981000175106", + "display": "Nurse complex case manager (occupation)" + }, + { + "code": "398130009", + "display": "Medical student (occupation)" + }, + { + "code": "398238009", + "display": "Acting obstetric registrar (occupation)" + }, + { + "code": "40127002", + "display": "Dietitian (general) (occupation)" + }, + { + "code": "40204001", + "display": "Hematologist (occupation)" + }, + { + "code": "404940000", + "display": "Physiotherapist technical instructor (occupation)" + }, + { + "code": "405277009", + "display": "Resident physician (occupation)" + }, + { + "code": "405278004", + "display": "Certified registered nurse anesthetist (occupation)" + }, + { + "code": "405279007", + "display": "Attending physician (occupation)" + }, + { + "code": "405623001", + "display": "Assigned practitioner (occupation)" + }, + { + "code": "405684005", + "display": "Professional initiating surgical case (occupation)" + }, + { + "code": "405685006", + "display": "Professional providing staff relief during surgical procedure (occupation)" + }, + { + "code": "40570005", + "display": "Interpreter (occupation)" + }, + { + "code": "407542009", + "display": "Informal caregiver (person)" + }, + { + "code": "407543004", + "display": "Primary caregiver (person)" + }, + { + "code": "408290003", + "display": "Diabetes key contact (occupation)" + }, + { + "code": "408798009", + "display": "Consultant pediatrician (occupation)" + }, + { + "code": "408799001", + "display": "Consultant neonatologist (occupation)" + }, + { + "code": "409974004", + "display": "Health educator (occupation)" + }, + { + "code": "409975003", + "display": "Certified health education specialist (occupation)" + }, + { + "code": "413854007", + "display": "Circulating nurse (occupation)" + }, + { + "code": "415075003", + "display": "Perioperative nurse (occupation)" + }, + { + "code": "415506007", + "display": "Scrub nurse (occupation)" + }, + { + "code": "416034003", + "display": "Primary screener (person)" + }, + { + "code": "416035002", + "display": "Secondary screener (person)" + }, + { + "code": "416160000", + "display": "Fellow of American Academy of Osteopathy (occupation)" + }, + { + "code": "4162009", + "display": "Dental assistant (occupation)" + }, + { + "code": "41672002", + "display": "Respiratory disease specialist (occupation)" + }, + { + "code": "416800000", + "display": "Inpatient (person)" + }, + { + "code": "41904004", + "display": "Medical X-ray technician (occupation)" + }, + { + "code": "420158005", + "display": "Performer of method (person)" + }, + { + "code": "420409002", + "display": "Oculoplastic surgeon (occupation)" + }, + { + "code": "420678001", + "display": "Retinal surgeon (occupation)" + }, + { + "code": "421841007", + "display": "Admitting physician (occupation)" + }, + { + "code": "422140007", + "display": "Medical ophthalmologist (occupation)" + }, + { + "code": "422234006", + "display": "Ophthalmologist (occupation)" + }, + { + "code": "428024001", + "display": "Clinical trial participant (person)" + }, + { + "code": "429577009", + "display": "Patient advocate (person)" + }, + { + "code": "43018001", + "display": "Babysitter (occupation)" + }, + { + "code": "432100008", + "display": "Health coach (occupation)" + }, + { + "code": "43702002", + "display": "Occupational health nurse (occupation)" + }, + { + "code": "440051000124108", + "display": "Medical examiner (occupation)" + }, + { + "code": "442251000124100", + "display": "Licensed practical nurse (occupation)" + }, + { + "code": "442867008", + "display": "Respiratory therapist (occupation)" + }, + { + "code": "443090005", + "display": "Podiatric surgeon (occupation)" + }, + { + "code": "444912007", + "display": "Hypnotherapist (occupation)" + }, + { + "code": "445313000", + "display": "Asthma nurse specialist (occupation)" + }, + { + "code": "445451001", + "display": "Nurse case manager (occupation)" + }, + { + "code": "445521000124102", + "display": "Advanced practice midwife (occupation)" + }, + { + "code": "445531000124104", + "display": "Lay midwife (occupation)" + }, + { + "code": "446050000", + "display": "Primary care physician (occupation)" + }, + { + "code": "44652006", + "display": "Pharmaceutical assistant (occupation)" + }, + { + "code": "446701002", + "display": "Addiction medicine specialist (occupation)" + }, + { + "code": "449161006", + "display": "Physician assistant (occupation)" + }, + { + "code": "450044741000087100", + "display": "Acupuncturist (occupation)" + }, + { + "code": "453061000124100", + "display": "Pharmacist specialist (person)" + }, + { + "code": "453071000124107", + "display": "Primary care pharmacist (person)" + }, + { + "code": "453081000124105", + "display": "Infusion pharmacist (person)" + }, + { + "code": "453091000124108", + "display": "Receiving provider (person)" + }, + { + "code": "453101000124102", + "display": "Consultant pharmacist (person)" + }, + { + "code": "453111000124104", + "display": "Dispensing pharmacist (person)" + }, + { + "code": "453121000124107", + "display": "Emergency department healthcare professional (occupation)" + }, + { + "code": "453231000124104", + "display": "Primary care provider (occupation)" + }, + { + "code": "45440000", + "display": "Rheumatologist (occupation)" + }, + { + "code": "45544007", + "display": "Neurosurgeon (occupation)" + }, + { + "code": "457141000124107", + "display": "Locum tenens attending physician (occupation)" + }, + { + "code": "457151000124109", + "display": "Locum tenens admitting physician (occupation)" + }, + { + "code": "45956004", + "display": "Sanitarian (occupation)" + }, + { + "code": "46255001", + "display": "Pharmacist (occupation)" + }, + { + "code": "471302004", + "display": "Government midwife (occupation)" + }, + { + "code": "48639005", + "display": "Ordained minister (occupation)" + }, + { + "code": "48740002", + "display": "Philologist (occupation)" + }, + { + "code": "49203003", + "display": "Dispensing optician (occupation)" + }, + { + "code": "49993003", + "display": "Oral surgeon (occupation)" + }, + { + "code": "50149000", + "display": "Endodontist (occupation)" + }, + { + "code": "5191000124109", + "display": "Private midwife (occupation)" + }, + { + "code": "5275007", + "display": "Auxiliary nurse (occupation)" + }, + { + "code": "53564008", + "display": "Ordained clergy (occupation)" + }, + { + "code": "54503009", + "display": "Faith healer (occupation)" + }, + { + "code": "56397003", + "display": "Neurologist (occupation)" + }, + { + "code": "56466003", + "display": "Public health physician (occupation)" + }, + { + "code": "56542007", + "display": "Medical record administrator (occupation)" + }, + { + "code": "56545009", + "display": "Cardiovascular surgeon (occupation)" + }, + { + "code": "57654006", + "display": "Fixed prosthodontist (occupation)" + }, + { + "code": "59058001", + "display": "General physician (occupation)" + }, + { + "code": "59169001", + "display": "Orthopedic technician (occupation)" + }, + { + "code": "59317003", + "display": "Dental prosthesis maker and repairer (occupation)" + }, + { + "code": "59944000", + "display": "Psychologist (occupation)" + }, + { + "code": "60008001", + "display": "Public health nutritionist (occupation)" + }, + { + "code": "611581000124105", + "display": "Cognitive neuropsychologist (occupation)" + }, + { + "code": "611601000124100", + "display": "Neonatal nurse practitioner (occupation)" + }, + { + "code": "611611000124102", + "display": "Counseling psychologist (occupation)" + }, + { + "code": "611621000124105", + "display": "Clinical neuropsychologist (occupation)" + }, + { + "code": "611701000124107", + "display": "Sleep psychologist (occupation)" + }, + { + "code": "61207006", + "display": "Medical pathologist (occupation)" + }, + { + "code": "61246008", + "display": "Laboratory medicine specialist (occupation)" + }, + { + "code": "61345009", + "display": "Otorhinolaryngologist (occupation)" + }, + { + "code": "61894003", + "display": "Endocrinologist (occupation)" + }, + { + "code": "62247001", + "display": "Family medicine specialist (occupation)" + }, + { + "code": "63098009", + "display": "Clinical immunologist (occupation)" + }, + { + "code": "64220005", + "display": "Religious worker (member of religious order) (occupation)" + }, + { + "code": "651501000124106", + "display": "Pediatric emergency medicine physician (occupation)" + }, + { + "code": "65803006", + "display": "Missionary (occupation)" + }, + { + "code": "66476003", + "display": "Oral pathologist (occupation)" + }, + { + "code": "66862007", + "display": "Radiologist (occupation)" + }, + { + "code": "671101000124102", + "display": "Family nurse practitioner (occupation)" + }, + { + "code": "67811000052107", + "display": "Pediatric hematology and oncology physician (occupation)" + }, + { + "code": "6816002", + "display": "Specialized nurse (occupation)" + }, + { + "code": "68191000052106", + "display": "Neuropsychologist (occupation)" + }, + { + "code": "6868009", + "display": "Hospital administrator (occupation)" + }, + { + "code": "68867008", + "display": "Public health dentist (occupation)" + }, + { + "code": "68950000", + "display": "Prosthodontist (occupation)" + }, + { + "code": "69280009", + "display": "Specialized physician (occupation)" + }, + { + "code": "71838004", + "display": "Gastroenterologist (occupation)" + }, + { + "code": "720503005", + "display": "Sleep medicine specialist (occupation)" + }, + { + "code": "721936008", + "display": "Occupation medicine specialist (occupation)" + }, + { + "code": "721937004", + "display": "Preventive medicine specialist (occupation)" + }, + { + "code": "721938009", + "display": "Tropical medicine specialist (occupation)" + }, + { + "code": "721939001", + "display": "Vascular medicine specialist (occupation)" + }, + { + "code": "721940004", + "display": "Legal medicine specialist (occupation)" + }, + { + "code": "721941000", + "display": "Health psychologist (occupation)" + }, + { + "code": "721942007", + "display": "Cardiovascular perfusionist (occupation)" + }, + { + "code": "721943002", + "display": "Clinical immunology and allergy specialist (occupation)" + }, + { + "code": "73265009", + "display": "Nursing aid (occupation)" + }, + { + "code": "734293001", + "display": "Clinical pharmacist (occupation)" + }, + { + "code": "734294007", + "display": "Pharmacist prescriber (occupation)" + }, + { + "code": "75271001", + "display": "Professional midwife (occupation)" + }, + { + "code": "76166008", + "display": "Practical aid (pharmacy) (occupation)" + }, + { + "code": "76231001", + "display": "Osteopath (occupation)" + }, + { + "code": "763292005", + "display": "Radiation oncologist (occupation)" + }, + { + "code": "768730001", + "display": "Home health aide (occupation)" + }, + { + "code": "768731002", + "display": "Home helper (occupation)" + }, + { + "code": "768732009", + "display": "School health educator (occupation)" + }, + { + "code": "768733004", + "display": "Spiritual advisor (occupation)" + }, + { + "code": "768734005", + "display": "Research study coordinator (occupation)" + }, + { + "code": "768815003", + "display": "Investigative specialist (occupation)" + }, + { + "code": "768816002", + "display": "Associate investigator (occupation)" + }, + { + "code": "768817006", + "display": "Co-principal investigator (occupation)" + }, + { + "code": "768818001", + "display": "Principal investigator (occupation)" + }, + { + "code": "768819009", + "display": "Medically responsible investigator (occupation)" + }, + { + "code": "768820003", + "display": "Care coordinator (occupation)" + }, + { + "code": "768821004", + "display": "Care team coordinator (occupation)" + }, + { + "code": "768822006", + "display": "Rehabilitation coordinator (occupation)" + }, + { + "code": "768825008", + "display": "Doula (occupation)" + }, + { + "code": "768826009", + "display": "Crisis counselor (occupation)" + }, + { + "code": "768827000", + "display": "Nutritionist (occupation)" + }, + { + "code": "768828005", + "display": "Epidemiologist (occupation)" + }, + { + "code": "768829002", + "display": "Community dietician (occupation)" + }, + { + "code": "768832004", + "display": "Case manager (occupation)" + }, + { + "code": "768833009", + "display": "Discharging physician (occupation)" + }, + { + "code": "768834003", + "display": "Disease manager (occupation)" + }, + { + "code": "768836001", + "display": "Patient navigator (occupation)" + }, + { + "code": "768837005", + "display": "Hospitalist (occupation)" + }, + { + "code": "768839008", + "display": "Consultant (occupation)" + }, + { + "code": "76899008", + "display": "Infectious disease specialist (occupation)" + }, + { + "code": "769038007", + "display": "Researcher (occupation)" + }, + { + "code": "78703002", + "display": "General surgeon (occupation)" + }, + { + "code": "78729002", + "display": "Diagnostic radiologist (occupation)" + }, + { + "code": "789543004", + "display": "Sonographer (occupation)" + }, + { + "code": "79898004", + "display": "Auxiliary midwife (occupation)" + }, + { + "code": "79918004", + "display": "Ordained priest (occupation)" + }, + { + "code": "80409005", + "display": "Translator (occupation)" + }, + { + "code": "80546007", + "display": "Occupational therapist (occupation)" + }, + { + "code": "80584001", + "display": "Psychiatrist (occupation)" + }, + { + "code": "80933006", + "display": "Nuclear medicine specialist (occupation)" + }, + { + "code": "81464008", + "display": "Clinical pathologist (occupation)" + }, + { + "code": "82296001", + "display": "Pediatrician (occupation)" + }, + { + "code": "83273008", + "display": "Anatomic pathologist (occupation)" + }, + { + "code": "83685006", + "display": "Gynecologist (occupation)" + }, + { + "code": "840583002", + "display": "Allied health assistant (occupation)" + }, + { + "code": "840584008", + "display": "Allied health student (occupation)" + }, + { + "code": "85733003", + "display": "General pathologist (occupation)" + }, + { + "code": "8724009", + "display": "Plastic surgeon (occupation)" + }, + { + "code": "878785002", + "display": "Clinical respiratory physiologist (occupation)" + }, + { + "code": "878786001", + "display": "Operating room technician (occupation)" + }, + { + "code": "878787005", + "display": "Anesthesia technician (occupation)" + }, + { + "code": "88189002", + "display": "Anesthesiologist (occupation)" + }, + { + "code": "897187007", + "display": "Sexual assault nurse examiner (occupation)" + }, + { + "code": "90201008", + "display": "Pedodontist (occupation)" + }, + { + "code": "90655003", + "display": "Geriatrics specialist (occupation)" + }, + { + "code": "9371000175105", + "display": "Adolescent medicine specialist (occupation)" + } + ] + }, + { + "system": "ParticipationFunction", + "concept": [ + { + "code": "ADMPHYS", + "display": "admitting physician" + }, + { + "code": "ANEST", + "display": "anesthesist" + }, + { + "code": "ANRS", + "display": "anesthesia nurse" + }, + { + "code": "ASSEMBLER", + "display": "assembly software" + }, + { + "code": "ATTPHYS", + "display": "attending physician" + }, + { + "code": "AUCG", + "display": "caregiver information receiver" + }, + { + "code": "AUCOV", + "display": "consent overrider" + }, + { + "code": "AUEMROV", + "display": "emergency overrider" + }, + { + "code": "AULR", + "display": "legitimate relationship information receiver" + }, + { + "code": "AUTM", + "display": "care team information receiver" + }, + { + "code": "AUWA", + "display": "work area information receiver" + }, + { + "code": "CLMADJ", + "display": "claims adjudication" + }, + { + "code": "COMPOSER", + "display": "composer software" + }, + { + "code": "DISPHYS", + "display": "discharging physician" + }, + { + "code": "ENROLL", + "display": "enrollment broker" + }, + { + "code": "FASST", + "display": "first assistant surgeon" + }, + { + "code": "FFSMGT", + "display": "ffs management" + }, + { + "code": "FULINRD", + "display": "fully insured" + }, + { + "code": "GRDCON", + "display": "legal guardian consent author" + }, + { + "code": "MCMGT", + "display": "managed care management" + }, + { + "code": "MDWF", + "display": "midwife" + }, + { + "code": "NASST", + "display": "nurse assistant" + }, + { + "code": "PAYORCNTR", + "display": "payor contracting" + }, + { + "code": "PCP", + "display": "primary care physician" + }, + { + "code": "POACON", + "display": "healthcare power of attorney consent author" + }, + { + "code": "PRCON", + "display": "personal representative consent author" + }, + { + "code": "PRISURG", + "display": "primary surgeon" + }, + { + "code": "PROMSK", + "display": "authorized provider masking author" + }, + { + "code": "PROVMGT", + "display": "provider management" + }, + { + "code": "REINS", + "display": "reinsures" + }, + { + "code": "RETROCES", + "display": "retrocessionaires" + }, + { + "code": "REVIEWER", + "display": "reviewer" + }, + { + "code": "RNDPHYS", + "display": "rounding physician" + }, + { + "code": "SASST", + "display": "second assistant surgeon" + }, + { + "code": "SELFINRD", + "display": "self insured" + }, + { + "code": "SNRS", + "display": "scrub nurse" + }, + { + "code": "SUBCON", + "display": "subject of consent author" + }, + { + "code": "SUBCTRT", + "display": "subcontracting risk" + }, + { + "code": "TASST", + "display": "third assistant" + }, + { + "code": "UMGT", + "display": "utilization management" + }, + { + "code": "UNDERWRTNG", + "display": "underwriting" + }, + { + "code": "_AuthorizedParticipationFunction", + "display": "AuthorizedParticipationFunction" + }, + { + "code": "_AuthorizedReceiverParticipationFunction", + "display": "AuthorizedReceiverParticipationFunction" + }, + { + "code": "_ConsenterParticipationFunction", + "display": "ConsenterParticipationFunction" + }, + { + "code": "_CoverageParticipationFunction", + "display": "CoverageParticipationFunction" + }, + { + "code": "_OverriderParticipationFunction", + "display": "OverriderParticipationFunction" + }, + { + "code": "_PayorParticipationFunction", + "display": "PayorParticipationFunction" + }, + { + "code": "_SponsorParticipationFunction", + "display": "SponsorParticipationFunction" + }, + { + "code": "_UnderwriterParticipationFunction", + "display": "UnderwriterParticipationFunction" + } + ] + } + ] + } + } + } + ] +} diff --git a/examples/medplum-chat-demo/index.html b/examples/medplum-chat-demo/index.html new file mode 100644 index 0000000000..db26a0f118 --- /dev/null +++ b/examples/medplum-chat-demo/index.html @@ -0,0 +1,14 @@ + + + + + + + Medplum Chat Demo + + +
+ + + + diff --git a/examples/medplum-chat-demo/medplum-chat-demo-screenshot.png b/examples/medplum-chat-demo/medplum-chat-demo-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..e38d0c49bcf68866db8770101adef2a76272592c GIT binary patch literal 697068 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%0D_X zVQfJHk(4w0^`{hIa#Y2iT_qliY2H7yne%V00!8~T)Wx?Q>&65$$a|X~HV@FLeoE3w z@m9J0WO7zQZ855Ke@nIODchh7kfw7zXha4cwT6*&UgVyJ)ya~Cd=H}FY&lL@6g(DMLFiqm~o-E z)8g=P(bA+|nCOL4p9dLHnYT5{u(W=8&A*%*`|{itjJ=N_91|x%!-~YKIA=>%S&k@=jzTS5a zser30)XF3%j3MWpGBAmAKYoU+i6@c99Z zZTM+LSWtT=RdU_#g+>QHvQ&SLMgdM%PxGjHN*v?-^)p=a#31}_Uc8Zdn$Lap!#$o4 zHU~;_?pA)6(p~3VC$*b-MEugmS}UJg336G}XGDV*(}iU=5vL9=dqRVg&-Bo9h}O2h zc;geXq^#WL1BtX!w#LSxi$2hEI3z~zQ7K&2j0Y{owgi4RfbZ3a+q{HEHLxRNlr$W? ziX53wK-T8!HKlqr0vRPgCIpx|Nxaie-6}NWlMd-@1fNHbn44TFJ3~XsKYgle1W{9j zA9pvH;Zy;dXT$;{5ynV7FLSw`lgm7N{pDiA1@Ax-6O`I983!W9U3=z@?_sYR{mO|! zPEy&pzP7pucpWPr?jQSf>9c*hYQcuGo2JHd%~u3{v-$QZsP2Wbjt=KiJkATLJS-wg z@WHFniRv64!(X|S3fL{{ODv(ED`sq2t)R=ZVG8Jph;6RZ68q!AZ7`JqduX{!N#5Og zM0t^c4FIME%b!01y)4%=QJvuLZ;DYY@hRsjg{5=bS08vvRV{H_op+b&-H_ zIH=77wov*@Ujy!+Ubzsaw{Na9KkVxM3a7jfLxlsk?BY=E>{hm4kv(=t)>67Z6AZ4g zO8M)^2_aR2>t*R;UIW=*bs8TxI6e2@I4RzaxOz3Ftox^#EVp9tLt1Px7xq5-V@N(u zcH_awRu-zkqOwZ;9Stl$JLWT|pm39pVb!)Ird_j)}m)-IvH#o;N`&Qzd+Dbv3 zQo!fPV_pV#Q(buty7TVG0b z6d`q{gCf0_vp#&(B(C@TPpE}u^ z3t<627;E2jYjTBINz{x-zB)h_IrYFL9ZVFFEqZ4oR$r$-DNXb4S})Z&|Ac?lq1}9A z_^u+aaFHI6XVSNDTLoO^D%CuBYT{!o+$QY~Jp=sS4S>Mg@Ts~qKvzWv6xeOwP1V>g z5uLDrD0>nA$Qq5*K~;tdwjCz#LA;(M3^V)rlh?0@$5_1JHZ@$1nHVJL$isBz9JH)6 z)|s;sm0tHHviFmfRrrYmH9VNV!S3TetdWpgt%%0DqcW)BfT5e}Ltwun+5C5c(f*fo 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

tSH=;NN%1<4v=#~I`frqlN-y?s`ZB5?= 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%0D4rAwq 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 literal 0 HcmV?d00001 diff --git a/examples/medplum-chat-demo/package.json b/examples/medplum-chat-demo/package.json new file mode 100644 index 0000000000..7baccda948 --- /dev/null +++ b/examples/medplum-chat-demo/package.json @@ -0,0 +1,42 @@ +{ + "name": "medplum-chat-demo", + "version": "3.1.2", + "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.6.2", + "@mantine/hooks": "7.6.2", + "@mantine/notifications": "7.6.2", + "@medplum/core": "3.1.2", + "@medplum/eslint-config": "3.1.2", + "@medplum/fhirtypes": "3.1.2", + "@medplum/react": "3.1.2", + "@tabler/icons-react": "3.0.0", + "@types/node": "20.11.28", + "@types/react": "18.2.66", + "@types/react-dom": "18.2.22", + "@vitejs/plugin-react": "4.2.1", + "postcss": "8.4.36", + "postcss-preset-mantine": "1.13.0", + "react": "18.2.0", + "react-dom": "18.2.0", + "react-router-dom": "6.22.3", + "typescript": "5.4.2", + "vite": "5.1.6" + } +} diff --git a/examples/medplum-chat-demo/postcss.config.mjs b/examples/medplum-chat-demo/postcss.config.mjs new file mode 100644 index 0000000000..feba649756 --- /dev/null +++ b/examples/medplum-chat-demo/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-chat-demo/public/favicon.ico b/examples/medplum-chat-demo/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..b8a7ba9c7bccae4a7e955df5455ed097a2f632db GIT binary patch literal 22382 zcmd6v33L=i8i1RJ7zKUeTix||I*CcR7giIJkPyP5E-dQ`DiIPu4hhFhCI`9C;S@03 znKK~)fViX}U%R#8z7nc=AY|4AzJbgFxLW;$W=Ughtq?yA4O zzpATi{;q0{>%?{Dh7RRG`f|O7b6g0=alXFI@h#msZZf2G2Z`rr-NkWtzszyH;2Oxn z=^$XboIgScr)040|AIR-V}8$rujR!aoRpi?Fd-}N_{>FlUrk;x)0%7^Vp~<&RjBWJ z(Ysx&DV7N9oWeM`Cd;Zd=2_EZq?}DvnHBiAUtc0H&j5{75h1QjZkXzzrb}x z)|8@<%cs9{%NFg$4{hA@z{KqBlvgOvzy4-3w~)!*xW-TiedQTf+evZ$FMjNFASNq& z|AfrzLS`Conz~q7_S-hQ1^r>EExXi5t{s%*|1@=Fw*wP0cT-N5JRXw^eSU3w@i&(n zZ}`}nWO_`F4V2!Vm1jSgfqHuU}i(wr#}f+aBp!V>GJ|xXTao zmwRBWZ)CBu(teszaA*0|`U|#oQrKth58;Gs(5t?flG>(`OQl1||4m zUiTny{?1}$rF}6;cW2w4+D5In+0xgk_%?8-{h{r1S)5{N-%fqQJNVHK@dk@0{GX<+ z>aJmyCVFewAJ7hc*!VqxRuT@MDaT zVr88>b*JY<(yQFvh#ee_%q^3KpYO+F9`NNcwPSeL7ld^p&pjXC9GaWu5zB`Z90j zKX?7mmtx(&&2NQo7O(FFd^cE(Qfa@$t?*8M=x?^%^^b@dfPdfP@u8`@eo~xb=Pu4% z>9zdeQwDE2`%lP!aB}WH#Tcd1FU?x*z5H6kOg25&rvJlpZMES!?{{Hxrh9JTZfi*+g@C)sM@#vkS ziDA`r^1-xJ@8*{n|9YCkC-d3y_^Hi|B>bE+u;D96)PWCU*Y^m_ky@&zTE zM~d>BLQk^zu@CfcqYqFUmkDCtFF47(VA~8t5?gJJ+W)Arx^VgrD`Dbyn5WySqm zoD;EZQULcsht88>9U=Aai1GI`N6upLveJ~e4^Ayy)KNCLY0E5m)ER$x$*5i|R#uwI z?D_LxMfuc@;ZHPGP|fE|O~>IY|Z z?eM-(qp#yL^N*eJ%Ett88zUw^`>Y~2SZ;X9$VdH*19!Q?&A%4?2g1G(w4juPByF>Ythp73ty;rm z2+JX?gSDF_z*`La-|`@gg?(Rta>nQEyeIrv_KKq>?wO)tV}R=fpUstHzqi6j;GQVb zvvy=Fdy5Tjjn3NH*BOH+=RM`8y0M&ImWY4BT%W%Ba-s zI)3>|9=8X-uzqv)q}~0P;pwNtOGgd%w}cHJSo+-X0j42A!^_5~=6|@%p>0QH?Hon9 z`4Vg}Q&_GN@s8kkrJlcCzRE?+tt#iU<8fH>wkt2@?Gl~GyP|`Ct+(-G-Qri)-48Z6 z$+rRYi&%cWl{)rDeq;-uTlE->1&wHf=Q4IIQ;Jtuj<@n-JzvOs=P#qPs~2tF_l?6C zA!P6*&gaK+Eg;iqy?9hW|(bU^#r>U?fQVu0sLLYwJNCfp{^@ZKuII`TL+M%(77@{VOjoXI?||!g|#>*n@w8 zu1S-<4qvVR73!3Dc4JMO@=+)K6Vrbpv7ieumr}hbKbz>r+82#}2khVON_kK^)qnmn z2m7!cbfbQ^_g|?CYixUux7xv(e$bW1=f-1?_y4H%^QaA^^dFj2=zBk@$kKx zy6`6Wk$O9`Kj_^c)QxdP_PuHiTcq^wk4}FG`n!##|B6z3&k{{fh}j?Z1!-Bj4=cO( zD@RN}u-CBkEBw8IeWLnb&G?O_U*SF9H?Gfriw-~bu>ku7e0#x{r5n=}-+QIj_oo#= zTcG~^x3!rwVbu|x{>f1-XPQcVB3X-nn!kX*TD248>Jy65{Rt$bIg0H83!?g;Fa*U>{V1i@CXc9no-{7>yf5B3_El%9A9T*( z>vy&u4C~<0yS;cr1AqJh-46U%mw;;NcaAe*-J#p-Pc?cSU;k64kl}oOJXRIKdMGDr zLwCFTjz7f`$kz|;e=qo6uLmyt4 z#(Smozp!z9H(z70=vqbATHRb&gDhjc*@xCyYh}&9fgi`>CNVqM`y}taz%|ek`mexm zA>H-O@8eIo*15Ns{pmF^Tc+O+YZiBi)dBBKx=wBErZiHN@HRQCu^fSV9Bm2jfXsyO z5G_+E`S%|l3(sR5-jR-gbyFXT)dR=L0c)4tZ1HW?vdR!klcS&J?PnhB3w4x9LUdTN{?PeJ;)8OuC}qSy_=ORU5GmLsGZ@cUMDWo3>`J11h?S6{xT(hr2%1@D#ljG1w4^PuQ0&_*vv6$Q= zf6QKc)c!85x&PyRV1nOyPf%{m%g+zLaRR`f@Ey)IMfY^83x5XRhW#6a{QQuoD}*mG z@6i~i>s#=fWnBgN6EQCanREI?LH_+T|M&9t;~%5>PsT0bXkL^*%KVO#Exou4mH~PC z@bkN{A3SJEU1FDr!tj5;Uil(XZ+(fZGE5==OOm}-ItcE048(ui?e%Cl4?DI()_3T- z(){?&K5&oYeupoYED#^VHTuty9r8bn`Z4&GInI%jtH+7wSL<8bFTfa5OUv-Dbli8+ zIG;JEZL-c@+uDC%9dmEUe}z@X!8L8Z&4S+>9gOz?ZGSw5ypa%oq-7ur+GESAMdY%^ za2Dn#-$K4Jcy6iidJO#h7>B$MLCAu2RC_@l^(zY59h6h{AMo)nqIvjIn4f=+9>e(A y6UOg~6JMQkxX} + menus={[ + { + // A section of the sidebar that displays links to see all threads and threads the user is a part of + title: 'My Links', + links: [ + { + icon: , + label: 'All Threads', + href: '/Communication?part-of:missing=true&status:not=completed', + }, + { icon: , label: 'My Threads', href: `/Communication${myThreadsQuery}` }, + ], + }, + { + // A section of the sidebar that links to a page to upload example data for the app + title: 'Upload Data', + links: [{ icon: , label: 'Upload Example Data', href: 'upload/example' }], + }, + ]} + // This adds notification icons for unread messages and active tasks for the current user + notifications={ + profile && ( + <> + + + + ) + } + > + }> + + : } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + ); +} + +interface NotificationProps { + profile: ProfileResource; + navigate: NavigateFunction; +} + +function MessageNotification({ profile, navigate }: NotificationProps): JSX.Element { + return ( + } + onClick={() => + navigate( + `/Communication?recipient=${getReferenceString(profile as ProfileResource)}&status:not=completed&part-of:missing=false&_fields=sender,recipient,subject,status,_lastUpdated` + ) + } + /> + ); +} + +function TaskNotification({ profile, navigate }: NotificationProps): JSX.Element { + return ( + } + onClick={() => + navigate( + `/Task?owner=${getReferenceString(profile as ProfileResource)}&status:not=completed&_fields=subject,code,description,status,_lastUpdated` + ) + } + /> + ); +} diff --git a/examples/medplum-chat-demo/src/components/CommunicationDetails.tsx b/examples/medplum-chat-demo/src/components/CommunicationDetails.tsx new file mode 100644 index 0000000000..81efd073a0 --- /dev/null +++ b/examples/medplum-chat-demo/src/components/CommunicationDetails.tsx @@ -0,0 +1,45 @@ +import { Paper, Tabs, Title } from '@mantine/core'; +import { Communication } from '@medplum/fhirtypes'; +import { CodeableConceptDisplay, ResourceHistoryTable, ResourceTable } from '@medplum/react'; +import { useNavigate } from 'react-router-dom'; + +interface CommunicationDetailsProps { + readonly communication: Communication; +} + +export function CommunicationDetails({ communication }: CommunicationDetailsProps): JSX.Element { + const navigate = useNavigate(); + const id = communication.id as string; + const tabs = ['Details', 'History']; + + // Get the current tab + const tab = window.location.pathname.split('/').pop(); + const currentTab = tab && tabs.map((t) => t.toLowerCase()).includes(tab) ? tab : tabs[0].toLowerCase(); + + function handleTabChange(newTab: string | null): void { + navigate(`/Communication/${id}/${newTab ?? ''}`); + } + + return ( + + + <CodeableConceptDisplay value={communication.topic} /> + + + + {tabs.map((tab) => ( + + {tab} + + ))} + + + + + + + + + + ); +} diff --git a/examples/medplum-chat-demo/src/components/PatientDetails.tsx b/examples/medplum-chat-demo/src/components/PatientDetails.tsx new file mode 100644 index 0000000000..9f5d2dc3c3 --- /dev/null +++ b/examples/medplum-chat-demo/src/components/PatientDetails.tsx @@ -0,0 +1,104 @@ +import { Loader, Tabs } from '@mantine/core'; +import { showNotification } from '@mantine/notifications'; +import { getReferenceString, normalizeErrorString, Operator, SearchRequest } from '@medplum/core'; +import { Patient, Practitioner, Resource } from '@medplum/fhirtypes'; +import { + Document, + ResourceForm, + ResourceHistoryTable, + ResourceTable, + SearchControl, + useMedplum, + useMedplumProfile, + useResource, +} from '@medplum/react'; +import { IconCircleCheck, IconCircleOff } from '@tabler/icons-react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { PatientHeader } from '../pages/PatientHeader'; +import { cleanResource } from '../utils'; + +interface PatientDetailsProps { + onChange: (patient: Patient) => void; +} + +export function PatientDetails({ onChange }: PatientDetailsProps): JSX.Element { + const medplum = useMedplum(); + const navigate = useNavigate(); + const profile = useMedplumProfile() as Practitioner; + const { id } = useParams() as { id: string }; + const patient = useResource({ reference: `Patient/${id}` }); + + const tabs = ['Details', 'Threads', 'Edit', 'History']; + const tab = window.location.pathname.split('/').pop(); + const currentTab = tab && tabs.map((t) => t.toLowerCase()).includes(tab) ? tab : tabs[0].toLowerCase(); + + // Create a search request to get all threads that the current patient is a participant in or a subject of. + const threadSearch: SearchRequest = { + resourceType: 'Communication', + filters: [ + { code: 'part-of:missing', operator: Operator.EQUALS, value: 'true' }, + { code: 'subject', operator: Operator.EQUALS, value: `Patient/${id}` }, + { code: 'recipient', operator: Operator.EQUALS, value: getReferenceString(profile) }, + ], + fields: ['topic', 'category', '_lastUpdated'], + }; + + async function handlePatientEdit(newPatient: Resource): Promise { + try { + const updatedPatient = (await medplum.updateResource(cleanResource(newPatient))) as Patient; + showNotification({ + icon: , + title: 'Success', + message: 'Patient updated', + }); + onChange(updatedPatient); + window.scrollTo(0, 0); + } catch (err) { + showNotification({ + icon: , + title: 'Error', + message: normalizeErrorString(err), + }); + } + } + + function handleTabChange(newTab: string | null): void { + navigate(`/Patient/${id}/${newTab ?? ''}`); + } + + if (!patient) { + return ; + } + + return ( + + + + + {tabs.map((tab) => ( + + {tab} + + ))} + + + + + + navigate(`/${getReferenceString(e.resource)}`)} + /> + + + + + + + + + + ); +} diff --git a/examples/medplum-chat-demo/src/components/actions/AddParticipant.tsx b/examples/medplum-chat-demo/src/components/actions/AddParticipant.tsx new file mode 100644 index 0000000000..b6370e74e4 --- /dev/null +++ b/examples/medplum-chat-demo/src/components/actions/AddParticipant.tsx @@ -0,0 +1,129 @@ +import { Button, Modal } from '@mantine/core'; +import { useDisclosure } from '@mantine/hooks'; +import { showNotification } from '@mantine/notifications'; +import { normalizeErrorString, PatchOperation } from '@medplum/core'; +import { Communication, Questionnaire, QuestionnaireResponse } from '@medplum/fhirtypes'; +import { QuestionnaireForm, useMedplum } from '@medplum/react'; +import { IconCircleCheck, IconCircleOff } from '@tabler/icons-react'; +import { checkForInvalidRecipient, getRecipients } from '../../utils'; + +interface AddParticipantProps { + readonly communication: Communication; + readonly onChange: (communication: Communication) => void; +} + +export function AddParticipant(props: AddParticipantProps): JSX.Element { + const medplum = useMedplum(); + const [opened, handlers] = useDisclosure(false); + + function onQuestionnaireSubmit(formData: QuestionnaireResponse): void { + const newParticipantsData = getRecipients(formData); + if (!newParticipantsData) { + throw new Error('Please select a valid person to add to this thread.'); + } + const newParticipants = newParticipantsData?.map( + (participant) => participant.valueReference + ) as Communication['recipient']; + + if (!newParticipants) { + throw new Error('Please select a valid person to add to this thread.'); + } + + const invalidRecipients = checkForInvalidRecipient(newParticipants); + + if (invalidRecipients) { + showNotification({ + color: 'red', + icon: , + title: 'Error', + message: 'Invalid recipient type', + }); + throw new Error('Invalid recipient type'); + } + + addNewParticipant(newParticipants).catch(console.error); + handlers.close(); + } + + async function addNewParticipant(newParticipant: Communication['recipient']): Promise { + if (!newParticipant) { + return; + } + + // Get the communication id and the participants that are already a part of the thread + const communicationId = props.communication.id as string; + const currentParticipants = props.communication.recipient ?? []; + + // If there are no participants, we will add a participant array, otherwise we will replace the current one with an udpated version. + const op = currentParticipants.length === 0 ? 'add' : 'replace'; + + // Add the new participants to the array + const updatedParticipants = currentParticipants.concat(newParticipant); + + const ops: PatchOperation[] = [ + // Test to prevent race conditions + { op: 'test', path: '/meta/versionId', value: props.communication.meta?.versionId }, + { op, path: '/recipient', value: updatedParticipants }, + ]; + + try { + // Patch the thread with the updated participants + const result = await medplum.patchResource('Communication', communicationId, ops); + showNotification({ + icon: , + title: 'Success', + message: 'User added to thread.', + }); + props.onChange(result); + } catch (err) { + showNotification({ + icon: , + title: 'Error', + message: normalizeErrorString(err), + }); + } + } + + return ( +
+ + + + +
+ ); +} + +const addParticipantQuestionnaire: Questionnaire = { + resourceType: 'Questionnaire', + status: 'active', + id: 'add-participant', + item: [ + { + linkId: 'participants', + type: 'reference', + text: 'Add someone to this thread:', + repeats: true, + extension: [ + { + url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-referenceResource', + valueCodeableConcept: { + coding: [ + { + code: 'Patient', + }, + { + code: 'Practitioner', + }, + { + code: 'RelatedPerson', + }, + ], + }, + }, + ], + }, + ], +}; diff --git a/examples/medplum-chat-demo/src/components/actions/AddSubject.tsx b/examples/medplum-chat-demo/src/components/actions/AddSubject.tsx new file mode 100644 index 0000000000..56db27d127 --- /dev/null +++ b/examples/medplum-chat-demo/src/components/actions/AddSubject.tsx @@ -0,0 +1,93 @@ +import { Button, Modal } from '@mantine/core'; +import { useDisclosure } from '@mantine/hooks'; +import { showNotification } from '@mantine/notifications'; +import { getQuestionnaireAnswers, normalizeErrorString, PatchOperation } from '@medplum/core'; +import { Communication, Patient, Questionnaire, QuestionnaireResponse, Reference } from '@medplum/fhirtypes'; +import { QuestionnaireForm, useMedplum } from '@medplum/react'; +import { IconCircleCheck, IconCircleOff } from '@tabler/icons-react'; + +interface AddSubjectProps { + readonly communication: Communication; + readonly onChange: (communication: Communication) => void; +} + +export function AddSubject(props: AddSubjectProps): JSX.Element { + const medplum = useMedplum(); + const [opened, handlers] = useDisclosure(false); + + function onQuestionnaireSubmit(formData: QuestionnaireResponse): void { + // Throw an error if there is already a subject on the thread + if (props.communication.subject) { + throw new Error('Thread already has a subject.'); + } + const subjectData = getQuestionnaireAnswers(formData)['add-subject'].valueReference as Reference; + if (!subjectData) { + throw new Error('Invalid subject'); + } + addSubjectToThread(subjectData).catch(console.error); + handlers.close(); + } + + async function addSubjectToThread(subjectData: Reference): Promise { + const communicationId = props.communication.id as string; + const ops: PatchOperation[] = [ + // Test to prevent race conditions + { op: 'test', path: '/meta/versionId', value: props.communication.meta?.versionId }, + // Patch the new subject to the thread's subject path + { op: 'add', path: '/subject', value: subjectData }, + ]; + + try { + const result = await medplum.patchResource('Communication', communicationId, ops); + props.onChange(result); + showNotification({ + icon: , + title: 'Success', + message: 'Subject added', + }); + } catch (err) { + showNotification({ + color: 'red', + icon: , + title: 'Error', + message: normalizeErrorString(err), + }); + } + } + + return ( +
+ + + + +
+ ); +} + +const addSubjectQuestionnaire: Questionnaire = { + resourceType: 'Questionnaire', + status: 'active', + id: 'add-subject', + item: [ + { + linkId: 'add-subject', + type: 'reference', + text: 'Add a subject to the thread', + extension: [ + { + url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-referenceResource', + valueCodeableConcept: { + coding: [ + { + code: 'Patient', + }, + ], + }, + }, + ], + }, + ], +}; diff --git a/examples/medplum-chat-demo/src/components/actions/CloseOpenThread.tsx b/examples/medplum-chat-demo/src/components/actions/CloseOpenThread.tsx new file mode 100644 index 0000000000..09a3f47e94 --- /dev/null +++ b/examples/medplum-chat-demo/src/components/actions/CloseOpenThread.tsx @@ -0,0 +1,70 @@ +import { Button, Group, Modal } from '@mantine/core'; +import { useDisclosure } from '@mantine/hooks'; +import { showNotification } from '@mantine/notifications'; +import { normalizeErrorString, PatchOperation } from '@medplum/core'; +import { Communication } from '@medplum/fhirtypes'; +import { useMedplum } from '@medplum/react'; +import { IconCircleCheck, IconCircleOff } from '@tabler/icons-react'; + +interface CloseOpenThreadProps { + readonly communication: Communication; + readonly onChange: (communication: Communication) => void; +} + +export function CloseOpenThread(props: CloseOpenThreadProps): JSX.Element { + const medplum = useMedplum(); + const [opened, handlers] = useDisclosure(false); + const status = props.communication.status; + + // Check the status to see if the thread should be closed or reopened + const display = status === 'completed' ? 'Reopen' : 'Close'; + + async function handleStatusUpdate(): Promise { + const communicationId = props.communication.id as string; + // Update the status to the opposite of the current status + const updatedStatus = status === 'completed' ? 'in-progress' : 'completed'; + + const ops: PatchOperation[] = [ + { op: 'test', path: '/meta/versionId', value: props.communication.meta?.versionId }, + { op: 'replace', path: '/status', value: updatedStatus }, + ]; + + try { + // Update the thread to the new status + const result = await medplum.patchResource('Communication', communicationId, ops); + showNotification({ + icon: , + title: 'Success', + message: `Thread ${display === 'Close' ? 'closed.' : 'reopened.'}`, + }); + props.onChange(result); + handlers.close(); + } catch (err) { + showNotification({ + icon: , + title: 'Error', + message: normalizeErrorString(err), + }); + } + } + + return ( +
+ + + + + + + +
+ ); +} diff --git a/examples/medplum-chat-demo/src/components/actions/CommunicationActions.tsx b/examples/medplum-chat-demo/src/components/actions/CommunicationActions.tsx new file mode 100644 index 0000000000..b2679be6b0 --- /dev/null +++ b/examples/medplum-chat-demo/src/components/actions/CommunicationActions.tsx @@ -0,0 +1,45 @@ +import { Stack, Title } from '@mantine/core'; +import { parseReference } from '@medplum/core'; +import { Communication } from '@medplum/fhirtypes'; +import { AddParticipant } from './AddParticipant'; +import { AddSubject } from './AddSubject'; +import { CloseOpenThread } from './CloseOpenThread'; +import { CreateEncounter } from './CreateEncounter'; +import { EditThreadTopic } from './EditThreadTopic'; + +interface CommunicationActionsProps { + readonly communication: Communication; + readonly onChange: (communication: Communication) => void; +} + +export function CommunicationActions(props: CommunicationActionsProps): JSX.Element { + return ( + + Thread Actions + + + {!props.communication.subject ? ( + + ) : null} + + {props.communication.status === 'completed' && checkThreadForPatient(props.communication) ? ( + + ) : null} + + ); +} + +function checkThreadForPatient(thread: Communication): boolean { + const recipients = thread.recipient; + if (!recipients || recipients.length === 0) { + return false; + } + + for (const recipient of recipients) { + if (parseReference(recipient)[0] === 'Patient') { + return true; + } + } + + return false; +} diff --git a/examples/medplum-chat-demo/src/components/actions/CreateEncounter.tsx b/examples/medplum-chat-demo/src/components/actions/CreateEncounter.tsx new file mode 100644 index 0000000000..99b01cbd6d --- /dev/null +++ b/examples/medplum-chat-demo/src/components/actions/CreateEncounter.tsx @@ -0,0 +1,148 @@ +import { Button, Modal } from '@mantine/core'; +import { useDisclosure } from '@mantine/hooks'; +import { showNotification } from '@mantine/notifications'; +import { createReference, normalizeErrorString, parseReference, PatchOperation } from '@medplum/core'; +import { + Communication, + Encounter, + Group, + Patient, + Period, + Practitioner, + Reference, + Resource, +} from '@medplum/fhirtypes'; +import { ResourceForm, useMedplum, useMedplumProfile } from '@medplum/react'; +import { IconCircleCheck, IconCircleOff } from '@tabler/icons-react'; +import { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { getAttenders } from '../../utils'; + +interface CreateEncounterProps { + readonly communication: Communication; + readonly onChange: (communication: Communication) => void; +} + +export function CreateEncounter(props: CreateEncounterProps): JSX.Element { + const medplum = useMedplum(); + const navigate = useNavigate(); + const profile = useMedplumProfile() as Practitioner; + const [opened, handlers] = useDisclosure(false); + const [period, setPeriod] = useState(); + + async function onEncounterSubmit(resource: Resource): Promise { + const encounterData = resource as Encounter; + encounterData.period = period; + + try { + // Create the encounter and update the communication to be linked to it. For more details see https://www.medplum.com/docs/communications/async-encounters + const encounter = await medplum.createResource(encounterData); + linkEncounterToCommunication(encounter, props.communication).catch(console.error); + showNotification({ + icon: , + title: 'Success', + message: 'Encounter created.', + }); + handlers.close(); + navigate(`/Encounter/${encounter.id}`); + } catch (err) { + showNotification({ + icon: , + title: 'Error', + message: normalizeErrorString(err), + }); + } + } + + // A function that links a Communication to an Encounter using the Communication.encounter field. For more details see https://www.medplum.com/docs/communications/async-encounters + async function linkEncounterToCommunication(encounter: Encounter, communication: Communication): Promise { + const communicationId = communication.id as string; + const encounterReference = createReference(encounter); + + const ops: PatchOperation[] = [ + // Test to prevent race conditions + { op: 'test', path: '/meta/versionId', value: communication.meta?.versionId }, + // Patch the encounter field of the communication + { op: 'add', path: '/encounter', value: encounterReference }, + ]; + + try { + // Update the communication + const result = await medplum.patchResource('Communication', communicationId, ops); + props.onChange(result); + } catch (err) { + console.error(err); + } + } + + useEffect(() => { + // When creating an encounter, the period should be from the time the first message in the thread was sent until the last message was sent + const getEncounterPeriod = async (thread: Communication): Promise => { + const messages = await medplum.searchResources('Communication', { + 'part-of': `Communication/${thread.id}`, + _sort: 'sent', + }); + + const period: Period = { + start: messages[0].sent, + end: messages[messages.length - 1].sent, + }; + + return period; + }; + + getEncounterPeriod(props.communication).then(setPeriod).catch(console.error); + }, [props.communication]); + + const attenders = getAttenders(props.communication.recipient, profile, false); + const subject = getEncounterSubject(props.communication); + + // A default encounter to pre-fill the form with + const defaultEncounter: Encounter = { + resourceType: 'Encounter', + status: 'in-progress', + class: { + system: 'http://terminology.hl7.org/CodeSystem/v3-ActCode', + code: 'VR', + display: 'virtual', + }, + subject: subject, + participant: attenders, + period: period, + }; + + return ( +
+ + + + +
+ ); +} + +function getEncounterSubject(thread: Communication): Reference | undefined { + // If the thread has a subject, this will be the Encounter subject + if (thread.subject) { + return thread.subject; + } + + if (!thread.recipient || thread.recipient.length === 0) { + return undefined; + } + + // Filter for only the recipients that are patients + const patients = thread.recipient.filter( + (recipient) => parseReference(recipient)[0] === 'Patient' + ) as Reference[]; + + // If there are none, or more than one do not return a subject + if (patients.length !== 1) { + return undefined; + } + + // Return a the patient if there is only one + return patients[0]; +} diff --git a/examples/medplum-chat-demo/src/components/actions/CreateThread.tsx b/examples/medplum-chat-demo/src/components/actions/CreateThread.tsx new file mode 100644 index 0000000000..cfffd76508 --- /dev/null +++ b/examples/medplum-chat-demo/src/components/actions/CreateThread.tsx @@ -0,0 +1,187 @@ +import { Modal } from '@mantine/core'; +import { showNotification } from '@mantine/notifications'; +import { createReference, getQuestionnaireAnswers, normalizeErrorString, parseReference } from '@medplum/core'; +import { + Communication, + Patient, + Practitioner, + Questionnaire, + QuestionnaireResponse, + QuestionnaireResponseItemAnswer, + Reference, +} from '@medplum/fhirtypes'; +import { QuestionnaireForm, useMedplum, useMedplumProfile } from '@medplum/react'; +import { IconCircleCheck, IconCircleOff } from '@tabler/icons-react'; +import { useNavigate } from 'react-router-dom'; +import { getRecipients, checkForInvalidRecipient } from '../../utils'; + +interface CreateThreadProps { + opened: boolean; + handlers: { + readonly open: () => void; + readonly close: () => void; + readonly toggle: () => void; + }; +} + +export function CreateThread({ opened, handlers }: CreateThreadProps): JSX.Element { + const medplum = useMedplum(); + const profile = useMedplumProfile() as Practitioner; + const navigate = useNavigate(); + + function onQuestionnaireSubmit(formData: QuestionnaireResponse): void { + const participants = getRecipients(formData) as QuestionnaireResponseItemAnswer[]; + const answers = getQuestionnaireAnswers(formData); + const topic = answers.topic.valueString as string; + const subject = answers.subject?.valueReference as Reference; + + if (subject && parseReference(subject)[0] !== 'Patient') { + showNotification({ + color: 'red', + icon: , + title: 'Error', + message: 'The subject of a thread must be a patient', + }); + throw new Error('The subject of a thread must be a patient'); + } + + handleCreateThread(topic, participants, subject).catch(console.error); + handlers.close(); + } + + async function handleCreateThread( + topic: string, + participants: QuestionnaireResponseItemAnswer[], + subject?: Reference + ): Promise { + // The suggested way to handle threads is by including all participants in the `recipients` field. This gets all people that are a entered as a recipient + const profileReference = createReference(profile); + + const recipients = participants + ?.filter((participant) => participant.valueReference?.reference !== profileReference.reference) + .map((participant) => participant.valueReference) as Communication['recipient']; + + if (!topic || !recipients) { + throw new Error('Please ensure a valid input.'); + } + + const invalidRecipients = checkForInvalidRecipient(recipients); + + if (invalidRecipients) { + showNotification({ + color: 'red', + icon: , + title: 'Error', + message: 'Invalid recipient type', + }); + throw new Error('Invalid recipient type'); + } + + // Add the user that created the trhead as a participant + recipients?.push(profileReference); + + const thread: Communication = { + resourceType: 'Communication', + status: 'in-progress', + topic: { + coding: [ + { + display: topic, + }, + ], + }, + recipient: recipients, + sender: profileReference, + }; + + if (subject) { + thread.subject = subject; + } + + try { + // Create the thread + const result = await medplum.createResource(thread); + showNotification({ + icon: , + title: 'Success', + message: 'Thread created', + }); + navigate(`/Communication/${result.id}`); + } catch (err) { + showNotification({ + icon: , + title: 'Error', + message: normalizeErrorString(err), + }); + } + } + + return ( + + + + ); +} + +const createThreadQuestionnaire: Questionnaire = { + resourceType: 'Questionnaire', + status: 'active', + title: 'Start a New Thread', + id: 'new-thread', + item: [ + { + linkId: 'topic', + type: 'string', + text: 'Thread Topic:', + required: true, + }, + { + linkId: 'participants', + type: 'reference', + text: 'Add thread participants:', + repeats: true, + required: true, + extension: [ + { + url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-referenceResource', + valueCodeableConcept: { + coding: [ + { + code: 'Patient', + }, + { + code: 'Practitioner', + }, + { + code: 'RelatedPerson', + }, + ], + }, + }, + ], + }, + { + linkId: 'category', + type: 'choice', + text: 'Thread Category', + answerValueSet: 'https://example.org/thread-categories', + }, + { + linkId: 'subject', + type: 'reference', + text: 'Select a patient that is the subject of this thread (Optional)', + extension: [ + { + url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-referenceResource', + valueCodeableConcept: { + coding: [ + { + code: 'Patient', + }, + ], + }, + }, + ], + }, + ], +}; diff --git a/examples/medplum-chat-demo/src/components/actions/EditThreadTopic.tsx b/examples/medplum-chat-demo/src/components/actions/EditThreadTopic.tsx new file mode 100644 index 0000000000..6195dc5ca3 --- /dev/null +++ b/examples/medplum-chat-demo/src/components/actions/EditThreadTopic.tsx @@ -0,0 +1,88 @@ +import { Button, Modal } from '@mantine/core'; +import { useDisclosure } from '@mantine/hooks'; +import { showNotification } from '@mantine/notifications'; +import { getQuestionnaireAnswers, normalizeErrorString, PatchOperation } from '@medplum/core'; +import { CodeableConcept, Communication, Questionnaire, QuestionnaireResponse } from '@medplum/fhirtypes'; +import { QuestionnaireForm, useMedplum } from '@medplum/react'; +import { IconCircleCheck, IconCircleOff } from '@tabler/icons-react'; + +interface EditTopicThreadProps { + readonly communication: Communication; + readonly onChange: (communication: Communication) => void; +} + +export function EditThreadTopic({ communication, onChange }: EditTopicThreadProps): JSX.Element { + const medplum = useMedplum(); + const [opened, handlers] = useDisclosure(false); + + function onQuestionnaireSubmit(formData: QuestionnaireResponse): void { + const newTopic = getQuestionnaireAnswers(formData)['edit-topic'].valueString; + + if (!newTopic) { + throw new Error('Please enter a new topic'); + } + + handleTopicUpdate(newTopic).catch(console.error); + handlers.close(); + } + + async function handleTopicUpdate(newTopic: string): Promise { + const communicationId = communication.id as string; + // Create a codeable concept for the topic + const topicCodeable: CodeableConcept = { + coding: [ + { + display: newTopic, + }, + ], + }; + + // Add a topic or replace the previous topic + const ops: PatchOperation[] = [ + { op: 'test', path: '/meta/versionId', value: communication.meta?.versionId }, + { op: communication.topic ? 'replace' : 'add', path: '/topic', value: topicCodeable }, + ]; + + try { + // Update the thread + const result = await medplum.patchResource(communication.resourceType, communicationId, ops); + showNotification({ + icon: , + title: 'Success', + message: 'Topic updated.', + }); + onChange(result); + } catch (err) { + showNotification({ + icon: , + title: 'Error', + message: normalizeErrorString(err), + }); + } + } + + return ( +
+ + + + +
+ ); +} + +const editTopicQuestionnaire: Questionnaire = { + resourceType: 'Questionnaire', + status: 'active', + id: 'edit-thread-topic', + item: [ + { + linkId: 'edit-topic', + type: 'string', + text: 'Edit Thread Topic', + required: true, + }, + ], +}; diff --git a/examples/medplum-chat-demo/src/main.tsx b/examples/medplum-chat-demo/src/main.tsx new file mode 100644 index 0000000000..f83370501c --- /dev/null +++ b/examples/medplum-chat-demo/src/main.tsx @@ -0,0 +1,49 @@ +import { MantineProvider, createTheme } from '@mantine/core'; +import '@mantine/core/styles.css'; +import '@mantine/notifications/styles.css'; +import { Notifications } from '@mantine/notifications'; +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 { createBrowserRouter, RouterProvider } 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; also change `googleClientId` in `./pages/SignInPage.tsx` +}); + +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); +const router = createBrowserRouter([{ path: '*', element: }]); +root.render( + + + + + + + + +); diff --git a/examples/medplum-chat-demo/src/pages/CommunicationPage.tsx b/examples/medplum-chat-demo/src/pages/CommunicationPage.tsx new file mode 100644 index 0000000000..ee765d071c --- /dev/null +++ b/examples/medplum-chat-demo/src/pages/CommunicationPage.tsx @@ -0,0 +1,49 @@ +import { Communication } from '@medplum/fhirtypes'; +import { Loading, useMedplum } from '@medplum/react'; +import { useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { MessagePage } from './MessagePage'; +import { ThreadPage } from './ThreadPage'; + +export function CommunicationPage(): JSX.Element { + const medplum = useMedplum(); + const { id } = useParams(); + const [communication, setCommunication] = useState(); + const [isThread, setIsThread] = useState(); + + function onCommunicationChange(newCommunication: Communication): void { + setCommunication(newCommunication); + } + + useEffect(() => { + const fetchData = async (): Promise => { + try { + if (id) { + const communication = await medplum.readResource('Communication', id); + setCommunication(communication); + + // If the Communication is a part of another communication, it is a message, otherwise it is a thread. For more details see https://www.medplum.com/docs/communications/organizing-communications + setIsThread(communication.partOf ? false : true); // eslint-disable-line no-unneeded-ternary + } + } catch (err) { + console.error(err); + } + }; + + fetchData().catch(console.error); + }); + + if (!communication) { + return ; + } + + return ( +
+ {isThread ? ( + + ) : ( + + )} +
+ ); +} diff --git a/examples/medplum-chat-demo/src/pages/LandingPage.tsx b/examples/medplum-chat-demo/src/pages/LandingPage.tsx new file mode 100644 index 0000000000..1605b639ee --- /dev/null +++ b/examples/medplum-chat-demo/src/pages/LandingPage.tsx @@ -0,0 +1,21 @@ +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 Chat Demo shows how to build a simple React application that fetches messaging 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-chat-demo/src/pages/MessagePage.tsx b/examples/medplum-chat-demo/src/pages/MessagePage.tsx new file mode 100644 index 0000000000..13d82bc5f3 --- /dev/null +++ b/examples/medplum-chat-demo/src/pages/MessagePage.tsx @@ -0,0 +1,55 @@ +import { Grid, GridCol } from '@mantine/core'; +import { parseReference, resolveId } from '@medplum/core'; +import { Communication, Patient } from '@medplum/fhirtypes'; +import { PatientSummary, useMedplum } from '@medplum/react'; +import { useEffect, useState } from 'react'; +import { CommunicationDetails } from '../components/CommunicationDetails'; + +interface MessagePageProps { + readonly message: Communication; + readonly onChange: (communication: Communication) => void; +} + +export function MessagePage(props: MessagePageProps): JSX.Element { + const medplum = useMedplum(); + const [patient, setPatient] = useState(); + + // Get a reference to the patient if the sender of the message is a patient + const patientReference = getPatientReference(props.message); + + useEffect(() => { + const patientId = resolveId(patientReference); + + if (patientId) { + // Get the patient resource to display their summary + medplum.readResource('Patient', patientId).then(setPatient).catch(console.error); + } + }, [patientReference, medplum]); + + return ( +
+ {patient ? ( + + + + + + + + + ) : ( + + )} +
+ ); +} + +// If the sender of the message is a patient, return a reference to that patient +function getPatientReference(message: Communication): Communication['sender'] | undefined { + const sender = parseReference(message.sender); + if (sender[0] === 'Patient') { + return message.sender; + } + + return undefined; +} diff --git a/examples/medplum-chat-demo/src/pages/PatientHeader.module.css b/examples/medplum-chat-demo/src/pages/PatientHeader.module.css new file mode 100644 index 0000000000..71fbb95a41 --- /dev/null +++ b/examples/medplum-chat-demo/src/pages/PatientHeader.module.css @@ -0,0 +1,26 @@ +.root { + display: flex; + flex-direction: row; + align-items: center; + padding: 8px 10px; + background: var(--mantine-color-white); + + & dl { + display: inline-block; + margin: 5px 20px 5px 5px; + } + + & dt { + color: var(--mantine-color-gray-6); + text-transform: uppercase; + font-size: var(--mantine-font-size-xs); + white-space: nowrap; + } + + & dd { + font-size: var(--mantine-font-size-md); + font-weight: 600; + margin-left: 0; + white-space: nowrap; + } +} diff --git a/examples/medplum-chat-demo/src/pages/PatientHeader.tsx b/examples/medplum-chat-demo/src/pages/PatientHeader.tsx new file mode 100644 index 0000000000..c576995a97 --- /dev/null +++ b/examples/medplum-chat-demo/src/pages/PatientHeader.tsx @@ -0,0 +1,60 @@ +import { calculateAgeString } from '@medplum/core'; +import { Patient, Reference } from '@medplum/fhirtypes'; +import { HumanNameDisplay, MedplumLink, ResourceAvatar, useResource } from '@medplum/react'; +import classes from './PatientHeader.module.css'; + +export interface PatientHeaderProps { + readonly patient: Patient | Reference; +} + +export function PatientHeader(props: PatientHeaderProps): JSX.Element | null { + const patient = useResource(props.patient); + if (!patient) { + return null; + } + return ( +
+ +
+
Name
+
+ + {patient.name ? : '[blank]'} + +
+
+ {patient.birthDate && ( + <> +
+
DoB
+
{patient.birthDate}
+
+
+
Age
+
{calculateAgeString(patient.birthDate)}
+
+ + )} + {patient.gender && ( +
+
Gender
+
{patient.gender}
+
+ )} + {patient.address && ( + <> +
+
State
+
{patient.address?.[0]?.state}
+
+ + )} + {patient.identifier?.map((identifier, index) => ( +
+
{identifier?.system}
+
{identifier?.value}
+
+ ))} +
+ ); +} diff --git a/examples/medplum-chat-demo/src/pages/PatientPage.tsx b/examples/medplum-chat-demo/src/pages/PatientPage.tsx new file mode 100644 index 0000000000..0df0f637ea --- /dev/null +++ b/examples/medplum-chat-demo/src/pages/PatientPage.tsx @@ -0,0 +1,43 @@ +import { Grid, GridCol, Loader } from '@mantine/core'; +import { showNotification } from '@mantine/notifications'; +import { normalizeErrorString } from '@medplum/core'; +import { Patient } from '@medplum/fhirtypes'; +import { PatientSummary, useMedplum } from '@medplum/react'; +import { IconCircleOff } from '@tabler/icons-react'; +import { useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { PatientDetails } from '../components/PatientDetails'; + +export function PatientPage(): JSX.Element { + const medplum = useMedplum(); + const { id } = useParams() as { id: string }; + const [patient, setPatient] = useState(); + + useEffect(() => { + medplum + .readResource('Patient', id) + .then(setPatient) + .catch((err) => { + showNotification({ + icon: , + title: 'Error', + message: normalizeErrorString(err), + }); + }); + }, [id, medplum]); + + if (!patient) { + return ; + } + + return ( + + + + + + + + + ); +} diff --git a/examples/medplum-chat-demo/src/pages/ResourcePage.tsx b/examples/medplum-chat-demo/src/pages/ResourcePage.tsx new file mode 100644 index 0000000000..ee8952bfa2 --- /dev/null +++ b/examples/medplum-chat-demo/src/pages/ResourcePage.tsx @@ -0,0 +1,103 @@ +import { Grid, Tabs, Title } from '@mantine/core'; +import { showNotification } from '@mantine/notifications'; +import { getDisplayString, normalizeErrorString } from '@medplum/core'; +import { Patient, Reference, Resource, ResourceType } from '@medplum/fhirtypes'; +import { + Document, + PatientSummary, + ResourceForm, + ResourceHistoryTable, + ResourceTable, + useMedplum, +} from '@medplum/react'; +import { IconCircleCheck, IconCircleOff } from '@tabler/icons-react'; +import { useEffect, useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { cleanResource, shouldShowPatientSummary } from '../utils'; + +/** + * 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 navigate = useNavigate(); + const { resourceType, id } = useParams(); + const [resource, setResource] = useState(undefined); + const tabs = ['Details', 'Edit', 'History']; + + const tab = window.location.pathname.split('/').pop(); + const currentTab = tab && tabs.map((t) => t.toLowerCase()).includes(tab) ? tab : tabs[0]; + + useEffect(() => { + if (resourceType && id) { + medplum + .readResource(resourceType as ResourceType, id) + .then(setResource) + .catch(console.error); + } + }, [medplum, resourceType, id]); + + async function handleResourceEdit(newResource: Resource): Promise { + try { + const updatedResource = await medplum.updateResource(cleanResource(newResource)); + setResource(updatedResource); + showNotification({ + icon: , + title: 'Success', + message: `${resourceType} edited`, + }); + } catch (err) { + showNotification({ + color: 'red', + icon: , + title: 'Error', + message: normalizeErrorString(err), + }); + } + } + + function handleTabChange(newTab: string | null): void { + navigate(`/${resourceType}/${id}/${newTab ?? ''}`); + } + + if (!resource) { + return null; + } + + return ( +
+ + {resource.resourceType === 'Encounter' && shouldShowPatientSummary(resource) ? ( + + } m="sm" /> + + ) : null} + + + {getDisplayString(resource)} + + + {tabs.map((tab) => ( + + {tab} + + ))} + + + + + + + + + + + + + + +
+ ); +} diff --git a/examples/medplum-chat-demo/src/pages/SearchPage.tsx b/examples/medplum-chat-demo/src/pages/SearchPage.tsx new file mode 100644 index 0000000000..7caceef227 --- /dev/null +++ b/examples/medplum-chat-demo/src/pages/SearchPage.tsx @@ -0,0 +1,164 @@ +import { Tabs } from '@mantine/core'; +import { useDisclosure } from '@mantine/hooks'; +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'; +import { CreateThread } from '../components/actions/CreateThread'; +import { getPopulatedSearch } from '../utils'; + +export function SearchPage(): JSX.Element { + const medplum = useMedplum(); + const navigate = useNavigate(); + const location = useLocation(); + const [search, setSearch] = useState(); + const [opened, handlers] = useDisclosure(false); + + // Only show the active and complete tabs when viewing Communication resources + const [showTabs, setShowTabs] = useState(() => { + const search = parseSearchRequest(location.pathname + location.search); + return shouldShowTabs(search); + }); + + const tabs = ['Active', 'Completed']; + const searchQuery = window.location.search; + const currentSearch = searchQuery ? parseSearchRequest(searchQuery) : null; + const currentTab = currentSearch ? handleInitialTab(currentSearch) : null; + + useEffect(() => { + const searchQuery = parseSearchRequest(location.pathname + location.search); + setShowTabs(shouldShowTabs(searchQuery)); + }, [location]); + + useEffect(() => { + const parsedSearch = parseSearchRequest(location.pathname + location.search); + // Navigate to view Communication resources by default + if (!parsedSearch.resourceType) { + navigate('/Communication'); + return; + } + + // Populate the search with details for a given resource type + const populatedSearch = getPopulatedSearch(parsedSearch); + + if ( + location.pathname === `/${populatedSearch.resourceType}` && + location.search === formatSearchQuery(populatedSearch) + ) { + // If you are alrady at the correct url, execute the search + setSearch(populatedSearch); + } else { + // Otherwise, navigate to the correct url before executing + navigate(`/${populatedSearch.resourceType}${formatSearchQuery(populatedSearch)}`); + } + }, [medplum, navigate, location]); + + function handleTabChange(newTab: string | null): void { + if (!search) { + throw new Error('Error: No valid search'); + } + + const updatedSearch = updateSearch(newTab ?? 'active', search); + const updatedSearchQuery = formatSearchQuery(updatedSearch); + navigate(`/Communication${updatedSearchQuery}`); + } + + if (!search?.resourceType || !search.fields || search.fields.length === 0) { + return ; + } + + return ( + + + {showTabs ? ( + + + {tabs.map((tab) => ( + + {tab} + + ))} + + + navigate(`/${getReferenceString(e.resource)}`)} + hideFilters={true} + hideToolbar={false} + onNew={handlers.open} + onChange={(e) => { + navigate(`/${search.resourceType}${formatSearchQuery(e.definition)}`); + }} + /> + + + navigate(`/${getReferenceString(e.resource)}`)} + hideFilters={true} + hideToolbar={false} + onNew={handlers.open} + onChange={(e) => { + navigate(`/${search.resourceType}${formatSearchQuery(e.definition)}`); + }} + /> + + + ) : ( + navigate(`/${getReferenceString(e.resource)}`)} + hideFilters={true} + hideToolbar={true} + /> + )} + + ); +} + +function handleInitialTab(currentSearch: SearchRequest): string { + if (!currentSearch.filters) { + return 'active'; + } + + for (const filter of currentSearch.filters) { + if (filter.value === 'completed') { + const tab = filter.operator; + if (tab === Operator.NOT) { + return 'active'; + } else { + return 'completed'; + } + } + } + return 'active'; +} + +function updateSearch(newTab: string, search: SearchRequest): SearchRequest { + const filters = search.filters || []; + const newCode = newTab === 'active' ? 'status:not' : 'status'; + + if (filters.length === 0) { + filters.push({ code: newCode, operator: Operator.EQUALS, value: 'completed' }); + } else { + for (const filter of filters) { + if (filter.value === 'completed') { + filter.code = newCode; + filter.operator = Operator.EQUALS; + } + } + } + + return { + ...search, + filters, + }; +} + +function shouldShowTabs(search: SearchRequest): boolean { + if (search.resourceType !== 'Communication') { + return false; + } + + return true; +} diff --git a/examples/medplum-chat-demo/src/pages/SignInPage.tsx b/examples/medplum-chat-demo/src/pages/SignInPage.tsx new file mode 100644 index 0000000000..d45c4dd67a --- /dev/null +++ b/examples/medplum-chat-demo/src/pages/SignInPage.tsx @@ -0,0 +1,18 @@ +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-chat-demo/src/pages/ThreadPage.tsx b/examples/medplum-chat-demo/src/pages/ThreadPage.tsx new file mode 100644 index 0000000000..b2e6fb4c32 --- /dev/null +++ b/examples/medplum-chat-demo/src/pages/ThreadPage.tsx @@ -0,0 +1,92 @@ +import { Anchor, Grid, GridCol, List, Paper, Stack, Title } from '@mantine/core'; +import { resolveId } from '@medplum/core'; +import { Communication, Patient } from '@medplum/fhirtypes'; +import { PatientSummary, ThreadChat, useMedplum } from '@medplum/react'; +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { CommunicationActions } from '../components/actions/CommunicationActions'; + +interface ThreadPageProps { + readonly thread: Communication; + readonly onChange: (communication: Communication) => void; +} + +export function ThreadPage(props: ThreadPageProps): JSX.Element { + const medplum = useMedplum(); + const navigate = useNavigate(); + const [patient, setPatient] = useState(); + + const patientReference = props.thread.subject; + + useEffect(() => { + const patientId = resolveId(patientReference); + + // Get the patient linked to this thread to display their information. + if (patientId) { + medplum.readResource('Patient', patientId).then(setPatient).catch(console.error); + } + }, [patientReference, medplum]); + + return ( +
+ {patient ? ( + + + + + + + + + + + + + + + + Participants + + {props.thread.recipient?.map((participant, index) => ( + + navigate(`/${participant.reference}`)}> + {participant.display ?? participant.reference} + + + ))} + + + + + + ) : ( + + + + + + + + + + + + + Participants + + {props.thread.recipient?.map((participant, index) => ( + + navigate(`/${participant.reference}`)}> + {participant.display ?? participant.reference} + + + ))} + + + + + + )} +
+ ); +} diff --git a/examples/medplum-chat-demo/src/pages/UploadDataPage.tsx b/examples/medplum-chat-demo/src/pages/UploadDataPage.tsx new file mode 100644 index 0000000000..e775bb9e52 --- /dev/null +++ b/examples/medplum-chat-demo/src/pages/UploadDataPage.tsx @@ -0,0 +1,55 @@ +import { Button } from '@mantine/core'; +import { showNotification } from '@mantine/notifications'; +import { capitalize, MedplumClient, normalizeErrorString } from '@medplum/core'; +import { Bundle } from '@medplum/fhirtypes'; +import { Document, useMedplum } from '@medplum/react'; +import { IconCircleCheck, IconCircleOff } from '@tabler/icons-react'; +import { useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import exampleDataSet from '../../data/example/example-data.json'; + +export function UploadDataPage(): JSX.Element { + const medplum = useMedplum(); + const navigate = useNavigate(); + const [buttonDisabled, setButtonDisabled] = useState(false); + + // Get the data type and capitalize the first letter + const { dataType } = useParams(); + const dataTypeDisplay = dataType ? capitalize(dataType) : ''; + + function handleDataUpload(): void { + setButtonDisabled(true); + uploadData(medplum, dataType) + .then(() => navigate('/')) + .catch((err) => { + showNotification({ + color: 'red', + icon: , + title: 'Error', + message: normalizeErrorString(err), + }); + }) + .finally(() => setButtonDisabled(false)); + } + + return ( + + + + ); +} + +async function uploadData(medplum: MedplumClient, dataType?: string): Promise { + if (dataType === 'example') { + await medplum.executeBatch(exampleDataSet as Bundle); + showNotification({ + icon: , + title: 'Success', + message: `${capitalize(dataType)} data uploaded`, + }); + } else { + throw new Error('Invalid data type'); + } +} diff --git a/examples/medplum-chat-demo/src/utils.tsx b/examples/medplum-chat-demo/src/utils.tsx new file mode 100644 index 0000000000..cf06dcf32d --- /dev/null +++ b/examples/medplum-chat-demo/src/utils.tsx @@ -0,0 +1,186 @@ +import { Filter, getReferenceString, Operator, parseReference, SearchRequest } from '@medplum/core'; +import { + Communication, + Encounter, + EncounterParticipant, + Practitioner, + QuestionnaireResponse, + QuestionnaireResponseItemAnswer, + Resource, +} from '@medplum/fhirtypes'; + +export function cleanResource(resource: Resource): Resource { + let meta = resource.meta; + if (meta) { + meta = { + ...meta, + lastUpdated: undefined, + versionId: undefined, + author: undefined, + }; + } + return { + ...resource, + meta, + }; +} + +export function getPopulatedSearch(search: SearchRequest): SearchRequest { + const filters = search.filters ?? getDefaultFilters(search.resourceType); + const fields = search.fields ?? getDefaultFields(search.resourceType); + const sortRules = search.sortRules ?? [{ code: '-_lastUpdated' }]; + + return { + resourceType: search.resourceType, + filters, + fields, + sortRules, + }; +} + +export function getDefaultFilters(resourceType: string): Filter[] { + const filters = []; + + switch (resourceType) { + case 'Communication': + filters.push( + { code: 'part-of:missing', operator: Operator.EQUALS, value: 'true' }, + { code: 'status:not', operator: Operator.EQUALS, value: 'completed' } + ); + break; + } + + return filters; +} + +export function getDefaultFields(resourceType: string): string[] { + const fields = []; + + switch (resourceType) { + case 'Communication': + fields.push('topic', 'sender', 'recipient', 'sent'); + break; + case 'Patient': + fields.push('name', '_lastUpdated'); + break; + default: + fields.push('id'); + } + + return fields; +} + +// A helper function to specifically get all of the people entered as a participant in the form +export function getRecipients(formData: QuestionnaireResponse): QuestionnaireResponseItemAnswer[] | undefined { + const items = formData.item; + const recipients: QuestionnaireResponseItemAnswer[] = []; + + if (!items) { + return recipients; + } + + for (const item of items) { + if (item.linkId === 'participants') { + if (!item.answer) { + return recipients; + } + recipients.push(...item.answer); + } + } + + return recipients; +} + +export function checkForInvalidRecipient(recipients: Communication['recipient']): boolean { + if (!recipients) { + return true; + } + + for (const recipient of recipients) { + const resourceType = parseReference(recipient)[0]; + if ( + resourceType !== 'Patient' && + resourceType !== 'Practitioner' && + resourceType !== 'RelatedPerson' && + resourceType !== 'CareTeam' && + resourceType !== 'Device' && + resourceType !== 'Organization' && + resourceType !== 'Group' && + resourceType !== 'HealthcareService' && + resourceType !== 'PractitionerRole' + ) { + return true; + } + } + + return false; +} + +export function getAttenders( + recipients: Communication['recipient'], + profile: Practitioner, + userIsParticipant: boolean = false +): EncounterParticipant[] { + // Filter recipients to only practitioners + const practitionerRecipients = recipients?.filter((recipient) => parseReference(recipient)[0] === 'Practitioner'); + + // Add all the practitioners that are included on the thread as attendants on the encounter + const attenders: EncounterParticipant[] = practitionerRecipients + ? practitionerRecipients?.map((recipient) => { + // check if the user is on the recipient list + if (getReferenceString(profile) === getReferenceString(recipient)) { + userIsParticipant = true; + } + + return { + type: [ + { + coding: [ + { + system: 'http://terminology.hl7.org/CodeSystem/v3-ParticipationType', + code: 'ATND', + display: 'attender', + }, + ], + }, + ], + individual: { + reference: getReferenceString(recipient), + }, + }; + }) + : []; + + if (!userIsParticipant) { + attenders.push({ + type: [ + { + coding: [ + { + system: 'http://terminology.hl7.org/CodeSystem/v3-ParticipationType', + code: 'ATND', + display: 'attender', + }, + ], + }, + ], + individual: { + reference: getReferenceString(profile), + }, + }); + } + + return attenders; +} + +export function shouldShowPatientSummary(encounter: Encounter): boolean { + if (!encounter.subject) { + return false; + } + + if (parseReference(encounter.subject)[0] === 'Patient') { + return true; + } + + return false; +} diff --git a/examples/medplum-chat-demo/src/vite-env.d.ts b/examples/medplum-chat-demo/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/medplum-chat-demo/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/medplum-chat-demo/tsconfig.json b/examples/medplum-chat-demo/tsconfig.json new file mode 100644 index 0000000000..79240f45dd --- /dev/null +++ b/examples/medplum-chat-demo/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-chat-demo/vercel.json b/examples/medplum-chat-demo/vercel.json new file mode 100644 index 0000000000..33c4ed5992 --- /dev/null +++ b/examples/medplum-chat-demo/vercel.json @@ -0,0 +1,5 @@ +{ + "installCommand": "npm install", + "buildCommand": "npm run build", + "routes": [{ "src": "/[^.]+", "dest": "/", "status": 200 }] +} diff --git a/examples/medplum-chat-demo/vite.config.ts b/examples/medplum-chat-demo/vite.config.ts new file mode 100644 index 0000000000..e4c46a598c --- /dev/null +++ b/examples/medplum-chat-demo/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 c2b683d9ae..d554f14ace 100644 --- a/package-lock.json +++ b/package-lock.json @@ -109,6 +109,864 @@ "vite": "5.2.8" } }, + "examples/medplum-chat-demo": { + "version": "3.1.2", + "devDependencies": { + "@mantine/core": "7.6.2", + "@mantine/hooks": "7.6.2", + "@mantine/notifications": "7.6.2", + "@medplum/core": "3.1.2", + "@medplum/eslint-config": "3.1.2", + "@medplum/fhirtypes": "3.1.2", + "@medplum/react": "3.1.2", + "@tabler/icons-react": "3.0.0", + "@types/node": "20.11.28", + "@types/react": "18.2.66", + "@types/react-dom": "18.2.22", + "@vitejs/plugin-react": "4.2.1", + "postcss": "8.4.36", + "postcss-preset-mantine": "1.13.0", + "react": "18.2.0", + "react-dom": "18.2.0", + "react-router-dom": "6.22.3", + "typescript": "5.4.2", + "vite": "5.1.6" + } + }, + "examples/medplum-chat-demo/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-chat-demo/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-chat-demo/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-chat-demo/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-chat-demo/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-chat-demo/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-chat-demo/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-chat-demo/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-chat-demo/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-chat-demo/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-chat-demo/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-chat-demo/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-chat-demo/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-chat-demo/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-chat-demo/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-chat-demo/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-chat-demo/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-chat-demo/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-chat-demo/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-chat-demo/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-chat-demo/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-chat-demo/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-chat-demo/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-chat-demo/node_modules/@mantine/core": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.6.2.tgz", + "integrity": "sha512-qmZhmQVc7ZZ8EKKhPkGuZbfBnLXR0xE45ikxfx+1E6/8hLY5Ypr4nWqh5Pk6p3b+K71yYnBqlbNXbtHLQH0h3g==", + "dev": true, + "dependencies": { + "@floating-ui/react": "^0.26.9", + "clsx": "2.1.0", + "react-number-format": "^5.3.1", + "react-remove-scroll": "^2.5.7", + "react-textarea-autosize": "8.5.3", + "type-fest": "^4.12.0" + }, + "peerDependencies": { + "@mantine/hooks": "7.6.2", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, + "examples/medplum-chat-demo/node_modules/@mantine/hooks": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.6.2.tgz", + "integrity": "sha512-ZrOgrZHoIGCDKrr2/9njDgK0al+jjusYQFlmR0YyEFyRtgY6eNSI4zuYLcAPx1haHmUm5RsLBrqY6Iy/TLdGXA==", + "dev": true, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "examples/medplum-chat-demo/node_modules/@mantine/notifications": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-7.6.2.tgz", + "integrity": "sha512-vLs6Y5nnxHipGkA5TSsxgjeus0N9uS+0+E9KZ5OG5QEtz7BdOsKPtNmytsQzBN8P8fjttFImhhoSUOLpYv0xtA==", + "dev": true, + "dependencies": { + "@mantine/store": "7.6.2", + "react-transition-group": "4.4.5" + }, + "peerDependencies": { + "@mantine/core": "7.6.2", + "@mantine/hooks": "7.6.2", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, + "examples/medplum-chat-demo/node_modules/@mantine/store": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@mantine/store/-/store-7.6.2.tgz", + "integrity": "sha512-IEGbyIs7LIXYQtjR87GQPiw12klgsiKiqplGu9LekJb8uW/7XSRNs31ggqKmdF+cMWO/WyQhEXZdpWNib6tVOw==", + "dev": true, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "examples/medplum-chat-demo/node_modules/@tabler/icons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.0.0.tgz", + "integrity": "sha512-DT27tfUt7v6wqn+NVBp5zSxYwfVuyImdgih0ULTbs0qQrAANmjobyMSrD+ATWvIsS5QuhbA+lX4hv8pEe7Lm/w==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + } + }, + "examples/medplum-chat-demo/node_modules/@tabler/icons-react": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.0.0.tgz", + "integrity": "sha512-KI3rFTvH6k/4fJVVegvqdqiaaPyo2YZ4X/4A0HExwJKfqZrUeZZHB2/iCTXnvecI9REWrexmFlyYMpthA9dEDw==", + "dev": true, + "dependencies": { + "@tabler/icons": "3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + }, + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0" + } + }, + "examples/medplum-chat-demo/node_modules/@types/node": { + "version": "20.11.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.28.tgz", + "integrity": "sha512-M/GPWVS2wLkSkNHVeLkrF2fD5Lx5UC4PxA0uZcKc6QqbIQUJyW1jVjueJYi1z8n0I5PxYrtpnPnWglE+y9A0KA==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "examples/medplum-chat-demo/node_modules/@types/react": { + "version": "18.2.66", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.66.tgz", + "integrity": "sha512-OYTmMI4UigXeFMF/j4uv0lBBEbongSgptPrHBxqME44h9+yNov+oL6Z3ocJKo0WyXR84sQUNeyIp9MRfckvZpg==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "examples/medplum-chat-demo/node_modules/@types/react-dom": { + "version": "18.2.22", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.22.tgz", + "integrity": "sha512-fHkBXPeNtfvri6gdsMYyW+dW7RXFo6Ad09nLFK0VQWR7yGLai/Cyvyj696gbwYvBnhGtevUG9cET0pmUbMtoPQ==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "examples/medplum-chat-demo/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-chat-demo/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-chat-demo/node_modules/lightningcss": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.24.1.tgz", + "integrity": "sha512-kUpHOLiH5GB0ERSv4pxqlL0RYKnOXtgGtVe7shDGfhS0AZ4D1ouKFYAcLcZhql8aMspDNzaUCumGHZ78tb2fTg==", + "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.24.1", + "lightningcss-darwin-x64": "1.24.1", + "lightningcss-freebsd-x64": "1.24.1", + "lightningcss-linux-arm-gnueabihf": "1.24.1", + "lightningcss-linux-arm64-gnu": "1.24.1", + "lightningcss-linux-arm64-musl": "1.24.1", + "lightningcss-linux-x64-gnu": "1.24.1", + "lightningcss-linux-x64-musl": "1.24.1", + "lightningcss-win32-x64-msvc": "1.24.1" + } + }, + "examples/medplum-chat-demo/node_modules/lightningcss-darwin-arm64": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.24.1.tgz", + "integrity": "sha512-1jQ12jBy+AE/73uGQWGSafK5GoWgmSiIQOGhSEXiFJSZxzV+OXIx+a9h2EYHxdJfX864M+2TAxWPWb0Vv+8y4w==", + "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-chat-demo/node_modules/lightningcss-darwin-x64": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.24.1.tgz", + "integrity": "sha512-R4R1d7VVdq2mG4igMU+Di8GPf0b64ZLnYVkubYnGG0Qxq1KaXQtAzcLI43EkpnoWvB/kUg8JKCWH4S13NfiLcQ==", + "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-chat-demo/node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.24.1.tgz", + "integrity": "sha512-NLQLnBQW/0sSg74qLNI8F8QKQXkNg4/ukSTa+XhtkO7v3BnK19TS1MfCbDHt+TTdSgNEBv0tubRuapcKho2EWw==", + "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-chat-demo/node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.24.1.tgz", + "integrity": "sha512-AQxWU8c9E9JAjAi4Qw9CvX2tDIPjgzCTrZCSXKELfs4mCwzxRkHh2RCxX8sFK19RyJoJAjA/Kw8+LMNRHS5qEg==", + "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-chat-demo/node_modules/lightningcss-linux-arm64-musl": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.24.1.tgz", + "integrity": "sha512-JCgH/SrNrhqsguUA0uJUM1PvN5+dVuzPIlXcoWDHSv2OU/BWlj2dUYr3XNzEw748SmNZPfl2NjQrAdzaPOn1lA==", + "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-chat-demo/node_modules/lightningcss-linux-x64-gnu": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.24.1.tgz", + "integrity": "sha512-TYdEsC63bHV0h47aNRGN3RiK7aIeco3/keN4NkoSQ5T8xk09KHuBdySltWAvKLgT8JvR+ayzq8ZHnL1wKWY0rw==", + "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-chat-demo/node_modules/lightningcss-linux-x64-musl": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.24.1.tgz", + "integrity": "sha512-HLfzVik3RToot6pQ2Rgc3JhfZkGi01hFetHt40HrUMoeKitLoqUUT5owM6yTZPTytTUW9ukLBJ1pc3XNMSvlLw==", + "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-chat-demo/node_modules/lightningcss-win32-x64-msvc": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.24.1.tgz", + "integrity": "sha512-joEupPjYJ7PjZtDsS5lzALtlAudAbgIBMGJPNeFe5HfdmJXFd13ECmEM+5rXNxYVMRHua2w8132R6ab5Z6K9Ow==", + "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-chat-demo/node_modules/postcss": { + "version": "8.4.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.36.tgz", + "integrity": "sha512-/n7eumA6ZjFHAsbX30yhHup/IMkOmlmvtEi7P+6RMYf+bGJSUHc3geH4a0NSZxAz/RJfiS9tooCTs9LAVYUZKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.1.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "examples/medplum-chat-demo/node_modules/postcss-preset-mantine": { + "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", + "postcss-nested": "^6.0.1" + }, + "peerDependencies": { + "postcss": ">=8.0.0" + } + }, + "examples/medplum-chat-demo/node_modules/typescript": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", + "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "examples/medplum-chat-demo/node_modules/vite": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.6.tgz", + "integrity": "sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==", + "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-client-external-idp-demo": { "version": "3.1.2", "devDependencies": { @@ -20248,6 +21106,12 @@ "@types/node": "*" } }, + "node_modules/@types/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-YIoDCTH3Af6XM5VuwGG/QL/CJqga1Zm3NkU3HZ4ZHK2fRMPYP1VczsTUqtsf43PH/iJNVlPHAo2oWX7BSdB2Hw==", + "dev": true + }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", @@ -31103,6 +31967,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", @@ -31116,6 +31981,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" } @@ -37038,6 +37904,27 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.24.1.tgz", + "integrity": "sha512-z6NberUUw5ALES6Ixn2shmjRRrM1cmEn1ZQPiM5IrZ6xHHL5a1lPin9pRv+w6eWfcrEo+qGG6R9XfJrpuY3e4g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lightningcss-linux-arm-gnueabihf": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.19.0.tgz", @@ -38408,6 +39295,10 @@ "resolved": "examples/medplum-chart-demo", "link": true }, + "node_modules/medplum-chat-demo": { + "resolved": "examples/medplum-chat-demo", + "link": true + }, "node_modules/medplum-client-external-idp-demo": { "resolved": "examples/medplum-client-external-idp-demo", "link": true @@ -55768,6 +56659,7 @@ "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, "engines": { "node": ">= 6" } From 446a902bb182e1037ed00192b9e1cf836c042183 Mon Sep 17 00:00:00 2001 From: ksmith94 <102421938+ksmith94@users.noreply.github.com> Date: Tue, 16 Apr 2024 23:39:28 -0400 Subject: [PATCH 23/52] Note that filter is not supported for chained search (#4380) --- packages/docs/docs/search/chained-search.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/docs/docs/search/chained-search.md b/packages/docs/docs/search/chained-search.md index 80812d1217..d223663432 100644 --- a/packages/docs/docs/search/chained-search.md +++ b/packages/docs/docs/search/chained-search.md @@ -88,6 +88,12 @@ You can include more than one link in your chained search. In the below example, +:::note Filtering Chained Searches + +The [`_filter` search parameter](/docs/search/filter-search-parameter) is not currently supported when using chained search. This is on the Medplum road map, but there is no firm date when it is expected to be implemented. You can follow [this issue](https://github.com/medplum/medplum/issues/3224) for updates. + +::: + ## Reverse Chained Search Chained references can also be constructed in reverse, filtering on other resources that reference your target search resource. This is done using the `_has` parameter, which has a special syntax: `_has:::`. From 53a1fb39c017a095f5d99c23f520eb3ae12f6134 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Wed, 17 Apr 2024 16:27:57 -0700 Subject: [PATCH 24/52] Fixed next.js build errors (#4392) * Fixed next.js build errors * Added missing files --- examples/medplum-nextauth-demo/app/layout.tsx | 11 +- examples/medplum-nextjs-demo/app/layout.tsx | 11 +- examples/medplum-nextjs-demo/app/root.tsx | 7 +- examples/medplum-nextjs-demo/app/theme.ts | 7 ++ examples/medplum-nextjs-demo/next.config.js | 6 - examples/medplum-nextjs-demo/next.config.mjs | 16 +++ examples/medplum-nextjs-demo/package.json | 1 + .../src/components/BundleDisplay.tsx | 4 +- package-lock.json | 112 +++++++++++++++--- packages/app/src/resource/ProfilesPage.tsx | 6 +- packages/app/src/test-utils/render.tsx | 7 +- .../AsyncAutocomplete/AsyncAutocomplete.tsx | 4 +- .../CheckboxFormSection.tsx | 2 +- .../src/ElementsInput/ElementsInput.utils.ts | 4 +- .../react/src/FormSection/FormSection.tsx | 4 +- packages/react/src/Panel/Panel.tsx | 3 +- .../ReferenceRangeEditor.tsx | 6 +- packages/react/src/SliceInput/SliceInput.tsx | 10 +- packages/react/src/buttons/ArrayAddButton.tsx | 5 +- .../react/src/buttons/ArrayRemoveButton.tsx | 3 +- .../react/src/chat/ChatModal/ChatModal.tsx | 4 +- packages/react/src/test-utils/render.tsx | 8 +- packages/react/src/utils/dom.ts | 4 +- .../react/src/utils/maybeWrapWithContext.tsx | 4 +- 24 files changed, 176 insertions(+), 73 deletions(-) create mode 100644 examples/medplum-nextjs-demo/app/theme.ts delete mode 100644 examples/medplum-nextjs-demo/next.config.js create mode 100644 examples/medplum-nextjs-demo/next.config.mjs diff --git a/examples/medplum-nextauth-demo/app/layout.tsx b/examples/medplum-nextauth-demo/app/layout.tsx index 3216050723..468d84ccf0 100644 --- a/examples/medplum-nextauth-demo/app/layout.tsx +++ b/examples/medplum-nextauth-demo/app/layout.tsx @@ -1,20 +1,15 @@ -import type { Metadata } from 'next'; import { MantineProvider } from '@mantine/core'; import '@mantine/core/styles.css'; import { Inter } from 'next/font/google'; +import { ReactNode } from 'react'; const inter = Inter({ subsets: ['latin'] }); -export const metadata: Metadata = { - title: 'Create Next App', - description: 'Generated by create next app', -}; - export default function RootLayout({ children, }: Readonly<{ - children: React.ReactNode; -}>): React.ReactNode { + children: ReactNode; +}>): ReactNode { return ( diff --git a/examples/medplum-nextjs-demo/app/layout.tsx b/examples/medplum-nextjs-demo/app/layout.tsx index 5ac7b3c633..764ad88518 100644 --- a/examples/medplum-nextjs-demo/app/layout.tsx +++ b/examples/medplum-nextjs-demo/app/layout.tsx @@ -1,6 +1,8 @@ +import { ColorSchemeScript, MantineProvider } from '@mantine/core'; import type { Metadata } from 'next'; import { ReactNode } from 'react'; import Root from './root'; +import { theme } from './theme'; // eslint-disable-next-line react-refresh/only-export-components export const metadata: Metadata = { @@ -15,8 +17,15 @@ export default function RootLayout(props: { children: ReactNode }): JSX.Element return ( + + + + + - {children} + + {children} + ); diff --git a/examples/medplum-nextjs-demo/app/root.tsx b/examples/medplum-nextjs-demo/app/root.tsx index c7f9a43c42..313559318f 100644 --- a/examples/medplum-nextjs-demo/app/root.tsx +++ b/examples/medplum-nextjs-demo/app/root.tsx @@ -1,6 +1,5 @@ 'use client'; -import { MantineProvider } from '@mantine/core'; import '@mantine/core/styles.css'; import { MedplumClient } from '@medplum/core'; import { MedplumProvider } from '@medplum/react'; @@ -22,9 +21,5 @@ const medplum = new MedplumClient({ }); export default function Root(props: { children: ReactNode }): JSX.Element { - return ( - - {props.children} - - ); + return {props.children}; } diff --git a/examples/medplum-nextjs-demo/app/theme.ts b/examples/medplum-nextjs-demo/app/theme.ts new file mode 100644 index 0000000000..7fdf832313 --- /dev/null +++ b/examples/medplum-nextjs-demo/app/theme.ts @@ -0,0 +1,7 @@ +'use client'; + +import { createTheme } from '@mantine/core'; + +export const theme = createTheme({ + /* Put your mantine theme override here */ +}); diff --git a/examples/medplum-nextjs-demo/next.config.js b/examples/medplum-nextjs-demo/next.config.js deleted file mode 100644 index 7f9a79bde9..0000000000 --- a/examples/medplum-nextjs-demo/next.config.js +++ /dev/null @@ -1,6 +0,0 @@ -const nextConfig = { - reactStrictMode: true, - swcMinify: true, -}; - -export default nextConfig; diff --git a/examples/medplum-nextjs-demo/next.config.mjs b/examples/medplum-nextjs-demo/next.config.mjs new file mode 100644 index 0000000000..8f38edd956 --- /dev/null +++ b/examples/medplum-nextjs-demo/next.config.mjs @@ -0,0 +1,16 @@ +import bundleAnalyzer from '@next/bundle-analyzer'; + +const withBundleAnalyzer = bundleAnalyzer({ + // eslint-disable-next-line no-undef + enabled: process.env.ANALYZE === 'true', +}); + +export default withBundleAnalyzer({ + reactStrictMode: false, + eslint: { + ignoreDuringBuilds: true, + }, + experimental: { + optimizePackageImports: ['@mantine/core', '@mantine/hooks'], + }, +}); diff --git a/examples/medplum-nextjs-demo/package.json b/examples/medplum-nextjs-demo/package.json index fd1ae631c7..43d6659c2f 100644 --- a/examples/medplum-nextjs-demo/package.json +++ b/examples/medplum-nextjs-demo/package.json @@ -15,6 +15,7 @@ "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/react": "3.1.2", + "@next/bundle-analyzer": "14.2.1", "next": "14.2.1", "react": "18.2.0", "react-dom": "18.2.0", diff --git a/examples/medplum-websocket-subscriptions-demo/src/components/BundleDisplay.tsx b/examples/medplum-websocket-subscriptions-demo/src/components/BundleDisplay.tsx index cc3b97f916..1e3a6fe94c 100644 --- a/examples/medplum-websocket-subscriptions-demo/src/components/BundleDisplay.tsx +++ b/examples/medplum-websocket-subscriptions-demo/src/components/BundleDisplay.tsx @@ -2,7 +2,7 @@ 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'; +import { SyntheticEvent, useCallback } from 'react'; export interface BundleDisplayProps { readonly bundle: Bundle; @@ -16,7 +16,7 @@ export function BundleDisplay(props: BundleDisplayProps): JSX.Element { const [recipientType, recipientId] = ((communication.recipient?.[0] as Reference).reference as string).split('/'); const markAsCompleted = useCallback( - (e: React.SyntheticEvent) => { + (e: SyntheticEvent) => { e.stopPropagation(); e.preventDefault(); medplum diff --git a/package-lock.json b/package-lock.json index d554f14ace..56923e7bba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1130,6 +1130,7 @@ "@mantine/notifications": "7.8.0", "@medplum/core": "3.1.2", "@medplum/react": "3.1.2", + "@next/bundle-analyzer": "14.2.1", "next": "14.2.1", "react": "18.2.0", "react-dom": "18.2.0", @@ -5098,7 +5099,6 @@ "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", - "dev": true, "engines": { "node": ">=10.0.0" } @@ -12067,6 +12067,98 @@ "tar-fs": "^2.1.1" } }, + "node_modules/@next/bundle-analyzer": { + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@next/bundle-analyzer/-/bundle-analyzer-14.2.1.tgz", + "integrity": "sha512-Qwy3Mu/dfnu4rs2xzCy7gKZlwzZzYtiq/rjPcK/7xq3BHSyLthkHf1NAF8NNfjVTouDwo2KchisHrmAamUNWWw==", + "dependencies": { + "webpack-bundle-analyzer": "4.10.1" + } + }, + "node_modules/@next/bundle-analyzer/node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/@next/bundle-analyzer/node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/@next/bundle-analyzer/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/bundle-analyzer/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@next/bundle-analyzer/node_modules/webpack-bundle-analyzer": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.1.tgz", + "integrity": "sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==", + "dependencies": { + "@discoveryjs/json-ext": "0.5.7", + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "commander": "^7.2.0", + "debounce": "^1.2.1", + "escape-string-regexp": "^4.0.0", + "gzip-size": "^6.0.0", + "html-escaper": "^2.0.2", + "is-plain-object": "^5.0.0", + "opener": "^1.5.2", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", + "ws": "^7.3.1" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/@next/bundle-analyzer/node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/@next/env": { "version": "14.2.1", "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.1.tgz", @@ -14177,8 +14269,7 @@ "node_modules/@polka/url": { "version": "1.0.0-next.25", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz", - "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==", - "dev": true + "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==" }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", @@ -27731,8 +27822,7 @@ "node_modules/debounce": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", - "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", - "dev": true + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" }, "node_modules/debug": { "version": "4.3.4", @@ -28583,8 +28673,7 @@ "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" }, "node_modules/duplexer2": { "version": "0.1.4", @@ -32836,7 +32925,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", - "dev": true, "dependencies": { "duplexer": "^0.1.2" }, @@ -33443,8 +33531,7 @@ "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" }, "node_modules/html-minifier-terser": { "version": "7.2.0", @@ -34742,7 +34829,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -44962,7 +45048,6 @@ "version": "1.5.2", "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", - "dev": true, "bin": { "opener": "bin/opener-bin.js" } @@ -51108,7 +51193,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", - "dev": true, "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", @@ -51122,7 +51206,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", - "dev": true, "engines": { "node": ">=10" } @@ -53569,7 +53652,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "dev": true, "engines": { "node": ">=6" } diff --git a/packages/app/src/resource/ProfilesPage.tsx b/packages/app/src/resource/ProfilesPage.tsx index 9ee6bc4f8b..14febcb7d3 100644 --- a/packages/app/src/resource/ProfilesPage.tsx +++ b/packages/app/src/resource/ProfilesPage.tsx @@ -3,10 +3,10 @@ import { showNotification } from '@mantine/notifications'; import { deepClone, normalizeErrorString, normalizeOperationOutcome } from '@medplum/core'; import { OperationOutcome, Resource, ResourceType } from '@medplum/fhirtypes'; import { Document, ResourceForm, SupportedProfileStructureDefinition, useMedplum } from '@medplum/react'; -import React, { useCallback, useEffect, useState } from 'react'; +import { FC, useCallback, useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; -import { addProfileToResource, cleanResource, removeProfileFromResource } from './utils'; import { ProfileTabs } from './ProfileTabs'; +import { addProfileToResource, cleanResource, removeProfileFromResource } from './utils'; export function ProfilesPage(): JSX.Element | null { const medplum = useMedplum(); @@ -57,7 +57,7 @@ type ProfileDetailProps = { readonly onResourceUpdated: (newResource: Resource) => void; }; -const ProfileDetail: React.FC = ({ profile, resource, onResourceUpdated }) => { +const ProfileDetail: FC = ({ profile, resource, onResourceUpdated }) => { const medplum = useMedplum(); const [outcome, setOutcome] = useState(); const [active, setActive] = useState(() => resource.meta?.profile?.includes(profile.url)); diff --git a/packages/app/src/test-utils/render.tsx b/packages/app/src/test-utils/render.tsx index cd5441f107..699d5ea5c1 100644 --- a/packages/app/src/test-utils/render.tsx +++ b/packages/app/src/test-utils/render.tsx @@ -5,15 +5,14 @@ import { MantineProvider } from '@mantine/core'; import { RenderResult, act, fireEvent, screen, render as testingLibraryRender, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { ReactNode } from 'react'; export { RenderResult, act, fireEvent, screen, userEvent, waitFor }; const theme = {}; -export function render(ui: React.ReactNode): RenderResult { +export function render(ui: ReactNode): RenderResult { return testingLibraryRender(<>{ui}, { - wrapper: ({ children }: { children: React.ReactNode }) => ( - {children} - ), + wrapper: ({ children }: { children: ReactNode }) => {children}, }); } diff --git a/packages/react/src/AsyncAutocomplete/AsyncAutocomplete.tsx b/packages/react/src/AsyncAutocomplete/AsyncAutocomplete.tsx index 277ae18f37..fac146d6dd 100644 --- a/packages/react/src/AsyncAutocomplete/AsyncAutocomplete.tsx +++ b/packages/react/src/AsyncAutocomplete/AsyncAutocomplete.tsx @@ -11,7 +11,7 @@ import { } from '@mantine/core'; import { showNotification } from '@mantine/notifications'; import { normalizeErrorString } from '@medplum/core'; -import { KeyboardEvent, ReactNode, useCallback, useEffect, useRef, useState } from 'react'; +import { KeyboardEvent, ReactNode, SyntheticEvent, useCallback, useEffect, useRef, useState } from 'react'; import { killEvent } from '../utils/dom'; export interface AsyncAutocompleteOption extends ComboboxItem { @@ -129,7 +129,7 @@ export function AsyncAutocomplete(props: AsyncAutocompleteProps): JSX.Elem }, [combobox, loadOptions, onChange, toOption]); const handleSearchChange = useCallback( - (e: React.SyntheticEvent): void => { + (e: SyntheticEvent): void => { if ((options && options.length > 0) || creatable) { combobox.openDropdown(); } diff --git a/packages/react/src/CheckboxFormSection/CheckboxFormSection.tsx b/packages/react/src/CheckboxFormSection/CheckboxFormSection.tsx index 6d985e7eee..0629914702 100644 --- a/packages/react/src/CheckboxFormSection/CheckboxFormSection.tsx +++ b/packages/react/src/CheckboxFormSection/CheckboxFormSection.tsx @@ -15,7 +15,7 @@ export interface CheckboxFormSectionProps { export function CheckboxFormSection(props: CheckboxFormSectionProps): JSX.Element { const { debugMode } = useContext(ElementsContext); - let label: React.ReactNode; + let label: ReactNode; if (debugMode && props.fhirPath) { label = `${props.title} - ${props.fhirPath}`; } else { diff --git a/packages/react/src/ElementsInput/ElementsInput.utils.ts b/packages/react/src/ElementsInput/ElementsInput.utils.ts index 67ff6a3e11..70e7da1a78 100644 --- a/packages/react/src/ElementsInput/ElementsInput.utils.ts +++ b/packages/react/src/ElementsInput/ElementsInput.utils.ts @@ -1,8 +1,8 @@ import { ElementsContextType, InternalSchemaElement, isPopulated } from '@medplum/core'; -import React from 'react'; +import { createContext } from 'react'; import { DEFAULT_IGNORED_NON_NESTED_PROPERTIES, DEFAULT_IGNORED_PROPERTIES } from '../constants'; -export const ElementsContext = React.createContext({ +export const ElementsContext = createContext({ path: '', profileUrl: undefined, elements: Object.create(null), diff --git a/packages/react/src/FormSection/FormSection.tsx b/packages/react/src/FormSection/FormSection.tsx index ef1e1c8707..06219fe1aa 100644 --- a/packages/react/src/FormSection/FormSection.tsx +++ b/packages/react/src/FormSection/FormSection.tsx @@ -1,8 +1,8 @@ import { Input } from '@mantine/core'; import { OperationOutcome } from '@medplum/fhirtypes'; import { ReactNode, useContext } from 'react'; -import { getErrorsForInput } from '../utils/outcomes'; import { ElementsContext } from '../ElementsInput/ElementsInput.utils'; +import { getErrorsForInput } from '../utils/outcomes'; export interface FormSectionProps { readonly title?: string; @@ -19,7 +19,7 @@ export interface FormSectionProps { export function FormSection(props: FormSectionProps): JSX.Element { const { debugMode } = useContext(ElementsContext); - let label: React.ReactNode; + let label: ReactNode; if (debugMode && props.fhirPath) { label = `${props.title} - ${props.fhirPath}`; } else { diff --git a/packages/react/src/Panel/Panel.tsx b/packages/react/src/Panel/Panel.tsx index 87ff3ec529..2d6aae14ac 100644 --- a/packages/react/src/Panel/Panel.tsx +++ b/packages/react/src/Panel/Panel.tsx @@ -1,11 +1,12 @@ import { Paper, PaperProps } from '@mantine/core'; import cx from 'clsx'; +import { ReactNode } from 'react'; import classes from './Panel.module.css'; export interface PanelProps extends PaperProps { readonly width?: number; readonly fill?: boolean; - readonly children?: React.ReactNode; + readonly children?: ReactNode; } export function Panel(props: PanelProps): JSX.Element { diff --git a/packages/react/src/ReferenceRangeEditor/ReferenceRangeEditor.tsx b/packages/react/src/ReferenceRangeEditor/ReferenceRangeEditor.tsx index 78d5b8a124..941c052234 100644 --- a/packages/react/src/ReferenceRangeEditor/ReferenceRangeEditor.tsx +++ b/packages/react/src/ReferenceRangeEditor/ReferenceRangeEditor.tsx @@ -179,7 +179,7 @@ export function ReferenceRangeGroupEditor(props: ReferenceRangeGroupEditorProps) data-testid={`remove-group-button-${intervalGroup.id}`} key={`remove-group-button-${intervalGroup.id}`} size="sm" - onClick={(e: React.MouseEvent) => { + onClick={(e: MouseEvent) => { killEvent(e); props.onRemoveGroup(intervalGroup); }} @@ -209,7 +209,7 @@ export function ReferenceRangeGroupEditor(props: ReferenceRangeGroupEditorProps) size="sm" key={`remove-interval-${interval.id}`} data-testid={`remove-interval-${interval.id}`} - onClick={(e: React.MouseEvent) => { + onClick={(e: MouseEvent) => { killEvent(e); props.onRemove(intervalGroup.id, interval); }} @@ -232,7 +232,7 @@ export function ReferenceRangeGroupEditor(props: ReferenceRangeGroupEditorProps) title="Add Interval" variant="subtle" size="sm" - onClick={(e: React.MouseEvent) => { + onClick={(e: MouseEvent) => { killEvent(e); props.onAdd(intervalGroup.id, { range: { diff --git a/packages/react/src/SliceInput/SliceInput.tsx b/packages/react/src/SliceInput/SliceInput.tsx index 1603a6a84f..6c953c6883 100644 --- a/packages/react/src/SliceInput/SliceInput.tsx +++ b/packages/react/src/SliceInput/SliceInput.tsx @@ -8,16 +8,16 @@ import { isEmpty, isPopulated, } from '@medplum/core'; -import { useContext, useMemo, useState } from 'react'; +import { MouseEvent, useContext, useMemo, useState } from 'react'; import { ElementsContext } from '../ElementsInput/ElementsInput.utils'; import { FormSection } from '../FormSection/FormSection'; +import classes from '../ResourceArrayInput/ResourceArrayInput.module.css'; import { ElementDefinitionTypeInput } from '../ResourcePropertyInput/ResourcePropertyInput'; +import { BaseInputProps } from '../ResourcePropertyInput/ResourcePropertyInput.utils'; import { ArrayAddButton } from '../buttons/ArrayAddButton'; import { ArrayRemoveButton } from '../buttons/ArrayRemoveButton'; import { killEvent } from '../utils/dom'; -import classes from '../ResourceArrayInput/ResourceArrayInput.module.css'; import { maybeWrapWithContext } from '../utils/maybeWrapWithContext'; -import { BaseInputProps } from '../ResourcePropertyInput/ResourcePropertyInput.utils'; export interface SliceInputProps extends BaseInputProps { readonly slice: SliceDefinitionWithTypes; @@ -96,7 +96,7 @@ export function SliceInput(props: SliceInputProps): JSX.Element | null { { + onClick={(e: MouseEvent) => { killEvent(e); const newValues = [...values]; newValues.splice(valueIndex, 1); @@ -111,7 +111,7 @@ export function SliceInput(props: SliceInputProps): JSX.Element | null { { + onClick={(e: MouseEvent) => { killEvent(e); const newValues = [...values, undefined]; setValuesWrapper(newValues); diff --git a/packages/react/src/buttons/ArrayAddButton.tsx b/packages/react/src/buttons/ArrayAddButton.tsx index 254818d9b6..874429ce1d 100644 --- a/packages/react/src/buttons/ArrayAddButton.tsx +++ b/packages/react/src/buttons/ArrayAddButton.tsx @@ -1,9 +1,10 @@ -import { Button, ActionIcon } from '@mantine/core'; +import { ActionIcon, Button } from '@mantine/core'; import { IconCirclePlus } from '@tabler/icons-react'; +import { MouseEventHandler } from 'react'; export interface ArrayAddButtonProps { readonly propertyDisplayName?: string; - readonly onClick: React.MouseEventHandler; + readonly onClick: MouseEventHandler; readonly testId?: string; } diff --git a/packages/react/src/buttons/ArrayRemoveButton.tsx b/packages/react/src/buttons/ArrayRemoveButton.tsx index bc78478711..40970a2b18 100644 --- a/packages/react/src/buttons/ArrayRemoveButton.tsx +++ b/packages/react/src/buttons/ArrayRemoveButton.tsx @@ -1,9 +1,10 @@ import { ActionIcon } from '@mantine/core'; import { IconCircleMinus } from '@tabler/icons-react'; +import { MouseEventHandler } from 'react'; export interface ArrayRemoveButtonProps { readonly propertyDisplayName?: string; - readonly onClick: React.MouseEventHandler; + readonly onClick: MouseEventHandler; readonly testId?: string; } diff --git a/packages/react/src/chat/ChatModal/ChatModal.tsx b/packages/react/src/chat/ChatModal/ChatModal.tsx index 4654078519..50dab73bdd 100644 --- a/packages/react/src/chat/ChatModal/ChatModal.tsx +++ b/packages/react/src/chat/ChatModal/ChatModal.tsx @@ -1,12 +1,12 @@ import { ActionIcon } from '@mantine/core'; import { useMedplumProfile } from '@medplum/react-hooks'; import { IconChevronDown, IconChevronUp } from '@tabler/icons-react'; -import { useEffect, useState } from 'react'; +import { ReactNode, useEffect, useState } from 'react'; import classes from './ChatModal.module.css'; export interface ChatModalProps { readonly open?: boolean; - readonly children: React.ReactNode; + readonly children: ReactNode; } export function ChatModal(props: ChatModalProps): JSX.Element | null { diff --git a/packages/react/src/test-utils/render.tsx b/packages/react/src/test-utils/render.tsx index 9a7e8639e7..8fcf582122 100644 --- a/packages/react/src/test-utils/render.tsx +++ b/packages/react/src/test-utils/render.tsx @@ -12,17 +12,15 @@ import { within, } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { ReactNode } from 'react'; export { act, fireEvent, screen, userEvent, waitFor, within }; const theme = {}; -export function render( - ui: React.ReactNode, - wrapper?: ({ children }: { children: React.ReactNode }) => JSX.Element -): RenderResult { +export function render(ui: ReactNode, wrapper?: ({ children }: { children: ReactNode }) => JSX.Element): RenderResult { return testingLibraryRender(ui, { - wrapper: ({ children }: { children: React.ReactNode }) => ( + wrapper: ({ children }: { children: ReactNode }) => ( {wrapper ? wrapper({ children }) : children} ), }); diff --git a/packages/react/src/utils/dom.ts b/packages/react/src/utils/dom.ts index 551b089fef..7b94e7cef4 100644 --- a/packages/react/src/utils/dom.ts +++ b/packages/react/src/utils/dom.ts @@ -1,10 +1,12 @@ +import { SyntheticEvent } from 'react'; + /** * Kills a browser event. * Prevents default behavior. * Stops event propagation. * @param e - The event. */ -export function killEvent(e: Event | React.SyntheticEvent): void { +export function killEvent(e: Event | SyntheticEvent): void { e.preventDefault(); e.stopPropagation(); } diff --git a/packages/react/src/utils/maybeWrapWithContext.tsx b/packages/react/src/utils/maybeWrapWithContext.tsx index c21d70acbb..08bfd8c3a1 100644 --- a/packages/react/src/utils/maybeWrapWithContext.tsx +++ b/packages/react/src/utils/maybeWrapWithContext.tsx @@ -1,5 +1,7 @@ +import { Context } from 'react'; + export function maybeWrapWithContext( - ContextProvider: React.Context['Provider'], + ContextProvider: Context['Provider'], contextValue: T | undefined, contents: JSX.Element ): JSX.Element { From b1d63dfb33d7fb6bc3173f91208cfe3844542bba Mon Sep 17 00:00:00 2001 From: Reshma Khilnani Date: Wed, 17 Apr 2024 20:21:15 -0700 Subject: [PATCH 25/52] Updating SKU information and enterprise page (#4395) * Updating SKU information and enterprise page * [autofix.ci] apply automated fixes --------- Co-authored-by: Reshma Khilnani Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- packages/docs/src/pages/enterprise.tsx | 31 ++- packages/docs/src/pages/pricing.tsx | 253 ++++++++++++++++++++----- 2 files changed, 230 insertions(+), 54 deletions(-) diff --git a/packages/docs/src/pages/enterprise.tsx b/packages/docs/src/pages/enterprise.tsx index b5ada72e37..fc3aed5ce0 100644 --- a/packages/docs/src/pages/enterprise.tsx +++ b/packages/docs/src/pages/enterprise.tsx @@ -91,10 +91,10 @@ export default function EnterprisePage(): JSX.Element { Medplum robot coding
-

Enterprise Integrations

+

Enterprise Identity Management

- Reliable integrations drive efficiency and safety. Medplum provides certificed enterprise integrations for - diagnostics, billing, medications, legacy EHR platforms and more. + Connect multiplie identity prociders and provision identities programmatically across your health record + system. Use SCM administration for robust and compliant identity administration.

@@ -119,6 +119,20 @@ export default function EnterprisePage(): JSX.Element { webUrl="/docs/auth/methods/google-auth" /> + + +
+ Medplum robot coding +
+
+

Enterprise Observability

+

+ Gain deep insights into systems performance and health. Enables proactive issue detection, efficient + troubleshooting, and improved system reliability. +

+
+
+
+ +
+ Medplum robot coding +
+
+

Enterprise Integrations

+

+ Enable reliable, compliant and auditable connectivity to service providers and partners. +

+
+
- Developer + Production
2 - Production + Premium 3 - Enterprise - - 4 - + Enterprise Community @@ -61,10 +58,7 @@ export default function PricingPage(): JSX.Element { - Enterprise - - 6 - + Enterprise @@ -73,10 +67,10 @@ export default function PricingPage(): JSX.Element { Pricing Free - $300/mo + $2,000/mo - $2,000/mo + $6,000/mo Contact us @@ -89,7 +83,7 @@ export default function PricingPage(): JSX.Element { Standard BAA - + ✔️ ✔️ ✔️ @@ -112,8 +106,8 @@ export default function PricingPage(): JSX.Element { 500 - 1,000 50,000 + 250,000 Contact us Contact us @@ -126,8 +120,8 @@ export default function PricingPage(): JSX.Element { None - 1,000 - 5,000 + 5,000/mo + 25,000/mo Contact us Contact us @@ -135,8 +129,8 @@ export default function PricingPage(): JSX.Element { Emails Sent None - 50/mo 500/mo + 2500/mo Contact us Contact us @@ -144,7 +138,7 @@ export default function PricingPage(): JSX.Element { Open Onboarding Testing only - Testing only + ✔️ ✔️ ✔️ @@ -153,14 +147,14 @@ export default function PricingPage(): JSX.Element { Custom Domains None - None 1 + 5 Contact us Contact us - Pre-built Integrations + UMLS Terminology @@ -169,7 +163,7 @@ export default function PricingPage(): JSX.Element { ✔️ - Single Tenant + Dedicated Infrastructure @@ -178,12 +172,90 @@ export default function PricingPage(): JSX.Element { - Support - Discord -
- GitHub + Communications + + + + + + + + + + + Websocket Subscriptions + + 11 + + + + ✔️ + ✔️ + + ✔️ + + + Concurrent Connections + + + 2000 + Contact Us + + Contact Us + + + + Integrations + + + + + + + + + + Lab/Diagnostics + + + ✔️ + ✔️ + + ✔️ + + + Medications/eRx + + + ✔️ + ✔️ + + ✔️ + + + HL7 Integration Engine + + + + ✔️ + + ✔️ + + + + Support + + + + + + + + + + Channels Discord
@@ -192,7 +264,12 @@ export default function PricingPage(): JSX.Element { Discord (SLA)
- Github (SLA) + GitHub (SLA) + + + Private Slack +
+ Email Contact us @@ -202,6 +279,15 @@ export default function PricingPage(): JSX.Element { Contact us + + Shared Roadmap + + + + ✔️ + + ✔️ + Security @@ -236,6 +322,20 @@ export default function PricingPage(): JSX.Element { DIY ✔️ + + + External Identity Providers + + 13 + + + + 1 + 2 + Contact Us + DIY + Contact Us + WAF Blocking ✔️ @@ -249,13 +349,13 @@ export default function PricingPage(): JSX.Element { IP Address Restrictions - + ✔️ ✔️ DIY ✔️ - Observability Suite + SCIM Administration @@ -264,7 +364,27 @@ export default function PricingPage(): JSX.Element { ✔️ - SAML + Access Policies + Testing only + 3 + 10 + Contact us + DIY + Contact us + + + + Observability + + + + + + + + + + Log Streaming @@ -273,13 +393,13 @@ export default function PricingPage(): JSX.Element { ✔️ - Access Policies - Testing only - 5 - 20 - Contact us - DIY - Contact us + CISO Dashboard + + + + ✔️ + + ✔️ @@ -301,7 +421,7 @@ export default function PricingPage(): JSX.Element { ✔️ ✔️ ✔️ - DIY + Contact us @@ -310,7 +430,7 @@ export default function PricingPage(): JSX.Element { Contact us - DIY + Contact us @@ -319,20 +439,34 @@ export default function PricingPage(): JSX.Element { ✔️ Contact us - DIY + Contact us - Sign Up + Audit Support + + 12 + + + + ✔️ + + ✔️ + + - Start Now + Sign Up + Start Now + + Start Now + Contact Us @@ -352,12 +486,12 @@ export default function PricingPage(): JSX.Element { Free: recommended for prototyping or learning.
  • - Developer: recommended for developer environments or test environments. -
  • -
  • Production: recommended for production use, e.g. treatment of patients or conducting research.
  • +
  • + Premium: recommended messaging heavy and integration heavy use cases. +
  • Enterprise: recommended for institutions with complex workflow, integration or data requirements. Read more details on our Enterprise offering page. @@ -367,24 +501,41 @@ export default function PricingPage(): JSX.Element { Medplum application.
  • - Enterprise Managed: recommended for those who must host the application on their own - cloud infrastructure. + Enterprise Self-Hosted: recommended for those who must host the application on their + own cloud infrastructure.
  • - Data usage refers to the creation of{' '} + FHIR Resources Stored: Data usage refers to the creation of{' '} FHIR Resources. This figure - is cumulative. + is cumulative. For Premium, Communication resources that are generated as part of messaging are not + included in the resource cap shown.
  • - Bots and automation refer to custom logic written by customers to execute their workflow.{' '} + Bot Invocations: refers to custom logic written by customers to execute their workflow.{' '} Automation documentation and{' '} integration are a good place to learn more.
  • -
  • Organizations can require that all logins go through Google Authentication.
  • +
  • + Required authentication methods: Organizations can require that all logins at their + domain go through their identity provider of choice. +
  • - Many complex compliance scenarios can be supported with this infrastructure. You can read more on the{' '} + Compliance: Many complex compliance scenarios can be supported with this + infrastructure. You can read more on the{' '} compliance page.
  • +
  • + Websocket Subscriptions: maximal number of concurrent websocket{' '} + subscriptions available. +
  • +
  • + Audit Support: receive support during common audits common in health system and payor + partnerships. +
  • +
  • + External Identity Providers: connect your Okta, Azure SSO, Auth0 or other oAuth based + identity provider. +
  • From 29e5900ffc8a40a8f42f7293c4fd762fa06b015f Mon Sep 17 00:00:00 2001 From: Matt Willer Date: Thu, 18 Apr 2024 13:53:38 -0700 Subject: [PATCH 26/52] Idempotent resource deletion (#4396) --- packages/server/src/fhir/repo.test.ts | 7 +++++++ packages/server/src/fhir/repo.ts | 11 ++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/server/src/fhir/repo.test.ts b/packages/server/src/fhir/repo.test.ts index 06093d67ed..e8282fb848 100644 --- a/packages/server/src/fhir/repo.test.ts +++ b/packages/server/src/fhir/repo.test.ts @@ -1048,4 +1048,11 @@ describe('FHIR Repo', () => { }) ).rejects.toThrow('Multiple resources found matching condition'); })); + + test('Double DELETE', async () => + withTestContext(async () => { + const patient = await systemRepo.createResource({ resourceType: 'Patient' }); + await systemRepo.deleteResource(patient.resourceType, patient.id as string); + await expect(systemRepo.deleteResource(patient.resourceType, patient.id as string)).resolves.toBeUndefined(); + })); }); diff --git a/packages/server/src/fhir/repo.ts b/packages/server/src/fhir/repo.ts index 195d30021c..a3f37299ca 100644 --- a/packages/server/src/fhir/repo.ts +++ b/packages/server/src/fhir/repo.ts @@ -964,9 +964,18 @@ export class Repository extends BaseRepository implements FhirRepository { + let resource: Resource; try { - const resource = await this.readResourceImpl(resourceType, id); + resource = await this.readResourceImpl(resourceType, id); + } catch (err) { + const outcomeErr = err as OperationOutcomeError; + if (isGone(outcomeErr.outcome)) { + return; // Resource is already deleted, return successfully + } + throw err; + } + try { if (!this.canWriteResourceType(resourceType) || !this.isResourceWriteable(undefined, resource)) { throw new OperationOutcomeError(forbidden); } From 12effd544001738a205a95fe4b66bee5728e1e28 Mon Sep 17 00:00:00 2001 From: Derrick Farris Date: Thu, 18 Apr 2024 17:30:19 -0700 Subject: [PATCH 27/52] feat(agent): add version to `Agent/$status` (#4359) * refactor(agent): minor naming changes * feat(agent): add version to `Agent/$status` * fix(agent): use `MEDPLUM_VERSION` since we already import core * test(agent): adjust tests to account for version * test(agent): check version after updating status * test(agent): wait for status to update * fix(agent): fix missing import in esbuild script * feat(agent): add version to `Agent/tools` page --- packages/agent/esbuild.mjs | 2 +- packages/agent/src/app.test.ts | 4 +- packages/agent/src/app.ts | 20 +++--- packages/app/src/resource/ToolsPage.test.tsx | 11 +++- packages/app/src/resource/ToolsPage.tsx | 6 ++ packages/core/src/agent.ts | 1 + packages/core/src/client.ts | 2 +- packages/server/src/agent/utils.ts | 11 ++++ packages/server/src/agent/websockets.test.ts | 30 +++++++-- packages/server/src/agent/websockets.ts | 65 ++++++++++++++----- .../src/fhir/operations/agentstatus.test.ts | 16 +++-- .../server/src/fhir/operations/agentstatus.ts | 13 ++-- 12 files changed, 131 insertions(+), 50 deletions(-) create mode 100644 packages/server/src/agent/utils.ts diff --git a/packages/agent/esbuild.mjs b/packages/agent/esbuild.mjs index 37e488c6c5..3509c70a03 100644 --- a/packages/agent/esbuild.mjs +++ b/packages/agent/esbuild.mjs @@ -2,7 +2,7 @@ /* eslint no-console: "off" */ import esbuild from 'esbuild'; -import { writeFileSync } from 'fs'; +import { writeFileSync } from 'node:fs'; const options = { entryPoints: ['./src/main.ts'], diff --git a/packages/agent/src/app.test.ts b/packages/agent/src/app.test.ts index 09629626cf..b7a1c05670 100644 --- a/packages/agent/src/app.test.ts +++ b/packages/agent/src/app.test.ts @@ -50,7 +50,7 @@ describe('App', () => { }); const app = new App(medplum, agent.id as string, LogLevel.INFO); - app.healthcheckPeriod = 1000; + app.heartbeatPeriod = 1000; await app.start(); // Wait for the WebSocket to connect @@ -108,7 +108,7 @@ describe('App', () => { }); const app = new App(medplum, agent.id as string, LogLevel.INFO); - app.healthcheckPeriod = 100; + app.heartbeatPeriod = 100; await app.start(); // Wait for the WebSocket to connect diff --git a/packages/agent/src/app.ts b/packages/agent/src/app.ts index 8be4a71f16..60ed2644d6 100644 --- a/packages/agent/src/app.ts +++ b/packages/agent/src/app.ts @@ -6,6 +6,7 @@ import { Hl7Message, LogLevel, Logger, + MEDPLUM_VERSION, MedplumClient, isValidHostname, normalizeErrorString, @@ -27,6 +28,7 @@ async function execAsync(command: string, options: ExecOptions): Promise<{ stdou if (ex) { const err = ex as Error; reject(err); + return; } resolve({ stdout, stderr }); }); @@ -41,8 +43,8 @@ export class App { readonly webSocketQueue: AgentMessage[] = []; readonly channels = new Map(); readonly hl7Queue: AgentMessage[] = []; - healthcheckPeriod = 10 * 1000; - private healthcheckTimer?: NodeJS.Timeout; + heartbeatPeriod = 10 * 1000; + private heartbeatTimer?: NodeJS.Timeout; private reconnectTimer?: NodeJS.Timeout; private webSocket?: WebSocket; private webSocketWorker?: Promise; @@ -78,11 +80,11 @@ export class App { private startWebSocket(): void { this.connectWebSocket(); - this.healthcheckTimer = setInterval(() => this.healthcheck(), this.healthcheckPeriod); + this.heartbeatTimer = setInterval(() => this.heartbeat(), this.heartbeatPeriod); } - private async healthcheck(): Promise { - if (!this.webSocket && !this.reconnectTimer) { + private async heartbeat(): Promise { + if (!(this.webSocket || this.reconnectTimer)) { this.log.warn('WebSocket not connected'); this.connectWebSocket(); return; @@ -144,7 +146,7 @@ export class App { this.startWebSocketWorker(); break; case 'agent:heartbeat:request': - await this.sendToWebSocket({ type: 'agent:heartbeat:response' }); + await this.sendToWebSocket({ type: 'agent:heartbeat:response', version: MEDPLUM_VERSION }); break; case 'agent:heartbeat:response': // Do nothing @@ -205,9 +207,9 @@ export class App { this.log.info('Medplum service stopping...'); this.shutdown = true; - if (this.healthcheckTimer) { - clearInterval(this.healthcheckTimer); - this.healthcheckTimer = undefined; + if (this.heartbeatTimer) { + clearInterval(this.heartbeatTimer); + this.heartbeatTimer = undefined; } if (this.reconnectTimer) { diff --git a/packages/app/src/resource/ToolsPage.test.tsx b/packages/app/src/resource/ToolsPage.test.tsx index 78905a5f24..bb18cc751d 100644 --- a/packages/app/src/resource/ToolsPage.test.tsx +++ b/packages/app/src/resource/ToolsPage.test.tsx @@ -1,5 +1,5 @@ import { Notifications } from '@mantine/notifications'; -import { ContentType, allOk, getReferenceString } from '@medplum/core'; +import { ContentType, MEDPLUM_VERSION, allOk, getReferenceString } from '@medplum/core'; import { Agent } from '@medplum/fhirtypes'; import { MockClient } from '@medplum/mock'; import { MedplumProvider } from '@medplum/react'; @@ -10,7 +10,13 @@ import { act, fireEvent, render, screen } from '../test-utils/render'; const medplum = new MockClient(); medplum.router.router.add('GET', 'Agent/:id/$status', async () => [ allOk, - { resourceType: 'Parameters', parameter: [{ name: 'status', valueCode: 'disconnected' }] }, + { + resourceType: 'Parameters', + parameter: [ + { name: 'status', valueCode: 'disconnected' }, + { name: 'version', valueString: MEDPLUM_VERSION }, + ], + }, ]); describe('ToolsPage', () => { @@ -46,6 +52,7 @@ describe('ToolsPage', () => { }); await expect(screen.findByText('disconnected', { exact: false })).resolves.toBeInTheDocument(); + expect(screen.getByText(MEDPLUM_VERSION)).toBeInTheDocument(); }); test('Renders last ping', async () => { diff --git a/packages/app/src/resource/ToolsPage.tsx b/packages/app/src/resource/ToolsPage.tsx index 6af857248c..846c8cc4c6 100644 --- a/packages/app/src/resource/ToolsPage.tsx +++ b/packages/app/src/resource/ToolsPage.tsx @@ -13,6 +13,7 @@ export function ToolsPage(): JSX.Element | null { const reference = useMemo(() => ({ reference: 'Agent/' + id }) as Reference, [id]); const [loadingStatus, setLoadingStatus] = useState(false); const [status, setStatus] = useState(); + const [version, setVersion] = useState(); const [lastUpdated, setLastUpdated] = useState(); const [lastPing, setLastPing] = useState(); const [pinging, setPinging] = useState(false); @@ -23,6 +24,7 @@ export function ToolsPage(): JSX.Element | null { .get(medplum.fhirUrl('Agent', id, '$status'), { cache: 'reload' }) .then((result: Parameters) => { setStatus(result.parameter?.find((p) => p.name === 'status')?.valueCode); + setVersion(result.parameter?.find((p) => p.name === 'version')?.valueString); setLastUpdated(result.parameter?.find((p) => p.name === 'lastUpdated')?.valueInstant); }) .catch((err) => showError(normalizeErrorString(err))) @@ -79,6 +81,10 @@ export function ToolsPage(): JSX.Element | null { + + Version + {version} + Last Updated {formatDateTime(lastUpdated)} diff --git a/packages/core/src/agent.ts b/packages/core/src/agent.ts index 656d0d9027..fa6257bc93 100644 --- a/packages/core/src/agent.ts +++ b/packages/core/src/agent.ts @@ -27,6 +27,7 @@ export interface AgentHeartbeatRequest extends BaseAgentRequestMessage { export interface AgentHeartbeatResponse extends BaseAgentMessage { type: 'agent:heartbeat:response'; + version: string; } export interface AgentTransmitRequest extends BaseAgentRequestMessage { diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index b5305ff7a6..89ad1798c5 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -88,7 +88,7 @@ import { sortStringArray, } from './utils'; -export const MEDPLUM_VERSION = import.meta.env.MEDPLUM_VERSION ?? ''; +export const MEDPLUM_VERSION: string = import.meta.env.MEDPLUM_VERSION ?? ''; export const MEDPLUM_CLI_CLIENT_ID = 'medplum-cli'; export const DEFAULT_ACCEPT = ContentType.FHIR_JSON + ', */*; q=0.1'; diff --git a/packages/server/src/agent/utils.ts b/packages/server/src/agent/utils.ts new file mode 100644 index 0000000000..a5b4263552 --- /dev/null +++ b/packages/server/src/agent/utils.ts @@ -0,0 +1,11 @@ +export enum AgentConnectionState { + UNKNOWN = 'unknown', + CONNECTED = 'connected', + DISCONNECTED = 'disconnected', +} + +export type AgentInfo = { + status: AgentConnectionState; + version: string; + lastUpdated?: string; +}; diff --git a/packages/server/src/agent/websockets.test.ts b/packages/server/src/agent/websockets.test.ts index eec0385fc0..f40444b13a 100644 --- a/packages/server/src/agent/websockets.test.ts +++ b/packages/server/src/agent/websockets.test.ts @@ -1,11 +1,13 @@ -import { allOk, ContentType, getReferenceString, Hl7Message } from '@medplum/core'; +import { allOk, ContentType, getReferenceString, Hl7Message, MEDPLUM_VERSION, sleep } from '@medplum/core'; import { Agent, Bot, Device } from '@medplum/fhirtypes'; import express from 'express'; import { Server } from 'http'; import request from 'superwstest'; import { initApp, shutdownApp } from '../app'; import { loadTestConfig, MedplumServerConfig } from '../config'; +import { getRedis } from '../redis'; import { initTestAuth } from '../test.setup'; +import { AgentConnectionState, AgentInfo } from './utils'; const app = express(); let config: MedplumServerConfig; @@ -19,6 +21,7 @@ describe('Agent WebSockets', () => { beforeAll(async () => { config = await loadTestConfig(); config.vmContextBotsEnabled = true; + config.heartbeatMilliseconds = 500; server = await initApp(app, config); accessToken = await initTestAuth({ membership: { admin: true } }); @@ -325,6 +328,7 @@ describe('Agent WebSockets', () => { .set('Authorization', 'Bearer ' + accessToken) .send({ waitForResponse: true, + waitTimeout: 500, destination: getReferenceString(device), contentType: ContentType.HL7_V2, body: @@ -407,14 +411,30 @@ describe('Agent WebSockets', () => { agentId: agent.id, }) ) - .expectText('{"type":"agent:connect:response"}') + .expectJson({ type: 'agent:connect:response' }) + .expectJson({ type: 'agent:heartbeat:request' }) // Send a ping - .sendText(JSON.stringify({ type: 'agent:heartbeat:request' })) - .expectText('{"type":"agent:heartbeat:response"}') + .sendJson({ type: 'agent:heartbeat:request' }) + .expectJson({ type: 'agent:heartbeat:response', version: MEDPLUM_VERSION }) // Simulate a ping response - .sendText(JSON.stringify({ type: 'agent:heartbeat:response' })) + .sendJson({ type: 'agent:heartbeat:response', version: MEDPLUM_VERSION }) .close() .expectClosed(); + + let info: AgentInfo = { status: AgentConnectionState.UNKNOWN, version: 'unknown' }; + for (let i = 0; i < 5; i++) { + await sleep(50); + const infoStr = (await getRedis().get(`medplum:agent:${agent.id as string}:info`)) as string; + info = JSON.parse(infoStr) as AgentInfo; + if (info.status === AgentConnectionState.DISCONNECTED) { + break; + } + } + expect(info).toMatchObject({ + status: AgentConnectionState.DISCONNECTED, + version: MEDPLUM_VERSION, + lastUpdated: expect.any(String), + }); }); test('Ping IP', async () => { diff --git a/packages/server/src/agent/websockets.ts b/packages/server/src/agent/websockets.ts index 154f578535..2d36c2076c 100644 --- a/packages/server/src/agent/websockets.ts +++ b/packages/server/src/agent/websockets.ts @@ -4,21 +4,24 @@ import { AgentTransmitRequest, ContentType, Hl7Message, + MEDPLUM_VERSION, getReferenceString, normalizeErrorString, } from '@medplum/core'; import { Agent, Bot, Reference } from '@medplum/fhirtypes'; -import { AsyncLocalStorage } from 'async_hooks'; -import { IncomingMessage } from 'http'; import { Redis } from 'ioredis'; +import { AsyncLocalStorage } from 'node:async_hooks'; +import { IncomingMessage } from 'node:http'; import ws from 'ws'; import { getRepoForLogin } from '../fhir/accesspolicy'; import { executeBot } from '../fhir/operations/execute'; import { heartbeat } from '../heartbeat'; +import { globalLogger } from '../logger'; import { getLoginForAccessToken } from '../oauth/utils'; import { getRedis, getRedisSubscriber } from '../redis'; +import { AgentConnectionState, AgentInfo } from './utils'; -const STATUS_EX_SECONDS = 24 * 60 * 60; // 24 hours in seconds +const INFO_EX_SECONDS = 24 * 60 * 60; // 24 hours in seconds /** * Handles a new WebSocket connection to the agent service. @@ -51,11 +54,11 @@ export async function handleAgentConnection(socket: ws.WebSocket, request: Incom break; case 'agent:heartbeat:request': - sendMessage({ type: 'agent:heartbeat:response' }); + sendMessage({ type: 'agent:heartbeat:response', version: MEDPLUM_VERSION }); break; case 'agent:heartbeat:response': - await updateStatus('connected'); + await updateAgentInfo({ status: AgentConnectionState.CONNECTED, version: command.version }); break; // @ts-expect-error - Deprecated message type @@ -83,7 +86,7 @@ export async function handleAgentConnection(socket: ws.WebSocket, request: Incom socket.on( 'close', AsyncLocalStorage.bind(async () => { - await updateStatus('disconnected'); + await updateAgentStatus(AgentConnectionState.DISCONNECTED); heartbeat.removeEventListener('heartbeat', heartbeatHandler); redisSubscriber?.disconnect(); redisSubscriber = undefined; @@ -129,7 +132,7 @@ export async function handleAgentConnection(socket: ws.WebSocket, request: Incom sendMessage({ type: 'agent:connect:response' }); // Update the agent status in Redis - await updateStatus('connected'); + await updateAgentStatus(AgentConnectionState.CONNECTED); } /** @@ -204,23 +207,53 @@ export async function handleAgentConnection(socket: ws.WebSocket, request: Incom } /** - * Updates the agent status in Redis. - * This is used by the Agent "$status" operation to monitor agent status. + * Updates the agent info in Redis. + * This is used by the Agent "$status" operation to monitor agent status and other info. * See packages/server/src/fhir/operations/agentstatus.ts for more details. - * @param status - The new status. + * @param info - The latest info received from the Agent. */ - async function updateStatus(status: string): Promise { + async function updateAgentInfo(info: AgentInfo): Promise { if (!agentId) { // Not connected } - await getRedis().set( - `medplum:agent:${agentId}:status`, + + let redis: Redis; + try { + redis = getRedis(); + } catch (err) { + globalLogger.warn(`[Agent]: Attempted to update agent info after server closed. ${normalizeErrorString(err)}`); + return; + } + + await redis.set( + `medplum:agent:${agentId}:info`, JSON.stringify({ - status, + ...info, lastUpdated: new Date().toISOString(), - }), + } satisfies AgentInfo), 'EX', - STATUS_EX_SECONDS + INFO_EX_SECONDS ); } + + async function updateAgentStatus(status: AgentConnectionState): Promise { + if (!agentId) { + // Not connected + } + + let redis: Redis; + try { + redis = getRedis(); + } catch (err) { + globalLogger.warn(`[Agent]: Attempted to update agent status after server closed. ${normalizeErrorString(err)}`); + return; + } + + const lastInfo = await redis.get(`medplum:agent:${agentId}:info`); + if (!lastInfo) { + await updateAgentInfo({ status, version: 'unknown', lastUpdated: new Date().toISOString() }); + return; + } + await updateAgentInfo({ ...(JSON.parse(lastInfo) as AgentInfo), status }); + } } diff --git a/packages/server/src/fhir/operations/agentstatus.test.ts b/packages/server/src/fhir/operations/agentstatus.test.ts index db592eec22..3a4242efba 100644 --- a/packages/server/src/fhir/operations/agentstatus.test.ts +++ b/packages/server/src/fhir/operations/agentstatus.test.ts @@ -3,6 +3,7 @@ import { Agent, Parameters } from '@medplum/fhirtypes'; import { randomUUID } from 'crypto'; import express from 'express'; import request from 'supertest'; +import { AgentConnectionState } from '../../agent/utils'; import { initApp, shutdownApp } from '../../app'; import { loadTestConfig } from '../../config'; import { getRedis } from '../../redis'; @@ -44,14 +45,16 @@ describe('Agent Status', () => { const parameters1 = res1.body as Parameters; expect(parameters1.resourceType).toBe('Parameters'); - expect(parameters1.parameter).toHaveLength(1); - expect(parameters1.parameter?.find((p) => p.name === 'status')?.valueCode).toBe('unknown'); + expect(parameters1.parameter).toHaveLength(2); + expect(parameters1.parameter?.find((p) => p.name === 'status')?.valueCode).toBe(AgentConnectionState.UNKNOWN); + expect(parameters1.parameter?.find((p) => p.name === 'version')?.valueString).toBe('unknown'); // Emulate a connection await getRedis().set( - `medplum:agent:${agent.id}:status`, + `medplum:agent:${agent.id}:info`, JSON.stringify({ - status: 'connected', + status: AgentConnectionState.CONNECTED, + version: '3.1.4', lastUpdated: new Date().toISOString(), }), 'EX', @@ -65,8 +68,9 @@ describe('Agent Status', () => { const parameters2 = res2.body as Parameters; expect(parameters2.resourceType).toBe('Parameters'); - expect(parameters2.parameter).toHaveLength(2); - expect(parameters2.parameter?.find((p) => p.name === 'status')?.valueCode).toBe('connected'); + expect(parameters2.parameter).toHaveLength(3); + expect(parameters2.parameter?.find((p) => p.name === 'status')?.valueCode).toBe(AgentConnectionState.CONNECTED); + expect(parameters2.parameter?.find((p) => p.name === 'version')?.valueString).toBe('3.1.4'); expect(parameters2.parameter?.find((p) => p.name === 'lastUpdated')?.valueInstant).toBeTruthy(); }); }); diff --git a/packages/server/src/fhir/operations/agentstatus.ts b/packages/server/src/fhir/operations/agentstatus.ts index e8e1ba1e1b..b19fdd3974 100644 --- a/packages/server/src/fhir/operations/agentstatus.ts +++ b/packages/server/src/fhir/operations/agentstatus.ts @@ -1,16 +1,12 @@ import { allOk, badRequest } from '@medplum/core'; import { FhirRequest, FhirResponse } from '@medplum/fhir-router'; import { OperationDefinition } from '@medplum/fhirtypes'; +import { AgentConnectionState, AgentInfo } from '../../agent/utils'; import { getAuthenticatedContext } from '../../context'; import { getRedis } from '../../redis'; import { getAgentForRequest } from './agentutils'; import { buildOutputParameters } from './utils/parameters'; -interface AgentStatusOutput { - status: string; - lastUpdated?: string; -} - const operation: OperationDefinition = { resourceType: 'OperationDefinition', name: 'agent-status', @@ -24,6 +20,7 @@ const operation: OperationDefinition = { instance: false, parameter: [ { use: 'out', name: 'status', type: 'code', min: 1, max: '1' }, + { use: 'out', name: 'version', type: 'string', min: 1, max: '1' }, { use: 'out', name: 'lastUpdated', type: 'instant', min: 0, max: '1' }, ], }; @@ -46,16 +43,16 @@ export async function agentStatusHandler(req: FhirRequest): Promise Date: Fri, 19 Apr 2024 11:03:32 -0700 Subject: [PATCH 28/52] Fix express trust proxy config (#4405) --- packages/server/src/app.test.ts | 22 ++++++++++++++++++++++ packages/server/src/app.ts | 2 +- packages/server/src/config.ts | 1 + packages/server/src/context.test.ts | 5 +++++ packages/server/src/context.ts | 15 ++++++--------- packages/server/src/oauth/middleware.ts | 5 ++--- 6 files changed, 37 insertions(+), 13 deletions(-) diff --git a/packages/server/src/app.test.ts b/packages/server/src/app.test.ts index 2251926ba6..f134b5ab3e 100644 --- a/packages/server/src/app.test.ts +++ b/packages/server/src/app.test.ts @@ -63,6 +63,28 @@ describe('App', () => { expect(await shutdownApp()).toBeUndefined(); }); + test('X-Forwarded-For spoofing', async () => { + const app = express(); + const config = await loadTestConfig(); + config.logLevel = 'info'; + config.logRequests = true; + + const originalWrite = process.stdout.write; + process.stdout.write = jest.fn(); + + await initApp(app, config); + const res = await request(app).get('/').set('X-Forwarded-For', '1.1.1.1, 2.2.2.2'); + expect(res.status).toBe(200); + expect(process.stdout.write).toHaveBeenCalledTimes(1); + + const logLine = (process.stdout.write as jest.Mock).mock.calls[0][0]; + const logObj = JSON.parse(logLine); + expect(logObj.ip).toBe('2.2.2.2'); + + expect(await shutdownApp()).toBeUndefined(); + process.stdout.write = originalWrite; + }); + test('Internal Server Error', async () => { const app = express(); app.get('/throw', () => { diff --git a/packages/server/src/app.ts b/packages/server/src/app.ts index 510113a0b5..089b5c77b1 100644 --- a/packages/server/src/app.ts +++ b/packages/server/src/app.ts @@ -135,7 +135,7 @@ export async function initApp(app: Express, config: MedplumServerConfig): Promis initWebSockets(server); app.set('etag', false); - app.set('trust proxy', true); + app.set('trust proxy', 1); app.set('x-powered-by', false); app.use(standardHeaders); app.use(cors(corsOptions)); diff --git a/packages/server/src/config.ts b/packages/server/src/config.ts index c2a152eaa2..dac56e7df0 100644 --- a/packages/server/src/config.ts +++ b/packages/server/src/config.ts @@ -181,6 +181,7 @@ export async function loadTestConfig(): Promise { config.redis.password = process.env['REDIS_PASSWORD_DISABLED_IN_TESTS'] ? undefined : config.redis.password; config.approvedSenderEmails = 'no-reply@example.com'; config.emailProvider = 'none'; + config.logLevel = 'error'; return config; } diff --git a/packages/server/src/context.test.ts b/packages/server/src/context.test.ts index fd139a7da2..35df84789b 100644 --- a/packages/server/src/context.test.ts +++ b/packages/server/src/context.test.ts @@ -1,4 +1,5 @@ import { Request } from 'express'; +import { loadTestConfig } from './config'; import { RequestContext, buildTracingExtension, @@ -12,6 +13,10 @@ import { import { withTestContext } from './test.setup'; describe('RequestContext', () => { + beforeAll(async () => { + await loadTestConfig(); + }); + test('tryGetRequestContext', async () => { expect(tryGetRequestContext()).toBeUndefined(); withTestContext(() => expect(tryGetRequestContext()).toBeDefined()); diff --git a/packages/server/src/context.ts b/packages/server/src/context.ts index 2717e8f577..13f4085695 100644 --- a/packages/server/src/context.ts +++ b/packages/server/src/context.ts @@ -1,8 +1,9 @@ -import { LogLevel, Logger, ProfileResource, isUUID } from '@medplum/core'; +import { LogLevel, Logger, ProfileResource, isUUID, parseLogLevel } from '@medplum/core'; import { Extension, Login, Project, ProjectMembership, Reference } from '@medplum/fhirtypes'; import { AsyncLocalStorage } from 'async_hooks'; import { randomUUID } from 'crypto'; import { NextFunction, Request, Response } from 'express'; +import { getConfig } from './config'; import { Repository, getSystemRepo } from './fhir/repo'; import { parseTraceparent } from './traceparent'; @@ -14,9 +15,7 @@ export class RequestContext { constructor(requestId: string, traceId: string, logger?: Logger) { this.requestId = requestId; this.traceId = traceId; - this.logger = - logger ?? - new Logger(write, { requestId, traceId }, process.env.NODE_ENV === 'test' ? LogLevel.ERROR : LogLevel.INFO); + this.logger = logger ?? new Logger(write, { requestId, traceId }, parseLogLevel(getConfig().logLevel ?? 'info')); } close(): void { @@ -44,10 +43,9 @@ export class AuthenticatedRequestContext extends RequestContext { project: Project, membership: ProjectMembership, repo: Repository, - logger?: Logger, accessToken?: string ) { - super(ctx.requestId, ctx.traceId, logger); + super(ctx.requestId, ctx.traceId, ctx.logger); this.repo = repo; this.project = project; @@ -63,12 +61,11 @@ export class AuthenticatedRequestContext extends RequestContext { static system(ctx?: { requestId?: string; traceId?: string }): AuthenticatedRequestContext { return new AuthenticatedRequestContext( - new RequestContext(ctx?.requestId ?? '', ctx?.traceId ?? ''), + new RequestContext(ctx?.requestId ?? '', ctx?.traceId ?? '', systemLogger), {} as unknown as Login, {} as unknown as Project, {} as unknown as ProjectMembership, - getSystemRepo(), - systemLogger + getSystemRepo() ); } } diff --git a/packages/server/src/oauth/middleware.ts b/packages/server/src/oauth/middleware.ts index 9622ec429f..9e7eecdcee 100644 --- a/packages/server/src/oauth/middleware.ts +++ b/packages/server/src/oauth/middleware.ts @@ -19,9 +19,8 @@ export function authenticateRequest(req: Request, res: Response, next: NextFunct .then(async ({ login, project, membership, accessToken }) => { const ctx = getRequestContext(); const repo = await getRepoForLogin(login, membership, project, isExtendedMode(req)); - requestContextStore.run( - new AuthenticatedRequestContext(ctx, login, project, membership, repo, undefined, accessToken), - () => next() + requestContextStore.run(new AuthenticatedRequestContext(ctx, login, project, membership, repo, accessToken), () => + next() ); }) .catch(next); From 709711c396bee9e829536dfe82ec9d0fbf020a71 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Fri, 19 Apr 2024 12:10:45 -0700 Subject: [PATCH 29/52] Block php file uploads (#4406) --- packages/server/src/fhir/storage.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server/src/fhir/storage.ts b/packages/server/src/fhir/storage.ts index ddbc2abb30..1d70d3fd12 100644 --- a/packages/server/src/fhir/storage.ts +++ b/packages/server/src/fhir/storage.ts @@ -190,6 +190,7 @@ const BLOCKED_FILE_EXTENSIONS = [ '.msp', '.mst', '.nsh', + '.php', '.pif', '.ps1', '.scr', From d4fc141a1a0681de9657a8290bb40e5dd4a6d4e3 Mon Sep 17 00:00:00 2001 From: Derrick Farris Date: Fri, 19 Apr 2024 12:13:07 -0700 Subject: [PATCH 30/52] feat(agent): add `Agent/$bulk-status` (#4379) * feat(agent): add `$bulk-status` operation * test(agent): test search params with Agent/$bulk-status * test(agent): add tests for edge cases * cleanup(tests): remove duplicate test * tweak(server): tag each status resp w/ agent in `$bulk-status` * test(agent): fix tests to expect new output with agent tag --- .../fhir/operations/agentbulkstatus.test.ts | 348 ++++++++++++++++++ .../src/fhir/operations/agentbulkstatus.ts | 82 +++++ .../server/src/fhir/operations/agentutils.ts | 11 + packages/server/src/fhir/routes.ts | 4 + 4 files changed, 445 insertions(+) create mode 100644 packages/server/src/fhir/operations/agentbulkstatus.test.ts create mode 100644 packages/server/src/fhir/operations/agentbulkstatus.ts diff --git a/packages/server/src/fhir/operations/agentbulkstatus.test.ts b/packages/server/src/fhir/operations/agentbulkstatus.test.ts new file mode 100644 index 0000000000..dd2573f521 --- /dev/null +++ b/packages/server/src/fhir/operations/agentbulkstatus.test.ts @@ -0,0 +1,348 @@ +import { ContentType } from '@medplum/core'; +import { + Agent, + Bundle, + BundleEntry, + OperationOutcome, + OperationOutcomeIssue, + Parameters, + ParametersParameter, +} from '@medplum/fhirtypes'; +import express from 'express'; +import { randomUUID } from 'node:crypto'; +import request, { Response } from 'supertest'; +import { AgentConnectionState, AgentInfo } from '../../agent/utils'; +import { initApp, shutdownApp } from '../../app'; +import { loadTestConfig } from '../../config'; +import { getRedis } from '../../redis'; +import { initTestAuth } from '../../test.setup'; +import { MAX_AGENTS_PER_PAGE } from './agentbulkstatus'; + +const NUM_DEFAULT_AGENTS = 2; + +describe('Agent/$bulk-status', () => { + const app = express(); + let accessToken: string; + const agents: Agent[] = []; + let connectedAgent: Agent; + let disabledAgent: Agent; + + beforeAll(async () => { + const config = await loadTestConfig(); + await initApp(app, config); + accessToken = await initTestAuth(); + + const promises = Array.from({ length: NUM_DEFAULT_AGENTS }) as Promise[]; + for (let i = 0; i < NUM_DEFAULT_AGENTS; i++) { + promises[i] = request(app) + .post('/fhir/R4/Agent') + .set('Content-Type', ContentType.FHIR_JSON) + .set('Authorization', 'Bearer ' + accessToken) + .send({ + resourceType: 'Agent', + identifier: [{ system: 'https://example.com/agent', value: randomUUID() }], + name: `Test Agent ${i + 1}`, + status: 'active', + }); + } + + const responses = await Promise.all(promises); + for (let i = 0; i < NUM_DEFAULT_AGENTS; i++) { + expect(responses[i].status).toBe(201); + agents[i] = responses[i].body; + } + + const agent1Res = await request(app) + .post('/fhir/R4/Agent') + .set('Authorization', 'Bearer ' + accessToken) + .type('json') + .send({ + identifier: [{ system: 'https://example.com/agent', value: randomUUID() }], + resourceType: 'Agent', + name: 'Medplum Agent', + status: 'active', + } satisfies Agent); + expect(agent1Res.status).toEqual(201); + + const agent2Res = await request(app) + .post('/fhir/R4/Agent') + .set('Authorization', 'Bearer ' + accessToken) + .type('json') + .send({ + identifier: [{ system: 'https://example.com/agent', value: randomUUID() }], + resourceType: 'Agent', + name: 'Old Medplum Agent', + status: 'off', + } satisfies Agent); + expect(agent2Res.status).toEqual(201); + + connectedAgent = agent1Res.body; + disabledAgent = agent2Res.body; + + // Emulate a connection + await getRedis().set( + `medplum:agent:${connectedAgent.id}:info`, + JSON.stringify({ + status: AgentConnectionState.CONNECTED, + version: '3.1.4', + lastUpdated: new Date().toISOString(), + }), + 'EX', + 60 + ); + + // Emulate a disconnected agent + await getRedis().set( + `medplum:agent:${disabledAgent.id}:info`, + JSON.stringify({ + status: AgentConnectionState.DISCONNECTED, + version: '3.1.2', + lastUpdated: new Date().toISOString(), + }), + 'EX', + 60 + ); + }); + + afterAll(async () => { + await shutdownApp(); + }); + + test('Get all agent statuses', async () => { + const res1 = await request(app) + .get('/fhir/R4/Agent/$bulk-status') + .set('Authorization', 'Bearer ' + accessToken); + expect(res1.status).toBe(200); + + const bundle1 = res1.body as Bundle; + expect(bundle1.resourceType).toBe('Bundle'); + expect(bundle1.entry).toHaveLength(4); + + for (const entry of bundle1.entry as BundleEntry[]) { + const parameters = entry.resource as Parameters; + expect(parameters).toBeDefined(); + expect(parameters.resourceType).toEqual('Parameters'); + expect(parameters.parameter?.length).toEqual(2); + } + + const res2 = await request(app) + .get('/fhir/R4/Agent/$bulk-status') + .set('Authorization', 'Bearer ' + accessToken); + expect(res2.status).toBe(200); + + const bundle2 = res2.body as Bundle; + expect(bundle2.resourceType).toBe('Bundle'); + expect(bundle2.entry).toHaveLength(4); + + const bundle2Entries = bundle2.entry as BundleEntry[]; + for (const entry of bundle2Entries) { + const parameters = entry.resource as Parameters; + expect(parameters).toBeDefined(); + expect(parameters.resourceType).toEqual('Parameters'); + } + + expectBundleToContainStatusEntry(bundle2, connectedAgent, { + status: AgentConnectionState.CONNECTED, + version: '3.1.4', + lastUpdated: expect.any(String), + }); + + expectBundleToContainStatusEntry(bundle2, disabledAgent, { + status: AgentConnectionState.DISCONNECTED, + version: '3.1.2', + lastUpdated: expect.any(String), + }); + + expectBundleToContainStatusEntry(bundle2, agents[0], { + status: AgentConnectionState.UNKNOWN, + version: 'unknown', + }); + }); + + test('Get agent statuses for agent with name containing Medplum', async () => { + const res = await request(app) + .get('/fhir/R4/Agent/$bulk-status') + .query({ 'name:contains': 'Medplum' }) + .set('Authorization', 'Bearer ' + accessToken); + expect(res.status).toBe(200); + + const bundle = res.body as Bundle; + expect(bundle.resourceType).toBe('Bundle'); + expect(bundle.entry).toHaveLength(2); + + const bundleEntries = bundle.entry as BundleEntry[]; + for (let i = 0; i < 2; i++) { + const parameters = bundleEntries[i].resource as Parameters; + expect(parameters).toBeDefined(); + expect(parameters.resourceType).toEqual('Parameters'); + expect(parameters.parameter?.length).toEqual(2); + } + + expectBundleToContainStatusEntry(bundle, connectedAgent, { + status: AgentConnectionState.CONNECTED, + version: '3.1.4', + lastUpdated: expect.any(String), + }); + + expectBundleToContainStatusEntry(bundle, disabledAgent, { + status: AgentConnectionState.DISCONNECTED, + version: '3.1.2', + lastUpdated: expect.any(String), + }); + }); + + test('Get agent statuses for ACTIVE agents with name containing Medplum', async () => { + const res = await request(app) + .get('/fhir/R4/Agent/$bulk-status') + .query({ 'name:contains': 'Medplum', status: 'active' }) + .set('Authorization', 'Bearer ' + accessToken); + expect(res.status).toBe(200); + + const bundle = res.body as Bundle; + expect(bundle.resourceType).toBe('Bundle'); + expect(bundle.entry).toHaveLength(1); + + const bundleEntries = bundle.entry as BundleEntry[]; + for (let i = 0; i < 1; i++) { + const parameters = bundleEntries[i].resource as Parameters; + expect(parameters).toBeDefined(); + expect(parameters.resourceType).toEqual('Parameters'); + expect(parameters.parameter?.length).toEqual(2); + } + + expectBundleToContainStatusEntry(bundle, connectedAgent, { + status: AgentConnectionState.CONNECTED, + version: '3.1.4', + lastUpdated: expect.any(String), + }); + }); + + test('Get agent statuses -- no matching agents', async () => { + const res = await request(app) + .get('/fhir/R4/Agent/$bulk-status') + .query({ name: 'INVALID_AGENT', status: 'active' }) + .set('Authorization', 'Bearer ' + accessToken); + expect(res.status).toBe(400); + + expect(res.body).toMatchObject({ + resourceType: 'OperationOutcome', + issue: expect.arrayContaining([ + expect.objectContaining({ severity: 'error', code: 'invalid' }), + ]), + }); + }); + + test('Get agent statuses -- invalid AgentInfo from Redis', async () => { + await getRedis().set( + `medplum:agent:${agents[1].id as string}:info`, + JSON.stringify({ + version: '3.1.4', + lastUpdated: new Date().toISOString(), + }), + 'EX', + 60 + ); + + const res = await request(app) + .get('/fhir/R4/Agent/$bulk-status') + .query({ name: 'Test Agent 2' }) + .set('Authorization', 'Bearer ' + accessToken); + expect(res.status).toBe(200); + + const bundle = res.body as Bundle; + expect(bundle.resourceType).toBe('Bundle'); + expect(bundle.entry).toHaveLength(1); + + expectBundleToContainOutcomeError(bundle, agents[1], { + issue: [expect.objectContaining({ severity: 'error', code: 'exception' })], + }); + + await getRedis().set( + `medplum:agent:${agents[1].id as string}:info`, + JSON.stringify({ + status: AgentConnectionState.UNKNOWN, + version: 'unknown', + lastUpdated: new Date().toISOString(), + } satisfies AgentInfo), + 'EX', + 60 + ); + }); + + test('Get agent statuses -- `_count` exceeding max page size', async () => { + const res = await request(app) + .get('/fhir/R4/Agent/$bulk-status') + .query({ 'name:contains': 'Medplum', _count: MAX_AGENTS_PER_PAGE + 1 }) + .set('Authorization', 'Bearer ' + accessToken); + expect(res.status).toBe(400); + + expect(res.body).toMatchObject({ + resourceType: 'OperationOutcome', + issue: expect.arrayContaining([ + expect.objectContaining({ severity: 'error', code: 'invalid' }), + ]), + }); + }); +}); + +function expectBundleToContainStatusEntry(bundle: Bundle, agent: Agent, info: AgentInfo): void { + const entries = bundle.entry as BundleEntry[]; + expect(entries).toContainEqual({ + resource: expect.objectContaining({ + resourceType: 'Parameters', + parameter: expect.arrayContaining([ + expect.objectContaining({ + name: 'agent', + resource: expect.objectContaining(agent), + }), + expect.objectContaining({ + name: 'result', + resource: expect.objectContaining({ + resourceType: 'Parameters', + parameter: expect.arrayContaining([ + expect.objectContaining({ + name: 'status', + valueCode: info.status, + }), + expect.objectContaining({ + name: 'version', + valueString: info.version, + }), + ...(info.lastUpdated !== undefined + ? [ + expect.objectContaining({ + name: 'lastUpdated', + valueInstant: info.lastUpdated, + }), + ] + : []), + ]), + }), + }), + ]), + }), + }); +} + +function expectBundleToContainOutcomeError( + bundle: Bundle, + agent: Agent, + outcome: Partial & { issue: OperationOutcomeIssue[] } +): void { + const entries = bundle.entry as BundleEntry[]; + expect(entries).toContainEqual({ + resource: expect.objectContaining({ + resourceType: 'Parameters', + parameter: expect.arrayContaining([ + expect.objectContaining({ + name: 'agent', + resource: expect.objectContaining(agent), + }), + expect.objectContaining({ + name: 'result', + resource: expect.objectContaining>(outcome), + }), + ]), + }), + }); +} diff --git a/packages/server/src/fhir/operations/agentbulkstatus.ts b/packages/server/src/fhir/operations/agentbulkstatus.ts new file mode 100644 index 0000000000..23ab29a21d --- /dev/null +++ b/packages/server/src/fhir/operations/agentbulkstatus.ts @@ -0,0 +1,82 @@ +import { allOk, badRequest, isOk, serverError } from '@medplum/core'; +import { FhirRequest, FhirResponse } from '@medplum/fhir-router'; +import { Agent, Bundle, BundleEntry, OperationDefinition, OperationOutcome, Parameters } from '@medplum/fhirtypes'; +import { getAuthenticatedContext } from '../../context'; +import { agentStatusHandler } from './agentstatus'; +import { getAgentsForRequest } from './agentutils'; + +export const MAX_AGENTS_PER_PAGE = 100; + +export const operation: OperationDefinition = { + resourceType: 'OperationDefinition', + name: 'agent-bulk-status', + status: 'active', + kind: 'operation', + code: 'bulk-status', + experimental: true, + resource: ['Agent'], + system: false, + type: true, + instance: false, + parameter: [{ use: 'out', name: 'return', type: 'Bundle', min: 1, max: '1' }], +}; + +/** + * Handles HTTP requests for the Agent $status operation. + * First reads the agent and makes sure it is valid and the user has access to it. + * Then tries to get the agent status from Redis. + * Returns the agent status details as a Parameters resource. + * + * @param req - The FHIR request. + * @returns The FHIR response. + */ +export async function agentBulkStatusHandler(req: FhirRequest): Promise { + const { repo } = getAuthenticatedContext(); + + if (req.query._count && Number.parseInt(req.query._count, 10) > MAX_AGENTS_PER_PAGE) { + return [badRequest(`'_count' of ${req.query._count} is greater than max of ${MAX_AGENTS_PER_PAGE}`)]; + } + + const agents = await getAgentsForRequest(req, repo); + if (!agents?.length) { + return [badRequest('No agent(s) for given query')]; + } + + const promises = agents.map((agent) => agentStatusHandler({ ...req, params: { id: agent.id as string } })); + const results = await Promise.allSettled(promises); + const entries = [] as BundleEntry[]; + for (let i = 0; i < results.length; i++) { + const result = results[i]; + if (result.status === 'rejected') { + entries.push(makeResultWrapperEntry(serverError(result.reason as Error), agents[i])); + continue; + } + const [outcome, params] = result.value; + if (!isOk(outcome)) { + entries.push(makeResultWrapperEntry(outcome, agents[i])); + continue; + } + entries.push(makeResultWrapperEntry(params as Parameters, agents[i])); + } + + return [ + allOk, + { + resourceType: 'Bundle', + type: 'collection', + entry: entries, + } satisfies Bundle, + ]; +} + +function makeResultWrapperEntry(result: Parameters | OperationOutcome, agent: Agent): BundleEntry { + return { + resource: { + resourceType: 'Parameters', + parameter: [ + { name: 'agent', resource: agent }, + { name: 'result', resource: result }, + ], + }, + }; +} diff --git a/packages/server/src/fhir/operations/agentutils.ts b/packages/server/src/fhir/operations/agentutils.ts index 0495609209..07006e6645 100644 --- a/packages/server/src/fhir/operations/agentutils.ts +++ b/packages/server/src/fhir/operations/agentutils.ts @@ -41,6 +41,17 @@ export async function getAgentForRequest(req: Request | FhirRequest, repo: Repos return undefined; } +/** + * Returns the Agents for a request. + * + * @param req - The HTTP request. + * @param repo - The repository. + * @returns The agent, or undefined if not found. + */ +export async function getAgentsForRequest(req: FhirRequest, repo: Repository): Promise { + return repo.searchResources(parseSearchRequest('Agent', req.query)); +} + export async function getDevice(repo: Repository, params: AgentPushParameters): Promise { const { destination, contentType } = params; if (destination.startsWith('Device/')) { diff --git a/packages/server/src/fhir/routes.ts b/packages/server/src/fhir/routes.ts index 1d7ffc840a..8377a2bd91 100644 --- a/packages/server/src/fhir/routes.ts +++ b/packages/server/src/fhir/routes.ts @@ -9,6 +9,7 @@ import { recordHistogramValue } from '../otel/otel'; import { bulkDataRouter } from './bulkdata'; import { jobRouter } from './job'; import { getCapabilityStatement } from './metadata'; +import { agentBulkStatusHandler } from './operations/agentbulkstatus'; import { agentPushHandler } from './operations/agentpush'; import { agentStatusHandler } from './operations/agentstatus'; import { codeSystemImportHandler } from './operations/codesystemimport'; @@ -185,6 +186,9 @@ function initInternalFhirRouter(): FhirRouter { router.add('GET', '/Agent/$status', agentStatusHandler); router.add('GET', '/Agent/:id/$status', agentStatusHandler); + // Agent $bulk-status operation + router.add('GET', '/Agent/$bulk-status', agentBulkStatusHandler); + // Bot $deploy operation router.add('POST', '/Bot/:id/$deploy', deployHandler); From 31350d1ce7a98435cf6b46306f9af203391fb438 Mon Sep 17 00:00:00 2001 From: ksmith94 <102421938+ksmith94@users.noreply.github.com> Date: Fri, 19 Apr 2024 15:34:43 -0700 Subject: [PATCH 31/52] Note expunging a project permanently deletes it (#4403) --- packages/docs/docs/fhir-datastore/deleting-data.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/docs/docs/fhir-datastore/deleting-data.md b/packages/docs/docs/fhir-datastore/deleting-data.md index 42b128f6ea..c2cce4922a 100644 --- a/packages/docs/docs/fhir-datastore/deleting-data.md +++ b/packages/docs/docs/fhir-datastore/deleting-data.md @@ -46,3 +46,9 @@ The Medplum `$expunge` operation supports an optional `everything` flag to syste ``` POST [base]/[resourceType]/[id]/$expunge?everything=true ``` + +:::warning Expunging a Project + +If you expunge a [`Project`](/docs/api/fhir/medplum/project), it will be _permanently_ deleted and you will no longer be able to sign in or access it in any way. + +::: From 668fac2591d6327994b7542d75e4e0b549737f4b Mon Sep 17 00:00:00 2001 From: Rahul Agarwal Date: Fri, 19 Apr 2024 16:27:08 -0700 Subject: [PATCH 32/52] Fix typo and incorrect bundle types in docs (#4410) --- packages/examples/src/fhir-datastore/fhir-batch-requests.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/examples/src/fhir-datastore/fhir-batch-requests.ts b/packages/examples/src/fhir-datastore/fhir-batch-requests.ts index b1dbc6da3b..94dc756da5 100644 --- a/packages/examples/src/fhir-datastore/fhir-batch-requests.ts +++ b/packages/examples/src/fhir-datastore/fhir-batch-requests.ts @@ -158,7 +158,7 @@ const internalReference: Bundle = // start-block internalReference { resourceType: 'Bundle', - type: 'batch', + type: 'transaction', entry: [ { // highlight-next-line @@ -216,7 +216,7 @@ const conditional: Bundle = // start-block conditionalCreate { resourceType: 'Bundle', - type: 'batch', + type: 'transaction', entry: [ { fullUrl: 'urn:uuid:4aac5fb6-c2ff-4851-b3cf-d66d63a82a17', @@ -234,7 +234,7 @@ const conditional: Bundle = method: 'POST', url: 'Organization', // highlight-next-line - ifNoneExist: 'identifer=https://example-org.com/organizations|example-organization', + ifNoneExist: 'identifier=https://example-org.com/organizations|example-organization', }, }, { From ecf4979a323c42f91bc4f9d37349a1070e875cde Mon Sep 17 00:00:00 2001 From: ksmith94 <102421938+ksmith94@users.noreply.github.com> Date: Fri, 19 Apr 2024 16:27:46 -0700 Subject: [PATCH 33/52] Mention system of record on solutions page (#4401) --- packages/docs/src/pages/solutions/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/docs/src/pages/solutions/index.md b/packages/docs/src/pages/solutions/index.md index 93054fca3d..241c08a4c9 100644 --- a/packages/docs/src/pages/solutions/index.md +++ b/packages/docs/src/pages/solutions/index.md @@ -40,7 +40,7 @@ Run and maintain an EMPI including patient identification, data accuracy/risk sc ## Interoperability Service -Highly customizable internal service that supports integrations that are common in healthcare such as FHIR and Smart-on-FHIR integrations, HL7 connections, SFTP, Lab data, home health integrations, logistics providers and more. Available hosted or self-hosted. [Learn More](/products/integration) +Highly customizable internal service that supports integrations that are common in healthcare such as FHIR and Smart-on-FHIR integrations, HL7 connections, SFTP, Lab data, home health integrations, logistics providers and more. Can also be used as a system of record between multiple integrations. Available hosted or self-hosted. [Learn More](/products/integration) ## Remote Patient Monitoring From a59117f102c2130ab0864effc26bb2b11f7053bc Mon Sep 17 00:00:00 2001 From: Derrick Farris Date: Fri, 19 Apr 2024 16:53:32 -0700 Subject: [PATCH 34/52] docs(agent): add docs for `$status` and `$bulk-status` operations (#4407) * docs(agent): add docs for `$status` and `$bulk-status` ops * test(agent): remove unnecessary request and assertions * docs(agent): reorder agent pages --- packages/docs/docs/agent/bulk-status.md | 199 ++++++++++++++++++ packages/docs/docs/agent/requirements.md | 2 +- packages/docs/docs/agent/status.md | 87 ++++++++ .../fhir/operations/agentbulkstatus.test.ts | 35 +-- 4 files changed, 297 insertions(+), 26 deletions(-) create mode 100644 packages/docs/docs/agent/bulk-status.md create mode 100644 packages/docs/docs/agent/status.md diff --git a/packages/docs/docs/agent/bulk-status.md b/packages/docs/docs/agent/bulk-status.md new file mode 100644 index 0000000000..d80fee911b --- /dev/null +++ b/packages/docs/docs/agent/bulk-status.md @@ -0,0 +1,199 @@ +--- +sidebar_position: 11 +--- + +# Agent Bulk Status + +Gets the status of an agent or agents based on given search criteria. Useful for seeing whether agents are connected and listing their current software version. + +## Invoke the `$bulk-status` operation + +``` +[base]/Agent/$bulk-status +``` + +For example: + +```bash +medplum get 'Agent/$bulk-status' +``` + +### Valid Response + +The response to this operation is a `Bundle` of `Parameters`. Each `Parameters` within the `Bundle` contains an `agent` and a `result`, +which is the result of calling the `$status` operation on this `Agent`, either a `Parameters` or `OperationOutcome` resource. + +Example response: + +```json +{ + "resourceType": "Bundle", + "type": "collection", + "entry": [ + { + "resource": { + "resourceType": "Parameters", + "parameter": [ + { + "name": "agent", + "resource": { + "resourceType": "Agent", + "name": "Test Agent 1", + "status": "active", + "id": "93f8b2fb-65a3-4977-a175-71b73b26fde7", + "meta": { + "versionId": "e182201a-6925-467f-a92b-496193fb4c39", + "lastUpdated": "2024-04-19T20:29:25.087Z" + } + } + }, + { + "name": "result", + "resource": { + "resourceType": "Parameters", + "parameter": [ + { + "name": "status", + "valueCode": "connected" + }, + { + "name": "version", + "valueString": "3.1.4" + }, + { + "name": "lastUpdated", + "valueInstant": "2024-04-19T00:00:00Z" + } + ] + } + } + ] + } + }, + { + "resource": { + "resourceType": "Parameters", + "parameter": [ + { + "name": "agent", + "resource": { + "resourceType": "Agent", + "name": "Test Agent 2", + "status": "active", + "id": "93f8b2fb-65a3-4977-a175-71b73b26fde7", + "meta": { + "versionId": "e182201a-6925-467f-a92b-496193fb4c39", + "lastUpdated": "2024-04-19T20:29:25.087Z" + } + } + }, + { + "name": "result", + "resource": { + "resourceType": "Parameters", + "parameter": [ + { + "name": "status", + "valueCode": "disconnected" + }, + { + "name": "version", + "valueString": "3.1.2" + }, + { + "name": "lastUpdated", + "valueInstant": "2024-04-19T00:00:00Z" + } + ] + } + } + ] + } + }, + { + "resource": { + "resourceType": "Parameters", + "parameter": [ + { + "name": "agent", + "resource": { + "resourceType": "Agent", + "name": "Test Agent 3", + "status": "off", + "id": "93f8b2fb-65a3-4977-a175-71b73b26fde7", + "meta": { + "versionId": "e182201a-6925-467f-a92b-496193fb4c39", + "lastUpdated": "2024-04-19T20:29:25.087Z" + } + } + }, + { + "name": "result", + "resource": { + "resourceType": "OperationOutcome", + "issue": [ + { + "severity": "error", + "code": "exception", + "details": { + "text": "Something weird happened when getting the status" + } + } + ], + } + } + ] + } + } + ] +} +``` + +### Invalid Response + +Example outcome when exceeding max `_count` limit: + +```json +{ + "resourceType": "OperationOutcome", + "issue": [ + { + "severity": "error", + "code": "invalid", + "details": { + "text": "'_count' of 101 is greater than max of 100" + } + } + ] +} +``` + +## Using search parameters + +All of the `Agent` search parameters can be used to select which agents to query the status of. + +Some useful search parameters are: +- `name` +- `status` +- `_count` and `_offset` + +### Recipes + +Getting the status for one agent by name: + +```bash +medplum get 'Agent/$bulk-status?name=Test+Agent+1' +``` + +Getting the status of all active agents: + +```bash +medplum get 'Agent/$bulk-status?status=active' +``` + +Paging through all agent statuses, 50 at a time: + +```bash +medplum get 'Agent/$bulk-status?_count=50&_offset=0' +medplum get 'Agent/$bulk-status?_count=50&_offset=50' +``` diff --git a/packages/docs/docs/agent/requirements.md b/packages/docs/docs/agent/requirements.md index fd88b6a8a6..1e392ca79e 100644 --- a/packages/docs/docs/agent/requirements.md +++ b/packages/docs/docs/agent/requirements.md @@ -1,5 +1,5 @@ --- -sidebar_position: 100 +sidebar_position: 3 --- # System Requirements diff --git a/packages/docs/docs/agent/status.md b/packages/docs/docs/agent/status.md new file mode 100644 index 0000000000..4a9e3dc2b5 --- /dev/null +++ b/packages/docs/docs/agent/status.md @@ -0,0 +1,87 @@ +--- +sidebar_position: 10 +--- + +# Agent Status + +Gets the status of a given agent. Useful for seeing whether an agent is connected and listing its current software version. + +> For querying multiple agent statuses at once, or using `SearchParameters` to select agents to query, see [Bulk Status](./bulk-status.md). + +## Invoke the `$status` operation + +``` +[base]/Agent/[id]/$status +``` + +For example: + +```bash +medplum get 'Agent/[id]/$status' +``` + +### Valid Response + +Valid status codes include: +- `connected` +- `disconnected` +- `unknown` + +Example response when the `Agent` is known and connected: + +```json +{ + "resourceType": "Parameters", + "parameter": [ + { + "name": "status", + "valueCode": "connected" + }, + { + "name": "version", + "valueString": "3.1.4" + }, + { + "name": "lastUpdated", + "valueInstant": "2024-04-19T00:00:00Z" + } + ] +} +``` + +In cases where status has not been reported yet, `status` and `version` may be `unknown`, and `lastUpdated` may not be present. + +```json +{ + "resourceType": "Parameters", + "parameter": [ + { + "name": "status", + "valueCode": "unknown" + }, + { + "name": "version", + "valueString": "unknown" + } + ] +} +``` + +### Invalid Response + +Example outcome when an ID was not supplied to the operation: + +```json +{ + "resourceType": "OperationOutcome", + "issue": [ + { + "severity": "error", + "code": "invalid", + "details": { + "text": "Must specify agent ID or identifier" + } + } + ] +} +``` diff --git a/packages/server/src/fhir/operations/agentbulkstatus.test.ts b/packages/server/src/fhir/operations/agentbulkstatus.test.ts index dd2573f521..cc2fe5dd8c 100644 --- a/packages/server/src/fhir/operations/agentbulkstatus.test.ts +++ b/packages/server/src/fhir/operations/agentbulkstatus.test.ts @@ -109,51 +109,36 @@ describe('Agent/$bulk-status', () => { }); test('Get all agent statuses', async () => { - const res1 = await request(app) + const res = await request(app) .get('/fhir/R4/Agent/$bulk-status') .set('Authorization', 'Bearer ' + accessToken); - expect(res1.status).toBe(200); + expect(res.status).toBe(200); - const bundle1 = res1.body as Bundle; - expect(bundle1.resourceType).toBe('Bundle'); - expect(bundle1.entry).toHaveLength(4); + const bundle = res.body as Bundle; + expect(bundle.resourceType).toBe('Bundle'); + expect(bundle.entry).toHaveLength(4); - for (const entry of bundle1.entry as BundleEntry[]) { + const bundleEntries = bundle.entry as BundleEntry[]; + for (const entry of bundleEntries) { const parameters = entry.resource as Parameters; expect(parameters).toBeDefined(); expect(parameters.resourceType).toEqual('Parameters'); expect(parameters.parameter?.length).toEqual(2); } - const res2 = await request(app) - .get('/fhir/R4/Agent/$bulk-status') - .set('Authorization', 'Bearer ' + accessToken); - expect(res2.status).toBe(200); - - const bundle2 = res2.body as Bundle; - expect(bundle2.resourceType).toBe('Bundle'); - expect(bundle2.entry).toHaveLength(4); - - const bundle2Entries = bundle2.entry as BundleEntry[]; - for (const entry of bundle2Entries) { - const parameters = entry.resource as Parameters; - expect(parameters).toBeDefined(); - expect(parameters.resourceType).toEqual('Parameters'); - } - - expectBundleToContainStatusEntry(bundle2, connectedAgent, { + expectBundleToContainStatusEntry(bundle, connectedAgent, { status: AgentConnectionState.CONNECTED, version: '3.1.4', lastUpdated: expect.any(String), }); - expectBundleToContainStatusEntry(bundle2, disabledAgent, { + expectBundleToContainStatusEntry(bundle, disabledAgent, { status: AgentConnectionState.DISCONNECTED, version: '3.1.2', lastUpdated: expect.any(String), }); - expectBundleToContainStatusEntry(bundle2, agents[0], { + expectBundleToContainStatusEntry(bundle, agents[0], { status: AgentConnectionState.UNKNOWN, version: 'unknown', }); From 9ccc80166f74f77d1b0e964cb441eb8965aeea3b Mon Sep 17 00:00:00 2001 From: Derrick Farris Date: Sat, 20 Apr 2024 17:03:17 -0700 Subject: [PATCH 35/52] Fix typo (#4414) --- packages/docs/docs/agent/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/docs/docs/agent/index.md b/packages/docs/docs/agent/index.md index b8bbabaf80..eac981f39a 100644 --- a/packages/docs/docs/agent/index.md +++ b/packages/docs/docs/agent/index.md @@ -163,7 +163,7 @@ HL7 Feeds can be extremely high volume, and before you go live with a high-volum ## Running from source -Testing the setup end-to-end on localhost can be done by doing the following steps. This assumes you are [running MØedplum on localhost](/docs/contributing/run-the-stack) as a prerequisite. +Testing the setup end-to-end on localhost can be done by doing the following steps. This assumes you are [running Medplum on localhost](/docs/contributing/run-the-stack) as a prerequisite. Navigate to the `medplum/packages/agent`` folder on your drive and run the following command in your terminal From f97cb11c942ae933261091eba7e63fbf20339c82 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Sat, 20 Apr 2024 17:56:31 -0700 Subject: [PATCH 36/52] Fixes #4415 - gracefully handle invalid external auth state (#4416) --- packages/server/src/auth/external.test.ts | 6 ++++++ packages/server/src/auth/external.ts | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/server/src/auth/external.test.ts b/packages/server/src/auth/external.test.ts index 3b7d193604..f12ce7e175 100644 --- a/packages/server/src/auth/external.test.ts +++ b/packages/server/src/auth/external.test.ts @@ -106,6 +106,12 @@ describe('External', () => { expect(res.body.issue[0].details.text).toBe('Missing state'); }); + test('Invalid JSON state', async () => { + const res = await request(app).get('/auth/external?code=xyz&state=xyz'); + expect(res.status).toBe(400); + expect(res.body.issue[0].details.text).toBe('Invalid state'); + }); + test('Unknown domain', async () => { // Build the external callback URL with an unrecognized domain const url = appendQueryParams('/auth/external', { diff --git a/packages/server/src/auth/external.ts b/packages/server/src/auth/external.ts index 82ad68d16c..2174ca2bd4 100644 --- a/packages/server/src/auth/external.ts +++ b/packages/server/src/auth/external.ts @@ -47,7 +47,13 @@ export const externalCallbackHandler = async (req: Request, res: Response): Prom return; } - const body = JSON.parse(state) as ExternalAuthState; + let body: ExternalAuthState; + try { + body = JSON.parse(state); + } catch (err) { + sendOutcome(res, badRequest('Invalid state')); + return; + } const { idp, client } = await getIdentityProvider(body); if (!idp) { From 2e758339cccb5abcbbf00ef0595ab35b56ec90d9 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Sun, 21 Apr 2024 17:30:15 -0700 Subject: [PATCH 37/52] Fixes #4420 - add gravatar to CSP (#4421) * Fixes #4420 - add gravatar to CSP * Remove subdomain --- packages/cdk/src/frontend.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cdk/src/frontend.ts b/packages/cdk/src/frontend.ts index 8a382c9afc..d4294df1ec 100644 --- a/packages/cdk/src/frontend.ts +++ b/packages/cdk/src/frontend.ts @@ -77,7 +77,7 @@ export class FrontEnd extends Construct { `form-action 'self' *.gstatic.com *.google.com`, `frame-ancestors 'none'`, `frame-src 'self' ${config.storageDomainName} *.medplum.com *.gstatic.com *.google.com`, - `img-src 'self' data: ${config.storageDomainName} *.gstatic.com *.google.com *.googleapis.com`, + `img-src 'self' data: ${config.storageDomainName} *.gstatic.com *.google.com *.googleapis.com gravatar.com`, `manifest-src 'self'`, `media-src 'self' ${config.storageDomainName}`, `script-src 'self' *.medplum.com *.gstatic.com *.google.com`, From 994e936adbce15f2b75541bf4d6c796bebe98223 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Sun, 21 Apr 2024 17:34:08 -0700 Subject: [PATCH 38/52] Remove runInBand from server tests (#4419) * Remove runInBand from server tests * Remove skip * Reduced heartbeat time * Fixed test.sh * Migration debug --- packages/server/package.json | 2 +- packages/server/src/agent/websockets.test.ts | 2 +- packages/server/src/fhircast/routes.test.ts | 17 ++++--- packages/server/src/fhircast/utils.test.ts | 2 - .../server/src/fhircast/websocket.test.ts | 3 -- packages/server/src/oauth/keys.test.ts | 22 --------- packages/server/src/seed.test.ts | 11 +++-- .../src/subscriptions/websockets.test.ts | 2 - packages/server/src/websockets.test.ts | 2 - .../server/src/workers/subscription.test.ts | 48 +++++++++---------- scripts/test.sh | 16 +++++-- 11 files changed, 53 insertions(+), 74 deletions(-) diff --git a/packages/server/package.json b/packages/server/package.json index 00a012dbc8..67d90f694f 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -18,7 +18,7 @@ "clean": "rimraf dist", "dev": "ts-node-dev --poll --respawn --transpile-only --require ./src/otel/instrumentation.ts src/index.ts", "start": "node --require ./dist/otel/instrumentation.js dist/index.js", - "test": "jest --runInBand" + "test": "jest" }, "dependencies": { "@aws-sdk/client-cloudwatch-logs": "3.554.0", diff --git a/packages/server/src/agent/websockets.test.ts b/packages/server/src/agent/websockets.test.ts index f40444b13a..d0fa8abb0f 100644 --- a/packages/server/src/agent/websockets.test.ts +++ b/packages/server/src/agent/websockets.test.ts @@ -21,7 +21,7 @@ describe('Agent WebSockets', () => { beforeAll(async () => { config = await loadTestConfig(); config.vmContextBotsEnabled = true; - config.heartbeatMilliseconds = 500; + config.heartbeatMilliseconds = 5000; server = await initApp(app, config); accessToken = await initTestAuth({ membership: { admin: true } }); diff --git a/packages/server/src/fhircast/routes.test.ts b/packages/server/src/fhircast/routes.test.ts index 7fc9c8031e..818c4b0c41 100644 --- a/packages/server/src/fhircast/routes.test.ts +++ b/packages/server/src/fhircast/routes.test.ts @@ -13,24 +13,26 @@ import { loadTestConfig } from '../config'; import { getRedis } from '../redis'; import { initTestAuth } from '../test.setup'; -const app = express(); -let accessToken: string; - const STU2_BASE_ROUTE = '/fhircast/STU2/'; const STU3_BASE_ROUTE = '/fhircast/STU3/'; describe('FHIRCast routes', () => { + const app = express(); + let accessToken: string; + beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); - await getRedis().flushdb(); - accessToken = await initTestAuth(); }); afterAll(async () => { await shutdownApp(); }); + beforeEach(async () => { + accessToken = await initTestAuth(); + }); + test('Get well known', async () => { let res; @@ -150,16 +152,17 @@ describe('FHIRCast routes', () => { }); test('Get context', async () => { + const topic = randomUUID(); let res; // Non-standard FHIRCast extension to support Nuance PowerCast Hub res = await request(app) - .get(`${STU2_BASE_ROUTE}my-topic`) + .get(`${STU2_BASE_ROUTE}${topic}`) .set('Authorization', 'Bearer ' + accessToken); expect(res.status).toBe(200); expect(res.body).toEqual([]); res = await request(app) - .get(`${STU3_BASE_ROUTE}my-topic`) + .get(`${STU3_BASE_ROUTE}${topic}`) .set('Authorization', 'Bearer ' + accessToken); expect(res.status).toBe(200); expect(res.body).toEqual({ 'context.type': '', context: [] }); diff --git a/packages/server/src/fhircast/utils.test.ts b/packages/server/src/fhircast/utils.test.ts index 8f256708ad..b27e24704d 100644 --- a/packages/server/src/fhircast/utils.test.ts +++ b/packages/server/src/fhircast/utils.test.ts @@ -8,8 +8,6 @@ describe('FHIRcast Utils', () => { beforeAll(async () => { const config = await loadTestConfig(); initRedis(config.redis); - expect(getRedis()).toBeDefined(); - await getRedis().flushdb(); }); afterAll(async () => { diff --git a/packages/server/src/fhircast/websocket.test.ts b/packages/server/src/fhircast/websocket.test.ts index 476a9b8ea4..4c5457e7e2 100644 --- a/packages/server/src/fhircast/websocket.test.ts +++ b/packages/server/src/fhircast/websocket.test.ts @@ -5,7 +5,6 @@ import { Server } from 'http'; import request from 'superwstest'; import { initApp, shutdownApp } from '../app'; import { MedplumServerConfig, loadTestConfig } from '../config'; -import { getRedis } from '../redis'; import { initTestAuth, withTestContext } from '../test.setup'; describe('FHIRcast WebSocket', () => { @@ -20,7 +19,6 @@ describe('FHIRcast WebSocket', () => { config = await loadTestConfig(); config.heartbeatEnabled = false; server = await initApp(app, config); - await getRedis().flushdb(); accessToken = await initTestAuth({ membership: { admin: true } }); await new Promise((resolve) => { server.listen(0, 'localhost', 511, resolve); @@ -88,7 +86,6 @@ describe('FHIRcast WebSocket', () => { config = await loadTestConfig(); config.heartbeatMilliseconds = 25; server = await initApp(app, config); - await getRedis().flushdb(); await new Promise((resolve) => { server.listen(0, 'localhost', 511, resolve); }); diff --git a/packages/server/src/oauth/keys.test.ts b/packages/server/src/oauth/keys.test.ts index d06847e766..03f20e4f10 100644 --- a/packages/server/src/oauth/keys.test.ts +++ b/packages/server/src/oauth/keys.test.ts @@ -2,14 +2,11 @@ import { randomUUID } from 'crypto'; import { generateKeyPair, SignJWT } from 'jose'; import { initAppServices, shutdownApp } from '../app'; import { loadTestConfig, MedplumServerConfig } from '../config'; -import { getDatabasePool } from '../database'; -import { withTestContext } from '../test.setup'; import { generateAccessToken, generateIdToken, generateRefreshToken, generateSecret, - getJwks, getSigningKey, initKeys, verifyJwt, @@ -25,25 +22,6 @@ describe('Keys', () => { await shutdownApp(); }); - test('Init keys', () => - withTestContext(async () => { - const config = await loadTestConfig(); - - // First, delete all existing keys - await getDatabasePool().query('DELETE FROM "JsonWebKey"'); - - // Init once - await initKeys(config); - const jwks1 = getJwks(); - expect(jwks1.keys.length).toBe(1); - - // Init again - await initKeys(config); - const jwks2 = getJwks(); - expect(jwks2.keys.length).toBe(1); - expect(jwks2.keys[0].kid).toEqual(jwks2.keys[0].kid); - })); - test('Missing issuer', async () => { const config = await loadTestConfig(); delete (config as any).issuer; diff --git a/packages/server/src/seed.test.ts b/packages/server/src/seed.test.ts index 2d72346245..1f5d77ace9 100644 --- a/packages/server/src/seed.test.ts +++ b/packages/server/src/seed.test.ts @@ -22,11 +22,14 @@ describe('Seed', () => { // First time, seeder should run await seedDatabase(); + // Make sure all database migrations have run + const pool = getDatabasePool(); + const result = await pool.query('SELECT "version" FROM "DatabaseMigration"'); + const version = result.rows[0]?.version ?? -1; + expect(version).toBeGreaterThanOrEqual(67); + // Make sure the first project is a super admin - const rows = await new SelectQuery('Project') - .column('content') - .where('name', '=', 'Super Admin') - .execute(getDatabasePool()); + const rows = await new SelectQuery('Project').column('content').where('name', '=', 'Super Admin').execute(pool); expect(rows.length).toBe(1); const project = JSON.parse(rows[0].content) as Project; diff --git a/packages/server/src/subscriptions/websockets.test.ts b/packages/server/src/subscriptions/websockets.test.ts index 8b3ec68548..df23569fbb 100644 --- a/packages/server/src/subscriptions/websockets.test.ts +++ b/packages/server/src/subscriptions/websockets.test.ts @@ -36,7 +36,6 @@ describe('WebSockets Subscriptions', () => { config = await loadTestConfig(); config.heartbeatEnabled = false; server = await initApp(app, config); - await getRedis().flushdb(); const result = await withTestContext(() => createTestProject({ @@ -270,7 +269,6 @@ describe('Subscription Heartbeat', () => { config = await loadTestConfig(); config.heartbeatMilliseconds = 25; server = await initApp(app, config); - await getRedis().flushdb(); const result = await withTestContext(() => createTestProject({ diff --git a/packages/server/src/websockets.test.ts b/packages/server/src/websockets.test.ts index e3f7333b29..cfbaa8f15f 100644 --- a/packages/server/src/websockets.test.ts +++ b/packages/server/src/websockets.test.ts @@ -6,7 +6,6 @@ import request from 'superwstest'; import WebSocket from 'ws'; import { initApp, shutdownApp } from './app'; import { MedplumServerConfig, loadTestConfig } from './config'; -import { getRedis } from './redis'; import { withTestContext } from './test.setup'; describe('WebSockets', () => { @@ -18,7 +17,6 @@ describe('WebSockets', () => { app = express(); config = await loadTestConfig(); server = await initApp(app, config); - await getRedis().flushdb(); await new Promise((resolve) => { server.listen(0, 'localhost', 511, resolve); diff --git a/packages/server/src/workers/subscription.test.ts b/packages/server/src/workers/subscription.test.ts index cb35074585..8b726802f8 100644 --- a/packages/server/src/workers/subscription.test.ts +++ b/packages/server/src/workers/subscription.test.ts @@ -23,7 +23,6 @@ import { createHmac, randomUUID } from 'crypto'; import fetch from 'node-fetch'; import { initAppServices, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; -import { getDatabasePool } from '../database'; import { Repository, getSystemRepo } from '../fhir/repo'; import { globalLogger } from '../logger'; import { getRedisSubscriber } from '../redis'; @@ -40,28 +39,18 @@ describe('Subscription Worker', () => { let mockLambdaClient: AwsClientStub; let superAdminRepo: Repository; - beforeEach(() => { - mockLambdaClient = mockClient(LambdaClient); - mockLambdaClient.on(InvokeCommand).callsFake(({ Payload }) => { - const decoder = new TextDecoder(); - const event = JSON.parse(decoder.decode(Payload)); - const output = typeof event.input === 'string' ? event.input : JSON.stringify(event.input); - const encoder = new TextEncoder(); - - return { - LogResult: `U1RBUlQgUmVxdWVzdElkOiAxNDZmY2ZjZi1jMzJiLTQzZjUtODJhNi1lZTBmMzEzMmQ4NzMgVmVyc2lvbjogJExBVEVTVAoyMDIyLTA1LTMwVDE2OjEyOjIyLjY4NVoJMTQ2ZmNmY2YtYzMyYi00M2Y1LTgyYTYtZWUwZjMxMzJkODczCUlORk8gdGVzdApFTkQgUmVxdWVzdElkOiAxNDZmY2ZjZi1jMzJiLTQzZjUtODJhNi1lZTBmMzEzMmQ4NzMKUkVQT1JUIFJlcXVlc3RJZDogMTQ2ZmNmY2YtYzMyYi00M2Y1LTgyYTYtZWUwZjMxMzJkODcz`, - Payload: encoder.encode(output), - }; - }); + beforeAll(async () => { + const config = await loadTestConfig(); + await initAppServices(config); }); - afterEach(() => { - mockLambdaClient.restore(); + afterAll(async () => { + await shutdownApp(); + await closeSubscriptionWorker(); // Double close to ensure quite ignore }); - beforeAll(async () => { - const config = await loadTestConfig(); - await initAppServices(config); + beforeEach(async () => { + (fetch as unknown as jest.Mock).mockClear(); // Create one simple project with no advanced features enabled const { client, repo: _repo } = await withTestContext(() => @@ -85,16 +74,23 @@ describe('Subscription Worker', () => { projects: [botProjectDetails.project.id as string], author: createReference(botProjectDetails.client), }); - }); - afterAll(async () => { - await shutdownApp(); - await closeSubscriptionWorker(); // Double close to ensure quite ignore + mockLambdaClient = mockClient(LambdaClient); + mockLambdaClient.on(InvokeCommand).callsFake(({ Payload }) => { + const decoder = new TextDecoder(); + const event = JSON.parse(decoder.decode(Payload)); + const output = typeof event.input === 'string' ? event.input : JSON.stringify(event.input); + const encoder = new TextEncoder(); + + return { + LogResult: `U1RBUlQgUmVxdWVzdElkOiAxNDZmY2ZjZi1jMzJiLTQzZjUtODJhNi1lZTBmMzEzMmQ4NzMgVmVyc2lvbjogJExBVEVTVAoyMDIyLTA1LTMwVDE2OjEyOjIyLjY4NVoJMTQ2ZmNmY2YtYzMyYi00M2Y1LTgyYTYtZWUwZjMxMzJkODczCUlORk8gdGVzdApFTkQgUmVxdWVzdElkOiAxNDZmY2ZjZi1jMzJiLTQzZjUtODJhNi1lZTBmMzEzMmQ4NzMKUkVQT1JUIFJlcXVlc3RJZDogMTQ2ZmNmY2YtYzMyYi00M2Y1LTgyYTYtZWUwZjMxMzJkODcz`, + Payload: encoder.encode(output), + }; + }); }); - beforeEach(async () => { - await getDatabasePool().query('DELETE FROM "Subscription"'); - (fetch as unknown as jest.Mock).mockClear(); + afterEach(() => { + mockLambdaClient.restore(); }); test('Send subscriptions', () => diff --git a/scripts/test.sh b/scripts/test.sh index 26c162705d..ae8f0a7084 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -9,6 +9,18 @@ set -x # Set node options export NODE_OPTIONS='--max-old-space-size=5120' +# Clear old code coverage data +rm -rf coverage +mkdir -p coverage/packages +mkdir -p coverage/combined + +# Seed the database +# This is a special "test" which runs all of the seed logic, such as setting up structure definitions +# On a normal developer machine, this is run only rarely when setting up a new database +# This test must be run first, and cannot be run concurrently with other tests +time npx turbo run test --filter=./packages/server -- seed.test.ts --coverage +cp "packages/server/coverage/coverage-final.json" "coverage/packages/coverage-server-seed.json" + # Test # Run them separately because code coverage is resource intensive @@ -26,10 +38,6 @@ done # Combine test coverage -rm -rf coverage -mkdir -p coverage/packages -mkdir -p coverage/combined - PACKAGES=( "agent" "app" From 89c2caea9ac41180f2f460d9a1caa9cd32c5167b Mon Sep 17 00:00:00 2001 From: ksmith94 <102421938+ksmith94@users.noreply.github.com> Date: Mon, 22 Apr 2024 07:03:15 -0700 Subject: [PATCH 39/52] Note that GraphQL does not support chained search (#4404) --- packages/docs/docs/graphql/basic-queries.mdx | 4 ++++ packages/docs/docs/search/chained-search.md | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/packages/docs/docs/graphql/basic-queries.mdx b/packages/docs/docs/graphql/basic-queries.mdx index b4ca75cb17..88d87592ec 100644 --- a/packages/docs/docs/graphql/basic-queries.mdx +++ b/packages/docs/docs/graphql/basic-queries.mdx @@ -175,6 +175,10 @@ In the example below, we first search for a `Patient` by id, and then find all t See the "[Reverse References](https://hl7.org/fhir/r4/graphql.html#searching)" section of the FHIR GraphQL specification for more information. +:::note Chained Search in GraphQL +When searching on references in GraphQL, you _cannot_ filter on the parameters of the referenced resources. This is called chained search and it is not supported by the FHIR GraphQL spec. However, it is supported in the FHIR Rest API. For more details see the [Chained Search docs](/docs/search/chained-search). +::: + ## Filtering lists with field arguments FHIR GraphQL supports filtering array properties using field arguments. For example, you can filter the `Patient.name` array by the `use` field: diff --git a/packages/docs/docs/search/chained-search.md b/packages/docs/docs/search/chained-search.md index d223663432..664379a3bf 100644 --- a/packages/docs/docs/search/chained-search.md +++ b/packages/docs/docs/search/chained-search.md @@ -9,6 +9,12 @@ Chaining search parameters allows you to filter your searches based on the param Chained searches are similar to using [`_include` or `_revinclude` parameters](/docs/search/includes), but it will not return the referenced resources, only filter based on their parameters. The primary benefit of this is it allows for easy pagination since you know you will only receive results of one resource type. See the [paginated search docs](/docs/search/paginated-search) for more details. +:::note Chained Search Availability + +Chained search is only available when using the FHIR Rest API as described here. If you are using GraphQL, chained search functionality is not supported. + +::: + ## Forward Chained Search [Search parameters](/docs/search/basic-search) with the `reference` type can be chained together to search on the elements of the referenced resource. From 4ca2087c3eafe1e7cf55741d7f80d4d23ed003c5 Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Mon, 22 Apr 2024 08:54:52 -0700 Subject: [PATCH 40/52] Added --update option to reinstall.sh (#4422) --- scripts/reinstall.sh | 9 +++++++-- scripts/upgrade.sh | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/scripts/reinstall.sh b/scripts/reinstall.sh index 6c735d4ccf..27abc9e3f6 100755 --- a/scripts/reinstall.sh +++ b/scripts/reinstall.sh @@ -7,7 +7,6 @@ set -e set -x rm -rf node_modules -rm -rf package-lock.json for dir in `ls packages`; do if test -d "packages/$dir/node_modules"; then @@ -21,4 +20,10 @@ for dir in `ls examples`; do fi done -npm i --strict-peer-deps +# If called with "--update", then use npm i +if [ "$1" == "--update" ]; then + rm -rf package-lock.json + npm i --strict-peer-deps +else + npm ci --strict-peer-deps +fi diff --git a/scripts/upgrade.sh b/scripts/upgrade.sh index 782cd3492a..71f7d50732 100755 --- a/scripts/upgrade.sh +++ b/scripts/upgrade.sh @@ -40,7 +40,7 @@ git push origin "$BRANCH_NAME" gh pr create --title "Dependency upgrades $DATE" --body "Dependency upgrades" --draft # Reinstall all dependencies -./scripts/reinstall.sh +./scripts/reinstall.sh --update # Commit and push after running NPM install git add -u . From 2906abdbfe1764011355741ea905868cfe20f62e Mon Sep 17 00:00:00 2001 From: Derrick Farris Date: Mon, 22 Apr 2024 09:20:13 -0700 Subject: [PATCH 41/52] fix(expo-polyfills): add min `Event` polyfill for ws subs (#4413) * fix(expo-polyfills): add min `Event` polyfill for ws subs * test(expo-polyfills): test event polyfill more --- .../medplum-react-native-example/src/Home.tsx | 29 +++++++++++++- packages/expo-polyfills/src/index.test.ts | 24 ++++------- packages/expo-polyfills/src/index.ts | 10 +++++ packages/expo-polyfills/src/polyfills.test.ts | 39 ++++++++++++------ .../expo-polyfills/src/polyfills/event.ts | 40 +++++++++++++++++++ 5 files changed, 113 insertions(+), 29 deletions(-) create mode 100644 packages/expo-polyfills/src/polyfills/event.ts diff --git a/examples/medplum-react-native-example/src/Home.tsx b/examples/medplum-react-native-example/src/Home.tsx index 4a4fb8f60b..009fee3feb 100644 --- a/examples/medplum-react-native-example/src/Home.tsx +++ b/examples/medplum-react-native-example/src/Home.tsx @@ -1,6 +1,6 @@ import { LoginAuthenticationResponse, getDisplayString } from '@medplum/core'; import { Patient } from '@medplum/fhirtypes'; -import { useMedplum, useMedplumContext, useMedplumProfile } from '@medplum/react-hooks'; +import { useMedplum, useMedplumContext, useMedplumProfile, useSubscription } from '@medplum/react-hooks'; import { StatusBar } from 'expo-status-bar'; import { useState } from 'react'; import { ActivityIndicator, ScrollView, StyleSheet, Text, TextInput, View } from 'react-native'; @@ -121,6 +121,7 @@ export default function Home(): JSX.Element { + )} @@ -130,6 +131,32 @@ export default function Home(): JSX.Element { ); } +interface NotificationsWidgitProps { + title?: string; + criteria: string; +} + +function NotificationsWidgit(props: NotificationsWidgitProps): JSX.Element { + const [notifications, setNotifications] = useState(0); + + useSubscription(props.criteria, () => { + setNotifications(notifications + 1); + }); + + function clearNotifications(): void { + setNotifications(0); + } + + return ( + + + {props.title ?? 'Notifications:'} {notifications} + + + + ); +} + const styles = StyleSheet.create({ container: { flex: 1, diff --git a/packages/expo-polyfills/src/index.test.ts b/packages/expo-polyfills/src/index.test.ts index 5b2d137813..651ec99c56 100644 --- a/packages/expo-polyfills/src/index.test.ts +++ b/packages/expo-polyfills/src/index.test.ts @@ -5,20 +5,6 @@ import { Platform } from 'react-native'; import { TextDecoder, TextEncoder } from 'text-encoding'; import { ExpoClientStorage, cleanupMedplumWebAPIs, polyfillMedplumWebAPIs } from '.'; -const originalWindow = window; - -beforeAll(() => { - Object.defineProperty(globalThis, 'window', { - value: { ...originalWindow }, - }); -}); - -afterAll(() => { - Object.defineProperty(globalThis, 'window', { - value: originalWindow, - }); -}); - jest.mock('expo-secure-store', () => { const store = new Map(); let getKeysShouldThrow = false; @@ -58,11 +44,19 @@ if (Platform.OS === 'web') { } describe('polyfillMedplumWebAPIs', () => { + const originalWindow = globalThis.window; + beforeAll(() => { + Object.defineProperty(globalThis, 'window', { + value: { ...originalWindow }, + }); polyfillMedplumWebAPIs(); }); afterAll(() => { + Object.defineProperty(globalThis, 'window', { + value: originalWindow, + }); cleanupMedplumWebAPIs(); }); @@ -110,8 +104,6 @@ describe('polyfillMedplumWebAPIs', () => { expect(window.crypto.subtle).toBeDefined(); expect(window.crypto.subtle.digest).toBeDefined(); }); - - // TODO: Add a test for `digest` }); describe('Location', () => { diff --git a/packages/expo-polyfills/src/index.ts b/packages/expo-polyfills/src/index.ts index 3b7cd4366a..562cfe13e8 100644 --- a/packages/expo-polyfills/src/index.ts +++ b/packages/expo-polyfills/src/index.ts @@ -6,6 +6,7 @@ import expoWebCrypto from 'expo-standard-web-crypto'; import { Platform } from 'react-native'; import { setupURLPolyfill } from 'react-native-url-polyfill'; import { TextDecoder, TextEncoder } from 'text-encoding'; +import { polyfillEvent } from './polyfills/event'; let polyfilled = false; let originalCryptoIsSet = false; @@ -23,6 +24,7 @@ export type PolyfillEnabledConfig = { sessionStorage?: boolean; textEncoder?: boolean; btoa?: boolean; + event?: boolean; }; export function cleanupMedplumWebAPIs(): void { @@ -76,6 +78,10 @@ export function cleanupMedplumWebAPIs(): void { Object.defineProperty(window, 'atob', { configurable: true, enumerable: true, value: undefined }); } + if (window.Event) { + Object.defineProperty(window, 'Event', { configurable: true, enumerable: true, value: undefined }); + } + polyfilled = false; } @@ -166,6 +172,10 @@ export function polyfillMedplumWebAPIs(config?: PolyfillEnabledConfig): void { }); } + if (config?.event !== false && typeof window.Event === 'undefined') { + polyfillEvent(); + } + polyfilled = true; } diff --git a/packages/expo-polyfills/src/polyfills.test.ts b/packages/expo-polyfills/src/polyfills.test.ts index 6e9e3ee04e..f6f2b894dc 100644 --- a/packages/expo-polyfills/src/polyfills.test.ts +++ b/packages/expo-polyfills/src/polyfills.test.ts @@ -1,23 +1,23 @@ import { Platform } from 'react-native'; import { cleanupMedplumWebAPIs, polyfillMedplumWebAPIs } from '.'; -const originalWindow = window; +describe('Medplum polyfills', () => { + const originalWindow = window; -beforeEach(() => { - Object.defineProperty(globalThis, 'window', { - value: { ...originalWindow }, + beforeEach(() => { + cleanupMedplumWebAPIs(); }); -}); -afterAll(() => { - Object.defineProperty(globalThis, 'window', { - value: originalWindow, + beforeEach(() => { + Object.defineProperty(globalThis, 'window', { + value: { ...originalWindow }, + }); }); -}); -describe('Medplum polyfills', () => { - beforeEach(() => { - cleanupMedplumWebAPIs(); + afterAll(() => { + Object.defineProperty(globalThis, 'window', { + value: originalWindow, + }); }); if (Platform.OS !== 'web') { @@ -46,6 +46,21 @@ describe('Medplum polyfills', () => { // There was specifically trouble with this object when calling polyfill multiple times before expect(window.crypto.subtle).toBeDefined(); }); + + test('Event should be constructable after polyfills', () => { + // @ts-expect-error Testing polyfill + globalThis.Event = undefined; + expect(() => new Event('foo')).toThrow(); + polyfillMedplumWebAPIs(); + + const event1 = new Event('foo'); + expect(event1).toBeInstanceOf(Event); + expect(event1.type).toEqual('foo'); + + const event2 = new Event('foo', { bubbles: true, cancelable: true, composed: true }); + expect(event2).toBeInstanceOf(Event); + expect(event2.type).toEqual('foo'); + }); }); describe('cleanupMedplumWebAPIs()', () => { diff --git a/packages/expo-polyfills/src/polyfills/event.ts b/packages/expo-polyfills/src/polyfills/event.ts new file mode 100644 index 0000000000..5628a357e6 --- /dev/null +++ b/packages/expo-polyfills/src/polyfills/event.ts @@ -0,0 +1,40 @@ +// Original source: https://github.com/benlesh/event-target-polyfill/blob/master/index.js +// The package is no longer maintained, so I figured we can vendor it + +export function polyfillEvent(): void { + const root = ((typeof globalThis !== 'undefined' && globalThis) || + (typeof self !== 'undefined' && self) || + (typeof global !== 'undefined' && global)) as typeof globalThis; + + const shouldPolyfillEvent = (() => { + try { + // eslint-disable-next-line no-new + new root.Event(''); + } catch (_error) { + return true; + } + return false; + })(); + + if (shouldPolyfillEvent) { + // @ts-expect-error Types don't quite match up but it should be mostly good enough + root.Event = (() => { + class Event { + readonly type: string; + readonly bubbles: boolean; + readonly cancelable: boolean; + readonly composed: boolean; + defaultPrevented = false; + + constructor(type: string, options: EventInit) { + this.bubbles = !!options && !!options.bubbles; + this.cancelable = !!options && !!options.cancelable; + this.composed = !!options && !!options.composed; + this.type = type; + } + } + + return Event; + })(); + } +} From 0efe9c89815dd2c8ff498ab8315826c24ea20a6b Mon Sep 17 00:00:00 2001 From: Matt Long Date: Mon, 22 Apr 2024 10:30:13 -0700 Subject: [PATCH 42/52] [Medplum Provider] Prefill patient reference when creating new resources (#4408) * Prefill patient element in new resources * Propagate errors * commit all the changes * Consider empty strings --- .../medplum-provider/src/hooks/usePatient.ts | 5 +- .../src/pages/CreateResourcePage.tsx | 59 ++++++++++++++++--- 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/examples/medplum-provider/src/hooks/usePatient.ts b/examples/medplum-provider/src/hooks/usePatient.ts index 313b2eb03a..7a0494928d 100644 --- a/examples/medplum-provider/src/hooks/usePatient.ts +++ b/examples/medplum-provider/src/hooks/usePatient.ts @@ -1,9 +1,10 @@ -import { Patient } from '@medplum/fhirtypes'; +import { OperationOutcome, Patient } from '@medplum/fhirtypes'; import { useResource } from '@medplum/react'; import { useParams } from 'react-router-dom'; type Options = { ignoreMissingPatientId?: boolean; + setOutcome?: (outcome: OperationOutcome) => void; }; export function usePatient(options?: Options): Patient | undefined { @@ -11,5 +12,5 @@ export function usePatient(options?: Options): Patient | undefined { if (!patientId && !options?.ignoreMissingPatientId) { throw new Error('Patient ID not found'); } - return useResource({ reference: `Patient/${patientId}` }); + return useResource({ reference: `Patient/${patientId}` }, options?.setOutcome); } diff --git a/examples/medplum-provider/src/pages/CreateResourcePage.tsx b/examples/medplum-provider/src/pages/CreateResourcePage.tsx index 8d79b380e7..6dbe55ab05 100644 --- a/examples/medplum-provider/src/pages/CreateResourcePage.tsx +++ b/examples/medplum-provider/src/pages/CreateResourcePage.tsx @@ -1,20 +1,59 @@ import { Stack, Text } from '@mantine/core'; import { showNotification } from '@mantine/notifications'; -import { normalizeErrorString, normalizeOperationOutcome } from '@medplum/core'; -import { OperationOutcome, Resource, ResourceType } from '@medplum/fhirtypes'; -import { Document, ResourceForm, useMedplum } from '@medplum/react'; -import { useState } from 'react'; +import { createReference, normalizeErrorString, normalizeOperationOutcome } from '@medplum/core'; +import { OperationOutcome, Patient, Resource, ResourceType } from '@medplum/fhirtypes'; +import { Document, Loading, ResourceForm, useMedplum } from '@medplum/react'; +import { useEffect, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { usePatient } from '../hooks/usePatient'; import { prependPatientPath } from './patient/PatientPage.utils'; +const PatientReferencesElements: Partial> = { + Task: ['for'], + MedicationRequest: ['subject'], + ServiceRequest: ['subject'], + Device: ['patient'], + DiagnosticReport: ['subject'], + DocumentReference: ['subject'], + Appointment: ['participant.actor'], + CarePlan: ['subject'], +}; + +function getDefaultValue(resourceType: ResourceType, patient: Patient | undefined): Partial { + const dv = { resourceType } as Partial; + const refKeys = PatientReferencesElements[resourceType]; + if (patient && refKeys) { + for (const key of refKeys) { + const keyParts = key.split('.'); + if (keyParts.length === 1) { + (dv as any)[key] = createReference(patient); + } else if (keyParts.length === 2) { + const [first, second] = keyParts; + (dv as any)[first] = [{ [second]: createReference(patient) }]; + } else { + throw new Error('Can only process keys with one or two parts'); + } + } + } + + return dv; +} + export function CreateResourcePage(): JSX.Element { const medplum = useMedplum(); - const patient = usePatient({ ignoreMissingPatientId: true }); - const navigate = useNavigate(); - const { resourceType } = useParams() as { resourceType: ResourceType }; const [outcome, setOutcome] = useState(); - const defaultValue = { resourceType } as Partial; + const patient = usePatient({ ignoreMissingPatientId: true, setOutcome }); + const navigate = useNavigate(); + const { patientId, resourceType } = useParams() as { patientId: string | undefined; resourceType: ResourceType }; + const [loadingPatient, setLoadingPatient] = useState(Boolean(patientId)); + const [defaultValue, setDefaultValue] = useState>(() => getDefaultValue(resourceType, patient)); + + useEffect(() => { + if (patient) { + setDefaultValue(getDefaultValue(resourceType, patient)); + } + setLoadingPatient(false); + }, [patient, resourceType]); const handleSubmit = (newResource: Resource): void => { if (outcome) { @@ -36,6 +75,10 @@ export function CreateResourcePage(): JSX.Element { }); }; + if (loadingPatient) { + return ; + } + return ( From 4bf85d72bff465e0a3c930ad023740e119806cb4 Mon Sep 17 00:00:00 2001 From: Medplum Bot <152649536+medplumbot@users.noreply.github.com> Date: Mon, 22 Apr 2024 11:30:21 -0700 Subject: [PATCH 43/52] Release Version 3.1.3 (#4427) Dependency upgrades - step 1 (#4339)Implement conditional update (#4307) fix(client): make `QueryTypes` more accurate (#4332) fix(agent): check for `ping` command, report transmit errors back on `Agent/$push` (#4328) Storybook Themes (#4337) Update index.md (#4347) Add config to set default Project Features (#4330) tweak(redis): add `getRedisSubscriber` for graceful shutdown safety (#4336) Clear auth state on auth failure (#4341) Agent SerialPort connections (#4348) feat(agent): allow valid hostnames to be pinged (#4350) fix(chat-demo): match only thread between the two recipients (#4349) Added server config setting for approved email senders (#4351) chore(actions): add timeouts to a lot of jobs (#4357) Update component handling in formatObservationValue function (#4361) Task Demo Sample Data (#4278) FHIR Mapper: Maintain type information for sub-properties of mapping target (#4363) Restrict email api to project admins (#4373) refactor($get-ws-binding-token): use backported `OperationDefinition` (#4368) Dependency upgrades 2024-04-15 (#4369) [Medplum Provider] Add Edit and History functionality to resource pages (#4376) Move AWS specific code to separate directory (#4329) Document how to revert changes to a resource (#4382) Document binary security context (#4370) Create a Chat Demo App (#4220) Note that filter is not supported for chained search (#4380) Fixed next.js build errors (#4392) Updating SKU information and enterprise page (#4395) Idempotent resource deletion (#4396) feat(agent): add version to `Agent/$status` (#4359) Fix express trust proxy config (#4405) Block php file uploads (#4406) feat(agent): add `Agent/$bulk-status` (#4379) Note expunging a project permanently deletes it (#4403) Fix typo and incorrect bundle types in docs (#4410) Mention system of record on solutions page (#4401) docs(agent): add docs for `$status` and `$bulk-status` operations (#4407) Fix typo (#4414) Fixes #4415 - gracefully handle invalid external auth state (#4416) Fixes #4420 - add gravatar to CSP (#4421) Remove runInBand from server tests (#4419) Note that GraphQL does not support chained search (#4404) Added --update option to reinstall.sh (#4422) fix(expo-polyfills): add min `Event` polyfill for ws subs (#4413) [Medplum Provider] Prefill patient reference when creating new resources (#4408) --- examples/foomedical/package.json | 12 +- examples/medplum-chart-demo/package.json | 10 +- examples/medplum-chat-demo/package.json | 10 +- .../package.json | 4 +- examples/medplum-demo-bots/package.json | 12 +- .../medplum-eligibility-demo/package.json | 14 +- examples/medplum-fhircast-demo/package.json | 10 +- examples/medplum-hello-world/package.json | 10 +- examples/medplum-live-chat-demo/package.json | 10 +- examples/medplum-nextauth-demo/package.json | 8 +- examples/medplum-nextjs-demo/package.json | 8 +- examples/medplum-provider/package.json | 10 +- .../medplum-react-native-example/package.json | 10 +- examples/medplum-task-demo/package.json | 12 +- .../package.json | 14 +- package-lock.json | 320 +++++++++--------- package.json | 2 +- packages/agent/package.json | 10 +- packages/app/package.json | 12 +- packages/bot-layer/package.json | 8 +- packages/cdk/package.json | 4 +- packages/cli/package.json | 10 +- packages/core/package.json | 6 +- packages/definitions/package.json | 2 +- packages/docs/package.json | 8 +- packages/eslint-config/package.json | 2 +- packages/examples/package.json | 8 +- packages/expo-polyfills/package.json | 6 +- packages/fhir-router/package.json | 8 +- packages/fhirtypes/package.json | 2 +- packages/generator/package.json | 8 +- packages/graphiql/package.json | 8 +- packages/health-gorilla/package.json | 8 +- packages/hl7/package.json | 6 +- packages/mock/package.json | 10 +- packages/react-hooks/package.json | 12 +- packages/react/package.json | 14 +- packages/server/package.json | 10 +- sonar-project.properties | 2 +- 39 files changed, 320 insertions(+), 320 deletions(-) diff --git a/examples/foomedical/package.json b/examples/foomedical/package.json index 57f3d29e7c..ca6a44c410 100644 --- a/examples/foomedical/package.json +++ b/examples/foomedical/package.json @@ -1,6 +1,6 @@ { "name": "foomedical", - "version": "3.1.2", + "version": "3.1.3", "type": "module", "scripts": { "build": "tsc && vite build", @@ -28,11 +28,11 @@ "@mantine/core": "7.8.0", "@mantine/hooks": "7.8.0", "@mantine/notifications": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/eslint-config": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/mock": "3.1.2", - "@medplum/react": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/eslint-config": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/mock": "3.1.3", + "@medplum/react": "3.1.3", "@tabler/icons-react": "3.2.0", "@testing-library/jest-dom": "6.4.2", "@testing-library/react": "15.0.2", diff --git a/examples/medplum-chart-demo/package.json b/examples/medplum-chart-demo/package.json index 1103fa9d50..6e1999d7d0 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.1.2", + "version": "3.1.3", "private": true, "type": "module", "scripts": { @@ -22,10 +22,10 @@ "@mantine/core": "7.8.0", "@mantine/hooks": "7.8.0", "@mantine/notifications": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/eslint-config": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/react": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/eslint-config": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/react": "3.1.3", "@tabler/icons-react": "3.2.0", "@types/node": "20.12.7", "@types/react": "18.2.78", diff --git a/examples/medplum-chat-demo/package.json b/examples/medplum-chat-demo/package.json index 7baccda948..9547a12138 100644 --- a/examples/medplum-chat-demo/package.json +++ b/examples/medplum-chat-demo/package.json @@ -1,6 +1,6 @@ { "name": "medplum-chat-demo", - "version": "3.1.2", + "version": "3.1.3", "private": true, "type": "module", "scripts": { @@ -22,10 +22,10 @@ "@mantine/core": "7.6.2", "@mantine/hooks": "7.6.2", "@mantine/notifications": "7.6.2", - "@medplum/core": "3.1.2", - "@medplum/eslint-config": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/react": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/eslint-config": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/react": "3.1.3", "@tabler/icons-react": "3.0.0", "@types/node": "20.11.28", "@types/react": "18.2.66", diff --git a/examples/medplum-client-external-idp-demo/package.json b/examples/medplum-client-external-idp-demo/package.json index 3b36c08a58..b1c254d4fc 100644 --- a/examples/medplum-client-external-idp-demo/package.json +++ b/examples/medplum-client-external-idp-demo/package.json @@ -1,6 +1,6 @@ { "name": "medplum-client-external-idp-demo", - "version": "3.1.2", + "version": "3.1.3", "private": true, "type": "module", "scripts": { @@ -13,7 +13,7 @@ "singleQuote": true }, "devDependencies": { - "@medplum/core": "3.1.2", + "@medplum/core": "3.1.3", "rimraf": "5.0.5", "typescript": "5.4.5", "vite": "5.2.8" diff --git a/examples/medplum-demo-bots/package.json b/examples/medplum-demo-bots/package.json index 3856e5068b..3d070f2d4e 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.1.2", + "version": "3.1.3", "description": "Medplum Demo Bots", "license": "Apache-2.0", "author": "Medplum ", @@ -29,11 +29,11 @@ "root": true }, "devDependencies": { - "@medplum/cli": "3.1.2", - "@medplum/core": "3.1.2", - "@medplum/eslint-config": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/mock": "3.1.2", + "@medplum/cli": "3.1.3", + "@medplum/core": "3.1.3", + "@medplum/eslint-config": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/mock": "3.1.3", "@types/node": "20.12.7", "@types/node-fetch": "2.6.11", "@types/ssh2-sftp-client": "9.0.3", diff --git a/examples/medplum-eligibility-demo/package.json b/examples/medplum-eligibility-demo/package.json index 366a0e6e6e..33452b0b06 100644 --- a/examples/medplum-eligibility-demo/package.json +++ b/examples/medplum-eligibility-demo/package.json @@ -1,6 +1,6 @@ { "name": "medplum-eligibility-demo", - "version": "3.1.2", + "version": "3.1.3", "private": true, "type": "module", "scripts": { @@ -24,12 +24,12 @@ "@mantine/core": "7.8.0", "@mantine/hooks": "7.8.0", "@mantine/notifications": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/definitions": "3.1.2", - "@medplum/eslint-config": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/mock": "3.1.2", - "@medplum/react": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/definitions": "3.1.3", + "@medplum/eslint-config": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/mock": "3.1.3", + "@medplum/react": "3.1.3", "@tabler/icons-react": "3.2.0", "@types/node": "20.12.7", "@types/react": "18.2.78", diff --git a/examples/medplum-fhircast-demo/package.json b/examples/medplum-fhircast-demo/package.json index 6a42a3a151..d121f9df2d 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.1.2", + "version": "3.1.3", "private": true, "type": "module", "scripts": { @@ -18,10 +18,10 @@ "@mantine/core": "7.8.0", "@mantine/hooks": "7.8.0", "@mantine/notifications": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/eslint-config": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/react": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/eslint-config": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/react": "3.1.3", "@tabler/icons-react": "3.2.0", "@types/react": "18.2.78", "@types/react-dom": "18.2.25", diff --git a/examples/medplum-hello-world/package.json b/examples/medplum-hello-world/package.json index 03617d1f25..ff4a9ab33c 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.1.2", + "version": "3.1.3", "private": true, "type": "module", "scripts": { @@ -22,10 +22,10 @@ "@mantine/core": "7.8.0", "@mantine/hooks": "7.8.0", "@mantine/notifications": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/eslint-config": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/react": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/eslint-config": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/react": "3.1.3", "@tabler/icons-react": "3.2.0", "@types/node": "20.12.7", "@types/react": "18.2.78", diff --git a/examples/medplum-live-chat-demo/package.json b/examples/medplum-live-chat-demo/package.json index 59f92d3156..f3c3674e73 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.1.2", + "version": "3.1.3", "private": true, "type": "module", "scripts": { @@ -22,10 +22,10 @@ "@mantine/core": "7.8.0", "@mantine/hooks": "7.8.0", "@mantine/notifications": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/eslint-config": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/react": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/eslint-config": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/react": "3.1.3", "@tabler/icons-react": "3.2.0", "@types/node": "20.12.7", "@types/react": "18.2.78", diff --git a/examples/medplum-nextauth-demo/package.json b/examples/medplum-nextauth-demo/package.json index e19bf7644c..d5ffff83c3 100644 --- a/examples/medplum-nextauth-demo/package.json +++ b/examples/medplum-nextauth-demo/package.json @@ -1,6 +1,6 @@ { "name": "medplum-nextauth-demo", - "version": "3.1.2", + "version": "3.1.3", "private": true, "type": "module", "scripts": { @@ -23,15 +23,15 @@ "@mantine/core": "7.8.0", "@mantine/hooks": "7.8.0", "@mantine/notifications": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/eslint-config": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/eslint-config": "3.1.3", "next": "14.2.1", "next-auth": "4.24.7", "react": "18.2.0", "react-dom": "18.2.0" }, "devDependencies": { - "@medplum/fhirtypes": "3.1.2", + "@medplum/fhirtypes": "3.1.3", "@types/node": "20.12.7", "@types/react": "18.2.78", "@types/react-dom": "18.2.25", diff --git a/examples/medplum-nextjs-demo/package.json b/examples/medplum-nextjs-demo/package.json index 43d6659c2f..d93fea723a 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.1.2", + "version": "3.1.3", "private": true, "type": "module", "scripts": { @@ -13,8 +13,8 @@ "@mantine/core": "7.8.0", "@mantine/hooks": "7.8.0", "@mantine/notifications": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/react": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/react": "3.1.3", "@next/bundle-analyzer": "14.2.1", "next": "14.2.1", "react": "18.2.0", @@ -22,7 +22,7 @@ "rfc6902": "5.1.1" }, "devDependencies": { - "@medplum/fhirtypes": "3.1.2", + "@medplum/fhirtypes": "3.1.3", "@types/node": "20.12.7", "@types/react": "18.2.78", "@types/react-dom": "18.2.25", diff --git a/examples/medplum-provider/package.json b/examples/medplum-provider/package.json index c8e3bb29b4..323006d2ed 100644 --- a/examples/medplum-provider/package.json +++ b/examples/medplum-provider/package.json @@ -1,6 +1,6 @@ { "name": "medplum-provider", - "version": "3.1.2", + "version": "3.1.3", "private": true, "type": "module", "scripts": { @@ -22,10 +22,10 @@ "@mantine/core": "7.8.0", "@mantine/hooks": "7.8.0", "@mantine/notifications": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/eslint-config": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/react": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/eslint-config": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/react": "3.1.3", "@tabler/icons-react": "3.2.0", "@types/node": "20.12.7", "@types/react": "18.2.78", diff --git a/examples/medplum-react-native-example/package.json b/examples/medplum-react-native-example/package.json index 231b14d4e7..aa06de41eb 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.1.2", + "version": "3.1.3", "main": "src/main.ts", "scripts": { "android": "expo start --android", @@ -20,10 +20,10 @@ }, "dependencies": { "@expo/metro-runtime": "3.1.3", - "@medplum/core": "3.1.2", - "@medplum/expo-polyfills": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/react-hooks": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/expo-polyfills": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/react-hooks": "3.1.3", "expo": "50.0.15", "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 ee72088df4..d0160ef44c 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.1.2", + "version": "3.1.3", "private": true, "type": "module", "scripts": { @@ -25,11 +25,11 @@ "@mantine/core": "7.8.0", "@mantine/hooks": "7.8.0", "@mantine/notifications": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/definitions": "3.1.2", - "@medplum/eslint-config": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/react": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/definitions": "3.1.3", + "@medplum/eslint-config": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/react": "3.1.3", "@tabler/icons-react": "3.2.0", "@types/node": "20.12.7", "@types/react": "18.2.78", diff --git a/examples/medplum-websocket-subscriptions-demo/package.json b/examples/medplum-websocket-subscriptions-demo/package.json index b0618fc8df..41e5b767e2 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.1.2", + "version": "3.1.3", "private": true, "type": "module", "scripts": { @@ -23,12 +23,12 @@ "@mantine/core": "7.8.0", "@mantine/hooks": "7.8.0", "@mantine/notifications": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/eslint-config": "3.1.2", - "@medplum/fhir-router": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/mock": "3.1.2", - "@medplum/react": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/eslint-config": "3.1.3", + "@medplum/fhir-router": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/mock": "3.1.3", + "@medplum/react": "3.1.3", "@tabler/icons-react": "3.2.0", "@types/node": "20.12.7", "@types/react": "18.2.78", diff --git a/package-lock.json b/package-lock.json index 56923e7bba..b5e3cbf0ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "root", - "version": "3.1.2", + "version": "3.1.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "root", - "version": "3.1.2", + "version": "3.1.3", "workspaces": [ "packages/*", "examples/*" @@ -46,7 +46,7 @@ } }, "examples/foomedical": { - "version": "3.1.2", + "version": "3.1.3", "devDependencies": { "@babel/core": "7.24.4", "@babel/preset-env": "7.24.4", @@ -55,11 +55,11 @@ "@mantine/core": "7.8.0", "@mantine/hooks": "7.8.0", "@mantine/notifications": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/eslint-config": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/mock": "3.1.2", - "@medplum/react": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/eslint-config": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/mock": "3.1.3", + "@medplum/react": "3.1.3", "@tabler/icons-react": "3.2.0", "@testing-library/jest-dom": "6.4.2", "@testing-library/react": "15.0.2", @@ -86,15 +86,15 @@ } }, "examples/medplum-chart-demo": { - "version": "3.1.2", + "version": "3.1.3", "devDependencies": { "@mantine/core": "7.8.0", "@mantine/hooks": "7.8.0", "@mantine/notifications": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/eslint-config": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/react": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/eslint-config": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/react": "3.1.3", "@tabler/icons-react": "3.2.0", "@types/node": "20.12.7", "@types/react": "18.2.78", @@ -110,15 +110,15 @@ } }, "examples/medplum-chat-demo": { - "version": "3.1.2", + "version": "3.1.3", "devDependencies": { "@mantine/core": "7.6.2", "@mantine/hooks": "7.6.2", "@mantine/notifications": "7.6.2", - "@medplum/core": "3.1.2", - "@medplum/eslint-config": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/react": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/eslint-config": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/react": "3.1.3", "@tabler/icons-react": "3.0.0", "@types/node": "20.11.28", "@types/react": "18.2.66", @@ -968,23 +968,23 @@ } }, "examples/medplum-client-external-idp-demo": { - "version": "3.1.2", + "version": "3.1.3", "devDependencies": { - "@medplum/core": "3.1.2", + "@medplum/core": "3.1.3", "rimraf": "5.0.5", "typescript": "5.4.5", "vite": "5.2.8" } }, "examples/medplum-demo-bots": { - "version": "3.1.2", + "version": "3.1.3", "license": "Apache-2.0", "devDependencies": { - "@medplum/cli": "3.1.2", - "@medplum/core": "3.1.2", - "@medplum/eslint-config": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/mock": "3.1.2", + "@medplum/cli": "3.1.3", + "@medplum/core": "3.1.3", + "@medplum/eslint-config": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/mock": "3.1.3", "@types/node": "20.12.7", "@types/node-fetch": "2.6.11", "@types/ssh2-sftp-client": "9.0.3", @@ -1003,17 +1003,17 @@ } }, "examples/medplum-eligibility-demo": { - "version": "3.1.2", + "version": "3.1.3", "devDependencies": { "@mantine/core": "7.8.0", "@mantine/hooks": "7.8.0", "@mantine/notifications": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/definitions": "3.1.2", - "@medplum/eslint-config": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/mock": "3.1.2", - "@medplum/react": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/definitions": "3.1.3", + "@medplum/eslint-config": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/mock": "3.1.3", + "@medplum/react": "3.1.3", "@tabler/icons-react": "3.2.0", "@types/node": "20.12.7", "@types/react": "18.2.78", @@ -1029,15 +1029,15 @@ } }, "examples/medplum-fhircast-demo": { - "version": "3.1.2", + "version": "3.1.3", "devDependencies": { "@mantine/core": "7.8.0", "@mantine/hooks": "7.8.0", "@mantine/notifications": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/eslint-config": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/react": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/eslint-config": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/react": "3.1.3", "@tabler/icons-react": "3.2.0", "@types/react": "18.2.78", "@types/react-dom": "18.2.25", @@ -1052,15 +1052,15 @@ } }, "examples/medplum-hello-world": { - "version": "3.1.2", + "version": "3.1.3", "devDependencies": { "@mantine/core": "7.8.0", "@mantine/hooks": "7.8.0", "@mantine/notifications": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/eslint-config": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/react": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/eslint-config": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/react": "3.1.3", "@tabler/icons-react": "3.2.0", "@types/node": "20.12.7", "@types/react": "18.2.78", @@ -1076,15 +1076,15 @@ } }, "examples/medplum-live-chat-demo": { - "version": "3.1.2", + "version": "3.1.3", "devDependencies": { "@mantine/core": "7.8.0", "@mantine/hooks": "7.8.0", "@mantine/notifications": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/eslint-config": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/react": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/eslint-config": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/react": "3.1.3", "@tabler/icons-react": "3.2.0", "@types/node": "20.12.7", "@types/react": "18.2.78", @@ -1100,20 +1100,20 @@ } }, "examples/medplum-nextauth-demo": { - "version": "3.1.2", + "version": "3.1.3", "dependencies": { "@mantine/core": "7.8.0", "@mantine/hooks": "7.8.0", "@mantine/notifications": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/eslint-config": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/eslint-config": "3.1.3", "next": "14.2.1", "next-auth": "4.24.7", "react": "18.2.0", "react-dom": "18.2.0" }, "devDependencies": { - "@medplum/fhirtypes": "3.1.2", + "@medplum/fhirtypes": "3.1.3", "@types/node": "20.12.7", "@types/react": "18.2.78", "@types/react-dom": "18.2.25", @@ -1123,13 +1123,13 @@ } }, "examples/medplum-nextjs-demo": { - "version": "3.1.2", + "version": "3.1.3", "dependencies": { "@mantine/core": "7.8.0", "@mantine/hooks": "7.8.0", "@mantine/notifications": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/react": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/react": "3.1.3", "@next/bundle-analyzer": "14.2.1", "next": "14.2.1", "react": "18.2.0", @@ -1137,7 +1137,7 @@ "rfc6902": "5.1.1" }, "devDependencies": { - "@medplum/fhirtypes": "3.1.2", + "@medplum/fhirtypes": "3.1.3", "@types/node": "20.12.7", "@types/react": "18.2.78", "@types/react-dom": "18.2.25", @@ -1149,15 +1149,15 @@ } }, "examples/medplum-provider": { - "version": "3.1.2", + "version": "3.1.3", "devDependencies": { "@mantine/core": "7.8.0", "@mantine/hooks": "7.8.0", "@mantine/notifications": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/eslint-config": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/react": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/eslint-config": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/react": "3.1.3", "@tabler/icons-react": "3.2.0", "@types/node": "20.12.7", "@types/react": "18.2.78", @@ -1173,13 +1173,13 @@ } }, "examples/medplum-react-native-example": { - "version": "3.1.2", + "version": "3.1.3", "dependencies": { "@expo/metro-runtime": "3.1.3", - "@medplum/core": "3.1.2", - "@medplum/expo-polyfills": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/react-hooks": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/expo-polyfills": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/react-hooks": "3.1.3", "expo": "50.0.15", "expo-status-bar": "1.11.1", "react": "18.2.0", @@ -1193,16 +1193,16 @@ } }, "examples/medplum-task-demo": { - "version": "3.1.2", + "version": "3.1.3", "devDependencies": { "@mantine/core": "7.8.0", "@mantine/hooks": "7.8.0", "@mantine/notifications": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/definitions": "3.1.2", - "@medplum/eslint-config": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/react": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/definitions": "3.1.3", + "@medplum/eslint-config": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/react": "3.1.3", "@tabler/icons-react": "3.2.0", "@types/node": "20.12.7", "@types/react": "18.2.78", @@ -1218,18 +1218,18 @@ } }, "examples/medplum-websocket-subscriptions-demo": { - "version": "3.1.2", + "version": "3.1.3", "devDependencies": { "@emotion/react": "11.11.4", "@mantine/core": "7.8.0", "@mantine/hooks": "7.8.0", "@mantine/notifications": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/eslint-config": "3.1.2", - "@medplum/fhir-router": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/mock": "3.1.2", - "@medplum/react": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/eslint-config": "3.1.3", + "@medplum/fhir-router": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/mock": "3.1.3", + "@medplum/react": "3.1.3", "@tabler/icons-react": "3.2.0", "@types/node": "20.12.7", "@types/react": "18.2.78", @@ -56870,19 +56870,19 @@ }, "packages/agent": { "name": "@medplum/agent", - "version": "3.1.2", + "version": "3.1.3", "license": "Apache-2.0", "dependencies": { - "@medplum/core": "3.1.2", - "@medplum/hl7": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/hl7": "3.1.3", "dcmjs-dimse": "0.1.27", "node-windows": "1.0.0-beta.8", "serialport": "12.0.0", "ws": "8.16.0" }, "devDependencies": { - "@medplum/fhirtypes": "3.1.2", - "@medplum/mock": "3.1.2", + "@medplum/fhirtypes": "3.1.3", + "@medplum/mock": "3.1.3", "@types/async-eventemitter": "0.2.4", "@types/node-windows": "0.1.6", "@types/ws": "8.5.10", @@ -56895,18 +56895,18 @@ }, "packages/app": { "name": "@medplum/app", - "version": "3.1.2", + "version": "3.1.3", "license": "Apache-2.0", "devDependencies": { "@mantine/core": "7.8.0", "@mantine/dropzone": "7.8.0", "@mantine/hooks": "7.8.0", "@mantine/notifications": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/definitions": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/mock": "3.1.2", - "@medplum/react": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/definitions": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/mock": "3.1.3", + "@medplum/react": "3.1.3", "@tabler/icons-react": "3.2.0", "@testing-library/jest-dom": "6.4.2", "@testing-library/react": "15.0.2", @@ -56927,11 +56927,11 @@ }, "packages/bot-layer": { "name": "@medplum/bot-layer", - "version": "3.1.2", + "version": "3.1.3", "license": "Apache-2.0", "dependencies": { - "@medplum/core": "3.1.2", - "@medplum/definitions": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/definitions": "3.1.3", "form-data": "4.0.0", "jose": "5.2.4", "node-fetch": "2.7.0", @@ -56946,7 +56946,7 @@ "node": ">=18.0.0" }, "peerDependencies": { - "@medplum/core": "3.1.2", + "@medplum/core": "3.1.3", "form-data": "^4.0.0", "node-fetch": "^2.7.0", "pdfmake": "^0.2.7", @@ -56956,11 +56956,11 @@ }, "packages/cdk": { "name": "@medplum/cdk", - "version": "3.1.2", + "version": "3.1.3", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.535.0", - "@medplum/core": "3.1.2", + "@medplum/core": "3.1.3", "aws-cdk-lib": "2.137.0", "cdk": "2.137.0", "cdk-nag": "2.28.89", @@ -56973,7 +56973,7 @@ }, "packages/cli": { "name": "@medplum/cli", - "version": "3.1.2", + "version": "3.1.3", "license": "Apache-2.0", "dependencies": { "@aws-sdk/client-acm": "3.554.0", @@ -56984,8 +56984,8 @@ "@aws-sdk/client-ssm": "3.554.0", "@aws-sdk/client-sts": "3.554.0", "@aws-sdk/types": "3.535.0", - "@medplum/core": "3.1.2", - "@medplum/hl7": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/hl7": "3.1.3", "aws-sdk-client-mock": "4.0.0", "commander": "12.0.0", "dotenv": "16.4.5", @@ -56997,8 +56997,8 @@ "medplum": "dist/cjs/index.cjs" }, "devDependencies": { - "@medplum/fhirtypes": "3.1.2", - "@medplum/mock": "3.1.2", + "@medplum/fhirtypes": "3.1.3", + "@medplum/mock": "3.1.3", "@types/node-fetch": "2.6.11" }, "engines": { @@ -57015,11 +57015,11 @@ }, "packages/core": { "name": "@medplum/core", - "version": "3.1.2", + "version": "3.1.3", "license": "Apache-2.0", "devDependencies": { - "@medplum/definitions": "3.1.2", - "@medplum/fhirtypes": "3.1.2", + "@medplum/definitions": "3.1.3", + "@medplum/fhirtypes": "3.1.3", "jest-websocket-mock": "2.5.0" }, "engines": { @@ -57036,7 +57036,7 @@ }, "packages/definitions": { "name": "@medplum/definitions", - "version": "3.1.2", + "version": "3.1.3", "license": "Apache-2.0", "engines": { "node": ">=18.0.0" @@ -57044,7 +57044,7 @@ }, "packages/docs": { "name": "@medplum/docs", - "version": "3.1.2", + "version": "3.1.3", "license": "Apache-2.0", "devDependencies": { "@docusaurus/core": "3.2.1", @@ -57054,9 +57054,9 @@ "@docusaurus/tsconfig": "3.2.1", "@docusaurus/types": "3.2.1", "@mdx-js/react": "3.0.1", - "@medplum/core": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/mock": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/mock": "3.1.3", "@svgr/webpack": "8.1.0", "clsx": "2.1.0", "file-loader": "6.2.0", @@ -57075,7 +57075,7 @@ }, "packages/eslint-config": { "name": "@medplum/eslint-config", - "version": "3.1.2", + "version": "3.1.3", "license": "Apache-2.0", "devDependencies": { "@typescript-eslint/eslint-plugin": "7.6.0", @@ -57101,13 +57101,13 @@ }, "packages/examples": { "name": "@medplum/examples", - "version": "3.1.2", + "version": "3.1.3", "license": "Apache-2.0", "devDependencies": { "@jest/globals": "29.7.0", - "@medplum/core": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/mock": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/mock": "3.1.3", "jest": "29.7.0" }, "engines": { @@ -57116,7 +57116,7 @@ }, "packages/expo-polyfills": { "name": "@medplum/expo-polyfills", - "version": "3.1.2", + "version": "3.1.3", "license": "Apache-2.0", "dependencies": { "base-64": "1.0.0", @@ -57124,7 +57124,7 @@ "text-encoding": "0.7.0" }, "devDependencies": { - "@medplum/core": "3.1.2", + "@medplum/core": "3.1.3", "@types/base-64": "1.0.2", "@types/react": "18.2.78", "@types/text-encoding": "0.0.39", @@ -57136,7 +57136,7 @@ "ts-jest": "29.1.2" }, "peerDependencies": { - "@medplum/core": "3.1.2", + "@medplum/core": "3.1.3", "expo": "*", "expo-crypto": "^12.6.0", "expo-secure-store": "^12.3.1", @@ -57147,12 +57147,12 @@ }, "packages/fhir-router": { "name": "@medplum/fhir-router", - "version": "3.1.2", + "version": "3.1.3", "license": "Apache-2.0", "dependencies": { - "@medplum/core": "3.1.2", - "@medplum/definitions": "3.1.2", - "@medplum/fhirtypes": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/definitions": "3.1.3", + "@medplum/fhirtypes": "3.1.3", "dataloader": "2.2.2", "graphql": "16.8.1", "rfc6902": "5.1.1" @@ -57163,7 +57163,7 @@ }, "packages/fhirtypes": { "name": "@medplum/fhirtypes", - "version": "3.1.2", + "version": "3.1.3", "license": "Apache-2.0", "engines": { "node": ">=18.0.0" @@ -57171,12 +57171,12 @@ }, "packages/generator": { "name": "@medplum/generator", - "version": "3.1.2", + "version": "3.1.3", "license": "Apache-2.0", "devDependencies": { - "@medplum/core": "3.1.2", - "@medplum/definitions": "3.1.2", - "@medplum/fhirtypes": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/definitions": "3.1.3", + "@medplum/fhirtypes": "3.1.3", "@types/json-schema": "7.0.15", "@types/pg": "8.11.5", "@types/unzipper": "0.10.9", @@ -57216,16 +57216,16 @@ }, "packages/graphiql": { "name": "@medplum/graphiql", - "version": "3.1.2", + "version": "3.1.3", "license": "Apache-2.0", "devDependencies": { "@graphiql/react": "0.21.0", "@graphiql/toolkit": "0.9.1", "@mantine/core": "7.8.0", "@mantine/hooks": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/react": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/react": "3.1.3", "@types/react": "18.2.78", "@types/react-dom": "18.2.25", "graphiql": "3.2.0", @@ -57243,14 +57243,14 @@ }, "packages/health-gorilla": { "name": "@medplum/health-gorilla", - "version": "3.1.2", + "version": "3.1.3", "license": "Apache-2.0", "dependencies": { - "@medplum/core": "3.1.2", - "@medplum/fhirtypes": "3.1.2" + "@medplum/core": "3.1.3", + "@medplum/fhirtypes": "3.1.3" }, "devDependencies": { - "@medplum/mock": "3.1.2" + "@medplum/mock": "3.1.3" }, "engines": { "node": ">=18.0.0" @@ -57258,13 +57258,13 @@ }, "packages/hl7": { "name": "@medplum/hl7", - "version": "3.1.2", + "version": "3.1.3", "license": "Apache-2.0", "dependencies": { - "@medplum/core": "3.1.2" + "@medplum/core": "3.1.3" }, "devDependencies": { - "@medplum/fhirtypes": "3.1.2" + "@medplum/fhirtypes": "3.1.3" }, "engines": { "node": ">=18.0.0" @@ -57272,13 +57272,13 @@ }, "packages/mock": { "name": "@medplum/mock", - "version": "3.1.2", + "version": "3.1.3", "license": "Apache-2.0", "dependencies": { - "@medplum/core": "3.1.2", - "@medplum/definitions": "3.1.2", - "@medplum/fhir-router": "3.1.2", - "@medplum/fhirtypes": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/definitions": "3.1.3", + "@medplum/fhir-router": "3.1.3", + "@medplum/fhirtypes": "3.1.3", "dataloader": "2.2.2", "jest-websocket-mock": "2.5.0", "rfc6902": "5.1.1" @@ -57292,17 +57292,17 @@ }, "packages/react": { "name": "@medplum/react", - "version": "3.1.2", + "version": "3.1.3", "license": "Apache-2.0", "devDependencies": { "@mantine/core": "7.8.0", "@mantine/hooks": "7.8.0", "@mantine/notifications": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/definitions": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/mock": "3.1.2", - "@medplum/react-hooks": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/definitions": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/mock": "3.1.3", + "@medplum/react-hooks": "3.1.3", "@storybook/addon-actions": "8.0.8", "@storybook/addon-essentials": "8.0.8", "@storybook/addon-links": "8.0.8", @@ -57343,7 +57343,7 @@ "@mantine/core": "^7.0.0", "@mantine/hooks": "^7.0.0", "@mantine/notifications": "^7.0.0", - "@medplum/core": "3.1.2", + "@medplum/core": "3.1.3", "react": "^17.0.2 || ^18.0.0", "react-dom": "^17.0.2 || ^18.0.0", "rfc6902": "^5.0.1" @@ -57365,13 +57365,13 @@ }, "packages/react-hooks": { "name": "@medplum/react-hooks", - "version": "3.1.2", + "version": "3.1.3", "license": "Apache-2.0", "devDependencies": { - "@medplum/core": "3.1.2", - "@medplum/definitions": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/mock": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/definitions": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/mock": "3.1.3", "@testing-library/dom": "10.0.0", "@testing-library/jest-dom": "6.4.2", "@testing-library/react": "15.0.2", @@ -57391,7 +57391,7 @@ "node": ">=18.0.0" }, "peerDependencies": { - "@medplum/core": "3.1.2", + "@medplum/core": "3.1.3", "react": "^17.0.2 || ^18.0.0", "react-dom": "^17.0.2 || ^18.0.0" } @@ -57437,7 +57437,7 @@ }, "packages/server": { "name": "@medplum/server", - "version": "3.1.2", + "version": "3.1.3", "license": "Apache-2.0", "dependencies": { "@aws-sdk/client-cloudwatch-logs": "3.554.0", @@ -57449,9 +57449,9 @@ "@aws-sdk/cloudfront-signer": "3.541.0", "@aws-sdk/lib-storage": "3.554.0", "@aws-sdk/types": "3.535.0", - "@medplum/core": "3.1.2", - "@medplum/definitions": "3.1.2", - "@medplum/fhir-router": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/definitions": "3.1.3", + "@medplum/fhir-router": "3.1.3", "@opentelemetry/auto-instrumentations-node": "0.44.0", "@opentelemetry/exporter-metrics-otlp-proto": "0.50.0", "@opentelemetry/exporter-trace-otlp-proto": "0.50.0", @@ -57490,7 +57490,7 @@ }, "devDependencies": { "@jest/test-sequencer": "29.7.0", - "@medplum/fhirtypes": "3.1.2", + "@medplum/fhirtypes": "3.1.3", "@types/bcryptjs": "2.4.6", "@types/body-parser": "1.19.5", "@types/bytes": "3.1.4", diff --git a/package.json b/package.json index 9a91d87e05..5d82c70d61 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "3.1.2", + "version": "3.1.3", "private": true, "workspaces": [ "packages/*", diff --git a/packages/agent/package.json b/packages/agent/package.json index 5f82b7d38d..b4c045c266 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/agent", - "version": "3.1.2", + "version": "3.1.3", "description": "Medplum Agent", "homepage": "https://www.medplum.com/", "bugs": { @@ -23,16 +23,16 @@ "test": "jest" }, "dependencies": { - "@medplum/core": "3.1.2", - "@medplum/hl7": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/hl7": "3.1.3", "dcmjs-dimse": "0.1.27", "node-windows": "1.0.0-beta.8", "serialport": "12.0.0", "ws": "8.16.0" }, "devDependencies": { - "@medplum/fhirtypes": "3.1.2", - "@medplum/mock": "3.1.2", + "@medplum/fhirtypes": "3.1.3", + "@medplum/mock": "3.1.3", "@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 3f8f8a8322..8f1f8be524 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/app", - "version": "3.1.2", + "version": "3.1.3", "description": "Medplum App", "homepage": "https://www.medplum.com/", "bugs": { @@ -33,11 +33,11 @@ "@mantine/dropzone": "7.8.0", "@mantine/hooks": "7.8.0", "@mantine/notifications": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/definitions": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/mock": "3.1.2", - "@medplum/react": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/definitions": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/mock": "3.1.3", + "@medplum/react": "3.1.3", "@tabler/icons-react": "3.2.0", "@testing-library/jest-dom": "6.4.2", "@testing-library/react": "15.0.2", diff --git a/packages/bot-layer/package.json b/packages/bot-layer/package.json index 67718f0e1a..6e1a78b217 100644 --- a/packages/bot-layer/package.json +++ b/packages/bot-layer/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/bot-layer", - "version": "3.1.2", + "version": "3.1.3", "description": "Medplum Bot Lambda Layer", "keywords": [ "medplum", @@ -22,8 +22,8 @@ "author": "Medplum ", "type": "module", "dependencies": { - "@medplum/core": "3.1.2", - "@medplum/definitions": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/definitions": "3.1.3", "form-data": "4.0.0", "jose": "5.2.4", "node-fetch": "2.7.0", @@ -35,7 +35,7 @@ "@types/node-fetch": "2.6.11" }, "peerDependencies": { - "@medplum/core": "3.1.2", + "@medplum/core": "3.1.3", "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 187c62480e..0711f44fee 100644 --- a/packages/cdk/package.json +++ b/packages/cdk/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/cdk", - "version": "3.1.2", + "version": "3.1.3", "description": "Medplum CDK Infra as Code", "homepage": "https://www.medplum.com/", "bugs": { @@ -24,7 +24,7 @@ }, "dependencies": { "@aws-sdk/types": "3.535.0", - "@medplum/core": "3.1.2", + "@medplum/core": "3.1.3", "aws-cdk-lib": "2.137.0", "cdk": "2.137.0", "cdk-nag": "2.28.89", diff --git a/packages/cli/package.json b/packages/cli/package.json index 3c55689793..790c836cb2 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/cli", - "version": "3.1.2", + "version": "3.1.3", "description": "Medplum Command Line Interface", "keywords": [ "medplum", @@ -49,8 +49,8 @@ "@aws-sdk/client-ssm": "3.554.0", "@aws-sdk/client-sts": "3.554.0", "@aws-sdk/types": "3.535.0", - "@medplum/core": "3.1.2", - "@medplum/hl7": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/hl7": "3.1.3", "aws-sdk-client-mock": "4.0.0", "commander": "12.0.0", "dotenv": "16.4.5", @@ -59,8 +59,8 @@ "tar": "7.0.1" }, "devDependencies": { - "@medplum/fhirtypes": "3.1.2", - "@medplum/mock": "3.1.2", + "@medplum/fhirtypes": "3.1.3", + "@medplum/mock": "3.1.3", "@types/node-fetch": "2.6.11" }, "engines": { diff --git a/packages/core/package.json b/packages/core/package.json index 43448a872f..604f561773 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/core", - "version": "3.1.2", + "version": "3.1.3", "description": "Medplum TS/JS Library", "keywords": [ "medplum", @@ -55,8 +55,8 @@ "test": "jest" }, "devDependencies": { - "@medplum/definitions": "3.1.2", - "@medplum/fhirtypes": "3.1.2", + "@medplum/definitions": "3.1.3", + "@medplum/fhirtypes": "3.1.3", "jest-websocket-mock": "2.5.0" }, "peerDependencies": { diff --git a/packages/definitions/package.json b/packages/definitions/package.json index 61304f6376..6bb8645f4a 100644 --- a/packages/definitions/package.json +++ b/packages/definitions/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/definitions", - "version": "3.1.2", + "version": "3.1.3", "description": "Medplum Data Definitions", "keywords": [ "medplum", diff --git a/packages/docs/package.json b/packages/docs/package.json index 845b6d5055..77c775ee1c 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/docs", - "version": "3.1.2", + "version": "3.1.3", "description": "Medplum Docs", "homepage": "https://www.medplum.com/", "bugs": { @@ -47,9 +47,9 @@ "@docusaurus/tsconfig": "3.2.1", "@docusaurus/types": "3.2.1", "@mdx-js/react": "3.0.1", - "@medplum/core": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/mock": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/mock": "3.1.3", "@svgr/webpack": "8.1.0", "clsx": "2.1.0", "file-loader": "6.2.0", diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 5f6368c94e..99418515bb 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/eslint-config", - "version": "3.1.2", + "version": "3.1.3", "description": "Shared ESLint configuration for Medplum projects", "keywords": [ "eslint", diff --git a/packages/examples/package.json b/packages/examples/package.json index f7ca166466..a4f556dab6 100644 --- a/packages/examples/package.json +++ b/packages/examples/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/examples", - "version": "3.1.2", + "version": "3.1.3", "description": "Medplum Code Examples", "homepage": "https://www.medplum.com/", "bugs": { @@ -19,9 +19,9 @@ }, "devDependencies": { "@jest/globals": "29.7.0", - "@medplum/core": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/mock": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/mock": "3.1.3", "jest": "29.7.0" }, "engines": { diff --git a/packages/expo-polyfills/package.json b/packages/expo-polyfills/package.json index 3c64b42ae7..eb51ffcfaf 100644 --- a/packages/expo-polyfills/package.json +++ b/packages/expo-polyfills/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/expo-polyfills", - "version": "3.1.2", + "version": "3.1.3", "description": "A module for polyfilling the minimum necessary web APIs for using the Medplum client on React Native", "keywords": [ "react-native", @@ -47,7 +47,7 @@ "text-encoding": "0.7.0" }, "devDependencies": { - "@medplum/core": "3.1.2", + "@medplum/core": "3.1.3", "@types/base-64": "1.0.2", "@types/react": "18.2.78", "@types/text-encoding": "0.0.39", @@ -59,7 +59,7 @@ "ts-jest": "29.1.2" }, "peerDependencies": { - "@medplum/core": "3.1.2", + "@medplum/core": "3.1.3", "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 20bc458fde..559214e229 100644 --- a/packages/fhir-router/package.json +++ b/packages/fhir-router/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/fhir-router", - "version": "3.1.2", + "version": "3.1.3", "description": "Medplum FHIR Router", "keywords": [ "medplum", @@ -53,9 +53,9 @@ "test": "jest" }, "dependencies": { - "@medplum/core": "3.1.2", - "@medplum/definitions": "3.1.2", - "@medplum/fhirtypes": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/definitions": "3.1.3", + "@medplum/fhirtypes": "3.1.3", "dataloader": "2.2.2", "graphql": "16.8.1", "rfc6902": "5.1.1" diff --git a/packages/fhirtypes/package.json b/packages/fhirtypes/package.json index e765b47b5c..1ccdff719f 100644 --- a/packages/fhirtypes/package.json +++ b/packages/fhirtypes/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/fhirtypes", - "version": "3.1.2", + "version": "3.1.3", "description": "Medplum FHIR Type Definitions", "keywords": [ "medplum", diff --git a/packages/generator/package.json b/packages/generator/package.json index 59f318a0e7..19d8a69459 100644 --- a/packages/generator/package.json +++ b/packages/generator/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/generator", - "version": "3.1.2", + "version": "3.1.3", "description": "Medplum Code Generator", "homepage": "https://www.medplum.com/", "repository": { @@ -24,9 +24,9 @@ "test": "jest" }, "devDependencies": { - "@medplum/core": "3.1.2", - "@medplum/definitions": "3.1.2", - "@medplum/fhirtypes": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/definitions": "3.1.3", + "@medplum/fhirtypes": "3.1.3", "@types/json-schema": "7.0.15", "@types/pg": "8.11.5", "@types/unzipper": "0.10.9", diff --git a/packages/graphiql/package.json b/packages/graphiql/package.json index 423014a70e..a574cc63ce 100644 --- a/packages/graphiql/package.json +++ b/packages/graphiql/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/graphiql", - "version": "3.1.2", + "version": "3.1.3", "description": "Medplum GraphiQL", "homepage": "https://www.medplum.com/", "bugs": { @@ -27,9 +27,9 @@ "@graphiql/toolkit": "0.9.1", "@mantine/core": "7.8.0", "@mantine/hooks": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/react": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/react": "3.1.3", "@types/react": "18.2.78", "@types/react-dom": "18.2.25", "graphiql": "3.2.0", diff --git a/packages/health-gorilla/package.json b/packages/health-gorilla/package.json index 5fe12d8fa4..4f8953c488 100644 --- a/packages/health-gorilla/package.json +++ b/packages/health-gorilla/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/health-gorilla", - "version": "3.1.2", + "version": "3.1.3", "description": "Medplum Health Gorilla SDK", "homepage": "https://www.medplum.com/", "bugs": { @@ -39,11 +39,11 @@ "test": "jest" }, "dependencies": { - "@medplum/core": "3.1.2", - "@medplum/fhirtypes": "3.1.2" + "@medplum/core": "3.1.3", + "@medplum/fhirtypes": "3.1.3" }, "devDependencies": { - "@medplum/mock": "3.1.2" + "@medplum/mock": "3.1.3" }, "engines": { "node": ">=18.0.0" diff --git a/packages/hl7/package.json b/packages/hl7/package.json index 151410d4c6..7e3ff747cf 100644 --- a/packages/hl7/package.json +++ b/packages/hl7/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/hl7", - "version": "3.1.2", + "version": "3.1.3", "description": "Medplum HL7 Utilities", "keywords": [ "medplum", @@ -53,10 +53,10 @@ "test": "jest" }, "dependencies": { - "@medplum/core": "3.1.2" + "@medplum/core": "3.1.3" }, "devDependencies": { - "@medplum/fhirtypes": "3.1.2" + "@medplum/fhirtypes": "3.1.3" }, "engines": { "node": ">=18.0.0" diff --git a/packages/mock/package.json b/packages/mock/package.json index 579f182644..98f3fb6bbc 100644 --- a/packages/mock/package.json +++ b/packages/mock/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/mock", - "version": "3.1.2", + "version": "3.1.3", "description": "Medplum Mock Client", "keywords": [ "medplum", @@ -53,10 +53,10 @@ "test": "jest" }, "dependencies": { - "@medplum/core": "3.1.2", - "@medplum/definitions": "3.1.2", - "@medplum/fhir-router": "3.1.2", - "@medplum/fhirtypes": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/definitions": "3.1.3", + "@medplum/fhir-router": "3.1.3", + "@medplum/fhirtypes": "3.1.3", "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 43de1313b1..f2e11f00df 100644 --- a/packages/react-hooks/package.json +++ b/packages/react-hooks/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/react-hooks", - "version": "3.1.2", + "version": "3.1.3", "description": "Medplum React Hooks Library", "keywords": [ "medplum", @@ -57,10 +57,10 @@ "test": "jest" }, "devDependencies": { - "@medplum/core": "3.1.2", - "@medplum/definitions": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/mock": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/definitions": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/mock": "3.1.3", "@testing-library/dom": "10.0.0", "@testing-library/jest-dom": "6.4.2", "@testing-library/react": "15.0.2", @@ -77,7 +77,7 @@ "typescript": "5.4.5" }, "peerDependencies": { - "@medplum/core": "3.1.2", + "@medplum/core": "3.1.3", "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 550b974866..3e8089231f 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/react", - "version": "3.1.2", + "version": "3.1.3", "description": "Medplum React Component Library", "keywords": [ "medplum", @@ -70,11 +70,11 @@ "@mantine/core": "7.8.0", "@mantine/hooks": "7.8.0", "@mantine/notifications": "7.8.0", - "@medplum/core": "3.1.2", - "@medplum/definitions": "3.1.2", - "@medplum/fhirtypes": "3.1.2", - "@medplum/mock": "3.1.2", - "@medplum/react-hooks": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/definitions": "3.1.3", + "@medplum/fhirtypes": "3.1.3", + "@medplum/mock": "3.1.3", + "@medplum/react-hooks": "3.1.3", "@storybook/addon-actions": "8.0.8", "@storybook/addon-essentials": "8.0.8", "@storybook/addon-links": "8.0.8", @@ -112,7 +112,7 @@ "@mantine/core": "^7.0.0", "@mantine/hooks": "^7.0.0", "@mantine/notifications": "^7.0.0", - "@medplum/core": "3.1.2", + "@medplum/core": "3.1.3", "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 67d90f694f..cfdc3739d6 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/server", - "version": "3.1.2", + "version": "3.1.3", "description": "Medplum Server", "homepage": "https://www.medplum.com/", "bugs": { @@ -30,9 +30,9 @@ "@aws-sdk/cloudfront-signer": "3.541.0", "@aws-sdk/lib-storage": "3.554.0", "@aws-sdk/types": "3.535.0", - "@medplum/core": "3.1.2", - "@medplum/definitions": "3.1.2", - "@medplum/fhir-router": "3.1.2", + "@medplum/core": "3.1.3", + "@medplum/definitions": "3.1.3", + "@medplum/fhir-router": "3.1.3", "@opentelemetry/auto-instrumentations-node": "0.44.0", "@opentelemetry/exporter-metrics-otlp-proto": "0.50.0", "@opentelemetry/exporter-trace-otlp-proto": "0.50.0", @@ -71,7 +71,7 @@ }, "devDependencies": { "@jest/test-sequencer": "29.7.0", - "@medplum/fhirtypes": "3.1.2", + "@medplum/fhirtypes": "3.1.3", "@types/bcryptjs": "2.4.6", "@types/body-parser": "1.19.5", "@types/bytes": "3.1.4", diff --git a/sonar-project.properties b/sonar-project.properties index 7c9e806049..b0bd407782 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.1.2 +sonar.projectVersion=3.1.3 sonar.sources=packages sonar.sourceEncoding=UTF-8 sonar.exclusions=**/node_modules/**,\ From e8550567ead40fc9826da0ce03acd8c424364b82 Mon Sep 17 00:00:00 2001 From: Derrick Farris Date: Mon, 22 Apr 2024 11:35:12 -0700 Subject: [PATCH 44/52] test(turbo): prevent test triggering docs build (#4423) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5d82c70d61..f186c49fcd 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "lint": "eslint .", "prettier": "prettier --write \"**/*.{ts,tsx,cts,mts,js,jsx,cjs,mjs,json}\"", "sort-package-json": "sort-package-json package.json \"packages/*/package.json\" \"examples/*/package.json\"", - "test": "turbo run test" + "test": "turbo run test --filter=!@medplum/docs" }, "prettier": { "printWidth": 120, From a8c2bd93672dbe7b1e778d2bc65a89f2278571c9 Mon Sep 17 00:00:00 2001 From: ksmith94 <102421938+ksmith94@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:17:47 -0700 Subject: [PATCH 45/52] Note that only admins can invite users (#4428) * Note that only admins can invite users * Link to Project Admins --------- Co-authored-by: Rahul Agarwal --- .../docs/auth/user-management-guide/user-management-guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/docs/docs/auth/user-management-guide/user-management-guide.md b/packages/docs/docs/auth/user-management-guide/user-management-guide.md index 8bfa908f29..ade0860701 100644 --- a/packages/docs/docs/auth/user-management-guide/user-management-guide.md +++ b/packages/docs/docs/auth/user-management-guide/user-management-guide.md @@ -304,7 +304,7 @@ It is important to spread the original `ProjectMembership` to ensure that you ar ## Invite via API -Inviting users can be done programmatically using the [`/invite` endpoint](/docs/api/project-admin/invite). +Inviting users can be done programmatically using the [`/invite` endpoint](/docs/api/project-admin/invite). Like inviting via the [Medplum App](https://app.medplum.com), this can only be done by [project admins](/docs/access/admin#project-admin). Prepare JSON payload: From f65e329b0e0329b62df62bbb65f511569af3b136 Mon Sep 17 00:00:00 2001 From: ksmith94 <102421938+ksmith94@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:42:52 -0700 Subject: [PATCH 46/52] Add note about restarting server on config change (#4425) * Add note about restarting server on config change * Update packages/docs/docs/self-hosting/config-settings.md --------- Co-authored-by: Rahul Agarwal --- packages/docs/docs/self-hosting/config-settings.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/docs/docs/self-hosting/config-settings.md b/packages/docs/docs/self-hosting/config-settings.md index 61993bf989..a369f669dc 100644 --- a/packages/docs/docs/self-hosting/config-settings.md +++ b/packages/docs/docs/self-hosting/config-settings.md @@ -161,7 +161,9 @@ Optionally override the trusted CA certificates. Default is to trust the well-kn :::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_ -To make changes to settings that affect your deployed Medplum App, you must _also_ make this change to your local configuration json file. +To make changes to settings that affect your deployed Medplum App, you must _also_ make these changes to your local configuration json file. + +Once you have made these changes, you will need to restart your server for them to take effect. The easiest way to do this in a zero-downtime manner is by using the `medplum aws update-server` command. For more details on this command see the [Upgrade the Server docs](/docs/self-hosting/install-on-aws#upgrade-the-server). ::: ### AWS Secrets From 19cfe9eaba8ded350b08349ee92c476689b90678 Mon Sep 17 00:00:00 2001 From: Matt Long Date: Mon, 22 Apr 2024 15:48:01 -0700 Subject: [PATCH 47/52] Don't warn on primitive extensions (#4433) * Don't warn on primitive extensions * remove duplicate test * use correct type --- .../core/src/typeschema/validation.test.ts | 105 ++++++++++++++++-- packages/core/src/typeschema/validation.ts | 33 +++++- 2 files changed, 128 insertions(+), 10 deletions(-) diff --git a/packages/core/src/typeschema/validation.test.ts b/packages/core/src/typeschema/validation.test.ts index 8ecc02a254..6f78938b37 100644 --- a/packages/core/src/typeschema/validation.test.ts +++ b/packages/core/src/typeschema/validation.test.ts @@ -1259,6 +1259,19 @@ describe('FHIR resource validation', () => { expect(() => validateResource(e3, { profile })).toThrow(); }); + // TODO: Change this check from warning to error + // Duplicate entries for choice-of-type property is currently a warning + // We need to first log and track this, and notify customers of breaking changes + function expectOneWarning(resource: Resource, textContains: string): void { + const issues = validateResource(resource); + expect(issues).toHaveLength(1); + expect(issues[0].severity).toBe('warning'); + expect(issues[0].details?.text).toContain(textContains); + } + + const DUPLICATE_CHOICE_OF_TYPE_PROPERTY = 'Duplicate choice of type property'; + const PRIMITIVE_EXTENSION_TYPE_MISMATCH = 'Type of primitive extension does not match the type of property'; + test('Multiple values for choice of type property', () => { const carePlan: CarePlan = { resourceType: 'CarePlan', @@ -1286,13 +1299,91 @@ describe('FHIR resource validation', () => { ], }; - // TODO: Change this check from warning to error - // Duplicate entries for choice-of-type property is currently a warning - // We need to first log and track this, and notify customers of breaking changes - const issues = validateResource(carePlan); - expect(issues).toHaveLength(1); - expect(issues[0].severity).toBe('warning'); - expect(issues[0].details?.text).toContain('Duplicate choice of type property'); + expectOneWarning(carePlan, DUPLICATE_CHOICE_OF_TYPE_PROPERTY); + }); + + test('Valid choice of type properties with primitive extensions', () => { + expect( + validateResource({ + resourceType: 'Patient', + multipleBirthInteger: 2, + } as Patient) + ).toHaveLength(0); + + expect( + validateResource({ + resourceType: 'Patient', + _multipleBirthInteger: { + extension: [], + }, + } as Patient) + ).toHaveLength(0); + + // check both orders of the properties + expect( + validateResource({ + resourceType: 'Patient', + multipleBirthInteger: 2, + _multipleBirthInteger: { + extension: [], + }, + } as Patient) + ).toHaveLength(0); + expect( + validateResource({ + resourceType: 'Patient', + multipleBirthInteger: 2, + _multipleBirthInteger: { + extension: [], + }, + } as Patient) + ).toHaveLength(0); + }); + + test('Invalid choice of type properties with primitive extensions', () => { + expectOneWarning( + { + resourceType: 'Patient', + multipleBirthBoolean: true, + multipleBirthInteger: 2, + } as Patient, + DUPLICATE_CHOICE_OF_TYPE_PROPERTY + ); + + expectOneWarning( + { + resourceType: 'Patient', + _multipleBirthInteger: { + extension: [], + }, + _multipleBirthBoolean: { + extension: [], + }, + } as Patient, + DUPLICATE_CHOICE_OF_TYPE_PROPERTY + ); + + // Primitive extension type mismatch, check both orders of the properties + expectOneWarning( + { + resourceType: 'Patient', + multipleBirthInteger: 2, + _multipleBirthBoolean: { + extension: [], + }, + } as Patient, + PRIMITIVE_EXTENSION_TYPE_MISMATCH + ); + expectOneWarning( + { + resourceType: 'Patient', + _multipleBirthBoolean: { + extension: [], + }, + multipleBirthInteger: 2, + } as Patient, + PRIMITIVE_EXTENSION_TYPE_MISMATCH + ); }); test('Reference type check', () => { diff --git a/packages/core/src/typeschema/validation.ts b/packages/core/src/typeschema/validation.ts index a1f7807234..f1fd419f34 100644 --- a/packages/core/src/typeschema/validation.ts +++ b/packages/core/src/typeschema/validation.ts @@ -283,13 +283,38 @@ class ResourceValidator implements ResourceVisitor { if (!object) { return; } - const choiceOfTypeElements: Record = {}; + const choiceOfTypeElements: Record = {}; for (const key of Object.keys(object)) { if (key === 'resourceType') { continue; // Skip special resource type discriminator property in JSON } const choiceOfTypeElementName = isChoiceOfType(parent, key, properties); if (choiceOfTypeElementName) { + // check that the type of the primitive extension matches the type of the property + let relatedElementName: string; + let requiredRelatedElementName: string; + if (choiceOfTypeElementName.startsWith('_')) { + relatedElementName = choiceOfTypeElementName.slice(1); + requiredRelatedElementName = key.slice(1); + } else { + relatedElementName = '_' + choiceOfTypeElementName; + requiredRelatedElementName = '_' + key; + } + + if ( + relatedElementName in choiceOfTypeElements && + choiceOfTypeElements[relatedElementName] !== requiredRelatedElementName + ) { + this.issues.push( + createOperationOutcomeIssue( + 'warning', + 'structure', + `Type of primitive extension does not match the type of property "${choiceOfTypeElementName.startsWith('_') ? choiceOfTypeElementName.slice(1) : choiceOfTypeElementName}"`, + choiceOfTypeElementName + ) + ); + } + if (choiceOfTypeElements[choiceOfTypeElementName]) { // Found a duplicate choice of type property // TODO: This should be an error, but it's currently a warning to avoid breaking existing code @@ -303,7 +328,7 @@ class ResourceValidator implements ResourceVisitor { ) ); } - choiceOfTypeElements[choiceOfTypeElementName] = true; + choiceOfTypeElements[choiceOfTypeElementName] = key; continue; } if (!(key in properties) && !(key.startsWith('_') && key.slice(1) in properties)) { @@ -486,8 +511,10 @@ function isChoiceOfType( key: string, propertyDefinitions: Record ): string | undefined { + let prefix = ''; if (key.startsWith('_')) { key = key.slice(1); + prefix = '_'; } const parts = key.split(/(?=[A-Z])/g); // Split before capital letters let testProperty = ''; @@ -496,7 +523,7 @@ function isChoiceOfType( const elementName = testProperty + '[x]'; if (propertyDefinitions[elementName]) { const typedPropertyValue = getTypedPropertyValue(typedValue, testProperty); - return typedPropertyValue ? elementName : undefined; + return typedPropertyValue ? prefix + elementName : undefined; } } return undefined; From d723c0af16065dd4e281132aef680427b14bc42b Mon Sep 17 00:00:00 2001 From: Cody Ebberson Date: Mon, 22 Apr 2024 16:09:18 -0700 Subject: [PATCH 48/52] Per-project rate limiter config (#4412) * Draft of per-project rate limiter config * Update temp limits * Added project settings, systemSettings, systemSecrets * Use project settings for rate limit values * PR feedback * Updated rate limit language * Fixed contact link * Fixed ESLint warning * Fixed ESlint error --- packages/app/src/admin/SecretsPage.tsx | 4 +- packages/core/src/client.ts | 4 +- .../dist/fhir/r4/profiles-medplum.json | 59 +++++++++++++++---- packages/docs/docs/rate-limits/index.md | 28 +++++++++ packages/docs/sidebars.ts | 6 ++ packages/fhirtypes/dist/Project.d.ts | 35 +++++++++-- packages/generator/src/index.ts | 7 +++ .../mock/src/mocks/structuredefinitions.json | 33 +++++++++-- packages/server/src/agent/websockets.ts | 16 ++++- packages/server/src/app.ts | 3 +- packages/server/src/auth/routes.ts | 8 +-- packages/server/src/context.ts | 14 ++++- .../server/src/fhir/operations/execute.ts | 6 +- packages/server/src/oauth/middleware.ts | 58 ++++++++---------- packages/server/src/oauth/routes.ts | 2 - packages/server/src/oauth/utils.ts | 15 +++-- packages/server/src/ratelimit.ts | 34 +++++++++-- 17 files changed, 249 insertions(+), 83 deletions(-) create mode 100644 packages/docs/docs/rate-limits/index.md diff --git a/packages/app/src/admin/SecretsPage.tsx b/packages/app/src/admin/SecretsPage.tsx index 4535d3f27a..ffa799261e 100644 --- a/packages/app/src/admin/SecretsPage.tsx +++ b/packages/app/src/admin/SecretsPage.tsx @@ -1,7 +1,7 @@ import { Button, Title } from '@mantine/core'; import { showNotification } from '@mantine/notifications'; import { InternalSchemaElement, deepClone, getElementDefinition } from '@medplum/core'; -import { ProjectSecret } from '@medplum/fhirtypes'; +import { ProjectSetting } from '@medplum/fhirtypes'; import { ResourcePropertyInput, useMedplum } from '@medplum/react'; import { FormEvent, useEffect, useState } from 'react'; import { getProjectId } from '../utils'; @@ -11,7 +11,7 @@ export function SecretsPage(): JSX.Element { const projectId = getProjectId(medplum); const projectDetails = medplum.get(`admin/projects/${projectId}`).read(); const [schemaLoaded, setSchemaLoaded] = useState(false); - const [secrets, setSecrets] = useState(); + const [secrets, setSecrets] = useState(); useEffect(() => { medplum diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 89ad1798c5..1fb4505c10 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -22,7 +22,7 @@ import { Project, ProjectMembership, ProjectMembershipAccess, - ProjectSecret, + ProjectSetting, Reference, Resource, ResourceType, @@ -429,7 +429,7 @@ export interface BotEvent; readonly contentType: string; readonly input: T; - readonly secrets: Record; + readonly secrets: Record; readonly traceId?: string; } diff --git a/packages/definitions/dist/fhir/r4/profiles-medplum.json b/packages/definitions/dist/fhir/r4/profiles-medplum.json index 6c91b75a53..c75186e8ad 100644 --- a/packages/definitions/dist/fhir/r4/profiles-medplum.json +++ b/packages/definitions/dist/fhir/r4/profiles-medplum.json @@ -240,23 +240,23 @@ } }, { - "id" : "Project.secret", - "path" : "Project.secret", - "definition" : "Secure environment variable that can be used to store secrets for bots.", + "id" : "Project.setting", + "path" : "Project.setting", + "definition" : "Option or parameter that can be adjusted within the Medplum Project to customize its behavior.", "min" : 0, "max" : "*", "type" : [{ "code" : "BackboneElement" }], "base" : { - "path" : "Project.secret", + "path" : "Project.setting", "min" : 0, "max" : "*" } }, { - "id" : "Project.secret.name", - "path" : "Project.secret.name", + "id" : "Project.setting.name", + "path" : "Project.setting.name", "definition" : "The secret name.", "min" : 1, "max" : "1", @@ -264,14 +264,14 @@ "code" : "string" }], "base" : { - "path" : "Project.secret.name", + "path" : "Project.setting.name", "min" : 1, "max" : "1" } }, { - "id" : "Project.secret.value[x]", - "path" : "Project.secret.value[x]", + "id" : "Project.setting.value[x]", + "path" : "Project.setting.value[x]", "definition" : "The secret value.", "min" : 1, "max" : "1", @@ -288,11 +288,50 @@ "code" : "integer" }], "base" : { - "path" : "Project.secret.value[x]", + "path" : "Project.setting.value[x]", "min" : 1, "max" : "1" } }, + { + "id" : "Project.secret", + "path" : "Project.secret", + "definition" : "Option or parameter that can be adjusted within the Medplum Project to customize its behavior, only visible to project administrators.", + "min" : 0, + "max" : "*", + "base" : { + "path" : "Project.secret", + "min" : 0, + "max" : "*" + }, + "contentReference" : "#Project.setting" + }, + { + "id" : "Project.systemSetting", + "path" : "Project.systemSetting", + "definition" : "Option or parameter that can be adjusted within the Medplum Project to customize its behavior, only modifiable by system administrators.", + "min" : 0, + "max" : "*", + "base" : { + "path" : "Project.systemSetting", + "min" : 0, + "max" : "*" + }, + "contentReference" : "#Project.setting" + }, + { + "id" : "Project.systemSecret", + "path" : "Project.systemSecret", + "definition" : "Option or parameter that can be adjusted within the Medplum Project to customize its behavior, only visible to system administrators.", + "min" : 0, + "max" : "*", + "base" : { + "path" : "Project.systemSecret", + "min" : 0, + "max" : "*" + }, + "contentReference" : "#Project.setting" + }, { "id" : "Project.site", "path" : "Project.site", diff --git a/packages/docs/docs/rate-limits/index.md b/packages/docs/docs/rate-limits/index.md new file mode 100644 index 0000000000..1ffa07caf3 --- /dev/null +++ b/packages/docs/docs/rate-limits/index.md @@ -0,0 +1,28 @@ +# Rate Limits + +The Medplum API uses a number of safeguards against bursts of incoming traffic to help maximize its stability. Users who send many requests in quick succession might see error responses that show up as status code `429`. + +## Default Rate Limits + +| Category | Free tier | Paid tier | +| ----------------------------- | ------------------------------ | ------------------------------- | +| Auth (`/auth/*`, `/oauth2/*`) | 1 request per IP per second | 1 request per IP per second | +| Others | 100 requests per IP per second | 1000 requests per IP per second | + +All rate limits are calculated per IP address on a 15 minute window. + +Rate limits can be increased for paid plans. Please [contact us](mailto:info+rate-limits@medplum.com?subject=Increase%20rate%20limits) for more information. + +## HTTP Headers + +All API calls affected by rate limits will include the following headers: + +- `X-Ratelimit-Limit`: The maximum number of requests that the consumer is permitted to make in a 15 minute window. +- `X-Ratelimit-Remaining`: The number of requests remaining in the current rate limit window. +- `X-Ratelimit-Reset`: The time at which the current rate limit window resets in UTC epoch seconds. + +``` +X-Ratelimit-Limit: 600 +X-Ratelimit-Remaining: 599 +X-Ratelimit-Reset: 1713810464 +``` diff --git a/packages/docs/sidebars.ts b/packages/docs/sidebars.ts index a2e88a8e13..0ecc9ad338 100644 --- a/packages/docs/sidebars.ts +++ b/packages/docs/sidebars.ts @@ -234,6 +234,12 @@ const sidebars: SidebarsConfig = { link: { type: 'doc', id: 'analytics/index' }, items: [{ type: 'autogenerated', dirName: 'analytics' }], }, + { + type: 'category', + label: 'Rate Limits', + link: { type: 'doc', id: 'rate-limits/index' }, + items: [{ type: 'autogenerated', dirName: 'rate-limits' }], + }, { type: 'category', label: 'Self-Hosting', diff --git a/packages/fhirtypes/dist/Project.d.ts b/packages/fhirtypes/dist/Project.d.ts index 6b3e2fd52e..6b9d7ea392 100644 --- a/packages/fhirtypes/dist/Project.d.ts +++ b/packages/fhirtypes/dist/Project.d.ts @@ -94,10 +94,28 @@ export interface Project { defaultPatientAccessPolicy?: Reference; /** - * Secure environment variable that can be used to store secrets for - * bots. + * Option or parameter that can be adjusted within the Medplum Project to + * customize its behavior. */ - secret?: ProjectSecret[]; + setting?: ProjectSetting[]; + + /** + * Option or parameter that can be adjusted within the Medplum Project to + * customize its behavior, only visible to project administrators. + */ + secret?: ProjectSetting[]; + + /** + * Option or parameter that can be adjusted within the Medplum Project to + * customize its behavior, only modifiable by system administrators. + */ + systemSetting?: ProjectSetting[]; + + /** + * Option or parameter that can be adjusted within the Medplum Project to + * customize its behavior, only visible to system administrators. + */ + systemSecret?: ProjectSetting[]; /** * Web application or web site that is associated with the project. @@ -122,10 +140,10 @@ export interface ProjectLink { } /** - * Secure environment variable that can be used to store secrets for - * bots. + * Option or parameter that can be adjusted within the Medplum Project to + * customize its behavior. */ -export interface ProjectSecret { +export interface ProjectSetting { /** * The secret name. @@ -200,3 +218,8 @@ export interface ProjectSite { */ recaptchaSecretKey?: string; } + +/** + * @deprecated Use ProjectSetting instead + */ +export type ProjectSecret = ProjectSetting; diff --git a/packages/generator/src/index.ts b/packages/generator/src/index.ts index 41917b0224..2ca1223fcf 100644 --- a/packages/generator/src/index.ts +++ b/packages/generator/src/index.ts @@ -144,6 +144,13 @@ function writeInterface(b: FileBuilder, fhirType: InternalTypeSchema): void { writeInterface(b, subType); } } + + if (typeName === 'Project') { + // TODO: Remove this in Medplum v4 + b.newLine(); + generateJavadoc(b, '@deprecated Use ProjectSetting instead'); + b.append('export type ProjectSecret = ProjectSetting;'); + } } function writeInterfaceProperty( diff --git a/packages/mock/src/mocks/structuredefinitions.json b/packages/mock/src/mocks/structuredefinitions.json index 9aec46297f..b1aa9e099a 100644 --- a/packages/mock/src/mocks/structuredefinitions.json +++ b/packages/mock/src/mocks/structuredefinitions.json @@ -12065,8 +12065,8 @@ ] }, { - "id": "Project.secret", - "path": "Project.secret", + "id": "Project.setting", + "path": "Project.setting", "min": 0, "max": "*", "type": [ @@ -12076,8 +12076,8 @@ ] }, { - "id": "Project.secret.name", - "path": "Project.secret.name", + "id": "Project.setting.name", + "path": "Project.setting.name", "min": 1, "max": "1", "type": [ @@ -12087,8 +12087,8 @@ ] }, { - "id": "Project.secret.value[x]", - "path": "Project.secret.value[x]", + "id": "Project.setting.value[x]", + "path": "Project.setting.value[x]", "min": 1, "max": "1", "type": [ @@ -12106,6 +12106,27 @@ } ] }, + { + "id": "Project.secret", + "path": "Project.secret", + "min": 0, + "max": "*", + "contentReference": "#Project.setting" + }, + { + "id": "Project.systemSetting", + "path": "Project.systemSetting", + "min": 0, + "max": "*", + "contentReference": "#Project.setting" + }, + { + "id": "Project.systemSecret", + "path": "Project.systemSecret", + "min": 0, + "max": "*", + "contentReference": "#Project.setting" + }, { "id": "Project.site", "path": "Project.site", diff --git a/packages/server/src/agent/websockets.ts b/packages/server/src/agent/websockets.ts index 2d36c2076c..feb5885c94 100644 --- a/packages/server/src/agent/websockets.ts +++ b/packages/server/src/agent/websockets.ts @@ -113,7 +113,13 @@ export async function handleAgentConnection(socket: ws.WebSocket, request: Incom agentId = command.agentId; - const { login, project, membership } = await getLoginForAccessToken(command.accessToken); + const authState = await getLoginForAccessToken(command.accessToken); + if (!authState) { + sendError('Invalid access token'); + return; + } + + const { login, project, membership } = authState; const repo = await getRepoForLogin(login, membership, project, true); const agent = await repo.readResource('Agent', agentId); @@ -161,7 +167,13 @@ export async function handleAgentConnection(socket: ws.WebSocket, request: Incom return; } - const { login, project, membership } = await getLoginForAccessToken(command.accessToken); + const authState = await getLoginForAccessToken(command.accessToken); + if (!authState) { + sendError('Invalid access token'); + return; + } + + const { login, project, membership } = authState; 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); diff --git a/packages/server/src/app.ts b/packages/server/src/app.ts index 089b5c77b1..ee51a467b7 100644 --- a/packages/server/src/app.ts +++ b/packages/server/src/app.ts @@ -36,7 +36,7 @@ import { keyValueRouter } from './keyvalue/routes'; import { initKeys } from './oauth/keys'; import { oauthRouter } from './oauth/routes'; import { openApiHandler } from './openapi'; -import { closeRateLimiter } from './ratelimit'; +import { closeRateLimiter, getRateLimiter } from './ratelimit'; import { closeRedis, initRedis } from './redis'; import { scimRouter } from './scim/routes'; import { seedDatabase } from './seed'; @@ -141,6 +141,7 @@ export async function initApp(app: Express, config: MedplumServerConfig): Promis app.use(cors(corsOptions)); app.use(compression()); app.use(attachRequestContext); + app.use(getRateLimiter()); app.use('/fhir/R4/Binary', binaryRouter); app.use( urlencoded({ diff --git a/packages/server/src/auth/routes.ts b/packages/server/src/auth/routes.ts index ed21829ef7..ee02e35db3 100644 --- a/packages/server/src/auth/routes.ts +++ b/packages/server/src/auth/routes.ts @@ -1,7 +1,8 @@ +import { badRequest } from '@medplum/core'; +import { OperationOutcome, Project } from '@medplum/fhirtypes'; import { Router } from 'express'; import { asyncWrap } from '../async'; import { authenticateRequest } from '../oauth/middleware'; -import { getRateLimiter } from '../ratelimit'; import { changePasswordHandler, changePasswordValidator } from './changepassword'; import { exchangeHandler, exchangeValidator } from './exchange'; import { externalCallbackHandler } from './external'; @@ -18,14 +19,11 @@ import { resetPasswordHandler, resetPasswordValidator } from './resetpassword'; import { revokeHandler, revokeValidator } from './revoke'; import { scopeHandler, scopeValidator } from './scope'; import { setPasswordHandler, setPasswordValidator } from './setpassword'; -import { verifyEmailHandler, verifyEmailValidator } from './verifyemail'; import { statusHandler, statusValidator } from './status'; -import { badRequest } from '@medplum/core'; -import { OperationOutcome, Project } from '@medplum/fhirtypes'; import { validateRecaptcha } from './utils'; +import { verifyEmailHandler, verifyEmailValidator } from './verifyemail'; export const authRouter = Router(); -authRouter.use(getRateLimiter()); authRouter.use('/mfa', mfaRouter); authRouter.post('/method', methodValidator, asyncWrap(methodHandler)); authRouter.get('/external', asyncWrap(externalCallbackHandler)); diff --git a/packages/server/src/context.ts b/packages/server/src/context.ts index 13f4085695..bfeb604736 100644 --- a/packages/server/src/context.ts +++ b/packages/server/src/context.ts @@ -4,7 +4,9 @@ import { AsyncLocalStorage } from 'async_hooks'; import { randomUUID } from 'crypto'; import { NextFunction, Request, Response } from 'express'; import { getConfig } from './config'; +import { getRepoForLogin } from './fhir/accesspolicy'; import { Repository, getSystemRepo } from './fhir/repo'; +import { authenticateTokenImpl, isExtendedMode } from './oauth/middleware'; import { parseTraceparent } from './traceparent'; export class RequestContext { @@ -94,7 +96,17 @@ export function getAuthenticatedContext(): AuthenticatedRequestContext { export async function attachRequestContext(req: Request, res: Response, next: NextFunction): Promise { const { requestId, traceId } = requestIds(req); - requestContextStore.run(new RequestContext(requestId, traceId), () => next()); + + let ctx = new RequestContext(requestId, traceId); + + const authState = await authenticateTokenImpl(req); + if (authState) { + const { login, membership, project, accessToken } = authState; + const repo = await getRepoForLogin(login, membership, project, isExtendedMode(req)); + ctx = new AuthenticatedRequestContext(ctx, login, project, membership, repo, accessToken); + } + + requestContextStore.run(ctx, () => next()); } export function closeRequestContext(): void { diff --git a/packages/server/src/fhir/operations/execute.ts b/packages/server/src/fhir/operations/execute.ts index 2f4fd8c079..f7f29aa652 100644 --- a/packages/server/src/fhir/operations/execute.ts +++ b/packages/server/src/fhir/operations/execute.ts @@ -19,7 +19,7 @@ import { Organization, Project, ProjectMembership, - ProjectSecret, + ProjectSetting, Reference, Subscription, } from '@medplum/fhirtypes'; @@ -59,7 +59,7 @@ export interface BotExecutionRequest { export interface BotExecutionContext extends BotExecutionRequest { readonly accessToken: string; - readonly secrets: Record; + readonly secrets: Record; } export interface BotExecutionResult { @@ -420,7 +420,7 @@ async function getBotAccessToken(runAs: ProjectMembership): Promise { return accessToken; } -async function getBotSecrets(bot: Bot): 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]) || []); diff --git a/packages/server/src/oauth/middleware.ts b/packages/server/src/oauth/middleware.ts index 9e7eecdcee..49de0ff492 100644 --- a/packages/server/src/oauth/middleware.ts +++ b/packages/server/src/oauth/middleware.ts @@ -2,8 +2,7 @@ import { OperationOutcomeError, createReference, unauthorized } from '@medplum/c import { ClientApplication, Login, Project, ProjectMembership, Reference } from '@medplum/fhirtypes'; import { NextFunction, Request, Response } from 'express'; import { IncomingMessage } from 'http'; -import { AuthenticatedRequestContext, getRequestContext, requestContextStore } from '../context'; -import { getRepoForLogin } from '../fhir/accesspolicy'; +import { AuthenticatedRequestContext, getRequestContext } from '../context'; import { getSystemRepo } from '../fhir/repo'; import { getClientApplicationMembership, getLoginForAccessToken, timingSafeEqualStr } from './utils'; @@ -14,64 +13,59 @@ export interface AuthState { accessToken?: string; } -export function authenticateRequest(req: Request, res: Response, next: NextFunction): Promise { - return authenticateTokenImpl(req) - .then(async ({ login, project, membership, accessToken }) => { - const ctx = getRequestContext(); - const repo = await getRepoForLogin(login, membership, project, isExtendedMode(req)); - requestContextStore.run(new AuthenticatedRequestContext(ctx, login, project, membership, repo, accessToken), () => - next() - ); - }) - .catch(next); +export function authenticateRequest(req: Request, res: Response, next: NextFunction): void { + const ctx = getRequestContext(); + if (ctx instanceof AuthenticatedRequestContext) { + next(); + } else { + next(new OperationOutcomeError(unauthorized)); + } } -export async function authenticateTokenImpl(req: IncomingMessage): Promise { - const [tokenType, token] = req.headers.authorization?.split(' ') ?? []; +export async function authenticateTokenImpl(req: IncomingMessage): Promise { + const authHeader = req.headers.authorization; + if (!authHeader) { + return undefined; + } + + const [tokenType, token] = authHeader.split(' '); if (!tokenType || !token) { - throw new OperationOutcomeError(unauthorized); + return undefined; } if (tokenType === 'Bearer') { - return authenticateBearerToken(req, token); + return getLoginForAccessToken(token); } + if (tokenType === 'Basic') { return authenticateBasicAuth(req, token); } - throw new OperationOutcomeError(unauthorized); -} -function authenticateBearerToken(req: IncomingMessage, token: string): Promise { - return getLoginForAccessToken(token).catch(() => { - throw new OperationOutcomeError(unauthorized); - }); + return undefined; } -async function authenticateBasicAuth(req: IncomingMessage, token: string): Promise { +async function authenticateBasicAuth(req: IncomingMessage, token: string): Promise { const credentials = Buffer.from(token, 'base64').toString('ascii'); const [username, password] = credentials.split(':'); if (!username || !password) { - throw new OperationOutcomeError(unauthorized); + return undefined; } const systemRepo = getSystemRepo(); - let client = undefined; + let client: ClientApplication; try { client = await systemRepo.readResource('ClientApplication', username); } catch (err) { - throw new OperationOutcomeError(unauthorized); - } - if (!client) { - throw new OperationOutcomeError(unauthorized); + return undefined; } if (!timingSafeEqualStr(client.secret as string, password)) { - throw new OperationOutcomeError(unauthorized); + return undefined; } const membership = await getClientApplicationMembership(client); if (!membership) { - throw new OperationOutcomeError(unauthorized); + return undefined; } const project = await systemRepo.readReference(membership.project as Reference); @@ -85,6 +79,6 @@ async function authenticateBasicAuth(req: IncomingMessage, token: string): Promi return { login, project, membership }; } -function isExtendedMode(req: Request): boolean { +export function isExtendedMode(req: Request): boolean { return req.headers['x-medplum'] === 'extended'; } diff --git a/packages/server/src/oauth/routes.ts b/packages/server/src/oauth/routes.ts index 246bcbe138..ceaa7999c9 100644 --- a/packages/server/src/oauth/routes.ts +++ b/packages/server/src/oauth/routes.ts @@ -1,6 +1,5 @@ import cookieParser from 'cookie-parser'; import { Router } from 'express'; -import { getRateLimiter } from '../ratelimit'; import { authorizeGetHandler, authorizePostHandler } from './authorize'; import { logoutHandler } from './logout'; import { authenticateRequest } from './middleware'; @@ -8,7 +7,6 @@ import { tokenHandler } from './token'; import { userInfoHandler } from './userinfo'; export const oauthRouter = Router(); -oauthRouter.use(getRateLimiter()); oauthRouter.get('/authorize', cookieParser(), authorizeGetHandler); oauthRouter.post('/authorize', cookieParser(), authorizePostHandler); oauthRouter.post('/token', tokenHandler); diff --git a/packages/server/src/oauth/utils.ts b/packages/server/src/oauth/utils.ts index 1e33aa0706..febac12823 100644 --- a/packages/server/src/oauth/utils.ts +++ b/packages/server/src/oauth/utils.ts @@ -11,7 +11,6 @@ import { ProfileResource, resolveId, tooManyRequests, - unauthorized, } from '@medplum/core'; import { AccessPolicy, @@ -792,8 +791,14 @@ export async function verifyMultipleMatchingException( * @param accessToken - The access token as provided by the client. * @returns On success, returns the login, membership, and project. On failure, throws an error. */ -export async function getLoginForAccessToken(accessToken: string): Promise { - const verifyResult = await verifyJwt(accessToken); +export async function getLoginForAccessToken(accessToken: string): Promise { + let verifyResult; + try { + verifyResult = await verifyJwt(accessToken); + } catch (err) { + return undefined; + } + const claims = verifyResult.payload as MedplumAccessTokenClaims; const systemRepo = getSystemRepo(); @@ -801,11 +806,11 @@ export async function getLoginForAccessToken(accessToken: string): Promise('Login', claims.login_id); } catch (err) { - throw new OperationOutcomeError(unauthorized); + return undefined; } if (!login?.membership || login.revoked) { - throw new OperationOutcomeError(unauthorized); + return undefined; } const membership = await systemRepo.readReference(login.membership); diff --git a/packages/server/src/ratelimit.ts b/packages/server/src/ratelimit.ts index b1ab00d979..0e0c27a6b9 100644 --- a/packages/server/src/ratelimit.ts +++ b/packages/server/src/ratelimit.ts @@ -1,10 +1,15 @@ +import { tooManyRequests } from '@medplum/core'; +import { Request } from 'express'; import rateLimit, { MemoryStore, RateLimitRequestHandler } from 'express-rate-limit'; -import { OperationOutcomeError, tooManyRequests } from '@medplum/core'; +import { AuthenticatedRequestContext, getRequestContext } from './context'; /* * MemoryStore must be shutdown to cleanly stop the server. */ +const DEFAULT_RATE_LIMIT_PER_15_MINUTES = 15 * 60 * 1000; // 1000 requests per second +const DEFAULT_AUTH_RATE_LIMIT_PER_15_MINUTES = 600; + let handler: RateLimitRequestHandler | undefined = undefined; let store: MemoryStore | undefined = undefined; @@ -13,12 +18,10 @@ export function getRateLimiter(): RateLimitRequestHandler { store = new MemoryStore(); handler = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes - max: 600, // limit each IP to 600 requests per windowMs - validate: false, // Ignore X-Forwarded-For warnings + limit: getRateLimitForRequest, + validate: true, store, - handler: (_, __, next) => { - next(new OperationOutcomeError(tooManyRequests)); - }, + message: tooManyRequests, }); } return handler; @@ -31,3 +34,22 @@ export function closeRateLimiter(): void { handler = undefined; } } + +async function getRateLimitForRequest(req: Request): Promise { + // Check if this is an "auth URL" (e.g., /auth/login, /auth/register, /oauth2/token) + // These URLs have a different rate limit than the rest of the API + const authUrl = req.originalUrl.startsWith('/auth/') || req.originalUrl.startsWith('/oauth2/'); + + let limit = authUrl ? DEFAULT_AUTH_RATE_LIMIT_PER_15_MINUTES : DEFAULT_RATE_LIMIT_PER_15_MINUTES; + + const ctx = getRequestContext(); + if (ctx instanceof AuthenticatedRequestContext) { + const systemSettingName = authUrl ? 'authRateLimit' : 'rateLimit'; + const systemSetting = ctx.project.systemSetting?.find((s) => s.name === systemSettingName); + if (systemSetting?.valueInteger) { + limit = systemSetting.valueInteger; + } + } + + return limit; +} From 811a408766e0261d965a155c51235f50f797f003 Mon Sep 17 00:00:00 2001 From: Derrick Farris Date: Sun, 7 Apr 2024 18:54:11 -0700 Subject: [PATCH 49/52] perf(seed): run rebuilds in parallel, add perf logs --- packages/server/src/seed.ts | 32 +++++++++++++++ packages/server/src/seeds/searchparameters.ts | 4 +- .../server/src/seeds/structuredefinitions.ts | 41 +++++++++++-------- packages/server/src/seeds/valuesets.ts | 32 +++++++++------ 4 files changed, 79 insertions(+), 30 deletions(-) diff --git a/packages/server/src/seed.ts b/packages/server/src/seed.ts index a63f1f1f9c..38d60ab5e9 100644 --- a/packages/server/src/seed.ts +++ b/packages/server/src/seed.ts @@ -16,6 +16,9 @@ export async function seedDatabase(): Promise { return; } + performance.mark('Starting to seed'); + globalLogger.info('Seeding database...'); + const systemRepo = getSystemRepo(); const [firstName, lastName, email] = ['Medplum', 'Admin', 'admin@example.com']; @@ -70,9 +73,38 @@ export async function seedDatabase(): Promise { admin: true, }); + globalLogger.info('Rebuilding system resources...'); + performance.mark('Starting rebuilds'); + + performance.mark('Starting rebuildR4StructureDefinitions'); await rebuildR4StructureDefinitions(); + const sdStats = performance.measure( + 'Finished rebuildR4StructureDefinitions', + 'Starting rebuildR4StructureDefinitions' + ); + globalLogger.info('Finished rebuildR4StructureDefinitions', { + duration: `${Math.ceil(sdStats.duration)} ms`, + }); + + performance.mark('Starting rebuildR4ValueSets'); await rebuildR4ValueSets(); + const valueSetsStats = performance.measure('Finished rebuildR4ValueSets', 'Starting rebuildR4ValueSets'); + globalLogger.info('Finished rebuildR4ValueSets', { duration: `${Math.ceil(valueSetsStats.duration)} ms` }); + + performance.mark('Starting rebuildR4SearchParameters'); await rebuildR4SearchParameters(); + const searchParamsStats = performance.measure( + 'Finished rebuildR4SearchParameters', + 'Starting rebuildR4SearchParameters' + ); + globalLogger.info('Finished rebuildR4SearchParameters', { + duration: `${Math.ceil(searchParamsStats.duration)} ms`, + }); + + const rebuildStats = performance.measure('Finished rebuilds', 'Starting rebuilds'); + globalLogger.info('Finished rebuilds', { duration: `${Math.ceil(rebuildStats.duration)} ms` }); + const seedingStats = performance.measure('Finished seeding', 'Starting to seed'); + globalLogger.info('Finished seeding', { duration: `${Math.ceil(seedingStats.duration)} ms` }); } /** diff --git a/packages/server/src/seeds/searchparameters.ts b/packages/server/src/seeds/searchparameters.ts index 90496b08ef..506e125ab4 100644 --- a/packages/server/src/seeds/searchparameters.ts +++ b/packages/server/src/seeds/searchparameters.ts @@ -14,11 +14,13 @@ export async function rebuildR4SearchParameters(): Promise { const systemRepo = getSystemRepo(); + const promises = []; for (const filename of SEARCH_PARAMETER_BUNDLE_FILES) { for (const entry of readJson(filename).entry as BundleEntry[]) { - await createParameter(systemRepo, entry.resource as SearchParameter); + promises.push(createParameter(systemRepo, entry.resource as SearchParameter)); } } + await Promise.all(promises); } async function createParameter(systemRepo: Repository, param: SearchParameter): Promise { diff --git a/packages/server/src/seeds/structuredefinitions.ts b/packages/server/src/seeds/structuredefinitions.ts index a5595d78a1..4eb89df68f 100644 --- a/packages/server/src/seeds/structuredefinitions.ts +++ b/packages/server/src/seeds/structuredefinitions.ts @@ -13,32 +13,39 @@ export async function rebuildR4StructureDefinitions(): Promise { await client.query(`DELETE FROM "StructureDefinition" WHERE "projectId" = $1`, [r4ProjectId]); 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); + await Promise.all([ + createStructureDefinitionsForBundle(systemRepo, readJson('fhir/r4/profiles-resources.json') as Bundle), + createStructureDefinitionsForBundle(systemRepo, readJson('fhir/r4/profiles-medplum.json') as Bundle), + createStructureDefinitionsForBundle(systemRepo, readJson('fhir/r4/profiles-others.json') as Bundle), + ]); } async function createStructureDefinitionsForBundle( systemRepo: Repository, structureDefinitions: Bundle ): Promise { + const promises = []; for (const entry of structureDefinitions.entry as BundleEntry[]) { const resource = entry.resource as Resource; - if (resource.resourceType === 'StructureDefinition' && resource.name) { - globalLogger.debug('StructureDefinition: ' + resource.name); - const result = await systemRepo.createResource({ - ...resource, - meta: { - ...resource.meta, - project: r4ProjectId, - lastUpdated: undefined, - versionId: undefined, - }, - text: undefined, - differential: undefined, - }); - globalLogger.debug('Created: ' + result.id); + promises.push(createAndLogStructureDefinition(systemRepo, resource)); } } + await Promise.all(promises); +} + +async function createAndLogStructureDefinition(systemRepo: Repository, resource: StructureDefinition): Promise { + globalLogger.debug('[StructureDefinition] creation started: ' + resource.name); + const result = await systemRepo.createResource({ + ...resource, + meta: { + ...resource.meta, + project: r4ProjectId, + lastUpdated: undefined, + versionId: undefined, + }, + text: undefined, + differential: undefined, + }); + globalLogger.debug(`[StructureDefinition] creation finished: ${result.name} - ID: ${result.id}`); } diff --git a/packages/server/src/seeds/valuesets.ts b/packages/server/src/seeds/valuesets.ts index d6397f9e98..73ceec3f5e 100644 --- a/packages/server/src/seeds/valuesets.ts +++ b/packages/server/src/seeds/valuesets.ts @@ -8,26 +8,32 @@ import { r4ProjectId } from '../seed'; * Imports all built-in ValueSets and CodeSystems into the database. */ export async function rebuildR4ValueSets(): Promise { + performance.mark('Start rebuildR4ValueSets'); const systemRepo = getSystemRepo(); 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; + const promises = []; for (const entry of bundle.entry as BundleEntry[]) { - const resource = entry.resource as CodeSystem | ValueSet; - await deleteExisting(systemRepo, resource, r4ProjectId); - await systemRepo.createResource({ - ...resource, - meta: { - ...resource.meta, - project: r4ProjectId, - lastUpdated: undefined, - versionId: undefined, - }, - }); + promises.push(overwriteResource(systemRepo, entry.resource as CodeSystem | ValueSet)); } + await Promise.all(promises); } } +async function overwriteResource(systemRepo: Repository, resource: CodeSystem | ValueSet): Promise { + await deleteExisting(systemRepo, resource, r4ProjectId); + await systemRepo.createResource({ + ...resource, + meta: { + ...resource.meta, + project: r4ProjectId, + lastUpdated: undefined, + versionId: undefined, + }, + }); +} + async function deleteExisting( systemRepo: Repository, resource: CodeSystem | ValueSet, @@ -40,10 +46,12 @@ async function deleteExisting( { code: '_project', operator: Operator.EQUALS, value: projectId }, ], }); + const promises = []; if (bundle.entry && bundle.entry.length > 0) { for (const entry of bundle.entry) { const existing = entry.resource as CodeSystem | ValueSet; - await systemRepo.deleteResource(existing.resourceType, existing.id as string); + promises.push(systemRepo.deleteResource(existing.resourceType, existing.id as string)); } } + await Promise.all(promises); } From 1a5dd9b30f1ca698e12ca2d272685f9b540b7ec0 Mon Sep 17 00:00:00 2001 From: Derrick Farris Date: Sun, 7 Apr 2024 20:27:39 -0700 Subject: [PATCH 50/52] cleanup: remove stray performance.mark --- packages/server/src/seeds/valuesets.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/server/src/seeds/valuesets.ts b/packages/server/src/seeds/valuesets.ts index 73ceec3f5e..d52d2a19cc 100644 --- a/packages/server/src/seeds/valuesets.ts +++ b/packages/server/src/seeds/valuesets.ts @@ -8,7 +8,6 @@ import { r4ProjectId } from '../seed'; * Imports all built-in ValueSets and CodeSystems into the database. */ export async function rebuildR4ValueSets(): Promise { - performance.mark('Start rebuildR4ValueSets'); const systemRepo = getSystemRepo(); const files = ['v2-tables.json', 'v3-codesystems.json', 'valuesets.json', 'valuesets-medplum.json']; for (const file of files) { From 9ded4bf21ef995d10a0db73f93e0e6d57997600d Mon Sep 17 00:00:00 2001 From: Derrick Farris Date: Mon, 8 Apr 2024 17:09:38 -0700 Subject: [PATCH 51/52] feat(server): add both `serial` and `parallel` modes for seed --- packages/server/src/seed.ts | 15 ++++-- packages/server/src/seeds/common.ts | 16 +++++++ packages/server/src/seeds/searchparameters.ts | 23 ++++++--- .../server/src/seeds/structuredefinitions.ts | 35 +++++++++++--- packages/server/src/seeds/valuesets.ts | 47 ++++++++++++++----- 5 files changed, 106 insertions(+), 30 deletions(-) create mode 100644 packages/server/src/seeds/common.ts diff --git a/packages/server/src/seed.ts b/packages/server/src/seed.ts index 38d60ab5e9..18016632e2 100644 --- a/packages/server/src/seed.ts +++ b/packages/server/src/seed.ts @@ -4,13 +4,20 @@ import { NIL as nullUuid, v5 } from 'uuid'; import { bcryptHashPassword } from './auth/utils'; import { getSystemRepo } from './fhir/repo'; import { globalLogger } from './logger'; +import { RebuildOptions } from './seeds/common'; import { rebuildR4SearchParameters } from './seeds/searchparameters'; import { rebuildR4StructureDefinitions } from './seeds/structuredefinitions'; import { rebuildR4ValueSets } from './seeds/valuesets'; export const r4ProjectId = v5('R4', nullUuid); -export async function seedDatabase(): Promise { +/** + * Seeds the database with system resources. + * + * @param options - Optional options for seeding the database. + * @returns A Promise that resolves when seeding is done. + */ +export async function seedDatabase(options?: RebuildOptions): Promise { if (await isSeeded()) { globalLogger.info('Already seeded'); return; @@ -77,7 +84,7 @@ export async function seedDatabase(): Promise { performance.mark('Starting rebuilds'); performance.mark('Starting rebuildR4StructureDefinitions'); - await rebuildR4StructureDefinitions(); + await rebuildR4StructureDefinitions({ parallel: true, ...options }); const sdStats = performance.measure( 'Finished rebuildR4StructureDefinitions', 'Starting rebuildR4StructureDefinitions' @@ -87,12 +94,12 @@ export async function seedDatabase(): Promise { }); performance.mark('Starting rebuildR4ValueSets'); - await rebuildR4ValueSets(); + await rebuildR4ValueSets({ parallel: true, ...options }); const valueSetsStats = performance.measure('Finished rebuildR4ValueSets', 'Starting rebuildR4ValueSets'); globalLogger.info('Finished rebuildR4ValueSets', { duration: `${Math.ceil(valueSetsStats.duration)} ms` }); performance.mark('Starting rebuildR4SearchParameters'); - await rebuildR4SearchParameters(); + await rebuildR4SearchParameters({ parallel: true, ...options }); const searchParamsStats = performance.measure( 'Finished rebuildR4SearchParameters', 'Starting rebuildR4SearchParameters' diff --git a/packages/server/src/seeds/common.ts b/packages/server/src/seeds/common.ts new file mode 100644 index 0000000000..6d9db22550 --- /dev/null +++ b/packages/server/src/seeds/common.ts @@ -0,0 +1,16 @@ +export interface RebuildOptions { + /** + * Whether the resources should be created in parallel. + * + * **WARNING: Can be CPU intensive and/or clog up the connection pool.** + */ + parallel: boolean; +} + +const defaultOptions = { + parallel: false, +}; + +export function buildRebuildOptions(options?: Partial): RebuildOptions { + return { ...defaultOptions, ...options }; +} diff --git a/packages/server/src/seeds/searchparameters.ts b/packages/server/src/seeds/searchparameters.ts index 506e125ab4..c19659df70 100644 --- a/packages/server/src/seeds/searchparameters.ts +++ b/packages/server/src/seeds/searchparameters.ts @@ -4,23 +4,34 @@ import { getDatabasePool } from '../database'; import { Repository, getSystemRepo } from '../fhir/repo'; import { globalLogger } from '../logger'; import { r4ProjectId } from '../seed'; +import { RebuildOptions, buildRebuildOptions } from './common'; /** * Creates all SearchParameter resources. + * @param options - Optional options for how rebuild should be done. */ -export async function rebuildR4SearchParameters(): Promise { +export async function rebuildR4SearchParameters(options?: Partial): Promise { + const finalOptions = buildRebuildOptions(options); const client = getDatabasePool(); await client.query('DELETE FROM "SearchParameter" WHERE "projectId" = $1', [r4ProjectId]); const systemRepo = getSystemRepo(); - const promises = []; - for (const filename of SEARCH_PARAMETER_BUNDLE_FILES) { - for (const entry of readJson(filename).entry as BundleEntry[]) { - promises.push(createParameter(systemRepo, entry.resource as SearchParameter)); + if (finalOptions.parallel) { + const promises = []; + for (const filename of SEARCH_PARAMETER_BUNDLE_FILES) { + for (const entry of readJson(filename).entry as BundleEntry[]) { + promises.push(createParameter(systemRepo, entry.resource as SearchParameter)); + } + } + await Promise.all(promises); + } else { + for (const filename of SEARCH_PARAMETER_BUNDLE_FILES) { + for (const entry of readJson(filename).entry as BundleEntry[]) { + await createParameter(systemRepo, entry.resource as SearchParameter); + } } } - await Promise.all(promises); } async function createParameter(systemRepo: Repository, param: SearchParameter): Promise { diff --git a/packages/server/src/seeds/structuredefinitions.ts b/packages/server/src/seeds/structuredefinitions.ts index 4eb89df68f..9baa37112d 100644 --- a/packages/server/src/seeds/structuredefinitions.ts +++ b/packages/server/src/seeds/structuredefinitions.ts @@ -4,23 +4,32 @@ import { getDatabasePool } from '../database'; import { Repository, getSystemRepo } from '../fhir/repo'; import { globalLogger } from '../logger'; import { r4ProjectId } from '../seed'; +import { RebuildOptions, buildRebuildOptions } from './common'; /** * Creates all StructureDefinition resources. + * @param options - Optional options for how rebuild should be done. */ -export async function rebuildR4StructureDefinitions(): Promise { +export async function rebuildR4StructureDefinitions(options?: Partial): Promise { + const finalOptions = buildRebuildOptions(options) as RebuildOptions; const client = getDatabasePool(); await client.query(`DELETE FROM "StructureDefinition" WHERE "projectId" = $1`, [r4ProjectId]); const systemRepo = getSystemRepo(); - await Promise.all([ - createStructureDefinitionsForBundle(systemRepo, readJson('fhir/r4/profiles-resources.json') as Bundle), - createStructureDefinitionsForBundle(systemRepo, readJson('fhir/r4/profiles-medplum.json') as Bundle), - createStructureDefinitionsForBundle(systemRepo, readJson('fhir/r4/profiles-others.json') as Bundle), - ]); + if (finalOptions.parallel) { + await Promise.all([ + createStructureDefinitionsForBundleParallel(systemRepo, readJson('fhir/r4/profiles-resources.json') as Bundle), + createStructureDefinitionsForBundleParallel(systemRepo, readJson('fhir/r4/profiles-medplum.json') as Bundle), + createStructureDefinitionsForBundleParallel(systemRepo, readJson('fhir/r4/profiles-others.json') as Bundle), + ]); + } else { + await createStructureDefinitionsForBundleSerial(systemRepo, readJson('fhir/r4/profiles-resources.json') as Bundle); + await createStructureDefinitionsForBundleSerial(systemRepo, readJson('fhir/r4/profiles-medplum.json') as Bundle); + await createStructureDefinitionsForBundleSerial(systemRepo, readJson('fhir/r4/profiles-others.json') as Bundle); + } } -async function createStructureDefinitionsForBundle( +async function createStructureDefinitionsForBundleParallel( systemRepo: Repository, structureDefinitions: Bundle ): Promise { @@ -34,6 +43,18 @@ async function createStructureDefinitionsForBundle( await Promise.all(promises); } +async function createStructureDefinitionsForBundleSerial( + systemRepo: Repository, + structureDefinitions: Bundle +): Promise { + for (const entry of structureDefinitions.entry as BundleEntry[]) { + const resource = entry.resource as Resource; + if (resource.resourceType === 'StructureDefinition' && resource.name) { + await createAndLogStructureDefinition(systemRepo, resource); + } + } +} + async function createAndLogStructureDefinition(systemRepo: Repository, resource: StructureDefinition): Promise { globalLogger.debug('[StructureDefinition] creation started: ' + resource.name); const result = await systemRepo.createResource({ diff --git a/packages/server/src/seeds/valuesets.ts b/packages/server/src/seeds/valuesets.ts index d52d2a19cc..aa6792cccb 100644 --- a/packages/server/src/seeds/valuesets.ts +++ b/packages/server/src/seeds/valuesets.ts @@ -3,25 +3,38 @@ import { readJson } from '@medplum/definitions'; import { Bundle, BundleEntry, CodeSystem, ValueSet } from '@medplum/fhirtypes'; import { Repository, getSystemRepo } from '../fhir/repo'; import { r4ProjectId } from '../seed'; +import { RebuildOptions, buildRebuildOptions } from './common'; /** * Imports all built-in ValueSets and CodeSystems into the database. + * @param options - Optional options for how rebuild should be done. */ -export async function rebuildR4ValueSets(): Promise { +export async function rebuildR4ValueSets(options?: Partial): Promise { + const finalOptions = buildRebuildOptions(options) as RebuildOptions; const systemRepo = getSystemRepo(); 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; - const promises = []; - for (const entry of bundle.entry as BundleEntry[]) { - promises.push(overwriteResource(systemRepo, entry.resource as CodeSystem | ValueSet)); + if (finalOptions.parallel) { + const promises = []; + for (const entry of bundle.entry as BundleEntry[]) { + promises.push(overwriteResource(systemRepo, entry.resource as CodeSystem | ValueSet, finalOptions)); + } + await Promise.all(promises); + } else { + for (const entry of bundle.entry as BundleEntry[]) { + await overwriteResource(systemRepo, entry.resource as CodeSystem | ValueSet, finalOptions); + } } - await Promise.all(promises); } } -async function overwriteResource(systemRepo: Repository, resource: CodeSystem | ValueSet): Promise { - await deleteExisting(systemRepo, resource, r4ProjectId); +async function overwriteResource( + systemRepo: Repository, + resource: CodeSystem | ValueSet, + options: RebuildOptions +): Promise { + await deleteExisting(systemRepo, resource, r4ProjectId, options); await systemRepo.createResource({ ...resource, meta: { @@ -36,7 +49,8 @@ async function overwriteResource(systemRepo: Repository, resource: CodeSystem | async function deleteExisting( systemRepo: Repository, resource: CodeSystem | ValueSet, - projectId: string + projectId: string, + options: RebuildOptions ): Promise { const bundle = await systemRepo.search({ resourceType: resource.resourceType, @@ -45,12 +59,19 @@ async function deleteExisting( { code: '_project', operator: Operator.EQUALS, value: projectId }, ], }); - const promises = []; if (bundle.entry && bundle.entry.length > 0) { - for (const entry of bundle.entry) { - const existing = entry.resource as CodeSystem | ValueSet; - promises.push(systemRepo.deleteResource(existing.resourceType, existing.id as string)); + if (options.parallel) { + const promises = []; + for (const entry of bundle.entry) { + const existing = entry.resource as CodeSystem | ValueSet; + promises.push(systemRepo.deleteResource(existing.resourceType, existing.id as string)); + } + await Promise.all(promises); + } else { + for (const entry of bundle.entry) { + const existing = entry.resource as CodeSystem | ValueSet; + await systemRepo.deleteResource(existing.resourceType, existing.id as string); + } } } - await Promise.all(promises); } From 000a017616ccc05c83bb4e784365b20487a1cb27 Mon Sep 17 00:00:00 2001 From: Derrick Farris Date: Mon, 22 Apr 2024 17:05:00 -0700 Subject: [PATCH 52/52] test(seed): split out seed tests, test in parallel to main tests --- .github/workflows/build.yml | 14 +++ .gitignore | 1 + docker-compose.seed.yml | 15 +++ docker-compose.yml | 13 +++ package-lock.json | 102 ++++++++++++++++++ package.json | 1 + packages/server/jest.config.json | 13 --- packages/server/jest.config.ts | 15 +++ packages/server/jest.seed.config.ts | 8 ++ packages/server/package.json | 4 +- .../server/seed-tests/seed-serial.test.ts | 42 ++++++++ .../server/{src => seed-tests}/seed.test.ts | 12 +-- packages/server/src/config.ts | 2 +- packages/server/tsconfig.json | 2 +- packages/server/turbo.json | 16 +++ scripts/test.sh | 21 +++- 16 files changed, 254 insertions(+), 27 deletions(-) create mode 100644 docker-compose.seed.yml delete mode 100644 packages/server/jest.config.json create mode 100644 packages/server/jest.config.ts create mode 100644 packages/server/jest.seed.config.ts create mode 100644 packages/server/seed-tests/seed-serial.test.ts rename packages/server/{src => seed-tests}/seed.test.ts (78%) create mode 100644 packages/server/turbo.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3b37d3a843..16317f7d0f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -178,6 +178,19 @@ jobs: --health-retries 5 ports: - 5432/tcp + postgres-seed: + image: postgres:${{ matrix.pg-version }} + env: + POSTGRES_DB: medplum_test + POSTGRES_USER: medplum + POSTGRES_PASSWORD: medplum + options: >- + --health-cmd pg_isready + --health-retries 5 + --health-interval 10s + --health-timeout 5s + ports: + - 5433/tcp:5432/tcp redis: image: redis:${{ matrix.redis-version }} options: >- @@ -225,6 +238,7 @@ jobs: env: POSTGRES_HOST: localhost POSTGRES_PORT: ${{ job.services.postgres.ports[5432] }} + POSTGRES_SEED_PORT: ${{ job.services.postgres.ports[5433] }} REDIS_PASSWORD_DISABLED_IN_TESTS: 1 - name: Upload code coverage if: ${{ matrix.node-version == 20 && matrix.pg-version == 14 && matrix.redis-version == 7 }} diff --git a/.gitignore b/.gitignore index 3e6907b917..7423c47f81 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,7 @@ packages/react/build-storybook.log # Jest code coverage coverage/ +packages/server/coverage-seed/ # TypeScript incremental build tsconfig.tsbuildinfo diff --git a/docker-compose.seed.yml b/docker-compose.seed.yml new file mode 100644 index 0000000000..17ecc8af88 --- /dev/null +++ b/docker-compose.seed.yml @@ -0,0 +1,15 @@ +version: '3.7' +services: + postgres-seed: + image: postgres:12 + restart: always + environment: + - POSTGRES_USER=medplum + - POSTGRES_PASSWORD=medplum + + volumes: + - ./postgres/postgres.conf:/usr/local/etc/postgres/postgres.conf + - ./postgres/:/docker-entrypoint-initdb.d/ + command: postgres -c config_file=/usr/local/etc/postgres/postgres.conf + ports: + - '5433:5432' diff --git a/docker-compose.yml b/docker-compose.yml index 6b072b5578..7726992e47 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,6 +17,19 @@ services: command: postgres -c config_file=/usr/local/etc/postgres/postgres.conf ports: - '5432:5432' + postgres-seed: + image: postgres:12 + restart: always + environment: + - POSTGRES_USER=medplum + - POSTGRES_PASSWORD=medplum + + volumes: + - ./postgres/postgres.conf:/usr/local/etc/postgres/postgres.conf + - ./postgres/:/docker-entrypoint-initdb.d/ + command: postgres -c config_file=/usr/local/etc/postgres/postgres.conf + ports: + - '5433:5432' redis: image: redis:7 restart: always diff --git a/package-lock.json b/package-lock.json index b5e3cbf0ce..7ad4d653ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@types/node": "20.12.7", "babel-jest": "29.7.0", "babel-preset-vite": "1.1.3", + "concurrently": "8.2.2", "cross-env": "7.0.3", "danger": "11.3.1", "esbuild": "0.20.2", @@ -26164,6 +26165,92 @@ "typedarray": "^0.0.6" } }, + "node_modules/concurrently": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", + "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.2", + "date-fns": "^2.30.0", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "spawn-command": "0.0.2", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": "^14.13.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/ansi-styles": { + "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" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/chalk": { + "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" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/chalk/node_modules/supports-color": { + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/concurrently/node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/config-chain": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", @@ -50331,6 +50418,15 @@ "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", "dev": true }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/sade": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", @@ -51752,6 +51848,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/spawn-command": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", + "dev": true + }, "node_modules/spawn-please": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/spawn-please/-/spawn-please-2.0.2.tgz", diff --git a/package.json b/package.json index f186c49fcd..fc1b07a932 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@types/node": "20.12.7", "babel-jest": "29.7.0", "babel-preset-vite": "1.1.3", + "concurrently": "8.2.2", "cross-env": "7.0.3", "danger": "11.3.1", "esbuild": "0.20.2", diff --git a/packages/server/jest.config.json b/packages/server/jest.config.json deleted file mode 100644 index 8e2a0d7497..0000000000 --- a/packages/server/jest.config.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "testEnvironment": "node", - "testTimeout": 600000, - "testSequencer": "/jest.sequencer.js", - "transform": { - "^.+\\.(js|jsx|ts|tsx)$": "babel-jest" - }, - "moduleFileExtensions": ["ts", "js", "json", "node"], - "testMatch": ["**/src/**/*.test.ts"], - "coverageDirectory": "coverage", - "coverageReporters": ["json", "text"], - "collectCoverageFrom": ["**/src/**/*", "!**/src/__mocks__/**/*.ts", "!**/src/migrations/**/*.ts"] -} diff --git a/packages/server/jest.config.ts b/packages/server/jest.config.ts new file mode 100644 index 0000000000..9c24d5533e --- /dev/null +++ b/packages/server/jest.config.ts @@ -0,0 +1,15 @@ +import type { Config } from 'jest'; + +export default { + testEnvironment: 'node', + testTimeout: 600000, + testSequencer: '/jest.sequencer.js', + transform: { + '^.+\\.(js|jsx|ts|tsx)$': 'babel-jest', + }, + testMatch: ['/src/**/*.test.ts'], + moduleFileExtensions: ['ts', 'js', 'json', 'node'], + coverageDirectory: 'coverage', + coverageReporters: ['json', 'text'], + collectCoverageFrom: ['**/src/**/*', '!**/src/__mocks__/**/*.ts', '!**/src/migrations/**/*.ts'], +} satisfies Config; diff --git a/packages/server/jest.seed.config.ts b/packages/server/jest.seed.config.ts new file mode 100644 index 0000000000..b428e8b3d5 --- /dev/null +++ b/packages/server/jest.seed.config.ts @@ -0,0 +1,8 @@ +import type { Config } from 'jest'; +import defaultConfig from './jest.config'; + +export default { + ...defaultConfig, + testMatch: ['/seed-tests/**/*.test.ts'], + collectCoverageFrom: ['/seed-tests/**/*'], +} satisfies Config; diff --git a/packages/server/package.json b/packages/server/package.json index cfdc3739d6..4abc16f616 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -18,7 +18,9 @@ "clean": "rimraf dist", "dev": "ts-node-dev --poll --respawn --transpile-only --require ./src/otel/instrumentation.ts src/index.ts", "start": "node --require ./dist/otel/instrumentation.js dist/index.js", - "test": "jest" + "test:seed:serial": "jest seed-serial.test.ts --config jest.seed.config.ts --coverageDirectory \"/coverage-seed/serial\"", + "test:seed:parallel": "jest seed.test.ts --config jest.seed.config.ts --coverageDirectory \"/coverage-seed/parallel\"", + "test": "docker-compose -f ../../docker-compose.seed.yml up -d && npm run test:seed:parallel && jest" }, "dependencies": { "@aws-sdk/client-cloudwatch-logs": "3.554.0", diff --git a/packages/server/seed-tests/seed-serial.test.ts b/packages/server/seed-tests/seed-serial.test.ts new file mode 100644 index 0000000000..1e8500b6c2 --- /dev/null +++ b/packages/server/seed-tests/seed-serial.test.ts @@ -0,0 +1,42 @@ +import { Project } from '@medplum/fhirtypes'; +import { initAppServices, shutdownApp } from '../src/app'; +import { loadTestConfig } from '../src/config'; +import { getDatabasePool } from '../src/database'; +import { SelectQuery } from '../src/fhir/sql'; +import { seedDatabase } from '../src/seed'; +import { withTestContext } from '../src/test.setup'; + +describe('Seed', () => { + beforeAll(async () => { + console.log = jest.fn(); + + const config = await loadTestConfig(); + config.database.port = process.env['POSTGRES_SEED_PORT'] + ? Number.parseInt(process.env['POSTGRES_SEED_PORT'], 10) + : 5433; + return withTestContext(() => initAppServices(config)); + }); + + afterAll(async () => { + await shutdownApp(); + }); + + test('Seeder completes successfully -- serial version', async () => { + // First time, seeder should run + await seedDatabase({ parallel: false }); + + // Make sure the first project is a super admin + const rows = await new SelectQuery('Project') + .column('content') + .where('name', '=', 'Super Admin') + .execute(getDatabasePool()); + expect(rows.length).toBe(1); + + const project = JSON.parse(rows[0].content) as Project; + expect(project.superAdmin).toBe(true); + expect(project.strictMode).toBe(true); + + // Second time, seeder should silently ignore + await seedDatabase({ parallel: false }); + }, 240000); +}); diff --git a/packages/server/src/seed.test.ts b/packages/server/seed-tests/seed.test.ts similarity index 78% rename from packages/server/src/seed.test.ts rename to packages/server/seed-tests/seed.test.ts index 1f5d77ace9..129bd383d2 100644 --- a/packages/server/src/seed.test.ts +++ b/packages/server/seed-tests/seed.test.ts @@ -1,10 +1,10 @@ import { Project } from '@medplum/fhirtypes'; -import { initAppServices, shutdownApp } from './app'; -import { loadTestConfig } from './config'; -import { getDatabasePool } from './database'; -import { SelectQuery } from './fhir/sql'; -import { seedDatabase } from './seed'; -import { withTestContext } from './test.setup'; +import { initAppServices, shutdownApp } from '../src/app'; +import { loadTestConfig } from '../src/config'; +import { getDatabasePool } from '../src/database'; +import { SelectQuery } from '../src/fhir/sql'; +import { seedDatabase } from '../src/seed'; +import { withTestContext } from '../src/test.setup'; describe('Seed', () => { beforeAll(async () => { diff --git a/packages/server/src/config.ts b/packages/server/src/config.ts index dac56e7df0..76d5a43556 100644 --- a/packages/server/src/config.ts +++ b/packages/server/src/config.ts @@ -175,7 +175,7 @@ export async function loadTestConfig(): Promise { config.binaryStorage = 'file:' + mkdtempSync(join(tmpdir(), 'medplum-temp-storage')); config.allowedOrigins = undefined; config.database.host = process.env['POSTGRES_HOST'] ?? 'localhost'; - config.database.port = process.env['POSTGRES_PORT'] ? parseInt(process.env['POSTGRES_PORT'], 10) : 5432; + config.database.port = process.env['POSTGRES_PORT'] ? Number.parseInt(process.env['POSTGRES_PORT'], 10) : 5432; config.database.dbname = 'medplum_test'; config.redis.db = 7; // Select logical DB `7` so we don't collide with existing dev Redis cache. config.redis.password = process.env['REDIS_PASSWORD_DISABLED_IN_TESTS'] ? undefined : config.redis.password; diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index a5cb75c562..e53cd271e9 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -1,4 +1,4 @@ { "extends": "../../tsconfig.json", - "include": ["src/**/*.ts"] + "include": ["src/**/*.ts", "seed-tests/seed-serial.test.ts", "seed-tests/seed.test.ts"] } diff --git a/packages/server/turbo.json b/packages/server/turbo.json new file mode 100644 index 0000000000..d00fb4125e --- /dev/null +++ b/packages/server/turbo.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://turborepo.org/schema.json", + "extends": ["//"], + "pipeline": { + "test:seed:serial": { + "dependsOn": ["build"], + "outputs": ["coverage/**"], + "inputs": ["src/**/*.tsx", "src/**/*.ts"] + }, + "test:seed:parallel": { + "dependsOn": ["build"], + "outputs": ["coverage/**"], + "inputs": ["src/**/*.tsx", "src/**/*.ts"] + } + } +} diff --git a/scripts/test.sh b/scripts/test.sh index ae8f0a7084..c338b04f6e 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -7,19 +7,29 @@ set -e set -x # Set node options -export NODE_OPTIONS='--max-old-space-size=5120' +export NODE_OPTIONS='--max-old-space-size=8192' # Clear old code coverage data rm -rf coverage +rm -rf coverage-seed mkdir -p coverage/packages mkdir -p coverage/combined +mkdir -p coverage-seed/serial +mkdir -p coverage-seed/parallel -# Seed the database +# Testing production path of seeding the database # This is a special "test" which runs all of the seed logic, such as setting up structure definitions # On a normal developer machine, this is run only rarely when setting up a new database -# This test must be run first, and cannot be run concurrently with other tests -time npx turbo run test --filter=./packages/server -- seed.test.ts --coverage -cp "packages/server/coverage/coverage-final.json" "coverage/packages/coverage-server-seed.json" +# We execute this in parallel with the main line of tests +{ + time npx turbo run test:seed:serial --filter=./packages/server -- --coverage + cp "packages/server/coverage-seed/serial/coverage-final.json" "coverage/packages/coverage-server-seed-serial.json" +} & + +# Seed the database before testing +# This is the parallel implementation so it's faster +time npx turbo run test:seed:parallel --filter=./packages/server -- --coverage +cp "packages/server/coverage-seed/parallel/coverage-final.json" "coverage/packages/coverage-server-seed-parallel.json" # Test # Run them separately because code coverage is resource intensive @@ -36,6 +46,7 @@ for dir in `ls examples`; do fi done +wait # Combine test coverage PACKAGES=(