From 9321bf49c61b5aae6fba4c06fbd527203637b2e8 Mon Sep 17 00:00:00 2001 From: bhansell1 Date: Thu, 12 Mar 2026 14:55:43 +0000 Subject: [PATCH 1/8] CCM-7942: add ProofRequested event to event-schemas package --- packages/event-schemas/README.md | 4 + .../__tests__/events/proof-requested.test.ts | 26 ++++ .../json-schemas/proof-requested.test.ts | 33 +++++ .../examples/ProofRequested/v1/email.json | 25 ++++ .../examples/ProofRequested/v1/nhsapp.json | 24 ++++ .../examples/ProofRequested/v1/sms.json | 24 ++++ .../schemas/ProofRequested/v1.json | 127 ++++++++++++++++++ .../scripts/generate-json-schemas.ts | 8 ++ packages/event-schemas/src/events/index.ts | 7 +- .../src/events/proof-requested.ts | 15 +++ packages/event-schemas/src/proof-request.ts | 33 +++++ 11 files changed, 323 insertions(+), 3 deletions(-) create mode 100644 packages/event-schemas/__tests__/events/proof-requested.test.ts create mode 100644 packages/event-schemas/__tests__/json-schemas/proof-requested.test.ts create mode 100644 packages/event-schemas/examples/ProofRequested/v1/email.json create mode 100644 packages/event-schemas/examples/ProofRequested/v1/nhsapp.json create mode 100644 packages/event-schemas/examples/ProofRequested/v1/sms.json create mode 100644 packages/event-schemas/schemas/ProofRequested/v1.json create mode 100644 packages/event-schemas/src/events/proof-requested.ts create mode 100644 packages/event-schemas/src/proof-request.ts diff --git a/packages/event-schemas/README.md b/packages/event-schemas/README.md index 11e021206..b64b044f9 100644 --- a/packages/event-schemas/README.md +++ b/packages/event-schemas/README.md @@ -19,6 +19,10 @@ Then run `npm install @nhsdigital/nhs-notify-event-schemas-template-management` ## Events +- `ProofRequested` +- `RoutingConfigCompleted` +- `RoutingConfigDeleted` +- `RoutingConfigDrafted` - `TemplateCompleted` - `TemplateDeleted` - `TemplateDrafted` diff --git a/packages/event-schemas/__tests__/events/proof-requested.test.ts b/packages/event-schemas/__tests__/events/proof-requested.test.ts new file mode 100644 index 000000000..ef72fb968 --- /dev/null +++ b/packages/event-schemas/__tests__/events/proof-requested.test.ts @@ -0,0 +1,26 @@ +/* eslint-disable security/detect-non-literal-fs-filename */ + +import fs from 'node:fs'; +import path from 'node:path'; +import { $ProofRequestedEventV1 } from '../../src/events/proof-requested'; + +const examplesDir = path.resolve(__dirname, '../../examples/ProofRequested/v1'); + +describe('ProofRequestedEventV1 Zod schema', () => { + it.each(fs.readdirSync(examplesDir))( + 'parses sample event %s without errors', + (filename) => { + const event = JSON.parse( + fs.readFileSync(path.join(examplesDir, filename), 'utf8') + ); + + const result = $ProofRequestedEventV1.safeParse(event); + + if (!result.success) { + console.log(result.error); + } + + expect(result.success).toBe(true); + } + ); +}); diff --git a/packages/event-schemas/__tests__/json-schemas/proof-requested.test.ts b/packages/event-schemas/__tests__/json-schemas/proof-requested.test.ts new file mode 100644 index 000000000..aac9ab0bb --- /dev/null +++ b/packages/event-schemas/__tests__/json-schemas/proof-requested.test.ts @@ -0,0 +1,33 @@ +/* eslint-disable security/detect-non-literal-fs-filename */ + +import fs from 'node:fs'; +import path from 'node:path'; +import { Ajv2020 } from 'ajv/dist/2020'; +import addFormats from 'ajv-formats'; + +import ProofRequestedEventV1Schema from '../../schemas/ProofRequested/v1.json'; + +const examplesDir = path.resolve(__dirname, '../../examples/ProofRequested/v1'); + +describe('ProofRequestedEventV1 JSON schema', () => { + it.each(fs.readdirSync(examplesDir))( + 'parses sample event %s without errors', + (filename) => { + const event = JSON.parse( + fs.readFileSync(path.join(examplesDir, filename), 'utf8') + ); + + const ajv = new Ajv2020(); + addFormats(ajv); + const validate = ajv.compile(ProofRequestedEventV1Schema); + + const valid = validate(event); + + if (!valid) { + console.log(validate.errors); + } + + expect(valid).toBe(true); + } + ); +}); diff --git a/packages/event-schemas/examples/ProofRequested/v1/email.json b/packages/event-schemas/examples/ProofRequested/v1/email.json new file mode 100644 index 000000000..63fa0783b --- /dev/null +++ b/packages/event-schemas/examples/ProofRequested/v1/email.json @@ -0,0 +1,25 @@ +{ + "data": { + "id": "c3d4e5f6-a7b8-4012-8def-123456789012", + "templateId": "8c7ae592-97cd-4900-897e-ef4794c8a745", + "templateType": "EMAIL", + "testPatientNhsNumber": "9000000009", + "contactDetails": { + "email": "test.patient@example.com" + }, + "personalisation": { + "firstName": "Jane", + "surgeryName": "Test Surgery" + } + }, + "datacontenttype": "application/json", + "dataschema": "https://notify.nhs.uk/events/schemas/ProofRequested/v1.json", + "dataschemaversion": "1.0.0", + "id": "b3c4d5e6-f7a8-9012-bcde-f12345678902", + "plane": "data", + "source": "//notify.nhs.uk/app/nhs-notify-template-management-prod/main", + "specversion": "1.0", + "subject": "c3d4e5f6-a7b8-4012-8def-123456789012", + "time": "2025-07-29T10:05:45.145Z", + "type": "uk.nhs.notify.template-management.ProofRequested.v1" +} diff --git a/packages/event-schemas/examples/ProofRequested/v1/nhsapp.json b/packages/event-schemas/examples/ProofRequested/v1/nhsapp.json new file mode 100644 index 000000000..063600877 --- /dev/null +++ b/packages/event-schemas/examples/ProofRequested/v1/nhsapp.json @@ -0,0 +1,24 @@ +{ + "data": { + "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "templateId": "3a58a370-75ab-4788-a75e-cd2572a68523", + "templateType": "NHS_APP", + "testPatientNhsNumber": "9000000009", + "personalisation": { + "firstName": "Jane", + "appointmentTime": "10:00", + "appointmentDate": "2025-08-01", + "surgeryName": "Test Surgery" + } + }, + "datacontenttype": "application/json", + "dataschema": "https://notify.nhs.uk/events/schemas/ProofRequested/v1.json", + "dataschemaversion": "1.0.0", + "id": "f1e2d3c4-b5a6-7890-fedc-ba0987654321", + "plane": "data", + "source": "//notify.nhs.uk/app/nhs-notify-template-management-prod/main", + "specversion": "1.0", + "subject": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "time": "2025-07-29T10:05:45.145Z", + "type": "uk.nhs.notify.template-management.ProofRequested.v1" +} diff --git a/packages/event-schemas/examples/ProofRequested/v1/sms.json b/packages/event-schemas/examples/ProofRequested/v1/sms.json new file mode 100644 index 000000000..a80d05afc --- /dev/null +++ b/packages/event-schemas/examples/ProofRequested/v1/sms.json @@ -0,0 +1,24 @@ +{ + "data": { + "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", + "templateId": "7b69c481-86bc-5899-b86f-de3683b79634", + "templateType": "SMS", + "testPatientNhsNumber": "9000000009", + "contactDetails": { + "sms": "07700900000" + }, + "personalisation": { + "firstName": "Jane" + } + }, + "datacontenttype": "application/json", + "dataschema": "https://notify.nhs.uk/events/schemas/ProofRequested/v1.json", + "dataschemaversion": "1.0.0", + "id": "a2b3c4d5-e6f7-8901-abcd-ef1234567891", + "plane": "data", + "source": "//notify.nhs.uk/app/nhs-notify-template-management-prod/main", + "specversion": "1.0", + "subject": "b2c3d4e5-f6a7-8901-bcde-f12345678901", + "time": "2025-07-29T10:05:45.145Z", + "type": "uk.nhs.notify.template-management.ProofRequested.v1" +} diff --git a/packages/event-schemas/schemas/ProofRequested/v1.json b/packages/event-schemas/schemas/ProofRequested/v1.json new file mode 100644 index 000000000..2b34a0437 --- /dev/null +++ b/packages/event-schemas/schemas/ProofRequested/v1.json @@ -0,0 +1,127 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "maxLength": 1000, + "description": "Unique ID for this event" + }, + "time": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z$", + "description": "Time the event was generated" + }, + "type": { + "type": "string", + "const": "uk.nhs.notify.template-management.ProofRequested.v1" + }, + "source": { + "type": "string", + "description": "Source of the event" + }, + "specversion": { + "type": "string", + "const": "1.0", + "description": "Version of the envelope event schema" + }, + "datacontenttype": { + "type": "string", + "const": "application/json", + "description": "Always application/json" + }, + "subject": { + "type": "string", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", + "description": "Unique identifier for the item in the event data" + }, + "dataschema": { + "type": "string", + "const": "https://notify.nhs.uk/events/schemas/ProofRequested/v1.json" + }, + "dataschemaversion": { + "type": "string", + "pattern": "^1\\..*" + }, + "plane": { + "type": "string", + "const": "data" + }, + "data": { + "$ref": "#/$defs/ProofRequestEventData" + } + }, + "required": [ + "id", + "time", + "type", + "source", + "specversion", + "datacontenttype", + "subject", + "dataschema", + "dataschemaversion", + "plane", + "data" + ], + "$defs": { + "ProofRequestEventData": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^[\\dA-Fa-f]{8}(?:-[\\dA-Fa-f]{4}){3}-[\\dA-Fa-f]{12}$", + "description": "Unique identifier of the proof request" + }, + "templateId": { + "type": "string", + "pattern": "^[\\dA-Fa-f]{8}(?:-[\\dA-Fa-f]{4}){3}-[\\dA-Fa-f]{12}$", + "description": "Unique identifier for the template being proofed" + }, + "templateType": { + "type": "string", + "enum": [ + "NHS_APP", + "SMS", + "EMAIL" + ], + "description": "Template type for the template being proofed" + }, + "testPatientNhsNumber": { + "type": "string", + "description": "NHS number of test patient to use in the proofing request" + }, + "contactDetails": { + "description": "Contact details to send the proof to", + "type": "object", + "properties": { + "sms": { + "type": "string" + }, + "email": { + "type": "string" + } + } + }, + "personalisation": { + "description": "Personalisation fields to use in the proof", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "id", + "templateId", + "templateType", + "testPatientNhsNumber" + ] + } + }, + "$id": "https://notify.nhs.uk/events/schemas/ProofRequested/v1.json" +} diff --git a/packages/event-schemas/scripts/generate-json-schemas.ts b/packages/event-schemas/scripts/generate-json-schemas.ts index 72ef759b5..ef5f6eab3 100644 --- a/packages/event-schemas/scripts/generate-json-schemas.ts +++ b/packages/event-schemas/scripts/generate-json-schemas.ts @@ -3,6 +3,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { + $ProofRequestedEventV1, $TemplateCompletedEventV1, $TemplateDeletedEventV1, $TemplateDraftedEventV1, @@ -62,6 +63,13 @@ function writeSchema( ); } +writeSchema( + 'ProofRequested', + $ProofRequestedEventV1, + '1', + 'https://notify.nhs.uk/events/schemas/ProofRequested/v1.json' +); + writeSchema( 'TemplateCompleted', $TemplateCompletedEventV1, diff --git a/packages/event-schemas/src/events/index.ts b/packages/event-schemas/src/events/index.ts index fe4096163..eb1cc3332 100644 --- a/packages/event-schemas/src/events/index.ts +++ b/packages/event-schemas/src/events/index.ts @@ -1,6 +1,7 @@ -export * from './template-completed'; -export * from './template-deleted'; -export * from './template-drafted'; +export * from './proof-requested'; export * from './routing-config-completed'; export * from './routing-config-deleted'; export * from './routing-config-drafted'; +export * from './template-completed'; +export * from './template-deleted'; +export * from './template-drafted'; diff --git a/packages/event-schemas/src/events/proof-requested.ts b/packages/event-schemas/src/events/proof-requested.ts new file mode 100644 index 000000000..a31fc0280 --- /dev/null +++ b/packages/event-schemas/src/events/proof-requested.ts @@ -0,0 +1,15 @@ +import { z } from 'zod'; +import { $NHSNotifyEventEnvelope } from '../event-envelope'; +import { $ProofRequestEventData } from '../proof-request'; + +export const $ProofRequestedEventV1 = $NHSNotifyEventEnvelope.extend({ + type: z.literal('uk.nhs.notify.template-management.ProofRequested.v1'), + dataschema: z.literal( + 'https://notify.nhs.uk/events/schemas/ProofRequested/v1.json' + ), + dataschemaversion: z.string().startsWith('1.'), + plane: z.literal('data'), + data: $ProofRequestEventData, +}); + +export type ProofRequestedEventV1 = z.infer; diff --git a/packages/event-schemas/src/proof-request.ts b/packages/event-schemas/src/proof-request.ts new file mode 100644 index 000000000..6cfbf2168 --- /dev/null +++ b/packages/event-schemas/src/proof-request.ts @@ -0,0 +1,33 @@ +import { z } from 'zod'; + +//eslint-disable-next-line security/detect-unsafe-regex +const UUID_REGEX = /^[\dA-Fa-f]{8}(?:-[\dA-Fa-f]{4}){3}-[\dA-Fa-f]{12}$/; + +export const $ProofRequestEventData = z + .object({ + id: z.string().regex(UUID_REGEX).meta({ + description: 'Unique identifier of the proof request', + }), + templateId: z.string().regex(UUID_REGEX).meta({ + description: 'Unique identifier for the template being proofed', + }), + templateType: z.enum(['NHS_APP', 'SMS', 'EMAIL']).meta({ + description: 'Template type for the template being proofed', + }), + testPatientNhsNumber: z.string().meta({ + description: 'NHS number of test patient to use in the proofing request', + }), + contactDetails: z + .object({ + sms: z.string().optional(), + email: z.string().optional(), + }) + .optional() + .meta({ description: 'Contact details to send the proof to' }), + personalisation: z.record(z.string(), z.string()).optional().meta({ + description: 'Personalisation fields to use in the proof', + }), + }) + .meta({ + id: 'ProofRequestEventData', + }); From aee97f7cbf819bfcfce208e34dcff8b70e2672dc Mon Sep 17 00:00:00 2001 From: bhansell1 Date: Thu, 12 Mar 2026 16:18:16 +0000 Subject: [PATCH 2/8] CCM-7942: publish ProofRequested event from event-publisher --- .../module_lambda_event_publisher.tf | 1 + .../__tests__/domain/event-builder.test.ts | 115 ++++++++++++++++++ lambdas/event-publisher/src/config.ts | 1 + lambdas/event-publisher/src/container.ts | 2 + .../src/domain/event-builder.ts | 53 +++++++- .../src/domain/input-schemas.ts | 3 + .../src/domain/output-schemas.ts | 10 +- 7 files changed, 179 insertions(+), 6 deletions(-) diff --git a/infrastructure/terraform/modules/backend-api/module_lambda_event_publisher.tf b/infrastructure/terraform/modules/backend-api/module_lambda_event_publisher.tf index 8ab88e03b..97d62919c 100644 --- a/infrastructure/terraform/modules/backend-api/module_lambda_event_publisher.tf +++ b/infrastructure/terraform/modules/backend-api/module_lambda_event_publisher.tf @@ -26,6 +26,7 @@ module "lambda_event_publisher" { lambda_env_vars = { EVENT_SOURCE = "//notify.nhs.uk/${var.component}/${var.group}/${var.environment}" + PROOF_REQUEST_TABLE_NAME = "TODO" # TODO: CCM-7941 ROUTING_CONFIG_TABLE_NAME = aws_dynamodb_table.routing_configuration.name SNS_TOPIC_ARN = coalesce(var.sns_topic_arn, aws_sns_topic.main.arn) TEMPLATES_TABLE_NAME = aws_dynamodb_table.templates.name diff --git a/lambdas/event-publisher/src/__tests__/domain/event-builder.test.ts b/lambdas/event-publisher/src/__tests__/domain/event-builder.test.ts index ab5ba1615..b5da3f675 100644 --- a/lambdas/event-publisher/src/__tests__/domain/event-builder.test.ts +++ b/lambdas/event-publisher/src/__tests__/domain/event-builder.test.ts @@ -23,11 +23,13 @@ const { logger: mockLogger } = createMockLogger(); const tables = { templates: 'templates-table', routing: 'routing-config-table', + proofRequests: 'proof-request-table', }; const eventBuilder = new EventBuilder( tables.templates, tables.routing, + tables.proofRequests, 'event-source', mockLogger ); @@ -378,6 +380,67 @@ const expectedRoutingConfigEvent = ( }, }); +const publishableProofRequestEventRecord = (): PublishableEventRecord => ({ + dynamodb: { + SequenceNumber: '4', + NewImage: { + id: { + S: '92b676e9-470f-4d04-ab14-965ef145e15d', + }, + templateId: { + S: 'bed3398c-bbe3-435d-80c1-58154d4bf7dd', + }, + templateType: { + S: 'SMS', + }, + testPatientNhsNumber: { + S: '9000000009', + }, + contactDetails: { + M: { + sms: { + S: '07700900000', + }, + }, + }, + personalisation: { + M: { + firstName: { + S: 'Jane', + }, + }, + }, + }, + }, + eventID: '7f2ae4b0-82c2-4911-9b84-8997d7f3f40d', + tableName: tables.proofRequests, +}); + +const expectedProofRequestedEvent = () => ({ + id: '7f2ae4b0-82c2-4911-9b84-8997d7f3f40d', + datacontenttype: 'application/json', + time: '2022-01-01T09:00:00.000Z', + source: 'event-source', + type: 'uk.nhs.notify.template-management.ProofRequested.v1', + specversion: '1.0', + dataschema: 'https://notify.nhs.uk/events/schemas/ProofRequested/v1.json', + dataschemaversion: VERSION, + plane: 'data', + subject: '92b676e9-470f-4d04-ab14-965ef145e15d', + data: { + id: '92b676e9-470f-4d04-ab14-965ef145e15d', + templateId: 'bed3398c-bbe3-435d-80c1-58154d4bf7dd', + templateType: 'SMS', + testPatientNhsNumber: '9000000009', + contactDetails: { + sms: '07700900000', + }, + personalisation: { + firstName: 'Jane', + }, + }, +}); + test('errors on unrecognised event table source', () => { const invalidpublishableTemplateEventRecord = { ...publishableTemplateEventRecord('SUBMITTED'), @@ -634,3 +697,55 @@ describe('routing config events', () => { expect(event).toEqual(undefined); }); }); + +describe('proof request events', () => { + test('builds proof requested event', () => { + const event = eventBuilder.buildEvent(publishableProofRequestEventRecord()); + + expect(event).toEqual(expectedProofRequestedEvent()); + }); + + test('errors on output schema validation failure after input parsing', () => { + const valid = publishableProofRequestEventRecord(); + + const invalidDomainEventRecord: PublishableEventRecord = { + ...valid, + dynamodb: { + ...valid.dynamodb, + NewImage: { + ...valid.dynamodb.NewImage, + templateType: { S: 'LETTER' }, + }, + }, + }; + + expect(() => eventBuilder.buildEvent(invalidDomainEventRecord)).toThrow( + expect.objectContaining({ + name: 'ZodError', + issues: [ + expect.objectContaining({ + code: 'invalid_value', + values: ['NHS_APP', 'SMS', 'EMAIL'], + path: ['data', 'templateType'], + }), + ], + }) + ); + }); + + test('does not build proof request event on hard delete', () => { + const hardDeletePublishableProofRequestEventRecord = { + ...publishableProofRequestEventRecord(), + dynamodb: { + SequenceNumber: '4', + NewImage: undefined, + }, + }; + + const event = eventBuilder.buildEvent( + hardDeletePublishableProofRequestEventRecord + ); + + expect(event).toEqual(undefined); + }); +}); diff --git a/lambdas/event-publisher/src/config.ts b/lambdas/event-publisher/src/config.ts index 977ec2660..27060b776 100644 --- a/lambdas/event-publisher/src/config.ts +++ b/lambdas/event-publisher/src/config.ts @@ -5,6 +5,7 @@ const $Config = z.object({ ROUTING_CONFIG_TABLE_NAME: z.string(), SNS_TOPIC_ARN: z.string(), TEMPLATES_TABLE_NAME: z.string(), + PROOF_REQUEST_TABLE_NAME: z.string(), }); export const loadConfig = () => { diff --git a/lambdas/event-publisher/src/container.ts b/lambdas/event-publisher/src/container.ts index 3199d25cd..9c9c0da48 100644 --- a/lambdas/event-publisher/src/container.ts +++ b/lambdas/event-publisher/src/container.ts @@ -11,6 +11,7 @@ export const createContainer = () => { ROUTING_CONFIG_TABLE_NAME, SNS_TOPIC_ARN, TEMPLATES_TABLE_NAME, + PROOF_REQUEST_TABLE_NAME, } = loadConfig(); const snsClient = new SNSClient({ region: 'eu-west-2' }); @@ -20,6 +21,7 @@ export const createContainer = () => { const eventBuilder = new EventBuilder( TEMPLATES_TABLE_NAME, ROUTING_CONFIG_TABLE_NAME, + PROOF_REQUEST_TABLE_NAME, EVENT_SOURCE, logger ); diff --git a/lambdas/event-publisher/src/domain/event-builder.ts b/lambdas/event-publisher/src/domain/event-builder.ts index d6d401333..6576c0166 100644 --- a/lambdas/event-publisher/src/domain/event-builder.ts +++ b/lambdas/event-publisher/src/domain/event-builder.ts @@ -5,6 +5,7 @@ import { } from '@nhsdigital/nhs-notify-event-schemas-template-management'; import { Logger } from 'nhs-notify-web-template-management-utils/logger'; import { + $DynamoDBProofRequest, $DynamoDBRoutingConfig, $DynamoDBTemplate, PublishableEventRecord, @@ -16,6 +17,7 @@ export class EventBuilder { constructor( private readonly templatesTableName: string, private readonly routingConfigTableName: string, + private readonly proofRequestTableName: string, private readonly eventSource: string, private readonly logger: Logger ) {} @@ -52,14 +54,19 @@ export class EventBuilder { } } - private buildEventMetadata(id: string, type: string, subject: string) { + private buildEventMetadata( + id: string, + type: string, + subject: string, + plane: 'control' | 'data' = 'control' + ) { return { id, datacontenttype: 'application/json', time: new Date().toISOString(), source: this.eventSource, specversion: '1.0', - plane: 'control', + plane, subject, type: `uk.nhs.notify.template-management.${type}.v${MAJOR_VERSION}`, dataschema: `https://notify.nhs.uk/events/schemas/${type}/v${MAJOR_VERSION}.json`, @@ -165,6 +172,45 @@ export class EventBuilder { return event.data; } + private buildProofRequestedEvent( + publishableEventRecord: PublishableEventRecord + ): Event | undefined { + if (!publishableEventRecord.dynamodb.NewImage) { + // No need to publish an event if a proof request has been deleted. + this.logger.debug({ + description: 'No new image found', + publishableEventRecord, + }); + + return undefined; + } + + const dynamoRecord = unmarshall(publishableEventRecord.dynamodb.NewImage); + + const databaseProofRequest = $DynamoDBProofRequest.parse(dynamoRecord); + + try { + return $Event.parse({ + ...this.buildEventMetadata( + publishableEventRecord.eventID, + 'ProofRequested', + databaseProofRequest.id, + 'data' + ), + data: dynamoRecord, + }); + } catch (error) { + this.logger + .child({ + description: 'Failed to parse outgoing event for proof request', + publishableEventRecord, + }) + .error(error); + + throw error; + } + } + buildEvent( publishableEventRecord: PublishableEventRecord ): Event | undefined { @@ -175,6 +221,9 @@ export class EventBuilder { case this.routingConfigTableName: { return this.buildRoutingConfigDatabaseEvent(publishableEventRecord); } + case this.proofRequestTableName: { + return this.buildProofRequestedEvent(publishableEventRecord); + } default: { this.logger.error({ description: 'Unrecognised event type', diff --git a/lambdas/event-publisher/src/domain/input-schemas.ts b/lambdas/event-publisher/src/domain/input-schemas.ts index b6b4bed70..51870ad46 100644 --- a/lambdas/event-publisher/src/domain/input-schemas.ts +++ b/lambdas/event-publisher/src/domain/input-schemas.ts @@ -53,6 +53,9 @@ export const $DynamoDBRoutingConfig = schemaFor>()( ); export type DynamoDBRoutingConfig = z.infer; +export const $DynamoDBProofRequest = z.object({ id: z.string() }); +export type DynamoDBProofRequest = z.infer; + // the lambda doesn't necessarily have to only accept inputs from a dynamodb stream via an // eventbridge pipe, but that's all it is doing at the moment export const $PublishableEventRecord = $DynamoDBStreamRecord; diff --git a/lambdas/event-publisher/src/domain/output-schemas.ts b/lambdas/event-publisher/src/domain/output-schemas.ts index e43f8b267..17c257c09 100644 --- a/lambdas/event-publisher/src/domain/output-schemas.ts +++ b/lambdas/event-publisher/src/domain/output-schemas.ts @@ -1,4 +1,5 @@ import { + $ProofRequestedEventV1, $RoutingConfigCompletedEventV1, $RoutingConfigDeletedEventV1, $RoutingConfigDraftedEventV1, @@ -9,11 +10,12 @@ import { import { z } from 'zod'; export const $Event = z.discriminatedUnion('type', [ - $TemplateCompletedEventV1, - $TemplateDraftedEventV1, - $TemplateDeletedEventV1, + $ProofRequestedEventV1, $RoutingConfigCompletedEventV1, - $RoutingConfigDraftedEventV1, $RoutingConfigDeletedEventV1, + $RoutingConfigDraftedEventV1, + $TemplateCompletedEventV1, + $TemplateDeletedEventV1, + $TemplateDraftedEventV1, ]); export type Event = z.infer; From 2064c47a1cd8c4891d03d248e89974597388e872 Mon Sep 17 00:00:00 2001 From: bhansell1 Date: Thu, 12 Mar 2026 16:35:24 +0000 Subject: [PATCH 3/8] CCM-7942: package incr --- packages/event-schemas/package.json | 2 +- scripts/config/vale/styles/config/vocabularies/words/accept.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/event-schemas/package.json b/packages/event-schemas/package.json index 32a6a1f3c..60080f05b 100644 --- a/packages/event-schemas/package.json +++ b/packages/event-schemas/package.json @@ -56,5 +56,5 @@ }, "type": "commonjs", "types": "./dist/index.d.ts", - "version": "1.4.4" + "version": "1.4.5" } diff --git a/scripts/config/vale/styles/config/vocabularies/words/accept.txt b/scripts/config/vale/styles/config/vocabularies/words/accept.txt index 8d5dc5918..4824dbba9 100644 --- a/scripts/config/vale/styles/config/vocabularies/words/accept.txt +++ b/scripts/config/vale/styles/config/vocabularies/words/accept.txt @@ -27,3 +27,4 @@ Terraform toolchain Trufflehog Zod +[Vv]alidators From 2953e94ad81870bbcabfbc03dd0151555e5968d6 Mon Sep 17 00:00:00 2001 From: bhansell1 Date: Thu, 12 Mar 2026 17:39:31 +0000 Subject: [PATCH 4/8] CCM-7942: add proof-request table, stream and pipe --- .../terraform/modules/backend-api/README.md | 1 + ..._group_pipe_proof_requests_table_events.tf | 5 + .../dynamodb_table_proof_requests.tf | 44 +++++++ .../module_lambda_event_publisher.tf | 2 +- ...qs_proof_requests_table_events_pipe_dlq.tf | 12 ++ .../pipes_pipe_proof_requests_table_events.tf | 113 ++++++++++++++++++ package-lock.json | 2 +- 7 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 infrastructure/terraform/modules/backend-api/cloudwatch_log_group_pipe_proof_requests_table_events.tf create mode 100644 infrastructure/terraform/modules/backend-api/dynamodb_table_proof_requests.tf create mode 100644 infrastructure/terraform/modules/backend-api/module_sqs_proof_requests_table_events_pipe_dlq.tf create mode 100644 infrastructure/terraform/modules/backend-api/pipes_pipe_proof_requests_table_events.tf diff --git a/infrastructure/terraform/modules/backend-api/README.md b/infrastructure/terraform/modules/backend-api/README.md index b3ffa7f60..f0ed81d6e 100644 --- a/infrastructure/terraform/modules/backend-api/README.md +++ b/infrastructure/terraform/modules/backend-api/README.md @@ -68,6 +68,7 @@ No requirements. | [s3bucket\_internal](#module\_s3bucket\_internal) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.4/terraform-s3bucket.zip | n/a | | [s3bucket\_quarantine](#module\_s3bucket\_quarantine) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.4/terraform-s3bucket.zip | n/a | | [sqs\_letter\_render](#module\_sqs\_letter\_render) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.28/terraform-sqs.zip | n/a | +| [sqs\_proof\_requests\_table\_events\_pipe\_dlq](#module\_sqs\_proof\_requests\_table\_events\_pipe\_dlq) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.28/terraform-sqs.zip | n/a | | [sqs\_routing\_config\_table\_events\_pipe\_dlq](#module\_sqs\_routing\_config\_table\_events\_pipe\_dlq) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.28/terraform-sqs.zip | n/a | | [sqs\_sftp\_upload](#module\_sqs\_sftp\_upload) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.28/terraform-sqs.zip | n/a | | [sqs\_template\_mgmt\_events](#module\_sqs\_template\_mgmt\_events) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.28/terraform-sqs.zip | n/a | diff --git a/infrastructure/terraform/modules/backend-api/cloudwatch_log_group_pipe_proof_requests_table_events.tf b/infrastructure/terraform/modules/backend-api/cloudwatch_log_group_pipe_proof_requests_table_events.tf new file mode 100644 index 000000000..e652d480c --- /dev/null +++ b/infrastructure/terraform/modules/backend-api/cloudwatch_log_group_pipe_proof_requests_table_events.tf @@ -0,0 +1,5 @@ +resource "aws_cloudwatch_log_group" "pipe_proof_requests_table_events" { + name = "/aws/vendedlogs/pipes/${local.csi}-proof-requests-table-events" + kms_key_id = var.kms_key_arn + retention_in_days = var.log_retention_in_days +} diff --git a/infrastructure/terraform/modules/backend-api/dynamodb_table_proof_requests.tf b/infrastructure/terraform/modules/backend-api/dynamodb_table_proof_requests.tf new file mode 100644 index 000000000..8183bbbed --- /dev/null +++ b/infrastructure/terraform/modules/backend-api/dynamodb_table_proof_requests.tf @@ -0,0 +1,44 @@ +resource "aws_dynamodb_table" "proof_requests" { + name = "${local.csi}-proof-requests" + billing_mode = "PAY_PER_REQUEST" + + hash_key = "owner" + range_key = "id" + + attribute { + name = "owner" + type = "S" + } + + attribute { + name = "id" + type = "S" + } + + ttl { + attribute_name = "ttl" + enabled = true + } + + point_in_time_recovery { + enabled = true + } + + server_side_encryption { + enabled = true + kms_key_arn = var.kms_key_arn + } + + tags = { + "NHSE-Enable-Dynamo-Backup" = var.enable_backup ? "True" : "False" + } + + lifecycle { + ignore_changes = [ + name, # To support backup and restore which will result in a new name otherwise + ] + } + + stream_enabled = true + stream_view_type = "NEW_AND_OLD_IMAGES" +} diff --git a/infrastructure/terraform/modules/backend-api/module_lambda_event_publisher.tf b/infrastructure/terraform/modules/backend-api/module_lambda_event_publisher.tf index 97d62919c..dadd507a2 100644 --- a/infrastructure/terraform/modules/backend-api/module_lambda_event_publisher.tf +++ b/infrastructure/terraform/modules/backend-api/module_lambda_event_publisher.tf @@ -26,7 +26,7 @@ module "lambda_event_publisher" { lambda_env_vars = { EVENT_SOURCE = "//notify.nhs.uk/${var.component}/${var.group}/${var.environment}" - PROOF_REQUEST_TABLE_NAME = "TODO" # TODO: CCM-7941 + PROOF_REQUEST_TABLE_NAME = aws_dynamodb_table.proof_requests.name ROUTING_CONFIG_TABLE_NAME = aws_dynamodb_table.routing_configuration.name SNS_TOPIC_ARN = coalesce(var.sns_topic_arn, aws_sns_topic.main.arn) TEMPLATES_TABLE_NAME = aws_dynamodb_table.templates.name diff --git a/infrastructure/terraform/modules/backend-api/module_sqs_proof_requests_table_events_pipe_dlq.tf b/infrastructure/terraform/modules/backend-api/module_sqs_proof_requests_table_events_pipe_dlq.tf new file mode 100644 index 000000000..79856eff1 --- /dev/null +++ b/infrastructure/terraform/modules/backend-api/module_sqs_proof_requests_table_events_pipe_dlq.tf @@ -0,0 +1,12 @@ +module "sqs_proof_requests_table_events_pipe_dlq" { + source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.28/terraform-sqs.zip" + + aws_account_id = var.aws_account_id + component = var.component + environment = var.environment + project = var.project + region = var.region + name = "proof-requests-table-events-pipe-dead-letter" + sqs_kms_key_arn = var.kms_key_arn + message_retention_seconds = 1209600 +} diff --git a/infrastructure/terraform/modules/backend-api/pipes_pipe_proof_requests_table_events.tf b/infrastructure/terraform/modules/backend-api/pipes_pipe_proof_requests_table_events.tf new file mode 100644 index 000000000..16551e0d3 --- /dev/null +++ b/infrastructure/terraform/modules/backend-api/pipes_pipe_proof_requests_table_events.tf @@ -0,0 +1,113 @@ +resource "aws_pipes_pipe" "proof_requests_table_events" { + depends_on = [module.sqs_proof_requests_table_events_pipe_dlq] + + name = "${local.csi}-proof-requests-table-events" + role_arn = aws_iam_role.pipe_proof_requests_table_events.arn + source = aws_dynamodb_table.proof_requests.stream_arn + target = module.sqs_template_mgmt_events.sqs_queue_arn + desired_state = "RUNNING" + kms_key_identifier = var.kms_key_arn + + source_parameters { + dynamodb_stream_parameters { + starting_position = "TRIM_HORIZON" + on_partial_batch_item_failure = "AUTOMATIC_BISECT" + batch_size = 10 + maximum_batching_window_in_seconds = 5 + maximum_retry_attempts = 5 + maximum_record_age_in_seconds = -1 + + dead_letter_config { + arn = module.sqs_proof_requests_table_events_pipe_dlq.sqs_queue_arn + } + } + } + + target_parameters { + input_template = <<-EOF + { + "dynamodb": <$.dynamodb>, + "eventID": <$.eventID>, + "eventName": <$.eventName>, + "eventSource": <$.eventSource>, + "tableName": "${aws_dynamodb_table.proof_requests.name}" + } + EOF + + sqs_queue_parameters { + message_group_id = "$.dynamodb.Keys.id.S" + message_deduplication_id = "$.eventID" + } + } + + log_configuration { + level = "ERROR" + include_execution_data = ["ALL"] + + cloudwatch_logs_log_destination { + log_group_arn = aws_cloudwatch_log_group.pipe_proof_requests_table_events.arn + } + } +} + +resource "aws_iam_role" "pipe_proof_requests_table_events" { + name = "${local.csi}-pipe-proof-requests-table-events" + description = "IAM Role for Pipe to forward proof requests table stream events to SQS" + assume_role_policy = data.aws_iam_policy_document.pipes_proof_requests_trust_policy.json +} + +data "aws_iam_policy_document" "pipes_proof_requests_trust_policy" { + statement { + sid = "PipesAssumeRole" + effect = "Allow" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["pipes.amazonaws.com"] + } + } +} + +resource "aws_iam_role_policy" "pipe_proof_requests_table_events" { + name = "${local.csi}-pipe-proof-requests-table-events" + role = aws_iam_role.pipe_proof_requests_table_events.id + policy = data.aws_iam_policy_document.pipe_proof_requests_table_events.json +} + +data "aws_iam_policy_document" "pipe_proof_requests_table_events" { + version = "2012-10-17" + + statement { + sid = "AllowDynamoStreamRead" + effect = "Allow" + actions = [ + "dynamodb:DescribeStream", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:ListStreams", + ] + resources = [aws_dynamodb_table.proof_requests.stream_arn] + } + + statement { + sid = "AllowSqsSendMessage" + effect = "Allow" + actions = ["sqs:SendMessage"] + resources = [ + module.sqs_template_mgmt_events.sqs_queue_arn, + module.sqs_proof_requests_table_events_pipe_dlq.sqs_queue_arn, + ] + } + + statement { + sid = "AllowKmsUsage" + effect = "Allow" + actions = [ + "kms:Decrypt", + "kms:Encrypt", + "kms:GenerateDataKey*" + ] + resources = [var.kms_key_arn] + } +} diff --git a/package-lock.json b/package-lock.json index 41c037ffa..5f2454b63 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27883,7 +27883,7 @@ }, "packages/event-schemas": { "name": "@nhsdigital/nhs-notify-event-schemas-template-management", - "version": "1.4.4", + "version": "1.4.5", "license": "MIT", "dependencies": { "zod": "^4.0.17" From b0b523907d42c49c19c927053835acfb3056dfbc Mon Sep 17 00:00:00 2001 From: bhansell1 Date: Fri, 13 Mar 2026 12:31:23 +0000 Subject: [PATCH 5/8] CCM-7942: add simple automated test to ensure event is published --- .../terraform/components/sbx/outputs.tf | 4 + .../terraform/modules/backend-api/outputs.tf | 4 + package-lock.json | 29 ++--- tests/test-team/global.d.ts | 1 + .../db/proof-requests-storage-helper.ts | 106 ++++++++++++++++++ tests/test-team/helpers/types.ts | 17 +++ .../proof-requests.event.spec.ts | 81 +++++++++++++ utils/backend-config/src/backend-config.ts | 5 + 8 files changed, 233 insertions(+), 14 deletions(-) create mode 100644 tests/test-team/helpers/db/proof-requests-storage-helper.ts create mode 100644 tests/test-team/template-mgmt-event-tests/proof-requests.event.spec.ts diff --git a/infrastructure/terraform/components/sbx/outputs.tf b/infrastructure/terraform/components/sbx/outputs.tf index cab82d420..8f6710538 100644 --- a/infrastructure/terraform/components/sbx/outputs.tf +++ b/infrastructure/terraform/components/sbx/outputs.tf @@ -73,3 +73,7 @@ output "routing_config_table_name" { output "events_sns_topic_arn" { value = module.eventpub.sns_topic.arn } + +output "proof_requests_table_name" { + value = module.backend_api.proof_requests_table_name +} diff --git a/infrastructure/terraform/modules/backend-api/outputs.tf b/infrastructure/terraform/modules/backend-api/outputs.tf index 0c43341b2..7f056468c 100644 --- a/infrastructure/terraform/modules/backend-api/outputs.tf +++ b/infrastructure/terraform/modules/backend-api/outputs.tf @@ -45,3 +45,7 @@ output "quarantine_bucket_name" { output "routing_config_table_name" { value = aws_dynamodb_table.routing_configuration.name } + +output proof_requests_table_name { + value = aws_dynamodb_table.proof_requests.name +} diff --git a/package-lock.json b/package-lock.json index 5f2454b63..64b5dd942 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7719,13 +7719,13 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.18", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.18.tgz", - "integrity": "sha512-kINzc5BBxdYBkPZ0/i1AMPMOk5b5QaFNbYMElVw5QTX13AKj6jcxnv/YNl9oW9mg+Y08ti19hh01HhyEAxsSJQ==", + "version": "3.972.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.19.tgz", + "integrity": "sha512-jOXdZ1o+CywQKr6gyxgxuUmnGwTTnY2Kxs1PM7fI6AYtDWDnmW/yKXayNqkF8KjP1unflqMWKVbVt5VgmE3L0g==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "^3.973.19", - "@aws-sdk/nested-clients": "^3.996.8", + "@aws-sdk/nested-clients": "^3.996.9", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", @@ -7826,9 +7826,9 @@ } }, "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/nested-clients": { - "version": "3.996.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.8.tgz", - "integrity": "sha512-6HlLm8ciMW8VzfB80kfIx16PBA9lOa9Dl+dmCBi78JDhvGlx3I7Rorwi5PpVRkL31RprXnYna3yBf6UKkD/PqA==", + "version": "3.996.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.9.tgz", + "integrity": "sha512-+RpVtpmQbbtzFOKhMlsRcXM/3f1Z49qTOHaA8gEpHOYruERmog6f2AUtf/oTRLCWjR9H2b3roqryV/hI7QMW8w==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -7842,7 +7842,7 @@ "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@aws-sdk/util-user-agent-browser": "^3.972.7", - "@aws-sdk/util-user-agent-node": "^3.973.5", + "@aws-sdk/util-user-agent-node": "^3.973.6", "@smithy/config-resolver": "^4.4.10", "@smithy/core": "^3.23.9", "@smithy/fetch-http-handler": "^5.3.13", @@ -7932,15 +7932,16 @@ } }, "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.973.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.5.tgz", - "integrity": "sha512-Dyy38O4GeMk7UQ48RupfHif//gqnOPbq/zlvRssc11E2mClT+aUfc3VS2yD8oLtzqO3RsqQ9I3gOBB4/+HjPOw==", + "version": "3.973.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.6.tgz", + "integrity": "sha512-iF7G0prk7AvmOK64FcLvc/fW+Ty1H+vttajL7PvJFReU8urMxfYmynTTuFKDTA76Wgpq3FzTPKwabMQIXQHiXQ==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.20", "@aws-sdk/types": "^3.973.5", "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", + "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "engines": { @@ -7970,9 +7971,9 @@ } }, "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws/lambda-invoke-store": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", - "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", + "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", "license": "Apache-2.0", "engines": { "node": ">=18.0.0" diff --git a/tests/test-team/global.d.ts b/tests/test-team/global.d.ts index 6654bfe88..c8a6e126a 100644 --- a/tests/test-team/global.d.ts +++ b/tests/test-team/global.d.ts @@ -9,6 +9,7 @@ declare global { ENVIRONMENT: string; EVENTS_SNS_TOPIC_ARN: string; PLAYWRIGHT_RUN_ID: string; + PROOF_REQUESTS_TABLE_NAME: string; REQUEST_PROOF_QUEUE_URL: string; ROUTING_CONFIG_TABLE_NAME: string; SFTP_ENVIRONMENT: string; diff --git a/tests/test-team/helpers/db/proof-requests-storage-helper.ts b/tests/test-team/helpers/db/proof-requests-storage-helper.ts new file mode 100644 index 000000000..7d70a7fbb --- /dev/null +++ b/tests/test-team/helpers/db/proof-requests-storage-helper.ts @@ -0,0 +1,106 @@ +import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; +import { + BatchWriteCommand, + DynamoDBDocumentClient, +} from '@aws-sdk/lib-dynamodb'; +import type { ProofRequest } from '../types'; + +type ProofRequestKey = { id: string; owner: string }; + +export class ProofRequestsStorageHelper { + private readonly dynamo: DynamoDBDocumentClient = DynamoDBDocumentClient.from( + new DynamoDBClient({ region: 'eu-west-2' }) + ); + + private seedData: ProofRequest[] = []; + + private adHocKeys: ProofRequestKey[] = []; + + /** + * Seed a load of proof requests into the database + */ + async seed(data: ProofRequest[]) { + this.seedData.push(...data); + + const chunks = ProofRequestsStorageHelper.chunk(data); + + await Promise.all( + chunks.map(async (chunk) => { + await this.dynamo.send( + new BatchWriteCommand({ + RequestItems: { + [process.env.PROOF_REQUESTS_TABLE_NAME]: chunk.map( + (proofRequest) => ({ + PutRequest: { + Item: proofRequest, + }, + }) + ), + }, + }) + ); + }) + ); + } + + /** + * Delete proof requests seeded by calls to seed + */ + public async deleteSeeded() { + await this.delete( + this.seedData.map(({ id, owner }) => ({ + id, + owner, + })) + ); + this.seedData = []; + } + + private async delete(keys: ProofRequestKey[]) { + const dbChunks = ProofRequestsStorageHelper.chunk(keys); + + await Promise.all( + dbChunks.map((chunk) => + this.dynamo.send( + new BatchWriteCommand({ + RequestItems: { + [process.env.PROOF_REQUESTS_TABLE_NAME]: chunk.map((key) => ({ + DeleteRequest: { + Key: key, + }, + })), + }, + }) + ) + ) + ); + } + + /** + * Stores references to proof requests created in tests (not via seeding) + */ + public addAdHocKey(key: ProofRequestKey) { + this.adHocKeys.push(key); + } + + /** + * Delete proof requests referenced by calls to addAdHocKey from database + */ + async deleteAdHoc() { + await this.delete(this.adHocKeys); + this.adHocKeys = []; + } + + /** + * Breaks a list into chunks of upto 25 items + */ + private static chunk(list: T[], size = 25): T[][] { + const chunks: T[][] = []; + + for (let i = 0; i < list.length; i += size) { + chunks.push(list.slice(i, i + size)); + } + + return chunks; + } +} diff --git a/tests/test-team/helpers/types.ts b/tests/test-team/helpers/types.ts index 2fdf5ebfd..e2844598b 100644 --- a/tests/test-team/helpers/types.ts +++ b/tests/test-team/helpers/types.ts @@ -4,6 +4,7 @@ import type { RoutingConfig, Language, LetterType, + TemplateType, } from 'nhs-notify-web-template-management-types'; export const templateTypeDisplayMappings: Record = { @@ -133,3 +134,19 @@ export type FactoryRoutingConfigWithModifiers = FactoryRoutingConfig & { templateId?: string ) => FactoryRoutingConfigWithModifiers; }; + +type DigitalTemplateType = Extract; + +export type ProofRequest = { + id: string; + owner: string; + createdAt: string; + personalisation: Record; + contactDetails?: { + sms?: string; + email?: string; + }; + templateId: string; + templateType: DigitalTemplateType; + testPatientNhsNumber: string; +}; diff --git a/tests/test-team/template-mgmt-event-tests/proof-requests.event.spec.ts b/tests/test-team/template-mgmt-event-tests/proof-requests.event.spec.ts new file mode 100644 index 000000000..45a229aab --- /dev/null +++ b/tests/test-team/template-mgmt-event-tests/proof-requests.event.spec.ts @@ -0,0 +1,81 @@ +import { randomUUID } from 'node:crypto'; +import { + templateManagementEventSubscriber as test, + expect, +} from '../fixtures/template-management-event-subscriber'; +import { + createAuthHelper, + type TestUser, + testUsers, +} from '../helpers/auth/cognito-auth-helper'; +import { eventWithId } from '../helpers/events/matchers'; +import { ProofRequestsStorageHelper } from 'helpers/db/proof-requests-storage-helper'; + +test.describe('ProofRequestedEvent', () => { + const authHelper = createAuthHelper(); + const proofRequestsStorageHelper = new ProofRequestsStorageHelper(); + + let user: TestUser; + + test.beforeAll(async () => { + user = await authHelper.getTestUser(testUsers.User1.userId); + }); + + test.afterAll(async () => { + await proofRequestsStorageHelper.deleteSeeded(); + }); + + test('Expect a ProofRequestedEventv1 to be published when a proof request is created', async ({ + eventSubscriber, + }) => { + const start = new Date(); + + const proofRequestId = randomUUID(); + + // TODO: CCM-7941 - use API rather than directly into DB. + await proofRequestsStorageHelper.seed([ + { + id: proofRequestId, + owner: `CLIENT#${user.clientId}`, + createdAt: new Date().toISOString(), + personalisation: { + gpSurgery: 'Test GP Surgery', + }, + contactDetails: { + sms: '07999999999', + }, + templateId: randomUUID(), + templateType: 'SMS', + testPatientNhsNumber: '9999999999', + }, + ]); + + await expect(async () => { + const events = await eventSubscriber.receive({ + since: start, + match: eventWithId(proofRequestId), + }); + + expect(events).toHaveLength(1); + + expect(events).toContainEqual( + expect.objectContaining({ + record: expect.objectContaining({ + type: 'uk.nhs.notify.template-management.ProofRequested.v1', + data: expect.objectContaining({ + id: proofRequestId, + testPatientNhsNumber: '9999999999', + templateType: 'SMS', + personalisation: { + gpSurgery: 'Test GP Surgery', + }, + contactDetails: { + sms: '07999999999', + }, + }), + }), + }) + ); + }).toPass({ timeout: 60_000, intervals: [1000, 3000, 5000] }); + }); +}); diff --git a/utils/backend-config/src/backend-config.ts b/utils/backend-config/src/backend-config.ts index 6dace6a49..edbd1cab1 100644 --- a/utils/backend-config/src/backend-config.ts +++ b/utils/backend-config/src/backend-config.ts @@ -21,6 +21,7 @@ export type BackendConfig = { testEmailBucketPrefix: string; userPoolId: string; userPoolClientId: string; + proofRequestsTableName: string; }; export const BackendConfigHelper = { @@ -31,6 +32,7 @@ export const BackendConfigHelper = { clientSsmPathPrefix: process.env.CLIENT_SSM_PATH_PREFIX ?? '', environment: process.env.ENVIRONMENT ?? '', eventsSnsTopicArn: process.env.EVENTS_SNS_TOPIC_ARN ?? '', + proofRequestsTableName: process.env.PROOF_REQUESTS_TABLE_NAME ?? '', requestProofQueueUrl: process.env.REQUEST_PROOF_QUEUE_URL ?? '', routingConfigTableName: process.env.ROUTING_CONFIG_TABLE_NAME ?? '', sftpEnvironment: process.env.SFTP_ENVIRONMENT ?? '', @@ -72,6 +74,7 @@ export const BackendConfigHelper = { process.env.SFTP_POLL_LAMBDA_NAME = config.sftpPollLambdaName; process.env.TEST_EMAIL_BUCKET_NAME = config.testEmailBucketName; process.env.TEST_EMAIL_BUCKET_PREFIX = config.testEmailBucketPrefix; + process.env.PROOF_REQUESTS_TABLE_NAME = config.proofRequestsTableName; }, fromTerraformOutputsFile(filepath: string): BackendConfig { @@ -85,6 +88,8 @@ export const BackendConfigHelper = { outputsFileContent.client_ssm_path_prefix?.value ?? '', environment: deployment.environment ?? '', eventsSnsTopicArn: outputsFileContent.events_sns_topic_arn?.value ?? '', + proofRequestsTableName: + outputsFileContent.proof_requests_table_name?.value ?? '', requestProofQueueUrl: outputsFileContent.request_proof_queue_url?.value ?? '', routingConfigTableName: From bc93bf8887a433c9de00ba2ae91e68c22fe384e2 Mon Sep 17 00:00:00 2001 From: bhansell1 Date: Fri, 13 Mar 2026 13:22:55 +0000 Subject: [PATCH 6/8] CCM-7942: terraform --- infrastructure/terraform/components/sbx/README.md | 1 + infrastructure/terraform/modules/backend-api/README.md | 1 + infrastructure/terraform/modules/backend-api/outputs.tf | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/infrastructure/terraform/components/sbx/README.md b/infrastructure/terraform/components/sbx/README.md index 216eae80c..78679a95e 100644 --- a/infrastructure/terraform/components/sbx/README.md +++ b/infrastructure/terraform/components/sbx/README.md @@ -42,6 +42,7 @@ | [download\_bucket\_name](#output\_download\_bucket\_name) | n/a | | [events\_sns\_topic\_arn](#output\_events\_sns\_topic\_arn) | n/a | | [internal\_bucket\_name](#output\_internal\_bucket\_name) | n/a | +| [proof\_requests\_table\_name](#output\_proof\_requests\_table\_name) | n/a | | [quarantine\_bucket\_name](#output\_quarantine\_bucket\_name) | n/a | | [request\_proof\_queue\_url](#output\_request\_proof\_queue\_url) | n/a | | [routing\_config\_table\_name](#output\_routing\_config\_table\_name) | n/a | diff --git a/infrastructure/terraform/modules/backend-api/README.md b/infrastructure/terraform/modules/backend-api/README.md index f0ed81d6e..e4caa4208 100644 --- a/infrastructure/terraform/modules/backend-api/README.md +++ b/infrastructure/terraform/modules/backend-api/README.md @@ -89,6 +89,7 @@ No requirements. | [download\_bucket\_name](#output\_download\_bucket\_name) | n/a | | [download\_bucket\_regional\_domain\_name](#output\_download\_bucket\_regional\_domain\_name) | n/a | | [internal\_bucket\_name](#output\_internal\_bucket\_name) | n/a | +| [proof\_requests\_table\_name](#output\_proof\_requests\_table\_name) | n/a | | [quarantine\_bucket\_name](#output\_quarantine\_bucket\_name) | n/a | | [request\_proof\_queue\_url](#output\_request\_proof\_queue\_url) | n/a | | [routing\_config\_table\_name](#output\_routing\_config\_table\_name) | n/a | diff --git a/infrastructure/terraform/modules/backend-api/outputs.tf b/infrastructure/terraform/modules/backend-api/outputs.tf index 7f056468c..ed6030b55 100644 --- a/infrastructure/terraform/modules/backend-api/outputs.tf +++ b/infrastructure/terraform/modules/backend-api/outputs.tf @@ -46,6 +46,6 @@ output "routing_config_table_name" { value = aws_dynamodb_table.routing_configuration.name } -output proof_requests_table_name { +output "proof_requests_table_name" { value = aws_dynamodb_table.proof_requests.name } From 3c32d7fd074aaab818a9d7f0c9aca2b96a328a4b Mon Sep 17 00:00:00 2001 From: bhansell1 Date: Mon, 16 Mar 2026 09:16:41 +0000 Subject: [PATCH 7/8] CCM-7942: standardise env var name --- .../modules/backend-api/module_lambda_event_publisher.tf | 2 +- lambdas/event-publisher/src/config.ts | 2 +- lambdas/event-publisher/src/container.ts | 4 ++-- lambdas/event-publisher/src/domain/event-builder.ts | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/infrastructure/terraform/modules/backend-api/module_lambda_event_publisher.tf b/infrastructure/terraform/modules/backend-api/module_lambda_event_publisher.tf index dadd507a2..097ea6368 100644 --- a/infrastructure/terraform/modules/backend-api/module_lambda_event_publisher.tf +++ b/infrastructure/terraform/modules/backend-api/module_lambda_event_publisher.tf @@ -26,7 +26,7 @@ module "lambda_event_publisher" { lambda_env_vars = { EVENT_SOURCE = "//notify.nhs.uk/${var.component}/${var.group}/${var.environment}" - PROOF_REQUEST_TABLE_NAME = aws_dynamodb_table.proof_requests.name + PROOF_REQUESTS_TABLE_NAME = aws_dynamodb_table.proof_requests.name ROUTING_CONFIG_TABLE_NAME = aws_dynamodb_table.routing_configuration.name SNS_TOPIC_ARN = coalesce(var.sns_topic_arn, aws_sns_topic.main.arn) TEMPLATES_TABLE_NAME = aws_dynamodb_table.templates.name diff --git a/lambdas/event-publisher/src/config.ts b/lambdas/event-publisher/src/config.ts index 27060b776..85aca7d40 100644 --- a/lambdas/event-publisher/src/config.ts +++ b/lambdas/event-publisher/src/config.ts @@ -5,7 +5,7 @@ const $Config = z.object({ ROUTING_CONFIG_TABLE_NAME: z.string(), SNS_TOPIC_ARN: z.string(), TEMPLATES_TABLE_NAME: z.string(), - PROOF_REQUEST_TABLE_NAME: z.string(), + PROOF_REQUESTS_TABLE_NAME: z.string(), }); export const loadConfig = () => { diff --git a/lambdas/event-publisher/src/container.ts b/lambdas/event-publisher/src/container.ts index 9c9c0da48..29ff65db0 100644 --- a/lambdas/event-publisher/src/container.ts +++ b/lambdas/event-publisher/src/container.ts @@ -11,7 +11,7 @@ export const createContainer = () => { ROUTING_CONFIG_TABLE_NAME, SNS_TOPIC_ARN, TEMPLATES_TABLE_NAME, - PROOF_REQUEST_TABLE_NAME, + PROOF_REQUESTS_TABLE_NAME, } = loadConfig(); const snsClient = new SNSClient({ region: 'eu-west-2' }); @@ -21,7 +21,7 @@ export const createContainer = () => { const eventBuilder = new EventBuilder( TEMPLATES_TABLE_NAME, ROUTING_CONFIG_TABLE_NAME, - PROOF_REQUEST_TABLE_NAME, + PROOF_REQUESTS_TABLE_NAME, EVENT_SOURCE, logger ); diff --git a/lambdas/event-publisher/src/domain/event-builder.ts b/lambdas/event-publisher/src/domain/event-builder.ts index 6576c0166..12428cc32 100644 --- a/lambdas/event-publisher/src/domain/event-builder.ts +++ b/lambdas/event-publisher/src/domain/event-builder.ts @@ -17,7 +17,7 @@ export class EventBuilder { constructor( private readonly templatesTableName: string, private readonly routingConfigTableName: string, - private readonly proofRequestTableName: string, + private readonly proofRequestsTableName: string, private readonly eventSource: string, private readonly logger: Logger ) {} @@ -176,7 +176,7 @@ export class EventBuilder { publishableEventRecord: PublishableEventRecord ): Event | undefined { if (!publishableEventRecord.dynamodb.NewImage) { - // No need to publish an event if a proof request has been deleted. + // Do not publish an event if a proof-request record is deleted this.logger.debug({ description: 'No new image found', publishableEventRecord, @@ -221,7 +221,7 @@ export class EventBuilder { case this.routingConfigTableName: { return this.buildRoutingConfigDatabaseEvent(publishableEventRecord); } - case this.proofRequestTableName: { + case this.proofRequestsTableName: { return this.buildProofRequestedEvent(publishableEventRecord); } default: { From 6436eac981154b30a9bc00002e78e67d75610fb9 Mon Sep 17 00:00:00 2001 From: bhansell1 Date: Mon, 16 Mar 2026 09:21:45 +0000 Subject: [PATCH 8/8] CCM-7942: trivvy --- package-lock.json | 589 +++++++++++++++++++++++----------------------- package.json | 3 +- 2 files changed, 297 insertions(+), 295 deletions(-) diff --git a/package-lock.json b/package-lock.json index 64b5dd942..2ad597c7c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7719,18 +7719,18 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.19", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.19.tgz", - "integrity": "sha512-jOXdZ1o+CywQKr6gyxgxuUmnGwTTnY2Kxs1PM7fI6AYtDWDnmW/yKXayNqkF8KjP1unflqMWKVbVt5VgmE3L0g==", + "version": "3.972.20", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.20.tgz", + "integrity": "sha512-gEWo54nfqp2jABMu6HNsjVC4hDLpg9HC8IKSJnp0kqWtxIJYHTmiLSsIfI4ScQjxEwpB+jOOH8dOLax1+hy/Hw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/nested-clients": "^3.996.9", - "@aws-sdk/types": "^3.973.5", - "@smithy/property-provider": "^4.2.11", - "@smithy/protocol-http": "^5.3.11", - "@smithy/shared-ini-file-loader": "^4.4.6", - "@smithy/types": "^4.13.0", + "@aws-sdk/core": "^3.973.20", + "@aws-sdk/nested-clients": "^3.996.10", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -7738,22 +7738,22 @@ } }, "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/core": { - "version": "3.973.19", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.19.tgz", - "integrity": "sha512-56KePyOcZnKTWCd89oJS1G6j3HZ9Kc+bh/8+EbvtaCCXdP6T7O7NzCiPuHRhFLWnzXIaXX3CxAz0nI5My9spHQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@aws-sdk/xml-builder": "^3.972.10", - "@smithy/core": "^3.23.9", - "@smithy/node-config-provider": "^4.3.11", - "@smithy/property-provider": "^4.2.11", - "@smithy/protocol-http": "^5.3.11", - "@smithy/signature-v4": "^5.3.11", - "@smithy/smithy-client": "^4.12.3", - "@smithy/types": "^4.13.0", + "version": "3.973.20", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.20.tgz", + "integrity": "sha512-i3GuX+lowD892F3IuJf8o6AbyDupMTdyTxQrCJGcn71ni5hTZ82L4nQhcdumxZ7XPJRJJVHS/CR3uYOIIs0PVA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/xml-builder": "^3.972.11", + "@smithy/core": "^3.23.11", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/signature-v4": "^5.3.12", + "@smithy/smithy-client": "^4.12.5", + "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", - "@smithy/util-middleware": "^4.2.11", + "@smithy/util-middleware": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, @@ -7762,14 +7762,14 @@ } }, "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.7.tgz", - "integrity": "sha512-aHQZgztBFEpDU1BB00VWCIIm85JjGjQW1OG9+98BdmaOpguJvzmXBGbnAiYcciCd+IS4e9BEq664lhzGnWJHgQ==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.8.tgz", + "integrity": "sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@smithy/protocol-http": "^5.3.11", - "@smithy/types": "^4.13.0", + "@aws-sdk/types": "^3.973.6", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -7777,13 +7777,13 @@ } }, "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/middleware-logger": { - "version": "3.972.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.7.tgz", - "integrity": "sha512-LXhiWlWb26txCU1vcI9PneESSeRp/RYY/McuM4SpdrimQR5NgwaPb4VJCadVeuGWgh6QmqZ6rAKSoL1ob16W6w==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.8.tgz", + "integrity": "sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@smithy/types": "^4.13.0", + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -7791,15 +7791,15 @@ } }, "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.7.tgz", - "integrity": "sha512-l2VQdcBcYLzIzykCHtXlbpiVCZ94/xniLIkAj0jpnpjY4xlgZx7f56Ypn+uV1y3gG0tNVytJqo3K9bfMFee7SQ==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.8.tgz", + "integrity": "sha512-BnnvYs2ZEpdlmZ2PNlV2ZyQ8j8AEkMTjN79y/YA475ER1ByFYrkVR85qmhni8oeTaJcDqbx364wDpitDAA/wCA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.5", + "@aws-sdk/types": "^3.973.6", "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.11", - "@smithy/types": "^4.13.0", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -7807,18 +7807,18 @@ } }, "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.20", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.20.tgz", - "integrity": "sha512-3kNTLtpUdeahxtnJRnj/oIdLAUdzTfr9N40KtxNhtdrq+Q1RPMdCJINRXq37m4t5+r3H70wgC3opW46OzFcZYA==", + "version": "3.972.21", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.21.tgz", + "integrity": "sha512-62XRl1GDYPpkt7cx1AX1SPy9wgNE9Iw/NPuurJu4lmhCWS7sGKO+kS53TQ8eRmIxy3skmvNInnk0ZbWrU5Dpyg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/types": "^3.973.5", - "@aws-sdk/util-endpoints": "^3.996.4", - "@smithy/core": "^3.23.9", - "@smithy/protocol-http": "^5.3.11", - "@smithy/types": "^4.13.0", - "@smithy/util-retry": "^4.2.11", + "@aws-sdk/core": "^3.973.20", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-endpoints": "^3.996.5", + "@smithy/core": "^3.23.11", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-retry": "^4.2.12", "tslib": "^2.6.2" }, "engines": { @@ -7826,47 +7826,47 @@ } }, "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/nested-clients": { - "version": "3.996.9", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.9.tgz", - "integrity": "sha512-+RpVtpmQbbtzFOKhMlsRcXM/3f1Z49qTOHaA8gEpHOYruERmog6f2AUtf/oTRLCWjR9H2b3roqryV/hI7QMW8w==", + "version": "3.996.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.10.tgz", + "integrity": "sha512-SlDol5Z+C7Ivnc2rKGqiqfSUmUZzY1qHfVs9myt/nxVwswgfpjdKahyTzLTx802Zfq0NFRs7AejwKzzzl5Co2w==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/middleware-host-header": "^3.972.7", - "@aws-sdk/middleware-logger": "^3.972.7", - "@aws-sdk/middleware-recursion-detection": "^3.972.7", - "@aws-sdk/middleware-user-agent": "^3.972.20", - "@aws-sdk/region-config-resolver": "^3.972.7", - "@aws-sdk/types": "^3.973.5", - "@aws-sdk/util-endpoints": "^3.996.4", - "@aws-sdk/util-user-agent-browser": "^3.972.7", - "@aws-sdk/util-user-agent-node": "^3.973.6", - "@smithy/config-resolver": "^4.4.10", - "@smithy/core": "^3.23.9", - "@smithy/fetch-http-handler": "^5.3.13", - "@smithy/hash-node": "^4.2.11", - "@smithy/invalid-dependency": "^4.2.11", - "@smithy/middleware-content-length": "^4.2.11", - "@smithy/middleware-endpoint": "^4.4.23", - "@smithy/middleware-retry": "^4.4.40", - "@smithy/middleware-serde": "^4.2.12", - "@smithy/middleware-stack": "^4.2.11", - "@smithy/node-config-provider": "^4.3.11", - "@smithy/node-http-handler": "^4.4.14", - "@smithy/protocol-http": "^5.3.11", - "@smithy/smithy-client": "^4.12.3", - "@smithy/types": "^4.13.0", - "@smithy/url-parser": "^4.2.11", + "@aws-sdk/core": "^3.973.20", + "@aws-sdk/middleware-host-header": "^3.972.8", + "@aws-sdk/middleware-logger": "^3.972.8", + "@aws-sdk/middleware-recursion-detection": "^3.972.8", + "@aws-sdk/middleware-user-agent": "^3.972.21", + "@aws-sdk/region-config-resolver": "^3.972.8", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-endpoints": "^3.996.5", + "@aws-sdk/util-user-agent-browser": "^3.972.8", + "@aws-sdk/util-user-agent-node": "^3.973.7", + "@smithy/config-resolver": "^4.4.11", + "@smithy/core": "^3.23.11", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/hash-node": "^4.2.12", + "@smithy/invalid-dependency": "^4.2.12", + "@smithy/middleware-content-length": "^4.2.12", + "@smithy/middleware-endpoint": "^4.4.25", + "@smithy/middleware-retry": "^4.4.42", + "@smithy/middleware-serde": "^4.2.14", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/node-http-handler": "^4.4.16", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.5", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", - "@smithy/util-defaults-mode-browser": "^4.3.39", - "@smithy/util-defaults-mode-node": "^4.2.42", - "@smithy/util-endpoints": "^3.3.2", - "@smithy/util-middleware": "^4.2.11", - "@smithy/util-retry": "^4.2.11", + "@smithy/util-defaults-mode-browser": "^4.3.41", + "@smithy/util-defaults-mode-node": "^4.2.44", + "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, @@ -7875,15 +7875,15 @@ } }, "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.972.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.7.tgz", - "integrity": "sha512-/Ev/6AI8bvt4HAAptzSjThGUMjcWaX3GX8oERkB0F0F9x2dLSBdgFDiyrRz3i0u0ZFZFQ1b28is4QhyqXTUsVA==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.8.tgz", + "integrity": "sha512-1eD4uhTDeambO/PNIDVG19A6+v4NdD7xzwLHDutHsUqz0B+i661MwQB2eYO4/crcCvCiQG4SRm1k81k54FEIvw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@smithy/config-resolver": "^4.4.10", - "@smithy/node-config-provider": "^4.3.11", - "@smithy/types": "^4.13.0", + "@aws-sdk/types": "^3.973.6", + "@smithy/config-resolver": "^4.4.11", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -7891,12 +7891,12 @@ } }, "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/types": { - "version": "3.973.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.5.tgz", - "integrity": "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ==", + "version": "3.973.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.6.tgz", + "integrity": "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -7904,15 +7904,15 @@ } }, "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/util-endpoints": { - "version": "3.996.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.4.tgz", - "integrity": "sha512-Hek90FBmd4joCFj+Vc98KLJh73Zqj3s2W56gjAcTkrNLMDI5nIFkG9YpfcJiVI1YlE2Ne1uOQNe+IgQ/Vz2XRA==", + "version": "3.996.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.5.tgz", + "integrity": "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@smithy/types": "^4.13.0", - "@smithy/url-parser": "^4.2.11", - "@smithy/util-endpoints": "^3.3.2", + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-endpoints": "^3.3.3", "tslib": "^2.6.2" }, "engines": { @@ -7920,27 +7920,27 @@ } }, "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.972.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.7.tgz", - "integrity": "sha512-7SJVuvhKhMF/BkNS1n0QAJYgvEwYbK2QLKBrzDiwQGiTRU6Yf1f3nehTzm/l21xdAOtWSfp2uWSddPnP2ZtsVw==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.8.tgz", + "integrity": "sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@smithy/types": "^4.13.0", + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.973.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.6.tgz", - "integrity": "sha512-iF7G0prk7AvmOK64FcLvc/fW+Ty1H+vttajL7PvJFReU8urMxfYmynTTuFKDTA76Wgpq3FzTPKwabMQIXQHiXQ==", + "version": "3.973.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.7.tgz", + "integrity": "sha512-Hz6EZMUAEzqUd7e+vZ9LE7mn+5gMbxltXy18v+YSFY+9LBJz15wkNZvw5JqfX3z0FS9n3bgUtz3L5rAsfh4YlA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.20", - "@aws-sdk/types": "^3.973.5", - "@smithy/node-config-provider": "^4.3.11", - "@smithy/types": "^4.13.0", + "@aws-sdk/middleware-user-agent": "^3.972.21", + "@aws-sdk/types": "^3.973.6", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, @@ -7957,12 +7957,12 @@ } }, "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/xml-builder": { - "version": "3.972.10", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.10.tgz", - "integrity": "sha512-OnejAIVD+CxzyAUrVic7lG+3QRltyja9LoNqCE/1YVs8ichoTbJlVSaZ9iSMcnHLyzrSNtvaOGjSDRP+d/ouFA==", + "version": "3.972.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.11.tgz", + "integrity": "sha512-iitV/gZKQMvY9d7ovmyFnFuTHbBAtrmLnvaSb/3X8vOKyevwtpmEtyc8AdhVWZe0pI/1GsHxlEvQeOePFzy7KQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.13.1", "fast-xml-parser": "5.4.1", "tslib": "^2.6.2" }, @@ -12821,12 +12821,12 @@ "license": "(Unlicense OR Apache-2.0)" }, "node_modules/@smithy/abort-controller": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.11.tgz", - "integrity": "sha512-Hj4WoYWMJnSpM6/kchsm4bUNTL9XiSyhvoMb2KIq4VJzyDt7JpGHUZHkVNPZVC7YE1tf8tPeVauxpFBKGW4/KQ==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.12.tgz", + "integrity": "sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -12859,16 +12859,16 @@ } }, "node_modules/@smithy/config-resolver": { - "version": "4.4.10", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.10.tgz", - "integrity": "sha512-IRTkd6ps0ru+lTWnfnsbXzW80A8Od8p3pYiZnW98K2Hb20rqfsX7VTlfUwhrcOeSSy68Gn9WBofwPuw3e5CCsg==", + "version": "4.4.11", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.11.tgz", + "integrity": "sha512-YxFiiG4YDAtX7WMN7RuhHZLeTmRRAOyCbr+zB8e3AQzHPnUhS8zXjB1+cniPVQI3xbWsQPM0X2aaIkO/ME0ymw==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.11", - "@smithy/types": "^4.13.0", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", - "@smithy/util-endpoints": "^3.3.2", - "@smithy/util-middleware": "^4.2.11", + "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" }, "engines": { @@ -12876,18 +12876,18 @@ } }, "node_modules/@smithy/core": { - "version": "3.23.9", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.9.tgz", - "integrity": "sha512-1Vcut4LEL9HZsdpI0vFiRYIsaoPwZLjAxnVQDUMQK8beMS+EYPLDQCXtbzfxmM5GzSgjfe2Q9M7WaXwIMQllyQ==", + "version": "3.23.11", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.11.tgz", + "integrity": "sha512-952rGf7hBRnhUIaeLp6q4MptKW8sPFe5VvkoZ5qIzFAtx6c/QZ/54FS3yootsyUSf9gJX/NBqEBNdNR7jMIlpQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^4.2.12", - "@smithy/protocol-http": "^5.3.11", - "@smithy/types": "^4.13.0", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", - "@smithy/util-middleware": "^4.2.11", - "@smithy/util-stream": "^4.5.17", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-stream": "^4.5.19", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" @@ -12897,15 +12897,15 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.11.tgz", - "integrity": "sha512-lBXrS6ku0kTj3xLmsJW0WwqWbGQ6ueooYyp/1L9lkyT0M02C+DWwYwc5aTyXFbRaK38ojALxNixg+LxKSHZc0g==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.12.tgz", + "integrity": "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.11", - "@smithy/property-provider": "^4.2.11", - "@smithy/types": "^4.13.0", - "@smithy/url-parser": "^4.2.11", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", "tslib": "^2.6.2" }, "engines": { @@ -12983,14 +12983,14 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.13", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.13.tgz", - "integrity": "sha512-U2Hcfl2s3XaYjikN9cT4mPu8ybDbImV3baXR0PkVlC0TTx808bRP3FaPGAzPtB8OByI+JqJ1kyS+7GEgae7+qQ==", + "version": "5.3.15", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.15.tgz", + "integrity": "sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.11", - "@smithy/querystring-builder": "^4.2.11", - "@smithy/types": "^4.13.0", + "@smithy/protocol-http": "^5.3.12", + "@smithy/querystring-builder": "^4.2.12", + "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" }, @@ -13014,12 +13014,12 @@ } }, "node_modules/@smithy/hash-node": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.11.tgz", - "integrity": "sha512-T+p1pNynRkydpdL015ruIoyPSRw9e/SQOWmSAMmmprfswMrd5Ow5igOWNVlvyVFZlxXqGmyH3NQwfwy8r5Jx0A==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.12.tgz", + "integrity": "sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.13.1", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" @@ -13043,12 +13043,12 @@ } }, "node_modules/@smithy/invalid-dependency": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.11.tgz", - "integrity": "sha512-cGNMrgykRmddrNhYy1yBdrp5GwIgEkniS7k9O1VLB38yxQtlvrxpZtUVvo6T4cKpeZsriukBuuxfJcdZQc/f/g==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.12.tgz", + "integrity": "sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -13082,13 +13082,13 @@ } }, "node_modules/@smithy/middleware-content-length": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.11.tgz", - "integrity": "sha512-UvIfKYAKhCzr4p6jFevPlKhQwyQwlJ6IeKLDhmV1PlYfcW3RL4ROjNEDtSik4NYMi9kDkH7eSwyTP3vNJ/u/Dw==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.12.tgz", + "integrity": "sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.11", - "@smithy/types": "^4.13.0", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -13096,18 +13096,18 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.4.23", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.23.tgz", - "integrity": "sha512-UEFIejZy54T1EJn2aWJ45voB7RP2T+IRzUqocIdM6GFFa5ClZncakYJfcYnoXt3UsQrZZ9ZRauGm77l9UCbBLw==", + "version": "4.4.25", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.25.tgz", + "integrity": "sha512-dqjLwZs2eBxIUG6Qtw8/YZ4DvzHGIf0DA18wrgtfP6a50UIO7e2nY0FPdcbv5tVJKqWCCU5BmGMOUwT7Puan+A==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.23.9", - "@smithy/middleware-serde": "^4.2.12", - "@smithy/node-config-provider": "^4.3.11", - "@smithy/shared-ini-file-loader": "^4.4.6", - "@smithy/types": "^4.13.0", - "@smithy/url-parser": "^4.2.11", - "@smithy/util-middleware": "^4.2.11", + "@smithy/core": "^3.23.11", + "@smithy/middleware-serde": "^4.2.14", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" }, "engines": { @@ -13115,18 +13115,18 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.4.40", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.40.tgz", - "integrity": "sha512-YhEMakG1Ae57FajERdHNZ4ShOPIY7DsgV+ZoAxo/5BT0KIe+f6DDU2rtIymNNFIj22NJfeeI6LWIifrwM0f+rA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.11", - "@smithy/protocol-http": "^5.3.11", - "@smithy/service-error-classification": "^4.2.11", - "@smithy/smithy-client": "^4.12.3", - "@smithy/types": "^4.13.0", - "@smithy/util-middleware": "^4.2.11", - "@smithy/util-retry": "^4.2.11", + "version": "4.4.42", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.42.tgz", + "integrity": "sha512-vbwyqHRIpIZutNXZpLAozakzamcINaRCpEy1MYmK6xBeW3xN+TyPRA123GjXnuxZIjc9848MRRCugVMTXxC4Eg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/service-error-classification": "^4.2.12", + "@smithy/smithy-client": "^4.12.5", + "@smithy/types": "^4.13.1", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.12", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" }, @@ -13135,13 +13135,14 @@ } }, "node_modules/@smithy/middleware-serde": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.12.tgz", - "integrity": "sha512-W9g1bOLui7Xn5FABRVS0o3rXL0gfN37d/8I/W7i0N7oxjx9QecUmXEMSUMADTODwdtka9cN43t5BI2CodLJpng==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.14.tgz", + "integrity": "sha512-+CcaLoLa5apzSRtloOyG7lQvkUw2ZDml3hRh4QiG9WyEPfW5Ke/3tPOPiPjUneuT59Tpn8+c3RVaUvvkkwqZwg==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.11", - "@smithy/types": "^4.13.0", + "@smithy/core": "^3.23.11", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -13149,12 +13150,12 @@ } }, "node_modules/@smithy/middleware-stack": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.11.tgz", - "integrity": "sha512-s+eenEPW6RgliDk2IhjD2hWOxIx1NKrOHxEwNUaUXxYBxIyCcDfNULZ2Mu15E3kwcJWBedTET/kEASPV1A1Akg==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.12.tgz", + "integrity": "sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -13162,14 +13163,14 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "4.3.11", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.11.tgz", - "integrity": "sha512-xD17eE7kaLgBBGf5CZQ58hh2YmwK1Z0O8YhffwB/De2jsL0U3JklmhVYJ9Uf37OtUDLF2gsW40Xwwag9U869Gg==", + "version": "4.3.12", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.12.tgz", + "integrity": "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.11", - "@smithy/shared-ini-file-loader": "^4.4.6", - "@smithy/types": "^4.13.0", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -13177,15 +13178,15 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.4.14", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.14.tgz", - "integrity": "sha512-DamSqaU8nuk0xTJDrYnRzZndHwwRnyj/n/+RqGGCcBKB4qrQem0mSDiWdupaNWdwxzyMU91qxDmHOCazfhtO3A==", + "version": "4.4.16", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.16.tgz", + "integrity": "sha512-ULC8UCS/HivdCB3jhi+kLFYe4B5gxH2gi9vHBfEIiRrT2jfKiZNiETJSlzRtE6B26XbBHjPtc8iZKSNqMol9bw==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.2.11", - "@smithy/protocol-http": "^5.3.11", - "@smithy/querystring-builder": "^4.2.11", - "@smithy/types": "^4.13.0", + "@smithy/abort-controller": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/querystring-builder": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -13193,12 +13194,12 @@ } }, "node_modules/@smithy/property-provider": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.11.tgz", - "integrity": "sha512-14T1V64o6/ndyrnl1ze1ZhyLzIeYNN47oF/QU6P5m82AEtyOkMJTb0gO1dPubYjyyKuPD6OSVMPDKe+zioOnCg==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.12.tgz", + "integrity": "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -13206,12 +13207,12 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "5.3.11", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.11.tgz", - "integrity": "sha512-hI+barOVDJBkNt4y0L2mu3Ugc0w7+BpJ2CZuLwXtSltGAAwCb3IvnalGlbDV/UCS6a9ZuT3+exd1WxNdLb5IlQ==", + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -13219,12 +13220,12 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.11.tgz", - "integrity": "sha512-7spdikrYiljpket6u0up2Ck2mxhy7dZ0+TDd+S53Dg2DHd6wg+YNJrTCHiLdgZmEXZKI7LJZcwL3721ZRDFiqA==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.12.tgz", + "integrity": "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.13.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" }, @@ -13233,12 +13234,12 @@ } }, "node_modules/@smithy/querystring-parser": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.11.tgz", - "integrity": "sha512-nE3IRNjDltvGcoThD2abTozI1dkSy8aX+a2N1Rs55en5UsdyyIXgGEmevUL3okZFoJC77JgRGe99xYohhsjivQ==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.12.tgz", + "integrity": "sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -13246,24 +13247,24 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.11.tgz", - "integrity": "sha512-HkMFJZJUhzU3HvND1+Yw/kYWXp4RPDLBWLcK1n+Vqw8xn4y2YiBhdww8IxhkQjP/QlZun5bwm3vcHc8AqIU3zw==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.12.tgz", + "integrity": "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0" + "@smithy/types": "^4.13.1" }, "engines": { "node": ">=18.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.6.tgz", - "integrity": "sha512-IB/M5I8G0EeXZTHsAxpx51tMQ5R719F3aq+fjEB6VtNcCHDc0ajFDIGDZw+FW9GxtEkgTduiPpjveJdA/CX7sw==", + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.7.tgz", + "integrity": "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -13271,16 +13272,16 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "5.3.11", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.11.tgz", - "integrity": "sha512-V1L6N9aKOBAN4wEHLyqjLBnAz13mtILU0SeDrjOaIZEeN6IFa6DxwRt1NNpOdmSpQUfkBj0qeD3m6P77uzMhgQ==", + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.12.tgz", + "integrity": "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^4.2.2", - "@smithy/protocol-http": "^5.3.11", - "@smithy/types": "^4.13.0", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", - "@smithy/util-middleware": "^4.2.11", + "@smithy/util-middleware": "^4.2.12", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" @@ -13290,17 +13291,17 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.12.3", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.3.tgz", - "integrity": "sha512-7k4UxjSpHmPN2AxVhvIazRSzFQjWnud3sOsXcFStzagww17j1cFQYqTSiQ8xuYK3vKLR1Ni8FzuT3VlKr3xCNw==", + "version": "4.12.5", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.5.tgz", + "integrity": "sha512-UqwYawyqSr/aog8mnLnfbPurS0gi4G7IYDcD28cUIBhsvWs1+rQcL2IwkUQ+QZ7dibaoRzhNF99fAQ9AUcO00w==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.23.9", - "@smithy/middleware-endpoint": "^4.4.23", - "@smithy/middleware-stack": "^4.2.11", - "@smithy/protocol-http": "^5.3.11", - "@smithy/types": "^4.13.0", - "@smithy/util-stream": "^4.5.17", + "@smithy/core": "^3.23.11", + "@smithy/middleware-endpoint": "^4.4.25", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-stream": "^4.5.19", "tslib": "^2.6.2" }, "engines": { @@ -13308,9 +13309,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.0.tgz", - "integrity": "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -13320,13 +13321,13 @@ } }, "node_modules/@smithy/url-parser": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.11.tgz", - "integrity": "sha512-oTAGGHo8ZYc5VZsBREzuf5lf2pAurJQsccMusVZ85wDkX66ojEc/XauiGjzCj50A61ObFTPe6d7Pyt6UBYaing==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.12.tgz", + "integrity": "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA==", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.2.11", - "@smithy/types": "^4.13.0", + "@smithy/querystring-parser": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -13397,14 +13398,14 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.39", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.39.tgz", - "integrity": "sha512-ui7/Ho/+VHqS7Km2wBw4/Ab4RktoiSshgcgpJzC4keFPs6tLJS4IQwbeahxQS3E/w98uq6E1mirCH/id9xIXeQ==", + "version": "4.3.41", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.41.tgz", + "integrity": "sha512-M1w1Ux0rSVvBOxIIiqbxvZvhnjQ+VUjJrugtORE90BbadSTH+jsQL279KRL3Hv0w69rE7EuYkV/4Lepz/NBW9g==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.11", - "@smithy/smithy-client": "^4.12.3", - "@smithy/types": "^4.13.0", + "@smithy/property-provider": "^4.2.12", + "@smithy/smithy-client": "^4.12.5", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -13412,17 +13413,17 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.42", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.42.tgz", - "integrity": "sha512-QDA84CWNe8Akpj15ofLO+1N3Rfg8qa2K5uX0y6HnOp4AnRYRgWrKx/xzbYNbVF9ZsyJUYOfcoaN3y93wA/QJ2A==", + "version": "4.2.44", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.44.tgz", + "integrity": "sha512-YPze3/lD1KmWuZsl9JlfhcgGLX7AXhSoaCDtiPntUjNW5/YY0lOHjkcgxyE9x/h5vvS1fzDifMGjzqnNlNiqOQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.4.10", - "@smithy/credential-provider-imds": "^4.2.11", - "@smithy/node-config-provider": "^4.3.11", - "@smithy/property-provider": "^4.2.11", - "@smithy/smithy-client": "^4.12.3", - "@smithy/types": "^4.13.0", + "@smithy/config-resolver": "^4.4.11", + "@smithy/credential-provider-imds": "^4.2.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/smithy-client": "^4.12.5", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -13430,13 +13431,13 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.2.tgz", - "integrity": "sha512-+4HFLpE5u29AbFlTdlKIT7jfOzZ8PDYZKTb3e+AgLz986OYwqTourQ5H+jg79/66DB69Un1+qKecLnkZdAsYcA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.3.tgz", + "integrity": "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.11", - "@smithy/types": "^4.13.0", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -13456,12 +13457,12 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.11.tgz", - "integrity": "sha512-r3dtF9F+TpSZUxpOVVtPfk09Rlo4lT6ORBqEvX3IBT6SkQAdDSVKR5GcfmZbtl7WKhKnmb3wbDTQ6ibR2XHClw==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -13469,13 +13470,13 @@ } }, "node_modules/@smithy/util-retry": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.11.tgz", - "integrity": "sha512-XSZULmL5x6aCTTii59wJqKsY1l3eMIAomRAccW7Tzh9r8s7T/7rdo03oektuH5jeYRlJMPcNP92EuRDvk9aXbw==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.12.tgz", + "integrity": "sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.2.11", - "@smithy/types": "^4.13.0", + "@smithy/service-error-classification": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -13483,14 +13484,14 @@ } }, "node_modules/@smithy/util-stream": { - "version": "4.5.17", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.17.tgz", - "integrity": "sha512-793BYZ4h2JAQkNHcEnyFxDTcZbm9bVybD0UV/LEWmZ5bkTms7JqjfrLMi2Qy0E5WFcCzLwCAPgcvcvxoeALbAQ==", + "version": "4.5.19", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.19.tgz", + "integrity": "sha512-v4sa+3xTweL1CLO2UP0p7tvIMH/Rq1X4KKOxd568mpe6LSLMQCnDHs4uv7m3ukpl3HvcN2JH6jiCS0SNRXKP/w==", "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.3.13", - "@smithy/node-http-handler": "^4.4.14", - "@smithy/types": "^4.13.0", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/node-http-handler": "^4.4.16", + "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", @@ -18756,9 +18757,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.0.tgz", + "integrity": "sha512-kC6Bb+ooptOIvWj5B63EQWkF0FEnNjV2ZNkLMLZRDDduIiWeFF4iKnslwhiWxjAdbg4NzTNo6h0qLuvFrcx+Sw==", "license": "ISC" }, "node_modules/fn.name": { diff --git a/package.json b/package.json index d92f56c06..4dfa0c361 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,8 @@ "react-is": "19.0.0" }, "react": "^19.0.0", - "underscore": "^1.13.8" + "underscore": "^1.13.8", + "flatted@3": "3.4.0" }, "scripts": { "build": "npm run create-env-file && npm run build --workspace frontend",