From 32977692dd04422373d9afac6328cb11ac7c9671 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 8 Jan 2026 15:07:11 +0000 Subject: [PATCH 01/75] Define the infrastucture. Dummy lambda that runs frequently --- .pre-commit-config.yaml | 12 +- ...scription-status-update-api.code-workspace | 4 + Makefile | 6 +- README.md | 17 +-- SAMtemplates/functions/main.yaml | 107 ++++++++++++++++++ SAMtemplates/messaging/main.yaml | 91 +++++++++++++++ package-lock.json | 38 +++---- package.json | 1 + packages/common/commonTypes/src/index.ts | 7 ++ packages/postDatedLambda/.jest/setEnvVars.js | 4 + packages/postDatedLambda/.vscode/launch.json | 35 ++++++ .../postDatedLambda/.vscode/settings.json | 7 ++ packages/postDatedLambda/jest.config.ts | 10 ++ packages/postDatedLambda/jest.debug.config.ts | 9 ++ packages/postDatedLambda/package.json | 26 +++++ packages/postDatedLambda/src/main.ts | 25 ++++ packages/postDatedLambda/tests/main.test.ts | 30 +++++ packages/postDatedLambda/tsconfig.json | 12 ++ .../src/updatePrescriptionStatus.ts | 2 +- .../src/utils/sqsClient.ts | 2 + sonar-project.properties | 4 + tsconfig.build.json | 55 ++++++--- 22 files changed, 460 insertions(+), 44 deletions(-) create mode 100644 packages/postDatedLambda/.jest/setEnvVars.js create mode 100644 packages/postDatedLambda/.vscode/launch.json create mode 100644 packages/postDatedLambda/.vscode/settings.json create mode 100644 packages/postDatedLambda/jest.config.ts create mode 100644 packages/postDatedLambda/jest.debug.config.ts create mode 100644 packages/postDatedLambda/package.json create mode 100644 packages/postDatedLambda/src/main.ts create mode 100644 packages/postDatedLambda/tests/main.test.ts create mode 100644 packages/postDatedLambda/tsconfig.json diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fb715fc821..1fae54bc68 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -86,7 +86,7 @@ repos: files: ^packages\/checkPrescriptionStatusUpdates types_or: [ts, tsx, javascript, jsx, json] pass_filenames: false - + - id: lint-nhsNotifyLambda name: Lint nhsNotifyLambda entry: npm @@ -97,6 +97,16 @@ repos: types_or: [ts, tsx, javascript, jsx, json] pass_filenames: false + - id: lint-postDatedLambda + name: List Post-dated handling lambda + entry: npm + args: + ["run", "--prefix=packages/postDatedLambda", "lint"] + language: system + files: ^packages\/postDatedLambda + types_or: [ts, tsx, javascript, jsx, json] + pass_filenames: false + - id: lint-commonTesting name: Lint common/testing entry: npm diff --git a/.vscode/eps-prescription-status-update-api.code-workspace b/.vscode/eps-prescription-status-update-api.code-workspace index 659926e2eb..f7159c7f89 100644 --- a/.vscode/eps-prescription-status-update-api.code-workspace +++ b/.vscode/eps-prescription-status-update-api.code-workspace @@ -32,6 +32,10 @@ "name": "packages/nhsNotifyLambda", "path": "../packages/nhsNotifyLambda" }, + { + "name": "packages/postDatedLambda", + "path": "../packages/postDatedLambda" + }, { "name": "packages/nhsNotifyUpdateCallback", "path": "../packages/nhsNotifyUpdateCallback" diff --git a/Makefile b/Makefile index 6533a32d93..f8c02a4413 100644 --- a/Makefile +++ b/Makefile @@ -56,7 +56,7 @@ sam-list-resources: guard-AWS_DEFAULT_PROFILE guard-stack_name sam-list-outputs: guard-AWS_DEFAULT_PROFILE guard-stack_name sam list stack-outputs --stack-name $$stack_name -sam-validate: +sam-validate: sam validate --template-file SAMtemplates/main_template.yaml --region eu-west-2 sam validate --template-file SAMtemplates/apis/main.yaml --region eu-west-2 sam validate --template-file SAMtemplates/apis/api_resources.yaml --region eu-west-2 @@ -120,6 +120,7 @@ lint-node: compile-node npm run lint --workspace packages/cpsuLambda npm run lint --workspace packages/checkPrescriptionStatusUpdates npm run lint --workspace packages/nhsNotifyLambda + npm run lint --workspace packages/postDatedLambda npm run lint --workspace packages/nhsNotifyUpdateCallback npm run lint --workspace packages/common/testing npm run lint --workspace packages/common/middyErrorHandler @@ -152,6 +153,7 @@ test: compile npm run test --workspace packages/cpsuLambda npm run test --workspace packages/checkPrescriptionStatusUpdates npm run test --workspace packages/nhsNotifyLambda + npm run test --workspace packages/postDatedLambda npm run test --workspace packages/nhsNotifyUpdateCallback npm run test --workspace packages/common/middyErrorHandler npm run test --workspace packages/psuRestoreValidationLambda @@ -172,6 +174,8 @@ clean: rm -rf packages/cpsuLambda/lib rm -rf packages/nhsNotifyLambda/coverage rm -rf packages/nhsNotifyLambda/lib + rm -rf packages/postDatedLambda/coverage + rm -rf packages/postDatedLambda/lib rm -rf packages/nhsNotifyUpdateCallback/coverage rm -rf packages/nhsNotifyUpdateCallback/lib rm -rf packages/checkPrescriptionStatusUpdates/lib diff --git a/README.md b/README.md index 9f911f2d16..570472976d 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # EPS Prescription Status Update API -![Build](https://github.com/NHSDigital/eps-prescription-status-update-api/actions/workflows/ci.yml/badge.svg?branch=main) +![Build](https://github.com/NHSDigital/eps-prescription-status-update-api/actions/workflows/ci.yml/badge.svg?branch=main) ![Release](https://github.com/NHSDigital/eps-prescription-status-update-api/actions/workflows/release.yml/badge.svg?branch=main) ## Versions and deployments -Version release history can be found ot https://github.com/NHSDigital/eps-prescription-status-update-api/releases. -We use eslint convention for commit messages for commits to main branch. Descriptions for the types of changes in a release can be found in the [contributing guidelines](./CONTRIBUTING.md) +Version release history can be found ot https://github.com/NHSDigital/eps-prescription-status-update-api/releases. +We use eslint convention for commit messages for commits to main branch. Descriptions for the types of changes in a release can be found in the [contributing guidelines](./CONTRIBUTING.md) Deployment history can be found at https://nhsdigital.github.io/eps-prescription-status-update-api/ ## Introduction @@ -20,6 +20,7 @@ This is the AWS layer that provides an API for EPS Prescription Status Update. - `packages/capabilityStatement` Returns a static capability statement. - `packages/cpsuLambda` Handles updating prescription status using a custom format. - `packages/nhsNotifyLambda` Handles sending prescription notifications to the NHS Notify service. +- `packages/postDatedLambda` Handles business logic for post-dated prescriptions getting notifications \[deprecated\]. - `packages/nhsNotifyUpdateCallback` Handles receiving notification updates from the NHS Notify service. - `packages/checkPrescriptionStatusUpdates` Validates and retrieves prescription status update data. - `packages/gsul` Expose data owned by PSU but needed by [PfP](https://github.com/NHSDigital/prescriptionsforpatients) @@ -56,9 +57,9 @@ The contents of this repository are protected by Crown Copyright (C). ## Development -It is recommended that you use visual studio code and a devcontainer as this will install all necessary components and correct versions of tools and languages. -See https://code.visualstudio.com/docs/devcontainers/containers for details on how to set this up on your host machine. -There is also a workspace file in .vscode that should be opened once you have started the devcontainer. The workspace file can also be opened outside of a devcontainer if you wish. +It is recommended that you use visual studio code and a devcontainer as this will install all necessary components and correct versions of tools and languages. +See https://code.visualstudio.com/docs/devcontainers/containers for details on how to set this up on your host machine. +There is also a workspace file in .vscode that should be opened once you have started the devcontainer. The workspace file can also be opened outside of a devcontainer if you wish. The project uses [SAM](https://aws.amazon.com/serverless/sam/) to develop and deploy the APIs and associated resources. All commits must be made using [signed commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits). @@ -239,8 +240,8 @@ Workflows are in the `.github/workflows` folder: ### Github pages -Github pages is used to display deployment information. The source for github pages is in the gh-pages branch. -As part of the ci and release workflows, the release tag (either the short commit SHA or release tag) is appended to \_data/{environment}\_deployments.csv so we have a history of releases and replaced in \_data/{environment}\_latest.csv so we now what the latest released version is. +Github pages is used to display deployment information. The source for github pages is in the gh-pages branch. +As part of the ci and release workflows, the release tag (either the short commit SHA or release tag) is appended to \_data/{environment}\_deployments.csv so we have a history of releases and replaced in \_data/{environment}\_latest.csv so we now what the latest released version is. There are different makefile targets in this branch. These are - `run-jekyll` - runs the site locally so changes can be previewed during development diff --git a/SAMtemplates/functions/main.yaml b/SAMtemplates/functions/main.yaml index 89476fe047..c9a70c294e 100644 --- a/SAMtemplates/functions/main.yaml +++ b/SAMtemplates/functions/main.yaml @@ -33,6 +33,10 @@ Parameters: Type: String Default: none + PostDatedNotificationsSQSQueueUrl: + Type: String + Default: none + SQSSaltSecret: Type: AWS::SecretsManager::Secret::Name @@ -121,6 +125,7 @@ Resources: Variables: TABLE_NAME: !Ref PrescriptionStatusUpdatesTableName NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL: !Ref NHSNotifyPrescriptionsSQSQueueUrl + POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL: !Ref PostDatedNotificationsSQSQueueUrl SQS_SALT: !Ref SQSSaltSecret ENABLED_SITE_ODS_CODES_PARAM: !Ref EnabledSiteODSCodesParam ENABLED_SYSTEMS_PARAM: !Ref EnabledSystemsParam @@ -165,6 +170,7 @@ Resources: - Fn::ImportValue: !Sub ${StackName}:tables:UsePrescriptionStatusUpdatesKMSKeyPolicyArn - Fn::ImportValue: !Sub ${StackName}-UseNotificationSQSQueueKMSKeyPolicyArn - Fn::ImportValue: !Sub ${StackName}-WriteNHSNotifyPrescriptionsSQSQueuePolicyArn + - Fn::ImportValue: !Sub ${StackName}-WritePostDatedNotificationsSQSQueuePolicyArn - Fn::ImportValue: !Sub ${StackName}-GetSQSSaltSecretPolicy - Fn::ImportValue: !Sub ${StackName}-GetNotificationsParameterPolicy LogRetentionInDays: !Ref LogRetentionInDays @@ -489,6 +495,98 @@ Resources: - Fn::ImportValue: !Sub ${StackName}-GetPSUSecretPolicy - Fn::ImportValue: !Sub ${StackName}-UsePSUSecretsKMSKeyPolicyArn + ## Post-dated lambda definitions + PostDatedNotifyLambdaScheduleEventRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - scheduler.amazonaws.com + Action: + - sts:AssumeRole + ManagedPolicyArns: + - !Ref PostDatedNotifyLambdaScheduleEventRolePolicy + + PostDatedNotifyLambdaScheduleEventRolePolicy: + Type: AWS::IAM::ManagedPolicy + Properties: + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - lambda:InvokeFunction + Resource: + - !GetAtt PostDatedNotifyLambda.Arn + + PostDatedNotifyLambda: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Sub ${StackName}-postDatedNotifyLambda + Timeout: 900 + CodeUri: ../../packages/ + Handler: main.handler + Role: !GetAtt PostDatedNotifyLambdaResources.Outputs.LambdaRoleArn + Environment: + Variables: + LOG_LEVEL: !Ref LogLevel + NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL: !Ref NHSNotifyPrescriptionsSQSQueueUrl + POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL: !Ref PostDatedNotificationsSQSQueueUrl + TABLE_NAME: !Ref PrescriptionNotificationStatesTableName + Events: + ScheduleEvent: + Type: ScheduleV2 + Properties: + Name: !Sub ${StackName}-PostDatedNotifySchedule + ScheduleExpression: "rate(3 minutes)" + RoleArn: !GetAtt PostDatedNotifyLambdaScheduleEventRole.Arn + Metadata: + BuildMethod: esbuild + guard: + SuppressedRules: + - LAMBDA_DLQ_CHECK + - LAMBDA_INSIDE_VPC + - LAMBDA_CONCURRENCY_CHECK + BuildProperties: + Minify: true + Target: es2020 + Sourcemap: true + packages: bundle + # set tsconfig path to whatever you actually have for this lambda package + tsconfig: postDatedLambda/tsconfig.json + EntryPoints: + - postDatedLambda/src/main.ts + + PostDatedNotifyLambdaResources: + Type: AWS::Serverless::Application + Properties: + Location: lambda_resources.yaml + Parameters: + StackName: !Ref StackName + LambdaName: !Sub ${StackName}-postDatedNotifyLambda + LambdaArn: !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${StackName}-postDatedNotifyLambda + LogRetentionInDays: !Ref LogRetentionInDays + CloudWatchKMSKeyId: !ImportValue account-resources:CloudwatchLogsKmsKeyArn + EnableSplunk: !Ref EnableSplunk + SplunkSubscriptionFilterRole: !ImportValue lambda-resources:SplunkSubscriptionFilterRole + SplunkDeliveryStreamArn: !ImportValue lambda-resources:SplunkDeliveryStream + IncludeAdditionalPolicies: true + AdditionalPolicies: !Join + - "," + - - Fn::ImportValue: !Sub ${StackName}-WriteNHSNotifyPrescriptionsSQSQueuePolicyArn + - Fn::ImportValue: !Sub ${StackName}-ReadNHSNotifyPrescriptionsSQSQueuePolicyArn + - Fn::ImportValue: !Sub ${StackName}-UseNotificationSQSQueueKMSKeyPolicyArn + - Fn::ImportValue: !Sub ${StackName}-WritePostDatedNotificationsSQSQueuePolicyArn + - Fn::ImportValue: !Sub ${StackName}-ReadPostDatedNotificationsSQSQueuePolicyArn + - Fn::ImportValue: !Sub ${StackName}:tables:${PrescriptionNotificationStatesTableName}:TableReadPolicyArn + - Fn::ImportValue: !Sub ${StackName}:tables:${PrescriptionNotificationStatesTableName}:TableWritePolicyArn + - Fn::ImportValue: !Sub ${StackName}:tables:UsePrescriptionNotificationStatesKMSKeyPolicyArn + ## End of post-dated lambda bits + NHSNotifyUpdateCallback: Type: AWS::Serverless::Function Properties: @@ -719,3 +817,12 @@ Outputs: NHSNotifyUpdateCallbackFunctionArn: Description: The function ARN of the NHSNotifyUpdateCallback lambda Value: !GetAtt NHSNotifyUpdateCallback.Arn + + # Post-dated lambda outputs + PostDatedNotifyLambdaFunctionName: + Description: The function name of the postDatedNotifyLambda + Value: !Ref PostDatedNotifyLambda + + PostDatedNotifyLambdaFunctionArn: + Description: The function ARN of the postDatedNotifyLambda + Value: !GetAtt PostDatedNotifyLambda.Arn diff --git a/SAMtemplates/messaging/main.yaml b/SAMtemplates/messaging/main.yaml index dea4007371..0166186af0 100644 --- a/SAMtemplates/messaging/main.yaml +++ b/SAMtemplates/messaging/main.yaml @@ -102,6 +102,65 @@ Resources: - kms:Decrypt Resource: !GetAtt NHSNotifyPrescriptionsSQSQueue.Arn + ## Post-dated SQS queue starts here + PostDatedNotificationsSQSQueue: + Type: AWS::SQS::Queue + Properties: + QueueName: !Sub ${StackName}-PostDatedNotifications.fifo + FifoQueue: true + ContentBasedDeduplication: false + KmsMasterKeyId: !Ref NotificationSQSQueueKMSKeyAlias + MessageRetentionPeriod: 86400 # 1 day in seconds + RedrivePolicy: + deadLetterTargetArn: !GetAtt PostDatedNotificationsDeadLetterQueue.Arn + maxReceiveCount: 13 # processed every 6 hours for 3 days, plus one for the first time it's placed on queue. + VisibilityTimeout: 300 + + PostDatedNotificationsDeadLetterQueue: + Type: AWS::SQS::Queue + Properties: + QueueName: !Sub ${StackName}-PostDatedNotificationsDeadLetter.fifo + FifoQueue: true + ContentBasedDeduplication: false + KmsMasterKeyId: !Ref NotificationSQSQueueKMSKeyAlias + MessageRetentionPeriod: 604800 # 1 week in seconds + VisibilityTimeout: 300 + + ReadPostDatedNotificationsSQSQueuePolicy: + Type: AWS::IAM::ManagedPolicy + Properties: + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - sqs:ReceiveMessage + - sqs:DeleteMessage + - sqs:ChangeMessageVisibility + - sqs:GetQueueAttributes + - sqs:GetQueueUrl + - kms:GenerateDataKey + - kms:Decrypt + Resource: !GetAtt PostDatedNotificationsSQSQueue.Arn + + WritePostDatedNotificationsSQSQueuePolicy: + Type: AWS::IAM::ManagedPolicy + Properties: + ManagedPolicyName: !Sub ${StackName}-PostDatedNotificationsSendMessagePolicy + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - sqs:SendMessage + - sqs:SendMessageBatch + - sqs:DeleteMessage + - sqs:GetQueueUrl + - kms:GenerateDataKey + - kms:Decrypt + Resource: !GetAtt PostDatedNotificationsSQSQueue.Arn + # End of post-dated stuff + Outputs: NHSNotifyPrescriptionsSQSQueueUrl: Description: The URL of the NHS Notify Prescriptions SQS Queue @@ -138,3 +197,35 @@ Outputs: Value: !Ref UseNotificationSQSQueueKMSKeyPolicy Export: Name: !Sub ${StackName}-UseNotificationSQSQueueKMSKeyPolicyArn + + # Post dated SQS outputs start here + PostDatedNotificationsSQSQueueUrl: + Description: The URL of the Post Dated Notifications SQS Queue + Value: !Ref PostDatedNotificationsSQSQueue + Export: + Name: !Sub ${StackName}-PostDatedNotificationsSQSQueueUrl + + PostDatedNotificationsSQSQueueArn: + Description: The ARN of the Post Dated Notifications SQS Queue + Value: !GetAtt PostDatedNotificationsSQSQueue.Arn + Export: + Name: !Sub ${StackName}-PostDatedNotificationsSQSQueueArn + + PostDatedNotificationsDeadLetterQueueArn: + Description: The ARN of the Post Dated Notifications Dead Letter Queue + Value: !GetAtt PostDatedNotificationsDeadLetterQueue.Arn + Export: + Name: !Sub ${StackName}-PostDatedNotificationsDeadLetterQueueArn + + ReadPostDatedNotificationsSQSQueuePolicyArn: + Description: ARN of policy granting permission to read the post dated notifications queue + Value: !Ref ReadPostDatedNotificationsSQSQueuePolicy + Export: + Name: !Sub ${StackName}-ReadPostDatedNotificationsSQSQueuePolicyArn + + WritePostDatedNotificationsSQSQueuePolicyArn: + Description: ARN of policy granting permission to write to the post dated notifications queue + Value: !Ref WritePostDatedNotificationsSQSQueuePolicy + Export: + Name: !Sub ${StackName}-WritePostDatedNotificationsSQSQueuePolicyArn + # End diff --git a/package-lock.json b/package-lock.json index af333edbe3..5e86c14317 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "packages/cpsuLambda", "packages/checkPrescriptionStatusUpdates", "packages/nhsNotifyLambda", + "packages/postDatedLambda", "packages/psuRestoreValidationLambda", "packages/nhsNotifyUpdateCallback", "packages/common/testing", @@ -333,7 +334,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.958.0.tgz", "integrity": "sha512-bd1Eu4vgas9pLTfqFs0CrWwmrRSucDdVmsoD2bFHngx/Q9uqp/ypo0IsjgPYhZpdLYwJbgfjkn5cW9IjhPIVbw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -1084,7 +1084,6 @@ "version": "7.28.4", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -2807,7 +2806,6 @@ "node_modules/@middy/validator/node_modules/ajv": { "version": "8.17.1", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -2911,7 +2909,6 @@ "version": "1.9.0", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=8.0.0" } @@ -4317,7 +4314,6 @@ "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -4399,7 +4395,6 @@ "integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.51.0", "@typescript-eslint/types": "8.51.0", @@ -4888,7 +4883,6 @@ "version": "8.15.0", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4926,7 +4920,6 @@ "node_modules/ajv": { "version": "6.12.6", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -5151,7 +5144,6 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", "license": "MIT", - "peer": true, "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", @@ -5374,7 +5366,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001741", @@ -6116,7 +6107,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -6175,7 +6165,6 @@ "version": "10.1.8", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -7263,7 +7252,6 @@ "version": "30.2.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "30.2.0", "@jest/types": "30.2.0", @@ -8307,7 +8295,6 @@ "version": "6.15.0", "dev": true, "license": "MIT", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/mobx" @@ -9075,6 +9062,10 @@ "dev": true, "license": "MIT" }, + "node_modules/postDatedLambda": { + "resolved": "packages/postDatedLambda", + "link": true + }, "node_modules/pratica": { "version": "2.3.0", "license": "Apache-2.0" @@ -9276,7 +9267,6 @@ "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -9287,7 +9277,6 @@ "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -10148,7 +10137,6 @@ "version": "6.1.19", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@emotion/is-prop-valid": "1.2.2", "@emotion/unitless": "0.8.1", @@ -10337,7 +10325,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -10466,7 +10453,6 @@ "version": "10.9.2", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -10545,7 +10531,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11269,6 +11254,19 @@ "axios-mock-adapter": "^2.1.0" } }, + "packages/postDatedLambda": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@aws-lambda-powertools/logger": "^2.30.0", + "@middy/core": "^6.4.5", + "@middy/input-output-logger": "^6.4.5", + "@nhs/fhir-middy-error-handler": "^2.1.65" + }, + "devDependencies": { + "@psu-common/testing": "^1.0.0" + } + }, "packages/psuRestoreValidationLambda": { "version": "1.0.0", "license": "MIT", diff --git a/package.json b/package.json index 652c707d4c..512eaa6a24 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "packages/cpsuLambda", "packages/checkPrescriptionStatusUpdates", "packages/nhsNotifyLambda", + "packages/postDatedLambda", "packages/psuRestoreValidationLambda", "packages/nhsNotifyUpdateCallback", "packages/common/testing", diff --git a/packages/common/commonTypes/src/index.ts b/packages/common/commonTypes/src/index.ts index 557f1ff257..c3d79bffbc 100644 --- a/packages/common/commonTypes/src/index.ts +++ b/packages/common/commonTypes/src/index.ts @@ -13,6 +13,7 @@ export interface PSUDataItem { ExpiryTime: number // (Optional, legacy batch-processors only) Indicates that {@link LastModified} is postdated; // contains the ISO 8601 timestamp when the postdated update was set. + // todo: This field needs to be passed to the sqs message for post-dated updates PostDatedLastModifiedSetAt?: string } @@ -24,6 +25,12 @@ export interface NotifyDataItem { Status: string } +// FIXME: This should be removed when we stop supporting post-dated updates +export interface PostDatedNotifyDataItem extends NotifyDataItem { + LastModified: string + LastUpdated: string // This is the meta.lastUpdated field from the FHIR resource +} + /** * The fields stored in the Notifications table potentially updated by the Notify callback. */ diff --git a/packages/postDatedLambda/.jest/setEnvVars.js b/packages/postDatedLambda/.jest/setEnvVars.js new file mode 100644 index 0000000000..a82f1bb0a1 --- /dev/null +++ b/packages/postDatedLambda/.jest/setEnvVars.js @@ -0,0 +1,4 @@ +/* eslint-disable no-undef */ +process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL = "dummy_notify_sqs"; +process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = "dummy_post_dated_sqs"; +process.env.AWS_REGION = "eu-west-2"; diff --git a/packages/postDatedLambda/.vscode/launch.json b/packages/postDatedLambda/.vscode/launch.json new file mode 100644 index 0000000000..7c9b0b4b3a --- /dev/null +++ b/packages/postDatedLambda/.vscode/launch.json @@ -0,0 +1,35 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "name": "vscode-jest-tests.v2", + "request": "launch", + "args": [ + "--runInBand", + "--watchAll=false", + "--testNamePattern", + "${jest.testNamePattern}", + "--runTestsByPath", + "${jest.testFile}", + "--config", + "${workspaceFolder}/jest.debug.config.ts" + ], + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "disableOptimisticBPs": true, + "program": "${workspaceFolder}/../../node_modules/.bin/jest", + "windows": { + "program": "${workspaceFolder}/node_modules/jest/bin/jest" + }, + "env": { + "POWERTOOLS_DEV": true, + "NODE_OPTIONS": "--experimental-vm-modules" + } + } + ] +} diff --git a/packages/postDatedLambda/.vscode/settings.json b/packages/postDatedLambda/.vscode/settings.json new file mode 100644 index 0000000000..3501264944 --- /dev/null +++ b/packages/postDatedLambda/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "jest.jestCommandLine": "/workspaces/eps-prescription-status-update-api/node_modules/.bin/jest --no-cache", + "jest.nodeEnv": { + "POWERTOOLS_DEV": true, + "NODE_OPTIONS": "--experimental-vm-modules" + } +} diff --git a/packages/postDatedLambda/jest.config.ts b/packages/postDatedLambda/jest.config.ts new file mode 100644 index 0000000000..32720a510e --- /dev/null +++ b/packages/postDatedLambda/jest.config.ts @@ -0,0 +1,10 @@ +import type {JestConfigWithTsJest} from "ts-jest" +import defaultConfig from "../../jest.default.config" + +const jestConfig: JestConfigWithTsJest = { + ...defaultConfig, + rootDir: "./", + setupFiles: ["/.jest/setEnvVars.js"] +} + +export default jestConfig diff --git a/packages/postDatedLambda/jest.debug.config.ts b/packages/postDatedLambda/jest.debug.config.ts new file mode 100644 index 0000000000..a306273831 --- /dev/null +++ b/packages/postDatedLambda/jest.debug.config.ts @@ -0,0 +1,9 @@ +import config from "./jest.config" +import type {JestConfigWithTsJest} from "ts-jest" + +const debugConfig: JestConfigWithTsJest = { + ...config, + "preset": "ts-jest" +} + +export default debugConfig diff --git a/packages/postDatedLambda/package.json b/packages/postDatedLambda/package.json new file mode 100644 index 0000000000..354d3d93b9 --- /dev/null +++ b/packages/postDatedLambda/package.json @@ -0,0 +1,26 @@ +{ + "name": "postDatedLambda", + "version": "1.0.0", + "description": "A lambda that handles the post-dated prescription logic for the notifications service", + "main": "main.js", + "author": "NHS Digital", + "license": "MIT", + "type": "module", + "scripts": { + "unit": "POWERTOOLS_DEV=true NODE_OPTIONS=--experimental-vm-modules jest --no-cache --coverage", + "lint": "eslint --max-warnings 0 --fix --config ../../eslint.config.mjs .", + "compile": "tsc", + "test": "npm run compile && npm run unit", + "check-licenses": "license-checker --failOn GPL --failOn LGPL --start ../..", + "cli:test-token": "npm run compile && node lib/cli/test-token-exchange.js" + }, + "dependencies": { + "@aws-lambda-powertools/logger": "^2.30.0", + "@middy/core": "^6.4.5", + "@middy/input-output-logger": "^6.4.5", + "@nhs/fhir-middy-error-handler": "^2.1.65" + }, + "devDependencies": { + "@psu-common/testing": "^1.0.0" + } +} diff --git a/packages/postDatedLambda/src/main.ts b/packages/postDatedLambda/src/main.ts new file mode 100644 index 0000000000..527930f6fd --- /dev/null +++ b/packages/postDatedLambda/src/main.ts @@ -0,0 +1,25 @@ +import {EventBridgeEvent} from "aws-lambda" +import {Logger} from "@aws-lambda-powertools/logger" +import {injectLambdaContext} from "@aws-lambda-powertools/logger/middleware" + +import middy from "@middy/core" +import inputOutputLogger from "@middy/input-output-logger" +import errorHandler from "@nhs/fhir-middy-error-handler" + +const logger = new Logger({serviceName: "postDatedLambda"}) + +/** + * Handler for the scheduled EventBridge trigger. + */ +export const lambdaHandler = async ( + event: EventBridgeEvent +): Promise => { + logger.info("Post-dated handling lambda triggered by scheduler", {event}) +} + +export const handler = middy(lambdaHandler) + .use(injectLambdaContext(logger, {clearState: true})) + .use( + inputOutputLogger({logger: (req) => logger.info(req)}) + ) + .use(errorHandler({logger})) diff --git a/packages/postDatedLambda/tests/main.test.ts b/packages/postDatedLambda/tests/main.test.ts new file mode 100644 index 0000000000..f973c46c87 --- /dev/null +++ b/packages/postDatedLambda/tests/main.test.ts @@ -0,0 +1,30 @@ +import { + jest, + describe, + it, + beforeAll, + afterEach +} from "@jest/globals" + +let lambdaHandler: typeof import("../src/main").lambdaHandler +beforeAll(async () => { + ({lambdaHandler} = await import("../src/main")) +}) + +import {mockEventBridgeEvent} from "@psu-common/testing" + +const ORIGINAL_ENV = {...process.env} + +describe("Unit test for NHS Notify lambda handler", () => { + afterEach(() => { + process.env = {...ORIGINAL_ENV} + + jest.clearAllMocks() + jest.restoreAllMocks() + }) + + it("should run the lambda handler successfully", async () => { + await expect(lambdaHandler(mockEventBridgeEvent)).resolves.toBeUndefined() + }) + +}) diff --git a/packages/postDatedLambda/tsconfig.json b/packages/postDatedLambda/tsconfig.json new file mode 100644 index 0000000000..7e1ad6b35c --- /dev/null +++ b/packages/postDatedLambda/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.defaults.json", + "compilerOptions": { + "rootDir": ".", + "outDir": "lib" + }, + "references": [ + {"path": "../common/testing"} + ], + "include": ["src/**/*", "tests/**/*", "cli/**/*"], + "exclude": ["node_modules"] +} diff --git a/packages/updatePrescriptionStatus/src/updatePrescriptionStatus.ts b/packages/updatePrescriptionStatus/src/updatePrescriptionStatus.ts index 5b2d8e200f..740d5dfa18 100644 --- a/packages/updatePrescriptionStatus/src/updatePrescriptionStatus.ts +++ b/packages/updatePrescriptionStatus/src/updatePrescriptionStatus.ts @@ -385,7 +385,7 @@ export function buildDataItems( } if (task.meta?.lastUpdated) { - (dataItem as any).PostDatedLastModifiedSetAt = task.meta.lastUpdated + dataItem.PostDatedLastModifiedSetAt = task.meta.lastUpdated } dataItems.push(dataItem) diff --git a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts index 2bde5b44e3..9dbb23bc1b 100644 --- a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts +++ b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts @@ -170,6 +170,8 @@ export async function pushPrescriptionToNotificationSQS( {nhsNumbers: allowedSitesAndSystemsData.map(e => e.current.PatientNHSNumber)} ) + // TODO: break out items that have a populated PostDatedLastModified field, and push to the alternative SQS queue + // SQS batch calls are limited to 10 messages per request, so chunk the data const batches = chunkArray(allEntries, 10) diff --git a/sonar-project.properties b/sonar-project.properties index c323354646..815621e84a 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -53,6 +53,10 @@ nhsNotifyLambda.sonar.projectBaseDir=packages/nhsNotifyLambda nhsNotifyLambda.sonar.sources=. nhsNotifyLambda.sonar.javascript.lcov.reportPaths=coverage/lcov.info +postDatedLambda.sonar.projectBaseDir=packages/postDatedLambda +postDatedLambda.sonar.sources=. +postDatedLambda.sonar.javascript.lcov.reportPaths=coverage/lcov.info + nhsNotifyUpdateCallback.sonar.projectBaseDir=packages/nhsNotifyUpdateCallback nhsNotifyUpdateCallback.sonar.sources=. nhsNotifyUpdateCallback.sonar.javascript.lcov.reportPaths=coverage/lcov.info diff --git a/tsconfig.build.json b/tsconfig.build.json index decbcf115a..479e9c0313 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -4,18 +4,47 @@ "files": [], // Building this project will build all of the following: "references": [ - {"path": "packages/common/utilities"}, - {"path": "packages/common/testing"}, - {"path": "packages/common/middyErrorHandler"}, - {"path": "packages/common/commonTypes"}, - {"path": "packages/gsul"}, - {"path": "packages/updatePrescriptionStatus"}, - {"path": "packages/nhsd-psu-sandbox"}, - {"path": "packages/statusLambda"}, - {"path": "packages/capabilityStatement"}, - {"path": "packages/cpsuLambda"}, - {"path": "packages/checkPrescriptionStatusUpdates"}, - {"path": "packages/nhsNotifyLambda"}, - {"path": "packages/nhsNotifyUpdateCallback"} + { + "path": "packages/common/utilities" + }, + { + "path": "packages/common/testing" + }, + { + "path": "packages/common/middyErrorHandler" + }, + { + "path": "packages/common/commonTypes" + }, + { + "path": "packages/gsul" + }, + { + "path": "packages/updatePrescriptionStatus" + }, + { + "path": "packages/nhsd-psu-sandbox" + }, + { + "path": "packages/statusLambda" + }, + { + "path": "packages/capabilityStatement" + }, + { + "path": "packages/cpsuLambda" + }, + { + "path": "packages/checkPrescriptionStatusUpdates" + }, + { + "path": "packages/nhsNotifyLambda" + }, + { + "path": "packages/postDatedLambda" + }, + { + "path": "packages/nhsNotifyUpdateCallback" + } ] } From 597059e3214a99bb7334b34ae60d3947c1b14d3a Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 8 Jan 2026 16:24:58 +0000 Subject: [PATCH 02/75] Update the PSU lambda to put post-dated data on the post-dated SQS --- .../src/utils/sqsClient.ts | 120 ++++++++++++------ 1 file changed, 83 insertions(+), 37 deletions(-) diff --git a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts index 9dbb23bc1b..2a3543affc 100644 --- a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts +++ b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts @@ -8,7 +8,17 @@ import {NotifyDataItem, PSUDataItemWithPrevious} from "@psu-common/commonTypes" import {checkSiteOrSystemIsNotifyEnabled} from "../validation/notificationSiteAndSystemFilters" +type SQSBatchMessage = { + Id: string + MessageBody: string + MessageDeduplicationId: string + MessageGroupId: string + MessageAttributes: {[key: string]: {DataType: string; StringValue: string}} +} + const sqsUrl: string | undefined = process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL +const postDatedSqsUrl: string | undefined = process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL + const fallbackSalt = "DEV SALT" // The AWS_REGION is always defined in lambda environments @@ -90,6 +100,52 @@ export async function getSaltValue(logger: Logger): Promise { return sqsSalt } +async function placeBatchInSQS( + batches: Array>, + sqsUrl: string, + requestId: string, + logger: Logger +) { + + // Used for the return value + let out: Array = [] + + for (const batch of batches) { + try { + logger.info( + "Pushing a batch of notification requests to SQS", + { + batchLength: batch.length, + deduplicationIds: batch.map(e => e.MessageDeduplicationId), + requestId + } + ) + + const command = new SendMessageBatchCommand({ + QueueUrl: sqsUrl, + Entries: batch + }) + const result = await sqs.send(command) + if (result.Successful?.length) { + logger.info("Successfully sent a batch of prescriptions to the notifications SQS", {result}) + + // For each successful message, get its message ID. I don't think there will ever be undefined + // actually in here, but the typing suggests that there could be so filter those out + out.push(...result.Successful.map(e => e.MessageId).filter(msg_id => msg_id !== undefined)) + } + // Some may succeed, and some may fail. So check for both + if (result.Failed?.length) { + throw new Error("Failed to send a batch of prescriptions to the notifications SQS") + } + } catch (error) { + logger.error("Failed to send a batch of prescriptions to the notifications SQS", {error}) + throw error + } + } + + return out +} + /** * Pushes an array of PSUDataItem to the notifications SQS queue * Uses SendMessageBatch to send up to 10 at a time @@ -112,6 +168,11 @@ export async function pushPrescriptionToNotificationSQS( throw new Error("Notifications SQS URL not configured") } + if (!postDatedSqsUrl) { + logger.warn("Post-dated Notifications SQS URL not found in environment variables") + throw new Error("Post-dated Notifications SQS URL not configured") + } + // Only allow through sites and systems that are allowedSitesAndSystems const allowedSitesAndSystemsData = await checkSiteOrSystemIsNotifyEnabled(data) @@ -142,7 +203,7 @@ export async function pushPrescriptionToNotificationSQS( .map(({current}) => current) // Build SQS batch entries with FIFO parameters - const allEntries = changedStatus + const allEntries: Array = changedStatus .map((item, idx) => ({ Id: idx.toString(), // Only post the required information to SQS @@ -170,45 +231,30 @@ export async function pushPrescriptionToNotificationSQS( {nhsNumbers: allowedSitesAndSystemsData.map(e => e.current.PatientNHSNumber)} ) - // TODO: break out items that have a populated PostDatedLastModified field, and push to the alternative SQS queue - - // SQS batch calls are limited to 10 messages per request, so chunk the data - const batches = chunkArray(allEntries, 10) - - // Used for the return value - let out: Array = [] + // Check for post-dated items AFTER building the entries (even though we have to do a json parse) + // so that all entries are definitely built the same way. + // Don't do any checking on the VALUE of the field here. + const allPostDated = allEntries.filter((entry) => { + const body: NotifyDataItem = JSON.parse(entry.MessageBody) + return "PostDatedLastModifiedSetAt" in body + }) - for (const batch of batches) { - try { - logger.info( - "Pushing a batch of notification requests to SQS", - { - batchLength: batch.length, - deduplicationIds: batch.map(e => e.MessageDeduplicationId), - requestId - } - ) + // Remove post-dated entries from the normal flow + const currentlyValidEntries = allEntries.filter(entry => { + const body: NotifyDataItem = JSON.parse(entry.MessageBody) + return !("PostDatedLastModifiedSetAt" in body) + }) - const command = new SendMessageBatchCommand({ - QueueUrl: sqsUrl, - Entries: batch - }) - const result = await sqs.send(command) - if (result.Successful?.length) { - logger.info("Successfully sent a batch of prescriptions to the notifications SQS", {result}) + // SQS batch calls are limited to 10 messages per request, so chunk the data + const batches = chunkArray(currentlyValidEntries, 10) + const out = await placeBatchInSQS(batches, sqsUrl, requestId, logger) - // For each successful message, get its message ID. I don't think there will ever be undefined - // actually in here, but the typing suggests that there could be so filter those out - out.push(...result.Successful.map(e => e.MessageId).filter(msg_id => msg_id !== undefined)) - } - // Some may succeed, and some may fail. So check for both - if (result.Failed?.length) { - throw new Error("Failed to send a batch of prescriptions to the notifications SQS") - } - } catch (error) { - logger.error("Failed to send a batch of prescriptions to the notifications SQS", {error}) - throw error - } + // Then, also do the post-dated entries if any + if (allPostDated.length) { + logger.info(`Also placing ${allPostDated.length} post-dated entries into the post-dated SQS queue`) + const postDatedBatches = chunkArray(allPostDated, 10) + const postDatedOut = await placeBatchInSQS(postDatedBatches, postDatedSqsUrl, requestId, logger) + out.push(...postDatedOut) // Their results are returned as usual } return out From 3cda946cf4f0ba42f88add2856d78f7bcce47b4a Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 8 Jan 2026 16:43:30 +0000 Subject: [PATCH 03/75] Add env var for sqs tests --- packages/updatePrescriptionStatus/.jest/setEnvVars.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/updatePrescriptionStatus/.jest/setEnvVars.js b/packages/updatePrescriptionStatus/.jest/setEnvVars.js index 2547377af2..a9a92250b6 100644 --- a/packages/updatePrescriptionStatus/.jest/setEnvVars.js +++ b/packages/updatePrescriptionStatus/.jest/setEnvVars.js @@ -1,5 +1,6 @@ /* eslint-disable no-undef */ process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL = "dummy_notify_sqs"; +process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = "dummy_postdated_sqs"; process.env.AWS_REGION = "eu-west-2"; process.env.SQS_SALT = "the quick brown fox something something" process.env.ENABLED_SITE_ODS_CODES_PARAM = "ENABLED_SITE_ODS_CODES_PARAM" From fe140ab9a144bda7792e41061ae665835502a90d Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Wed, 21 Jan 2026 13:10:05 +0000 Subject: [PATCH 04/75] Implement reading the SQS messages in the post dated lambda --- packages/postDatedLambda/src/businessLogic.ts | 25 ++ packages/postDatedLambda/src/main.ts | 9 + .../postDatedLambda/src/queueProcessing.ts | 116 ++++++++ packages/postDatedLambda/src/sqs.ts | 256 ++++++++++++++++++ packages/postDatedLambda/src/types.ts | 26 ++ packages/postDatedLambda/tests/main.test.ts | 26 +- .../src/utils/sqsClient.ts | 2 +- 7 files changed, 458 insertions(+), 2 deletions(-) create mode 100644 packages/postDatedLambda/src/businessLogic.ts create mode 100644 packages/postDatedLambda/src/queueProcessing.ts create mode 100644 packages/postDatedLambda/src/sqs.ts create mode 100644 packages/postDatedLambda/src/types.ts diff --git a/packages/postDatedLambda/src/businessLogic.ts b/packages/postDatedLambda/src/businessLogic.ts new file mode 100644 index 0000000000..ce12ae2a37 --- /dev/null +++ b/packages/postDatedLambda/src/businessLogic.ts @@ -0,0 +1,25 @@ +import {Logger} from "@aws-lambda-powertools/logger" + +import {PostDatedSQSMessage} from "./types" + +/** + * Process a single post-dated prescription message. + * This is a placeholder function that I'll implement properly later. + * + * @param logger - The AWS Lambda Powertools logger instance + * @param message - The SQS message containing post-dated prescription data + * @returns Promise - true if processing succeeded, false if it failed + */ +export async function processMessage( + logger: Logger, + message: PostDatedSQSMessage +): Promise { + logger.info("Processing post-dated prescription message (dummy)", { + messageId: message.MessageId, + prescriptionData: message.prescriptionData + }) + + // TODO: Implement actual business logic for post-dated prescription processing + + return true +} diff --git a/packages/postDatedLambda/src/main.ts b/packages/postDatedLambda/src/main.ts index 527930f6fd..b0711e3cfe 100644 --- a/packages/postDatedLambda/src/main.ts +++ b/packages/postDatedLambda/src/main.ts @@ -6,6 +6,9 @@ import middy from "@middy/core" import inputOutputLogger from "@middy/input-output-logger" import errorHandler from "@nhs/fhir-middy-error-handler" +import {reportQueueStatus} from "./sqs" +import {processPostDatedQueue} from "./queueProcessing" + const logger = new Logger({serviceName: "postDatedLambda"}) /** @@ -15,6 +18,12 @@ export const lambdaHandler = async ( event: EventBridgeEvent ): Promise => { logger.info("Post-dated handling lambda triggered by scheduler", {event}) + + // Report queue status *before* processing + await reportQueueStatus(logger) + + // work through the queue + await processPostDatedQueue(logger) } export const handler = middy(lambdaHandler) diff --git a/packages/postDatedLambda/src/queueProcessing.ts b/packages/postDatedLambda/src/queueProcessing.ts new file mode 100644 index 0000000000..85f01d875e --- /dev/null +++ b/packages/postDatedLambda/src/queueProcessing.ts @@ -0,0 +1,116 @@ +import {Logger} from "@aws-lambda-powertools/logger" + +import {processMessage} from "./businessLogic" +import { + removeSQSMessages, + returnMessagesToQueue, + receivePostDatedSQSMessages, + reportQueueStatus +} from "./sqs" +import {BatchProcessingResult, PostDatedSQSMessage} from "./types" + +const MAX_QUEUE_RUNTIME = 14 * 60 * 1000 // 14 minutes, to avoid Lambda timeout issues (timeout is 15 minutes) +const BATCH_SIZE = 10 +const FAILED_MESSAGE_VISIBILITY_TIMEOUT = 300 // 5 minutes in seconds + +/** + * Process a batch of SQS messages. + * Messages are processed individually, and results are tracked for success/failure handling. + * + * @param messages - Array of messages to process + * @returns Object containing arrays of successful and failed messages + */ +export async function processMessages( + messages: Array, + logger: Logger +): Promise { + if (messages.length === 0) { + logger.info("No messages to process in batch") + return {successful: [], failed: []} + } + + const successful: Array = [] + const failed: Array = [] + + for (const message of messages) { + try { + const success = await processMessage(logger, message) + if (success) { + successful.push(message) + } else { + failed.push(message) + } + } catch (error) { + logger.error("Error processing message", { + messageId: message.MessageId, + error + }) + failed.push(message) + } + } + + logger.info("Batch processing complete", { + totalMessages: messages.length, + successfulCount: successful.length, + failedCount: failed.length + }) + + return {successful, failed} +} + +/** + * Handle the results of message processing: + * - Delete successful messages from the queue + * - Return failed messages to the queue with a visibility timeout + * Does not alter the input result object, only performs side effects. + * + * @param result - The batch processing result + */ +export async function handleProcessedMessages( + result: BatchProcessingResult, + logger: Logger +): Promise { + const {successful, failed} = result + + // Delete successful messages + if (successful.length > 0) { + await removeSQSMessages(logger, successful) + } + + // Return failed messages to the queue with a 5 minute timeout + if (failed.length > 0) { + await returnMessagesToQueue(logger, failed, FAILED_MESSAGE_VISIBILITY_TIMEOUT) + } +} + +/** + * Drain the queue until empty or the MAX_QUEUE_RUNTIME has passed. + * Messages are processed in batches of 10. + */ +export async function processPostDatedQueue(logger: Logger): Promise { + const start = Date.now() + let empty = false + + while (!empty) { + // Lambdas can only run for so long, so guard against that + if (Date.now() - start >= MAX_QUEUE_RUNTIME) { + logger.warn("processPostDatedQueue timed out; exiting before queue is empty. Will report queue status", { + maxRuntimeMilliseconds: MAX_QUEUE_RUNTIME + }) + reportQueueStatus(logger) + break + } + + const {messages, isEmpty} = await receivePostDatedSQSMessages(logger, BATCH_SIZE) + empty = isEmpty + + if (messages.length === 0) { + logger.info("No messages retrieved from queue") + continue + } + + // Process messages for this batch + const result = await processMessages(messages, logger) + return await handleProcessedMessages(result, logger) + } +} diff --git a/packages/postDatedLambda/src/sqs.ts b/packages/postDatedLambda/src/sqs.ts new file mode 100644 index 0000000000..cd5f6d49c3 --- /dev/null +++ b/packages/postDatedLambda/src/sqs.ts @@ -0,0 +1,256 @@ +import { + SQSClient, + ReceiveMessageCommand, + DeleteMessageBatchCommand, + ChangeMessageVisibilityBatchCommand, + GetQueueAttributesCommand, + Message +} from "@aws-sdk/client-sqs" +import {Logger} from "@aws-lambda-powertools/logger" + +import {PostDatedNotifyDataItem} from "@psu-common/commonTypes" + +import {PostDatedSQSMessage, ReceivedPostDatedSQSResult} from "./types" + +const sqs = new SQSClient({region: process.env.AWS_REGION}) + +/** + * Get the SQS queue URL from environment variables. + * Throws an error if not configured. + */ +function getQueueUrl(logger: Logger): string { + const sqsUrl = process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL + if (!sqsUrl) { + logger.error("Post-dated prescriptions SQS URL not configured") + throw new Error("POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL not set") + } + return sqsUrl +} + +/** + * Report the current status of the post-dated prescriptions SQS queue. + * + * @param logger - The AWS logging object + */ +export async function reportQueueStatus(logger: Logger): Promise { + const sqsUrl = getQueueUrl(logger) + + const attrsCmd = new GetQueueAttributesCommand({ + QueueUrl: sqsUrl, + AttributeNames: [ + "ApproximateNumberOfMessages", + "ApproximateNumberOfMessagesNotVisible", + "ApproximateNumberOfMessagesDelayed" + ] + }) + const {Attributes} = await sqs.send(attrsCmd) + + // Fall back to a negative value so missing data can be identified + const ApproximateNumberOfMessages = Number.parseInt(Attributes?.ApproximateNumberOfMessages ?? "-1") + const ApproximateNumberOfMessagesNotVisible = Number.parseInt( + Attributes?.ApproximateNumberOfMessagesNotVisible ?? "-1" + ) + const ApproximateNumberOfMessagesDelayed = Number.parseInt(Attributes?.ApproximateNumberOfMessagesDelayed ?? "-1") + + logger.info( + "Current post-dated queue attributes (if a value failed to fetch, it will be reported as -1):", + { + ApproximateNumberOfMessages, + ApproximateNumberOfMessagesNotVisible, + ApproximateNumberOfMessagesDelayed + } + ) +} + +/** + * Pulls up to `maxTotal` messages off the queue (in batches of up to 10) and bundles them together. + * + * @param logger - The AWS logging object + * @param maxTotal - The maximum number of messages to fetch. Guaranteed to be less than this. + * @param small_batch_threshold - If a batch returns fewer than this number of messages, stop polling further. + * @returns + * - messages: the array of parsed PostDatedSQSMessage + * - isEmpty: true if the last receive returned fewer than 5 messages (or none), + * indicating the queue is effectively drained. + */ +export async function receivePostDatedSQSMessages( + logger: Logger, + maxTotal = 10, + small_batch_threshold = 5 +): Promise { + // eslint-disable-next-line max-len + // TODO: This is borrowed from the notify lambda, but here it is not necessary to process 100 messages at a time - 10 is enough. Consider refactoring this function away. + const sqsUrl = getQueueUrl(logger) + + const allMessages: Array = [] + let receivedSoFar = 0 + let isEmpty = false + let pollingIteration = 0 + + while (receivedSoFar < maxTotal) { + pollingIteration = pollingIteration + 1 + + const toFetch = Math.min(10, maxTotal - receivedSoFar) + const receiveCmd = new ReceiveMessageCommand({ + QueueUrl: sqsUrl, + MaxNumberOfMessages: toFetch, + // Use long polling to avoid getting empty responses when the queue is small + WaitTimeSeconds: 20, + MessageAttributeNames: ["All"] + }) + + const {Messages} = await sqs.send(receiveCmd) + + // if the queue is now empty, then break the loop + if (!Messages || Messages.length === 0) { + isEmpty = true + logger.info("No messages received; marking queue as empty", {pollingIteration}) + break + } + + logger.info( + "Received some messages from the post-dated queue. Parsing them...", + { + pollingIteration, + MessageIDs: Messages.map((m) => m.MessageId) + } + ) + + // Parse and validate messages + const parsedMessages: Array = Messages.flatMap((m) => { + if (!m.Body) { + logger.error( + "Received an invalid SQS message (missing Body) - omitting from processing.", + {offendingMessage: m} + ) + return [] + } + try { + const parsedBody: PostDatedNotifyDataItem = JSON.parse(m.Body) + return [ + { + ...m, + prescriptionData: parsedBody + } + ] + } catch (error) { + logger.error( + "Failed to parse SQS message body as JSON - omitting from processing.", + {offendingMessage: m, parseError: error} + ) + return [] + } + }) + + allMessages.push(...parsedMessages) + receivedSoFar += parsedMessages.length + + // if the last batch of messages was small, then break the loop + // This is to prevent a slow-loris style breakdown if the queue has + // barely enough messages to keep the processors alive + if (Messages.length < small_batch_threshold) { + isEmpty = true + logger.info( + "Received a small number of messages. Considering the queue drained.", + {batchLength: Messages.length, small_batch_threshold, pollingIteration} + ) + break + } + } + + logger.info(`In sum, retrieved ${allMessages.length} messages from post-dated SQS`, + {MessageIDs: allMessages.map(el => el.MessageId)} + ) + + return {messages: allMessages, isEmpty} +} + +/** + * Delete successfully processed messages from the SQS queue. + * + * @param logger - The logging object + * @param messages - The messages that were successfully processed and should be deleted + */ +export async function removeSQSMessages( + logger: Logger, + messages: Array +): Promise { + if (messages.length === 0) { + logger.info("No messages to delete") + return + } + + const sqsUrl = getQueueUrl(logger) + + const entries = messages.map((m) => ({ + Id: m.MessageId!, + ReceiptHandle: m.ReceiptHandle! + })) + + logger.info("Deleting messages from SQS", { + numberOfMessages: entries.length, + messageIds: entries.map((e) => e.Id) + }) + + const deleteCmd = new DeleteMessageBatchCommand({ + QueueUrl: sqsUrl, + Entries: entries + }) + const delResult = await sqs.send(deleteCmd) + + if (delResult.Failed && delResult.Failed.length > 0) { + logger.error("Some messages failed to delete", {failed: delResult.Failed}) + } else { + logger.info("Successfully deleted SQS messages", { + result: delResult, + messageIds: entries.map((e) => e.Id) + }) + } +} + +/** + * Return failed messages to the queue with a visibility timeout. + * This makes the messages invisible for the specified duration before they can be processed again. + * + * @param logger - The logging object + * @param messages - The messages that failed processing and should be returned to the queue + * @param visibilityTimeoutSeconds - The time in seconds before messages become visible again (default: 300 = 5 minutes) + */ +export async function returnMessagesToQueue( + logger: Logger, + messages: Array, + visibilityTimeoutSeconds = 300 +): Promise { + if (messages.length === 0) { + logger.info("No failed messages to return to queue") + return + } + + const sqsUrl = getQueueUrl(logger) + + const entries = messages.map((m) => ({ + Id: m.MessageId!, + ReceiptHandle: m.ReceiptHandle!, + VisibilityTimeout: visibilityTimeoutSeconds + })) + + logger.info( + `Returning messages to queue with ${visibilityTimeoutSeconds}s timeout`, + {numberOfMessages: entries.length, messageIds: entries.map((e) => e.Id)} + ) + + const changeVisibilityCmd = new ChangeMessageVisibilityBatchCommand({ + QueueUrl: sqsUrl, + Entries: entries + }) + const result = await sqs.send(changeVisibilityCmd) + + if (result.Failed && result.Failed.length > 0) { + logger.error("Some messages failed to have visibility changed in this batch", {failed: result.Failed}) + } else { + logger.info("Successfully returned SQS messages to queue", { + result: result, + messageIds: entries.map((e) => e.Id) + }) + } +} diff --git a/packages/postDatedLambda/src/types.ts b/packages/postDatedLambda/src/types.ts new file mode 100644 index 0000000000..2c86e8433f --- /dev/null +++ b/packages/postDatedLambda/src/types.ts @@ -0,0 +1,26 @@ +import {Message} from "@aws-sdk/client-sqs" +import {PostDatedNotifyDataItem} from "@psu-common/commonTypes" + +/** + * Extended SQS message interface for post-dated prescription messages. + * Contains the parsed prescription data from the message body. + */ +export interface PostDatedSQSMessage extends Message { + prescriptionData: PostDatedNotifyDataItem +} + +/** + * Result of processing a batch of messages. + */ +export interface BatchProcessingResult { + successful: Array + failed: Array +} + +/** + * Result of draining the queue. + */ +export interface ReceivedPostDatedSQSResult { + messages: Array + isEmpty: boolean +} diff --git a/packages/postDatedLambda/tests/main.test.ts b/packages/postDatedLambda/tests/main.test.ts index f973c46c87..3fd87596f0 100644 --- a/packages/postDatedLambda/tests/main.test.ts +++ b/packages/postDatedLambda/tests/main.test.ts @@ -6,6 +6,24 @@ import { afterEach } from "@jest/globals" +const mockReportQueueStatus = jest.fn() +jest.unstable_mockModule( + "../src/sqs", + async () => ({ + __esModule: true, + reportQueueStatus: mockReportQueueStatus + }) +) + +const mockDrainAndProcess = jest.fn() +jest.unstable_mockModule( + "../src/queueProcessing", + async () => ({ + __esModule: true, + drainAndProcess: mockDrainAndProcess + }) +) + let lambdaHandler: typeof import("../src/main").lambdaHandler beforeAll(async () => { ({lambdaHandler} = await import("../src/main")) @@ -15,7 +33,7 @@ import {mockEventBridgeEvent} from "@psu-common/testing" const ORIGINAL_ENV = {...process.env} -describe("Unit test for NHS Notify lambda handler", () => { +describe("Unit test for post-dated lambda handler", () => { afterEach(() => { process.env = {...ORIGINAL_ENV} @@ -24,7 +42,13 @@ describe("Unit test for NHS Notify lambda handler", () => { }) it("should run the lambda handler successfully", async () => { + mockReportQueueStatus.mockImplementation(() => Promise.resolve()) + mockDrainAndProcess.mockImplementation(() => Promise.resolve()) + await expect(lambdaHandler(mockEventBridgeEvent)).resolves.toBeUndefined() + + expect(mockReportQueueStatus).toHaveBeenCalledTimes(1) + expect(mockDrainAndProcess).toHaveBeenCalledTimes(1) }) }) diff --git a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts index 2a3543affc..93617bd714 100644 --- a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts +++ b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts @@ -251,7 +251,7 @@ export async function pushPrescriptionToNotificationSQS( // Then, also do the post-dated entries if any if (allPostDated.length) { - logger.info(`Also placing ${allPostDated.length} post-dated entries into the post-dated SQS queue`) + logger.info(`Placing ${allPostDated.length} post-dated entries into the post-dated SQS queue`) const postDatedBatches = chunkArray(allPostDated, 10) const postDatedOut = await placeBatchInSQS(postDatedBatches, postDatedSqsUrl, requestId, logger) out.push(...postDatedOut) // Their results are returned as usual From 0eeaa97db3b13bc833b50362acfc9113ab231f24 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Wed, 21 Jan 2026 14:45:53 +0000 Subject: [PATCH 05/75] Update package lock --- package-lock.json | 59 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 58d95fef65..c843b87623 100644 --- a/package-lock.json +++ b/package-lock.json @@ -386,7 +386,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.970.0.tgz", "integrity": "sha512-nMM0eeVuiLtw1taLRQ+H/H5Qp11rva8ILrzAQXSvlbDeVmbc7d8EeW5Q2xnCJu+3U+2JNZ1uxqIL22pB2sLEMA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/credential-provider-env": "3.970.0", "@aws-sdk/credential-provider-http": "3.970.0", @@ -410,7 +409,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.969.0.tgz", "integrity": "sha512-zIx4JkXALkiRHH37uYGvf72Syzl3EA+LESTLbSYZT9gpJ6J14/Vnk1YeRqY6zCe2ncPIl0pHicseUgctGsQGsQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/endpoint-cache": "3.968.0", "@aws-sdk/types": "3.969.0", @@ -428,7 +426,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.970.0.tgz", "integrity": "sha512-TNQpwIVD6SxMwkD+QKnaujKVyXy5ljN3O3jrI7nCHJ3GlJu5xJrd8yuBnanYCcrn3e2zwdfOh4d4zJAZvvIvVw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/middleware-user-agent": "3.970.0", "@aws-sdk/types": "3.969.0", @@ -453,7 +450,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.970.0.tgz", "integrity": "sha512-ArmgnOsSCXN5VyIvZb4kSP5hpqlRRHolrMtKQ/0N8Hw4MTb7/IeYHSZzVPNzzkuX6gn5Aj8txoUnDPM8O7pc9g==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -503,7 +499,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.970.0.tgz", "integrity": "sha512-TNQpwIVD6SxMwkD+QKnaujKVyXy5ljN3O3jrI7nCHJ3GlJu5xJrd8yuBnanYCcrn3e2zwdfOh4d4zJAZvvIvVw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/middleware-user-agent": "3.970.0", "@aws-sdk/types": "3.969.0", @@ -528,7 +523,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.970.0.tgz", "integrity": "sha512-L5R1hN1FY/xCmH65DOYMXl8zqCFiAq0bAq8tJZU32mGjIl1GzGeOkeDa9c461d81o7gsQeYzXyqFD3vXEbJ+kQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/core": "3.970.0", "@aws-sdk/credential-provider-env": "3.970.0", @@ -554,7 +548,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.970.0.tgz", "integrity": "sha512-C+1dcLr+p2E+9hbHyvrQTZ46Kj4vC2RoP6N935GEukHQa637ZjXs8VlyHJ2xTvbvwwLZQNiu56Cx7o/OFOqw1A==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/core": "3.970.0", "@aws-sdk/nested-clients": "3.970.0", @@ -574,7 +567,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.970.0.tgz", "integrity": "sha512-ROb+Aijw8nzkB14Nh2XRH861++SeTZykUzk427y8YtgTLxjAOjgDTchDUFW2Fx6GFWkSjqJ3sY7SZyb33IqyFw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/client-sso": "3.970.0", "@aws-sdk/core": "3.970.0", @@ -594,7 +586,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.970.0.tgz", "integrity": "sha512-r7tnYJJg+B6QvnsRHSW5vDol+ks6n+5jBZdCFdGyK63hjcMRMqHx59zEH8O47UR1PFv5hS2Q3uGz6HXvVtP40Q==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/core": "3.970.0", "@aws-sdk/nested-clients": "3.970.0", @@ -633,7 +624,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/endpoint-cache/-/endpoint-cache-3.968.0.tgz", "integrity": "sha512-d7T2mn4PDS1y7ZCr9Nbjtu6pQ87n0BIXty6T37wmURZ73g9Wv5KobFfNuK76MU4aOF9+wrgc780yXl3hgTzdcg==", "license": "Apache-2.0", - "peer": true, "dependencies": { "mnemonist": "0.38.3", "tslib": "^2.6.2" @@ -647,7 +637,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.970.0.tgz", "integrity": "sha512-RIl8s4DCa31MXtRFw23iU90OqEoWuwQxiZOZshzsPtjyrunhHFjyZJEqb+vuQcYd1o22SMaYa3lPJRp64OH35Q==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -697,7 +686,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.970.0.tgz", "integrity": "sha512-TNQpwIVD6SxMwkD+QKnaujKVyXy5ljN3O3jrI7nCHJ3GlJu5xJrd8yuBnanYCcrn3e2zwdfOh4d4zJAZvvIvVw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/middleware-user-agent": "3.970.0", "@aws-sdk/types": "3.969.0", @@ -722,7 +710,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.970.0.tgz", "integrity": "sha512-YO8KgJecxHIFMhfoP880q51VXFL9V1ELywK5yzVEqzyrwqoG93IUmnTygBUylQrfkbH+QqS0FxEdgwpP3fcwoQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/core": "3.970.0", "@aws-sdk/nested-clients": "3.970.0", @@ -741,6 +728,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.971.0.tgz", "integrity": "sha512-Afv8K2rYJxtNlEQ+3FatK+bPdYdUF80SxgcZH3adu56x0BZzQ75KDCXLRWDYjNrLn+8OH0eStX/NbAZdwPX9uA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -1471,6 +1459,7 @@ "version": "7.28.4", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -3227,6 +3216,7 @@ "node_modules/@middy/validator/node_modules/ajv": { "version": "8.17.1", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -3330,6 +3320,7 @@ "version": "1.9.0", "dev": true, "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -4813,6 +4804,7 @@ "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -4894,6 +4886,7 @@ "integrity": "sha512-npiaib8XzbjtzS2N4HlqPvlpxpmZ14FjSJrteZpPxGUaYPlvhzlzUZ4mZyABo0EFrOWnvyd0Xxroq//hKhtAWg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.53.0", "@typescript-eslint/types": "8.53.0", @@ -5382,6 +5375,7 @@ "version": "8.15.0", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5419,6 +5413,7 @@ "node_modules/ajv": { "version": "6.12.6", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -5643,6 +5638,7 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", "license": "MIT", + "peer": true, "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", @@ -5865,6 +5861,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001741", @@ -6606,6 +6603,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -6664,6 +6662,7 @@ "version": "10.1.8", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -7755,6 +7754,7 @@ "version": "30.2.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "30.2.0", "@jest/types": "30.2.0", @@ -8799,6 +8799,7 @@ "version": "6.15.0", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/mobx" @@ -9773,6 +9774,7 @@ "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -9783,6 +9785,7 @@ "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -10643,6 +10646,7 @@ "version": "6.1.19", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@emotion/is-prop-valid": "1.2.2", "@emotion/unitless": "0.8.1", @@ -10833,6 +10837,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -10961,6 +10966,7 @@ "version": "10.9.2", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -11039,6 +11045,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11775,6 +11782,32 @@ "@psu-common/testing": "^1.0.0" } }, + "packages/postDatedLambda/node_modules/@middy/core": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/@middy/core/-/core-6.4.5.tgz", + "integrity": "sha512-qRGCslDHjMr08fywcfVbWR9qpx16vGD481i9GpX3r5efi8Arjp/44JTjfeJkJJxvIb/8/+E9MLvU86+3oe1oJQ==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/willfarrell" + } + }, + "packages/postDatedLambda/node_modules/@middy/input-output-logger": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/@middy/input-output-logger/-/input-output-logger-6.4.5.tgz", + "integrity": "sha512-6fXiaN8iYQCJYuSmPG4bkK+SxXsaXpfCTAXOgwfxqIhQSfqr5HtTEChpfx2v78zcaYK18x7NAevmRYbWPjU7rQ==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/willfarrell" + } + }, "packages/psuRestoreValidationLambda": { "version": "1.0.0", "license": "MIT", From e3c8e77b09db3763dba8b3d78d5cb20592575a03 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Wed, 21 Jan 2026 14:58:56 +0000 Subject: [PATCH 06/75] Fix test config --- packages/postDatedLambda/jest.config.ts | 2 +- packages/postDatedLambda/tests/main.test.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/postDatedLambda/jest.config.ts b/packages/postDatedLambda/jest.config.ts index 32720a510e..8212011ed9 100644 --- a/packages/postDatedLambda/jest.config.ts +++ b/packages/postDatedLambda/jest.config.ts @@ -1,5 +1,5 @@ import type {JestConfigWithTsJest} from "ts-jest" -import defaultConfig from "../../jest.default.config" +import defaultConfig from "../../jest.default.config.ts" const jestConfig: JestConfigWithTsJest = { ...defaultConfig, diff --git a/packages/postDatedLambda/tests/main.test.ts b/packages/postDatedLambda/tests/main.test.ts index 3fd87596f0..510b55d75c 100644 --- a/packages/postDatedLambda/tests/main.test.ts +++ b/packages/postDatedLambda/tests/main.test.ts @@ -15,12 +15,12 @@ jest.unstable_mockModule( }) ) -const mockDrainAndProcess = jest.fn() +const mockProcessPostDatedQueue = jest.fn() jest.unstable_mockModule( "../src/queueProcessing", async () => ({ __esModule: true, - drainAndProcess: mockDrainAndProcess + processPostDatedQueue: mockProcessPostDatedQueue }) ) @@ -43,12 +43,12 @@ describe("Unit test for post-dated lambda handler", () => { it("should run the lambda handler successfully", async () => { mockReportQueueStatus.mockImplementation(() => Promise.resolve()) - mockDrainAndProcess.mockImplementation(() => Promise.resolve()) + mockProcessPostDatedQueue.mockImplementation(() => Promise.resolve()) await expect(lambdaHandler(mockEventBridgeEvent)).resolves.toBeUndefined() expect(mockReportQueueStatus).toHaveBeenCalledTimes(1) - expect(mockDrainAndProcess).toHaveBeenCalledTimes(1) + expect(mockProcessPostDatedQueue).toHaveBeenCalledTimes(1) }) }) From d32be2d5412a13ce270ca92d2e8389a149cf8b74 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 22 Jan 2026 09:39:14 +0000 Subject: [PATCH 07/75] log post dated information, if present --- .../src/updatePrescriptionStatus.ts | 6 ++++-- .../src/utils/sqsClient.ts | 14 +++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/updatePrescriptionStatus/src/updatePrescriptionStatus.ts b/packages/updatePrescriptionStatus/src/updatePrescriptionStatus.ts index 740d5dfa18..34e5abaf12 100644 --- a/packages/updatePrescriptionStatus/src/updatePrescriptionStatus.ts +++ b/packages/updatePrescriptionStatus/src/updatePrescriptionStatus.ts @@ -385,6 +385,7 @@ export function buildDataItems( } if (task.meta?.lastUpdated) { + logger.info("PostDated found") // FIXME: Delete this dataItem.PostDatedLastModifiedSetAt = task.meta.lastUpdated } @@ -413,7 +414,7 @@ async function logTransitions(dataItems: Array): Promis if (previousItem) { const newDate = new Date(currentItem.LastModified) const previousDate = new Date(previousItem.LastModified) - logger.info("Transitioning item status.", { + logger.info(LOG_MESSAGES.PSU0001, { prescriptionID: currentItem.PrescriptionID, lineItemID: currentItem.LineItemID, nhsNumber: currentItem.PatientNHSNumber, @@ -424,7 +425,8 @@ async function logTransitions(dataItems: Array): Promis newStatus: currentItem.Status, previousStatus: previousItem.Status, newTerminalStatus: currentItem.TerminalStatus, - previousTerminalStatus: previousItem.TerminalStatus + previousTerminalStatus: previousItem.TerminalStatus, + isPostDated: currentItem.PostDatedLastModifiedSetAt }) } } catch (e) { diff --git a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts index 93617bd714..42a2f2647d 100644 --- a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts +++ b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts @@ -146,6 +146,10 @@ async function placeBatchInSQS( return out } +function norm(str: string) { + return str.toLowerCase().trim() +} + /** * Pushes an array of PSUDataItem to the notifications SQS queue * Uses SendMessageBatch to send up to 10 at a time @@ -176,21 +180,17 @@ export async function pushPrescriptionToNotificationSQS( // Only allow through sites and systems that are allowedSitesAndSystems const allowedSitesAndSystemsData = await checkSiteOrSystemIsNotifyEnabled(data) - function norm(str: string) { - return str.toLowerCase().trim() - } - // Only these statuses will be pushed to the SQS - const updateStatuses: Array = [ + const updateStatuses: Set = new Set([ norm("ready to collect"), norm("ready to collect - partial") - ] + ]) // Salt for the deduplication hash const sqsSalt = await getSaltValue(logger) // Get only items which have the correct current statuses const candidates = allowedSitesAndSystemsData.filter( - (item) => updateStatuses.includes(norm(item.current.Status)) + (item) => updateStatuses.has(norm(item.current.Status)) ) // we don't want items that have gone from "ready to collect" to "ready to collect" From ee978cfef4e13883344857553d7b483a5567b4b7 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 22 Jan 2026 11:31:34 +0000 Subject: [PATCH 08/75] Forgot to pass the new SQS url through to the lambda functions --- SAMtemplates/main_template.yaml | 1 + .../src/updatePrescriptionStatus.ts | 3 +-- packages/updatePrescriptionStatus/src/utils/sqsClient.ts | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/SAMtemplates/main_template.yaml b/SAMtemplates/main_template.yaml index 5e9f48637e..7219907136 100644 --- a/SAMtemplates/main_template.yaml +++ b/SAMtemplates/main_template.yaml @@ -231,6 +231,7 @@ Resources: PrescriptionStatusUpdatesTableName: !GetAtt Tables.Outputs.PrescriptionStatusUpdatesTableName PrescriptionNotificationStatesTableName: !GetAtt Tables.Outputs.PrescriptionNotificationStatesTableName NHSNotifyPrescriptionsSQSQueueUrl: !GetAtt Messaging.Outputs.NHSNotifyPrescriptionsSQSQueueUrl + PostDatedNotificationsSQSQueueUrl: !GetAtt Messaging.Outputs.PostDatedNotificationsSQSQueueUrl SQSSaltSecret: !GetAtt Secrets.Outputs.SQSSaltSecret EnabledSiteODSCodesParam: !GetAtt Parameters.Outputs.EnabledSiteODSCodesParameterName EnabledSystemsParam: !GetAtt Parameters.Outputs.EnabledSystemsParameterName diff --git a/packages/updatePrescriptionStatus/src/updatePrescriptionStatus.ts b/packages/updatePrescriptionStatus/src/updatePrescriptionStatus.ts index 34e5abaf12..a9d72587ea 100644 --- a/packages/updatePrescriptionStatus/src/updatePrescriptionStatus.ts +++ b/packages/updatePrescriptionStatus/src/updatePrescriptionStatus.ts @@ -381,11 +381,10 @@ export function buildDataItems( TaskID: task.id!, TerminalStatus: task.status, ApplicationName: applicationName, - ExpiryTime: (Math.floor(+new Date() / 1000) + TTL_DELTA) + ExpiryTime: (Math.floor(Date.now() / 1000) + TTL_DELTA) } if (task.meta?.lastUpdated) { - logger.info("PostDated found") // FIXME: Delete this dataItem.PostDatedLastModifiedSetAt = task.meta.lastUpdated } diff --git a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts index 42a2f2647d..c27c74d1fd 100644 --- a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts +++ b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts @@ -62,10 +62,7 @@ export function saltedHash( export async function getSaltValue(logger: Logger): Promise { let sqsSalt: string - if (!process.env.SQS_SALT) { - // No secret name configured at all, so fall back - sqsSalt = fallbackSalt - } else { + if (process.env.SQS_SALT) { try { // grab the secret, expecting JSON like { "salt": "string" } const secretJson = await getSecret(process.env.SQS_SALT, {transform: "json"}) @@ -89,6 +86,9 @@ export async function getSaltValue(logger: Logger): Promise { logger.error("Failed to fetch SQS_SALT from Secrets Manager, using DEV SALT", {error}) sqsSalt = fallbackSalt } + } else { + // No secret name configured at all, so fall back + sqsSalt = fallbackSalt } if (sqsSalt === fallbackSalt) { From 99eecc10f3431ad5d0155142481f5f4bf34f74fe Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 22 Jan 2026 13:03:21 +0000 Subject: [PATCH 09/75] Fetch matching prescription ID records fromthe prescriptions data table --- package-lock.json | 6 +- packages/common/commonTypes/src/index.ts | 5 +- packages/postDatedLambda/package.json | 6 +- packages/postDatedLambda/src/businessLogic.ts | 12 +- .../postDatedLambda/src/databaseClient.ts | 167 ++++++++++++++++++ .../postDatedLambda/src/queueProcessing.ts | 45 ++--- packages/postDatedLambda/src/sqs.ts | 29 ++- packages/postDatedLambda/src/types.ts | 22 ++- .../src/utils/sqsClient.ts | 74 ++++++-- 9 files changed, 305 insertions(+), 61 deletions(-) create mode 100644 packages/postDatedLambda/src/databaseClient.ts diff --git a/package-lock.json b/package-lock.json index c843b87623..7d1bd1dd34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11774,9 +11774,13 @@ "license": "MIT", "dependencies": { "@aws-lambda-powertools/logger": "^2.30.0", + "@aws-sdk/client-dynamodb": "^3.817.0", + "@aws-sdk/client-sqs": "^3.817.0", + "@aws-sdk/util-dynamodb": "^3.817.0", "@middy/core": "^6.4.5", "@middy/input-output-logger": "^6.4.5", - "@nhs/fhir-middy-error-handler": "^2.1.65" + "@nhs/fhir-middy-error-handler": "^2.1.65", + "@psu-common/commonTypes": "^1.0.0" }, "devDependencies": { "@psu-common/testing": "^1.0.0" diff --git a/packages/common/commonTypes/src/index.ts b/packages/common/commonTypes/src/index.ts index c3d79bffbc..b0410e362e 100644 --- a/packages/common/commonTypes/src/index.ts +++ b/packages/common/commonTypes/src/index.ts @@ -14,6 +14,7 @@ export interface PSUDataItem { // (Optional, legacy batch-processors only) Indicates that {@link LastModified} is postdated; // contains the ISO 8601 timestamp when the postdated update was set. // todo: This field needs to be passed to the sqs message for post-dated updates + // FIXME: This should be called PostDatedLastUpdatedSetAt PostDatedLastModifiedSetAt?: string } @@ -28,7 +29,9 @@ export interface NotifyDataItem { // FIXME: This should be removed when we stop supporting post-dated updates export interface PostDatedNotifyDataItem extends NotifyDataItem { LastModified: string - LastUpdated: string // This is the meta.lastUpdated field from the FHIR resource + PostDatedLastModifiedSetAt: string // This is the meta.lastUpdated field from the FHIR resource + PrescriptionID: string + LineItemID: string } /** diff --git a/packages/postDatedLambda/package.json b/packages/postDatedLambda/package.json index 354d3d93b9..2ee890cca0 100644 --- a/packages/postDatedLambda/package.json +++ b/packages/postDatedLambda/package.json @@ -16,9 +16,13 @@ }, "dependencies": { "@aws-lambda-powertools/logger": "^2.30.0", + "@aws-sdk/client-dynamodb": "^3.817.0", + "@aws-sdk/client-sqs": "^3.817.0", + "@aws-sdk/util-dynamodb": "^3.817.0", "@middy/core": "^6.4.5", "@middy/input-output-logger": "^6.4.5", - "@nhs/fhir-middy-error-handler": "^2.1.65" + "@nhs/fhir-middy-error-handler": "^2.1.65", + "@psu-common/commonTypes": "^1.0.0" }, "devDependencies": { "@psu-common/testing": "^1.0.0" diff --git a/packages/postDatedLambda/src/businessLogic.ts b/packages/postDatedLambda/src/businessLogic.ts index ce12ae2a37..f7f9c98ecf 100644 --- a/packages/postDatedLambda/src/businessLogic.ts +++ b/packages/postDatedLambda/src/businessLogic.ts @@ -1,25 +1,29 @@ import {Logger} from "@aws-lambda-powertools/logger" -import {PostDatedSQSMessage} from "./types" +import {PostDatedSQSMessageWithExistingRecords} from "./types" /** * Process a single post-dated prescription message. * This is a placeholder function that I'll implement properly later. * * @param logger - The AWS Lambda Powertools logger instance - * @param message - The SQS message containing post-dated prescription data + * @param message - The SQS message containing post-dated prescription data and existing records * @returns Promise - true if processing succeeded, false if it failed */ export async function processMessage( logger: Logger, - message: PostDatedSQSMessage + message: PostDatedSQSMessageWithExistingRecords ): Promise { logger.info("Processing post-dated prescription message (dummy)", { messageId: message.MessageId, - prescriptionData: message.prescriptionData + prescriptionData: message.prescriptionData, + existingRecordsCount: message.existingRecords.length, + existingRecordTaskIds: message.existingRecords.map((r) => r.TaskID) }) // TODO: Implement actual business logic for post-dated prescription processing + // The existingRecords array contains all records from the DynamoDB table + // that match this prescription's PrescriptionID return true } diff --git a/packages/postDatedLambda/src/databaseClient.ts b/packages/postDatedLambda/src/databaseClient.ts new file mode 100644 index 0000000000..15880b499d --- /dev/null +++ b/packages/postDatedLambda/src/databaseClient.ts @@ -0,0 +1,167 @@ +import {Logger} from "@aws-lambda-powertools/logger" +import {DynamoDBClient, QueryCommand, QueryCommandInput} from "@aws-sdk/client-dynamodb" +import {marshall, unmarshall} from "@aws-sdk/util-dynamodb" + +import {PSUDataItem, PostDatedNotifyDataItem} from "@psu-common/commonTypes" +import { + PostDatedPrescriptionWithExistingRecords, + PostDatedSQSMessage, + PostDatedSQSMessageWithExistingRecords +} from "./types" + +const client = new DynamoDBClient() +const tableName = process.env.TABLE_NAME ?? "PrescriptionStatusUpdates" + +/** + * Query the PrescriptionStatusUpdates table for all records matching a given prescription ID. + * + * @param prescriptionID - The prescription ID to query for + * @param logger - The AWS Lambda Powertools logger instance + * @returns Array of PSUDataItem records matching the prescription ID + */ +export async function getExistingRecordsByPrescriptionID( + prescriptionID: string, + logger: Logger +): Promise> { + const query: QueryCommandInput = { + TableName: tableName, + KeyConditionExpression: "PrescriptionID = :pid", + ExpressionAttributeValues: { + ":pid": marshall(prescriptionID) + } + } + + let lastEvaluatedKey + let items: Array = [] + + try { + do { + if (lastEvaluatedKey) { + query.ExclusiveStartKey = lastEvaluatedKey + } + + logger.info("Querying DynamoDB for existing prescription records", { + prescriptionID, + tableName + }) + + const result = await client.send(new QueryCommand(query)) + + if (result.Items) { + const parsedItems = result.Items.map((item) => unmarshall(item) as PSUDataItem) + items = items.concat(parsedItems) + } + + lastEvaluatedKey = result.LastEvaluatedKey + } while (lastEvaluatedKey) + + logger.info("Retrieved existing prescription records from DynamoDB", { + prescriptionID, + recordCount: items.length + }) + + // Sort by LastModified ascending so most recent is last + items.sort((a, b) => new Date(a.LastModified).valueOf() - new Date(b.LastModified).valueOf()) + + return items + } catch (err) { + logger.error("Error querying DynamoDB for existing prescription records", { + prescriptionID, + error: err + }) + throw err + } +} + +/** + * For each post-dated prescription, fetch any existing records from DynamoDB + * that have a matching prescription ID. + * + * @param postDatedItems - Array of post-dated prescription data items + * @param logger - The AWS Lambda Powertools logger instance + * @returns Array of objects containing both the post-dated data and existing records + */ +export async function fetchExistingRecordsForPrescriptions( + postDatedItems: Array, + logger: Logger +): Promise> { + logger.info("Fetching existing records for post-dated prescriptions", { + prescriptionCount: postDatedItems.length + }) + + // Extract unique prescription IDs to avoid duplicate queries + const uniquePrescriptionIDs = [...new Set( + postDatedItems.map((item) => item.PrescriptionID) + )] + + // Create a map of prescription ID to existing records + const existingRecordsMap = new Map>() + + // Fetch existing records for each unique prescription ID + await Promise.all( + uniquePrescriptionIDs.map(async (prescriptionID) => { + try { + const records = await getExistingRecordsByPrescriptionID(prescriptionID, logger) + existingRecordsMap.set(prescriptionID, records) + } catch (error) { + logger.error("Failed to fetch existing records for prescription", { + prescriptionID, + error + }) + // Store empty array on error to allow processing to continue + existingRecordsMap.set(prescriptionID, []) + } + }) + ) + + // Map each post-dated item to its corresponding existing records + const results: Array = postDatedItems.map( + (postDatedData) => { + const existingRecords = existingRecordsMap.get(postDatedData.PrescriptionID) ?? [] + + return { + postDatedData, + existingRecords + } + }) + + logger.info("fetched existing prescription update records for all post-dated prescription IDs", { + totalPrescriptions: postDatedItems.length, + uniquePrescriptionIDs: uniquePrescriptionIDs.length + }) + + return results +} + +/** + * Enrich SQS messages with existing records from DynamoDB. + * For each prescription ID in the messages, fetches any matching records from the table. + * + * @param messages - Array of SQS messages to enrich + * @param logger - Logger instance + * @returns Array of enriched messages with existing records + */ +export async function enrichMessagesWithExistingRecords( + messages: Array, + logger: Logger +): Promise> { + if (messages.length === 0) { + return [] + } + + const postDatedItems = messages.map((m) => m.prescriptionData) + + const prescriptionsWithRecords = await fetchExistingRecordsForPrescriptions(postDatedItems, logger) + const recordsMap = new Map(prescriptionsWithRecords.map((p) => [p.postDatedData.PrescriptionID, p.existingRecords])) + const enrichedMessages: Array = messages.map((message) => ({ + ...message, + existingRecords: recordsMap.get(message.prescriptionData.PrescriptionID) ?? [] + })) + + logger.info("Enriched messages with existing records from DynamoDB", { + messageCount: messages.length, + messagesWithRecords: enrichedMessages.filter((m) => m.existingRecords.length > 0).length + }) + + return enrichedMessages +} diff --git a/packages/postDatedLambda/src/queueProcessing.ts b/packages/postDatedLambda/src/queueProcessing.ts index 85f01d875e..b316aecf67 100644 --- a/packages/postDatedLambda/src/queueProcessing.ts +++ b/packages/postDatedLambda/src/queueProcessing.ts @@ -1,23 +1,20 @@ import {Logger} from "@aws-lambda-powertools/logger" import {processMessage} from "./businessLogic" -import { - removeSQSMessages, - returnMessagesToQueue, - receivePostDatedSQSMessages, - reportQueueStatus -} from "./sqs" +import {enrichMessagesWithExistingRecords} from "./databaseClient" +import {receivePostDatedSQSMessages, reportQueueStatus, handleProcessedMessages} from "./sqs" import {BatchProcessingResult, PostDatedSQSMessage} from "./types" const MAX_QUEUE_RUNTIME = 14 * 60 * 1000 // 14 minutes, to avoid Lambda timeout issues (timeout is 15 minutes) const BATCH_SIZE = 10 -const FAILED_MESSAGE_VISIBILITY_TIMEOUT = 300 // 5 minutes in seconds /** * Process a batch of SQS messages. - * Messages are processed individually, and results are tracked for success/failure handling. + * Messages are enriched with existing records from DynamoDB and processed individually. + * Results are tracked for success/failure handling. * * @param messages - Array of messages to process + * @param logger - Logger instance * @returns Object containing arrays of successful and failed messages */ export async function processMessages( @@ -29,10 +26,13 @@ export async function processMessages( return {successful: [], failed: []} } + // Enrich messages with existing records from DynamoDB + const enrichedMessages = await enrichMessagesWithExistingRecords(messages, logger) + const successful: Array = [] const failed: Array = [] - for (const message of messages) { + for (const message of enrichedMessages) { try { const success = await processMessage(logger, message) if (success) { @@ -58,34 +58,11 @@ export async function processMessages( return {successful, failed} } -/** - * Handle the results of message processing: - * - Delete successful messages from the queue - * - Return failed messages to the queue with a visibility timeout - * Does not alter the input result object, only performs side effects. - * - * @param result - The batch processing result - */ -export async function handleProcessedMessages( - result: BatchProcessingResult, - logger: Logger -): Promise { - const {successful, failed} = result - - // Delete successful messages - if (successful.length > 0) { - await removeSQSMessages(logger, successful) - } - - // Return failed messages to the queue with a 5 minute timeout - if (failed.length > 0) { - await returnMessagesToQueue(logger, failed, FAILED_MESSAGE_VISIBILITY_TIMEOUT) - } -} - /** * Drain the queue until empty or the MAX_QUEUE_RUNTIME has passed. * Messages are processed in batches of 10. + * + * @param logger - Logger instance */ export async function processPostDatedQueue(logger: Logger): Promise { const start = Date.now() diff --git a/packages/postDatedLambda/src/sqs.ts b/packages/postDatedLambda/src/sqs.ts index cd5f6d49c3..b8f76f6ab0 100644 --- a/packages/postDatedLambda/src/sqs.ts +++ b/packages/postDatedLambda/src/sqs.ts @@ -10,9 +10,10 @@ import {Logger} from "@aws-lambda-powertools/logger" import {PostDatedNotifyDataItem} from "@psu-common/commonTypes" -import {PostDatedSQSMessage, ReceivedPostDatedSQSResult} from "./types" +import {BatchProcessingResult, PostDatedSQSMessage, ReceivedPostDatedSQSResult} from "./types" const sqs = new SQSClient({region: process.env.AWS_REGION}) +const FAILED_MESSAGE_VISIBILITY_TIMEOUT = 300 // 5 minutes in seconds /** * Get the SQS queue URL from environment variables. @@ -254,3 +255,29 @@ export async function returnMessagesToQueue( }) } } + +/** + * Handle the results of message processing: + * - Delete successful messages from the queue + * - Return failed messages to the queue with a visibility timeout + * Does not alter the input result object, only performs side effects. + * + * @param result - The batch processing result + * @param logger - The logging object + */ +export async function handleProcessedMessages( + result: BatchProcessingResult, + logger: Logger +): Promise { + const {successful, failed} = result + + // Delete successful messages + if (successful.length > 0) { + await removeSQSMessages(logger, successful) + } + + // Return failed messages to the queue with a 5 minute timeout + if (failed.length > 0) { + await returnMessagesToQueue(logger, failed, FAILED_MESSAGE_VISIBILITY_TIMEOUT) + } +} diff --git a/packages/postDatedLambda/src/types.ts b/packages/postDatedLambda/src/types.ts index 2c86e8433f..3c93aed52c 100644 --- a/packages/postDatedLambda/src/types.ts +++ b/packages/postDatedLambda/src/types.ts @@ -1,5 +1,5 @@ import {Message} from "@aws-sdk/client-sqs" -import {PostDatedNotifyDataItem} from "@psu-common/commonTypes" +import {PostDatedNotifyDataItem, PSUDataItem} from "@psu-common/commonTypes" /** * Extended SQS message interface for post-dated prescription messages. @@ -9,6 +9,26 @@ export interface PostDatedSQSMessage extends Message { prescriptionData: PostDatedNotifyDataItem } +/** + * Combines post-dated prescription data from SQS with any existing + * records from the PrescriptionStatusUpdates DynamoDB table. + */ +export interface PostDatedPrescriptionWithExistingRecords { + /** The post-dated prescription data from the SQS message */ + postDatedData: PostDatedNotifyDataItem + /** Existing records from DynamoDB that match the prescription ID */ + existingRecords: Array +} + +/** + * Extended SQS message interface that includes existing records from DynamoDB. + * Used during processing to have access to both the SQS message and related database records. + */ +export interface PostDatedSQSMessageWithExistingRecords extends PostDatedSQSMessage { + /** Existing records from DynamoDB that match the prescription ID */ + existingRecords: Array +} + /** * Result of processing a batch of messages. */ diff --git a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts index c27c74d1fd..9ce6ee3cb1 100644 --- a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts +++ b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts @@ -2,9 +2,14 @@ import {Logger} from "@aws-lambda-powertools/logger" import {SQSClient, SendMessageBatchCommand} from "@aws-sdk/client-sqs" import {getSecret} from "@aws-lambda-powertools/parameters/secrets" -import {createHmac} from "crypto" +import {createHmac} from "node:crypto" -import {NotifyDataItem, PSUDataItemWithPrevious} from "@psu-common/commonTypes" +import { + NotifyDataItem, + PostDatedNotifyDataItem, + PSUDataItem, + PSUDataItemWithPrevious +} from "@psu-common/commonTypes" import {checkSiteOrSystemIsNotifyEnabled} from "../validation/notificationSiteAndSystemFilters" @@ -202,8 +207,14 @@ export async function pushPrescriptionToNotificationSQS( }) .map(({current}) => current) + // Build two arrays, one of all post dated, and one of all non-post-dated + const postDatedItems = changedStatus.filter(item => item.PostDatedLastModifiedSetAt) + const nonPostDatedItems = changedStatus.filter(item => !item.PostDatedLastModifiedSetAt) + + sendPostDatedItemsToSQS(postDatedItems, requestId, logger) + // Build SQS batch entries with FIFO parameters - const allEntries: Array = changedStatus + const allEntries: Array = nonPostDatedItems .map((item, idx) => ({ Id: idx.toString(), // Only post the required information to SQS @@ -231,14 +242,6 @@ export async function pushPrescriptionToNotificationSQS( {nhsNumbers: allowedSitesAndSystemsData.map(e => e.current.PatientNHSNumber)} ) - // Check for post-dated items AFTER building the entries (even though we have to do a json parse) - // so that all entries are definitely built the same way. - // Don't do any checking on the VALUE of the field here. - const allPostDated = allEntries.filter((entry) => { - const body: NotifyDataItem = JSON.parse(entry.MessageBody) - return "PostDatedLastModifiedSetAt" in body - }) - // Remove post-dated entries from the normal flow const currentlyValidEntries = allEntries.filter(entry => { const body: NotifyDataItem = JSON.parse(entry.MessageBody) @@ -249,13 +252,48 @@ export async function pushPrescriptionToNotificationSQS( const batches = chunkArray(currentlyValidEntries, 10) const out = await placeBatchInSQS(batches, sqsUrl, requestId, logger) - // Then, also do the post-dated entries if any - if (allPostDated.length) { - logger.info(`Placing ${allPostDated.length} post-dated entries into the post-dated SQS queue`) - const postDatedBatches = chunkArray(allPostDated, 10) - const postDatedOut = await placeBatchInSQS(postDatedBatches, postDatedSqsUrl, requestId, logger) - out.push(...postDatedOut) // Their results are returned as usual + return out +} + +// FIXME: Remove this function once post-dated updates are deprecated +async function sendPostDatedItemsToSQS( + postDatedItems: Array, + requestId: string, + logger: Logger +): Promise { + if (postDatedItems.length === 0) { + logger.info("No post-dated items to send to SQS") + return } - return out + if (!postDatedSqsUrl) { + logger.error("Post-dated Notifications SQS URL not found in environment variables") + throw new Error("Post-dated Notifications SQS URL not configured") + } + + logger.info(`Placing ${postDatedItems.length} post-dated entries into the post-dated SQS queue`) + + const sqsSalt = await getSaltValue(logger) + + // This time, instead of posting NotifyDataItem, we use PostDatedNotifyDataItem + const allEntries: Array = postDatedItems + .map((item, idx) => ({ + Id: idx.toString(), + // Only post the required information to SQS + MessageBody: JSON.stringify(item as PostDatedNotifyDataItem), + // FIFO + // We dedupe on both nhs number and ods code + MessageDeduplicationId: saltedHash(`${item.PatientNHSNumber}:${item.PharmacyODSCode}`, sqsSalt), + MessageGroupId: requestId, + MessageAttributes: { + RequestId: { + DataType: "String", + StringValue: requestId + } + } + })) + + // SQS batch calls are limited to 10 messages per request, so chunk the data + const batches = chunkArray(allEntries, 10) + await placeBatchInSQS(batches, postDatedSqsUrl, requestId, logger) } From 58daa9936a5a3b77ea10a94cf2de83fef669775c Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 22 Jan 2026 13:06:46 +0000 Subject: [PATCH 10/75] Rename file --- packages/postDatedLambda/src/main.ts | 2 +- .../src/{queueProcessing.ts => orchestration.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/postDatedLambda/src/{queueProcessing.ts => orchestration.ts} (100%) diff --git a/packages/postDatedLambda/src/main.ts b/packages/postDatedLambda/src/main.ts index b0711e3cfe..b2f97fd3e2 100644 --- a/packages/postDatedLambda/src/main.ts +++ b/packages/postDatedLambda/src/main.ts @@ -7,7 +7,7 @@ import inputOutputLogger from "@middy/input-output-logger" import errorHandler from "@nhs/fhir-middy-error-handler" import {reportQueueStatus} from "./sqs" -import {processPostDatedQueue} from "./queueProcessing" +import {processPostDatedQueue} from "./orchestration" const logger = new Logger({serviceName: "postDatedLambda"}) diff --git a/packages/postDatedLambda/src/queueProcessing.ts b/packages/postDatedLambda/src/orchestration.ts similarity index 100% rename from packages/postDatedLambda/src/queueProcessing.ts rename to packages/postDatedLambda/src/orchestration.ts From d2c6e3d1e5654269de3c4ca02aff038749e7f154 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 22 Jan 2026 13:31:17 +0000 Subject: [PATCH 11/75] Missed the tests file --- packages/postDatedLambda/tests/main.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/postDatedLambda/tests/main.test.ts b/packages/postDatedLambda/tests/main.test.ts index 510b55d75c..b099d6d9e1 100644 --- a/packages/postDatedLambda/tests/main.test.ts +++ b/packages/postDatedLambda/tests/main.test.ts @@ -17,7 +17,7 @@ jest.unstable_mockModule( const mockProcessPostDatedQueue = jest.fn() jest.unstable_mockModule( - "../src/queueProcessing", + "../src/orchestration", async () => ({ __esModule: true, processPostDatedQueue: mockProcessPostDatedQueue From a3e3f4df6c450b37860ef11101ad792c169e19a1 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 22 Jan 2026 14:27:44 +0000 Subject: [PATCH 12/75] The data table fetching was not using the GSI - update it so that it is. Refactor the functions around that accordingly --- SAMtemplates/functions/main.yaml | 2 +- packages/nhsNotifyLambda/src/utils/dynamo.ts | 2 +- .../postDatedLambda/src/databaseClient.ts | 107 +++++++++++------- 3 files changed, 67 insertions(+), 44 deletions(-) diff --git a/SAMtemplates/functions/main.yaml b/SAMtemplates/functions/main.yaml index db29b62936..649558179e 100644 --- a/SAMtemplates/functions/main.yaml +++ b/SAMtemplates/functions/main.yaml @@ -542,7 +542,7 @@ Resources: Type: ScheduleV2 Properties: Name: !Sub ${StackName}-PostDatedNotifySchedule - ScheduleExpression: "rate(3 minutes)" + ScheduleExpression: "rate(2 minutes)" # TODO: Increase to 15 minutes RoleArn: !GetAtt PostDatedNotifyLambdaScheduleEventRole.Arn Metadata: BuildMethod: esbuild diff --git a/packages/nhsNotifyLambda/src/utils/dynamo.ts b/packages/nhsNotifyLambda/src/utils/dynamo.ts index b18fe7759d..aed6a2a352 100644 --- a/packages/nhsNotifyLambda/src/utils/dynamo.ts +++ b/packages/nhsNotifyLambda/src/utils/dynamo.ts @@ -43,7 +43,7 @@ export async function addPrescriptionMessagesToNotificationStateStore( NotifyMessageReference: data.messageReference, NotifyMessageBatchReference: data.messageBatchReference, // Will be undefined when request fails LastNotificationRequestTimestamp: new Date().toISOString(), - ExpiryTime: (Math.floor(+new Date() / 1000) + TTL_DELTA) + ExpiryTime: (Math.floor(Date.now() / 1000) + TTL_DELTA) } try { diff --git a/packages/postDatedLambda/src/databaseClient.ts b/packages/postDatedLambda/src/databaseClient.ts index 15880b499d..78e355c2bd 100644 --- a/packages/postDatedLambda/src/databaseClient.ts +++ b/packages/postDatedLambda/src/databaseClient.ts @@ -1,6 +1,6 @@ import {Logger} from "@aws-lambda-powertools/logger" import {DynamoDBClient, QueryCommand, QueryCommandInput} from "@aws-sdk/client-dynamodb" -import {marshall, unmarshall} from "@aws-sdk/util-dynamodb" +import {unmarshall} from "@aws-sdk/util-dynamodb" import {PSUDataItem, PostDatedNotifyDataItem} from "@psu-common/commonTypes" import { @@ -11,23 +11,36 @@ import { const client = new DynamoDBClient() const tableName = process.env.TABLE_NAME ?? "PrescriptionStatusUpdates" +const pharmacyPrescriptionIndexName = "PharmacyODSCodePrescriptionIDIndex" + +function createPrescriptionLookupKey(prescriptionID: string, pharmacyODSCode: string): string { + return `${prescriptionID.toUpperCase()}#${pharmacyODSCode.toUpperCase()}` +} /** - * Query the PrescriptionStatusUpdates table for all records matching a given prescription ID. + * Query the PrescriptionStatusUpdates table for all records matching a given prescription ID and ODS code. * * @param prescriptionID - The prescription ID to query for + * @param pharmacyODSCode - The pharmacy ODS code to query for * @param logger - The AWS Lambda Powertools logger instance * @returns Array of PSUDataItem records matching the prescription ID */ export async function getExistingRecordsByPrescriptionID( prescriptionID: string, + pharmacyODSCode: string, logger: Logger ): Promise> { + const normalizedPrescriptionID = prescriptionID.toUpperCase() + const normalizedPharmacyODSCode = pharmacyODSCode.toUpperCase() + + // Use the GSI to query by PharmacyODSCode and PrescriptionID const query: QueryCommandInput = { TableName: tableName, - KeyConditionExpression: "PrescriptionID = :pid", + IndexName: pharmacyPrescriptionIndexName, + KeyConditionExpression: "PharmacyODSCode = :ods AND PrescriptionID = :pid", ExpressionAttributeValues: { - ":pid": marshall(prescriptionID) + ":ods": {S: normalizedPharmacyODSCode}, + ":pid": {S: normalizedPrescriptionID} } } @@ -41,8 +54,10 @@ export async function getExistingRecordsByPrescriptionID( } logger.info("Querying DynamoDB for existing prescription records", { - prescriptionID, - tableName + prescriptionID: normalizedPrescriptionID, + pharmacyODSCode: normalizedPharmacyODSCode, + tableName, + indexName: pharmacyPrescriptionIndexName }) const result = await client.send(new QueryCommand(query)) @@ -56,7 +71,8 @@ export async function getExistingRecordsByPrescriptionID( } while (lastEvaluatedKey) logger.info("Retrieved existing prescription records from DynamoDB", { - prescriptionID, + prescriptionID: normalizedPrescriptionID, + pharmacyODSCode: normalizedPharmacyODSCode, recordCount: items.length }) @@ -66,7 +82,8 @@ export async function getExistingRecordsByPrescriptionID( return items } catch (err) { logger.error("Error querying DynamoDB for existing prescription records", { - prescriptionID, + prescriptionID: normalizedPrescriptionID, + pharmacyODSCode: normalizedPharmacyODSCode, error: err }) throw err @@ -89,45 +106,51 @@ export async function fetchExistingRecordsForPrescriptions( prescriptionCount: postDatedItems.length }) - // Extract unique prescription IDs to avoid duplicate queries - const uniquePrescriptionIDs = [...new Set( - postDatedItems.map((item) => item.PrescriptionID) - )] - - // Create a map of prescription ID to existing records - const existingRecordsMap = new Map>() - - // Fetch existing records for each unique prescription ID - await Promise.all( - uniquePrescriptionIDs.map(async (prescriptionID) => { - try { - const records = await getExistingRecordsByPrescriptionID(prescriptionID, logger) - existingRecordsMap.set(prescriptionID, records) - } catch (error) { - logger.error("Failed to fetch existing records for prescription", { - prescriptionID, - error - }) - // Store empty array on error to allow processing to continue - existingRecordsMap.set(prescriptionID, []) - } - }) - ) + // Cache fetch promises per unique prescription/ODS pair to avoid duplicate lookups + const recordsPromises = new Map>>() + + const getOrCreateRecordsPromise = ( + prescriptionID: string, + pharmacyODSCode: string + ): Promise> => { + const lookupKey = createPrescriptionLookupKey(prescriptionID, pharmacyODSCode) + + if (!recordsPromises.has(lookupKey)) { + const fetchPromise = (async () => { + try { + return await getExistingRecordsByPrescriptionID(prescriptionID, pharmacyODSCode, logger) + } catch (error) { + logger.error("Failed to fetch existing records for prescription", { + prescriptionID, + pharmacyODSCode, + error + }) + return [] + } + })() + + recordsPromises.set(lookupKey, fetchPromise) + } - // Map each post-dated item to its corresponding existing records - const results: Array = postDatedItems.map( - (postDatedData) => { - const existingRecords = existingRecordsMap.get(postDatedData.PrescriptionID) ?? [] + return recordsPromises.get(lookupKey)! + } - return { - postDatedData, - existingRecords - } - }) + // Each element of recordsPromises is a wrapper around the actual fetch promise for that ID/ODS pair + + // Now, we map over the fetch promise wrappers, and await them all in parallel + const results: Array = await Promise.all( + postDatedItems.map(async (postDatedData) => ({ + postDatedData, + existingRecords: await getOrCreateRecordsPromise( + postDatedData.PrescriptionID, + postDatedData.PharmacyODSCode + ) + })) + ) logger.info("fetched existing prescription update records for all post-dated prescription IDs", { totalPrescriptions: postDatedItems.length, - uniquePrescriptionIDs: uniquePrescriptionIDs.length + uniquePrescriptionLookups: recordsPromises.size }) return results From efc762975cb52687cc796a9e7357044660a2c2de Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 22 Jan 2026 15:14:04 +0000 Subject: [PATCH 13/75] When pushing to SQS, log what url it's going to --- packages/postDatedLambda/src/databaseClient.ts | 3 +-- packages/updatePrescriptionStatus/src/utils/sqsClient.ts | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/postDatedLambda/src/databaseClient.ts b/packages/postDatedLambda/src/databaseClient.ts index 78e355c2bd..934d4eb326 100644 --- a/packages/postDatedLambda/src/databaseClient.ts +++ b/packages/postDatedLambda/src/databaseClient.ts @@ -118,6 +118,7 @@ export async function fetchExistingRecordsForPrescriptions( if (!recordsPromises.has(lookupKey)) { const fetchPromise = (async () => { try { + // Each element of recordsPromises is a wrapper around the actual fetch promise for that ID/ODS pair return await getExistingRecordsByPrescriptionID(prescriptionID, pharmacyODSCode, logger) } catch (error) { logger.error("Failed to fetch existing records for prescription", { @@ -135,8 +136,6 @@ export async function fetchExistingRecordsForPrescriptions( return recordsPromises.get(lookupKey)! } - // Each element of recordsPromises is a wrapper around the actual fetch promise for that ID/ODS pair - // Now, we map over the fetch promise wrappers, and await them all in parallel const results: Array = await Promise.all( postDatedItems.map(async (postDatedData) => ({ diff --git a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts index 9ce6ee3cb1..1ef74d0810 100644 --- a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts +++ b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts @@ -122,7 +122,8 @@ async function placeBatchInSQS( { batchLength: batch.length, deduplicationIds: batch.map(e => e.MessageDeduplicationId), - requestId + requestId, + sqsUrl } ) From cecede61c91e6353e4507edde008d802caac96eb Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 22 Jan 2026 15:23:58 +0000 Subject: [PATCH 14/75] I was passing the incorrect table name in to the post dated lambda --- SAMtemplates/functions/main.yaml | 2 +- .../postDatedLambda/src/databaseClient.ts | 77 ++++++++++--------- 2 files changed, 41 insertions(+), 38 deletions(-) diff --git a/SAMtemplates/functions/main.yaml b/SAMtemplates/functions/main.yaml index 649558179e..ebe9abb897 100644 --- a/SAMtemplates/functions/main.yaml +++ b/SAMtemplates/functions/main.yaml @@ -536,7 +536,7 @@ Resources: LOG_LEVEL: !Ref LogLevel NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL: !Ref NHSNotifyPrescriptionsSQSQueueUrl POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL: !Ref PostDatedNotificationsSQSQueueUrl - TABLE_NAME: !Ref PrescriptionNotificationStatesTableName + TABLE_NAME: !Ref PrescriptionStatusUpdatesTableName Events: ScheduleEvent: Type: ScheduleV2 diff --git a/packages/postDatedLambda/src/databaseClient.ts b/packages/postDatedLambda/src/databaseClient.ts index 934d4eb326..0257899a1b 100644 --- a/packages/postDatedLambda/src/databaseClient.ts +++ b/packages/postDatedLambda/src/databaseClient.ts @@ -106,50 +106,53 @@ export async function fetchExistingRecordsForPrescriptions( prescriptionCount: postDatedItems.length }) - // Cache fetch promises per unique prescription/ODS pair to avoid duplicate lookups - const recordsPromises = new Map>>() - - const getOrCreateRecordsPromise = ( - prescriptionID: string, - pharmacyODSCode: string - ): Promise> => { - const lookupKey = createPrescriptionLookupKey(prescriptionID, pharmacyODSCode) - - if (!recordsPromises.has(lookupKey)) { - const fetchPromise = (async () => { - try { - // Each element of recordsPromises is a wrapper around the actual fetch promise for that ID/ODS pair - return await getExistingRecordsByPrescriptionID(prescriptionID, pharmacyODSCode, logger) - } catch (error) { - logger.error("Failed to fetch existing records for prescription", { - prescriptionID, - pharmacyODSCode, - error - }) - return [] - } - })() - - recordsPromises.set(lookupKey, fetchPromise) + const uniquePrescriptionLookups = new Map() + for (const item of postDatedItems) { + const lookupKey = createPrescriptionLookupKey(item.PrescriptionID, item.PharmacyODSCode) + if (!uniquePrescriptionLookups.has(lookupKey)) { + uniquePrescriptionLookups.set(lookupKey, { + prescriptionID: item.PrescriptionID, + pharmacyODSCode: item.PharmacyODSCode + }) } - - return recordsPromises.get(lookupKey)! } - // Now, we map over the fetch promise wrappers, and await them all in parallel - const results: Array = await Promise.all( - postDatedItems.map(async (postDatedData) => ({ - postDatedData, - existingRecords: await getOrCreateRecordsPromise( - postDatedData.PrescriptionID, - postDatedData.PharmacyODSCode - ) - })) + // Create a map of prescription ID to existing records + const existingRecordsMap = new Map>() + + // Fetch existing records for each unique prescription ID + await Promise.all( + Array.from(uniquePrescriptionLookups.entries()).map(async ([lookupKey, {prescriptionID, pharmacyODSCode}]) => { + try { + const records = await getExistingRecordsByPrescriptionID(prescriptionID, pharmacyODSCode, logger) + existingRecordsMap.set(lookupKey, records) + } catch (error) { + logger.error("Failed to fetch existing records for prescription", { + prescriptionID, + pharmacyODSCode, + error + }) + // Store empty array on error to allow processing to continue + existingRecordsMap.set(lookupKey, []) + } + }) ) + // Map each post-dated item to its corresponding existing records + const results: Array = postDatedItems.map( + (postDatedData) => { + const lookupKey = createPrescriptionLookupKey(postDatedData.PrescriptionID, postDatedData.PharmacyODSCode) + const existingRecords = existingRecordsMap.get(lookupKey) ?? [] + + return { + postDatedData, + existingRecords + } + }) + logger.info("fetched existing prescription update records for all post-dated prescription IDs", { totalPrescriptions: postDatedItems.length, - uniquePrescriptionLookups: recordsPromises.size + uniquePrescriptionLookups: existingRecordsMap.size }) return results From 7083f88b38d9d99bd0d42a1e222cd7ca6aacea68 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 22 Jan 2026 15:59:34 +0000 Subject: [PATCH 15/75] update table policies --- SAMtemplates/functions/main.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SAMtemplates/functions/main.yaml b/SAMtemplates/functions/main.yaml index ebe9abb897..6aa02cef71 100644 --- a/SAMtemplates/functions/main.yaml +++ b/SAMtemplates/functions/main.yaml @@ -582,9 +582,9 @@ Resources: - Fn::ImportValue: !Sub ${StackName}-UseNotificationSQSQueueKMSKeyPolicyArn - Fn::ImportValue: !Sub ${StackName}-WritePostDatedNotificationsSQSQueuePolicyArn - Fn::ImportValue: !Sub ${StackName}-ReadPostDatedNotificationsSQSQueuePolicyArn - - Fn::ImportValue: !Sub ${StackName}:tables:${PrescriptionNotificationStatesTableName}:TableReadPolicyArn - - Fn::ImportValue: !Sub ${StackName}:tables:${PrescriptionNotificationStatesTableName}:TableWritePolicyArn - - Fn::ImportValue: !Sub ${StackName}:tables:UsePrescriptionNotificationStatesKMSKeyPolicyArn + - Fn::ImportValue: !Sub ${StackName}:tables:${PrescriptionStatusUpdatesTableName}:TableWritePolicyArn + - Fn::ImportValue: !Sub ${StackName}:tables:${PrescriptionStatusUpdatesTableName}:TableReadPolicyArn + - Fn::ImportValue: !Sub ${StackName}:tables:UsePrescriptionStatusUpdatesKMSKeyPolicyArn ## End of post-dated lambda bits NHSNotifyUpdateCallback: From 939e2e70b8e623abdf91cd5fe1dfe62aded2a9d6 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 23 Jan 2026 10:01:38 +0000 Subject: [PATCH 16/75] Review yesterdays work. Leave some todo notes for the business logic --- packages/postDatedLambda/src/businessLogic.ts | 2 +- .../postDatedLambda/src/databaseClient.ts | 30 ++++++++------ packages/postDatedLambda/src/orchestration.ts | 18 ++++----- packages/postDatedLambda/src/sqs.ts | 39 +++++++++++-------- packages/postDatedLambda/src/types.ts | 4 +- 5 files changed, 53 insertions(+), 40 deletions(-) diff --git a/packages/postDatedLambda/src/businessLogic.ts b/packages/postDatedLambda/src/businessLogic.ts index f7f9c98ecf..3b503e09ac 100644 --- a/packages/postDatedLambda/src/businessLogic.ts +++ b/packages/postDatedLambda/src/businessLogic.ts @@ -8,7 +8,7 @@ import {PostDatedSQSMessageWithExistingRecords} from "./types" * * @param logger - The AWS Lambda Powertools logger instance * @param message - The SQS message containing post-dated prescription data and existing records - * @returns Promise - true if processing succeeded, false if it failed + * @returns Promise - true if the post-dated prescription has matured, and false otherwise */ export async function processMessage( logger: Logger, diff --git a/packages/postDatedLambda/src/databaseClient.ts b/packages/postDatedLambda/src/databaseClient.ts index 0257899a1b..285202da11 100644 --- a/packages/postDatedLambda/src/databaseClient.ts +++ b/packages/postDatedLambda/src/databaseClient.ts @@ -19,11 +19,13 @@ function createPrescriptionLookupKey(prescriptionID: string, pharmacyODSCode: st /** * Query the PrescriptionStatusUpdates table for all records matching a given prescription ID and ODS code. + * There should always be at least one result, but there may be multiple if the prescription has been + * updated multiple times. * * @param prescriptionID - The prescription ID to query for * @param pharmacyODSCode - The pharmacy ODS code to query for * @param logger - The AWS Lambda Powertools logger instance - * @returns Array of PSUDataItem records matching the prescription ID + * @returns Array of PSUDataItem records matching the prescription ID. Sorted by LastModified descending. */ export async function getExistingRecordsByPrescriptionID( prescriptionID: string, @@ -76,8 +78,8 @@ export async function getExistingRecordsByPrescriptionID( recordCount: items.length }) - // Sort by LastModified ascending so most recent is last - items.sort((a, b) => new Date(a.LastModified).valueOf() - new Date(b.LastModified).valueOf()) + // Sort by LastModified ascending so most recent is first + items.sort((a, b) => new Date(b.LastModified).valueOf() - new Date(a.LastModified).valueOf()) return items } catch (err) { @@ -96,7 +98,8 @@ export async function getExistingRecordsByPrescriptionID( * * @param postDatedItems - Array of post-dated prescription data items * @param logger - The AWS Lambda Powertools logger instance - * @returns Array of objects containing both the post-dated data and existing records + * @returns Array of objects containing both the post-dated data and existing records. + * Existing records are sorted by LastModified descending. */ export async function fetchExistingRecordsForPrescriptions( postDatedItems: Array, @@ -106,6 +109,8 @@ export async function fetchExistingRecordsForPrescriptions( prescriptionCount: postDatedItems.length }) + // The data table is indexed by both PrescriptionID and PharmacyODSCode, so build a map keyed by these + // in combination. Should avoid duplicate queries this way. const uniquePrescriptionLookups = new Map() for (const item of postDatedItems) { const lookupKey = createPrescriptionLookupKey(item.PrescriptionID, item.PharmacyODSCode) @@ -117,10 +122,7 @@ export async function fetchExistingRecordsForPrescriptions( } } - // Create a map of prescription ID to existing records const existingRecordsMap = new Map>() - - // Fetch existing records for each unique prescription ID await Promise.all( Array.from(uniquePrescriptionLookups.entries()).map(async ([lookupKey, {prescriptionID, pharmacyODSCode}]) => { try { @@ -133,6 +135,7 @@ export async function fetchExistingRecordsForPrescriptions( error }) // Store empty array on error to allow processing to continue + // TODO: Make sure later, that if existingRecords is empty, we handle that existingRecordsMap.set(lookupKey, []) } }) @@ -183,10 +186,15 @@ export async function enrichMessagesWithExistingRecords( existingRecords: recordsMap.get(message.prescriptionData.PrescriptionID) ?? [] })) - logger.info("Enriched messages with existing records from DynamoDB", { - messageCount: messages.length, - messagesWithRecords: enrichedMessages.filter((m) => m.existingRecords.length > 0).length - }) + for (const msg of enrichedMessages) { + logger.info("Prescription and most recent existing record", { + prescriptionID: msg.prescriptionData.PrescriptionID, + existingRecordCount: msg.existingRecords.length, + mostRecentExistingRecord: msg.existingRecords.length > 0 + ? msg.existingRecords[0] + : null + }) + } return enrichedMessages } diff --git a/packages/postDatedLambda/src/orchestration.ts b/packages/postDatedLambda/src/orchestration.ts index b316aecf67..65c45e679b 100644 --- a/packages/postDatedLambda/src/orchestration.ts +++ b/packages/postDatedLambda/src/orchestration.ts @@ -23,39 +23,39 @@ export async function processMessages( ): Promise { if (messages.length === 0) { logger.info("No messages to process in batch") - return {successful: [], failed: []} + return {maturedPrescriptionUpdates: [], immaturePrescriptionUpdates: []} } // Enrich messages with existing records from DynamoDB const enrichedMessages = await enrichMessagesWithExistingRecords(messages, logger) - const successful: Array = [] - const failed: Array = [] + const maturedPrescriptionUpdates: Array = [] + const immaturePrescriptionUpdates: Array = [] for (const message of enrichedMessages) { try { const success = await processMessage(logger, message) if (success) { - successful.push(message) + maturedPrescriptionUpdates.push(message) } else { - failed.push(message) + immaturePrescriptionUpdates.push(message) } } catch (error) { logger.error("Error processing message", { messageId: message.MessageId, error }) - failed.push(message) + immaturePrescriptionUpdates.push(message) } } logger.info("Batch processing complete", { totalMessages: messages.length, - successfulCount: successful.length, - failedCount: failed.length + maturedPrescriptionUpdatesCount: maturedPrescriptionUpdates.length, + immaturePrescriptionUpdatesCount: immaturePrescriptionUpdates.length }) - return {successful, failed} + return {maturedPrescriptionUpdates, immaturePrescriptionUpdates} } /** diff --git a/packages/postDatedLambda/src/sqs.ts b/packages/postDatedLambda/src/sqs.ts index b8f76f6ab0..fcd2ca7d47 100644 --- a/packages/postDatedLambda/src/sqs.ts +++ b/packages/postDatedLambda/src/sqs.ts @@ -13,7 +13,6 @@ import {PostDatedNotifyDataItem} from "@psu-common/commonTypes" import {BatchProcessingResult, PostDatedSQSMessage, ReceivedPostDatedSQSResult} from "./types" const sqs = new SQSClient({region: process.env.AWS_REGION}) -const FAILED_MESSAGE_VISIBILITY_TIMEOUT = 300 // 5 minutes in seconds /** * Get the SQS queue URL from environment variables. @@ -210,25 +209,27 @@ export async function removeSQSMessages( } /** - * Return failed messages to the queue with a visibility timeout. + * Edit failed that are on the queue to update their visibility timeout. * This makes the messages invisible for the specified duration before they can be processed again. + * This does not delete the messages, or post new ones; it only alters their visibility. * * @param logger - The logging object * @param messages - The messages that failed processing and should be returned to the queue - * @param visibilityTimeoutSeconds - The time in seconds before messages become visible again (default: 300 = 5 minutes) */ export async function returnMessagesToQueue( logger: Logger, - messages: Array, - visibilityTimeoutSeconds = 300 + messages: Array ): Promise { if (messages.length === 0) { - logger.info("No failed messages to return to queue") + logger.info("No messages to return to queue") return } const sqsUrl = getQueueUrl(logger) + // TODO: Each message needs to have an appropriate visibility timeout based on when it is due to be retried. + // For now, use a fixed 5 minute timeout for all messages. + const visibilityTimeoutSeconds = 300 const entries = messages.map((m) => ({ Id: m.MessageId!, ReceiptHandle: m.ReceiptHandle!, @@ -236,8 +237,11 @@ export async function returnMessagesToQueue( })) logger.info( - `Returning messages to queue with ${visibilityTimeoutSeconds}s timeout`, - {numberOfMessages: entries.length, messageIds: entries.map((e) => e.Id)} + `Returning messages to queue with timeouts`, + { + numberOfMessages: entries.length, + idAndTimeouts: entries.map((e) => ({id: e.Id, visibilityTimeout: e.VisibilityTimeout})) + } ) const changeVisibilityCmd = new ChangeMessageVisibilityBatchCommand({ @@ -258,8 +262,8 @@ export async function returnMessagesToQueue( /** * Handle the results of message processing: - * - Delete successful messages from the queue - * - Return failed messages to the queue with a visibility timeout + * - Delete matured messages from the queue + * - Return immature messages to the queue with a visibility timeout * Does not alter the input result object, only performs side effects. * * @param result - The batch processing result @@ -269,15 +273,16 @@ export async function handleProcessedMessages( result: BatchProcessingResult, logger: Logger ): Promise { - const {successful, failed} = result + const {maturedPrescriptionUpdates, immaturePrescriptionUpdates} = result - // Delete successful messages - if (successful.length > 0) { - await removeSQSMessages(logger, successful) + // Delete matured messages + if (maturedPrescriptionUpdates.length > 0) { + // TODO: Also need to send messages to the notification queue here (do that first, then delete) + await removeSQSMessages(logger, maturedPrescriptionUpdates) } - // Return failed messages to the queue with a 5 minute timeout - if (failed.length > 0) { - await returnMessagesToQueue(logger, failed, FAILED_MESSAGE_VISIBILITY_TIMEOUT) + // Return failed messages to the queue + if (immaturePrescriptionUpdates.length > 0) { + await returnMessagesToQueue(logger, immaturePrescriptionUpdates) } } diff --git a/packages/postDatedLambda/src/types.ts b/packages/postDatedLambda/src/types.ts index 3c93aed52c..e1d982d47d 100644 --- a/packages/postDatedLambda/src/types.ts +++ b/packages/postDatedLambda/src/types.ts @@ -33,8 +33,8 @@ export interface PostDatedSQSMessageWithExistingRecords extends PostDatedSQSMess * Result of processing a batch of messages. */ export interface BatchProcessingResult { - successful: Array - failed: Array + maturedPrescriptionUpdates: Array + immaturePrescriptionUpdates: Array } /** From a94ad39eff110c4367698ebbf157c541fb95aa41 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 23 Jan 2026 10:57:13 +0000 Subject: [PATCH 17/75] Simplify some logic, clean up some bits --- packages/common/commonTypes/src/index.ts | 2 +- packages/postDatedLambda/src/businessLogic.ts | 3 + .../postDatedLambda/src/databaseClient.ts | 1 - packages/postDatedLambda/src/orchestration.ts | 17 ++- packages/postDatedLambda/src/sqs.ts | 133 ++++++------------ packages/postDatedLambda/src/types.ts | 8 -- packages/postDatedLambda/tests/main.test.ts | 24 ++++ 7 files changed, 84 insertions(+), 104 deletions(-) diff --git a/packages/common/commonTypes/src/index.ts b/packages/common/commonTypes/src/index.ts index b0410e362e..013fd909d3 100644 --- a/packages/common/commonTypes/src/index.ts +++ b/packages/common/commonTypes/src/index.ts @@ -26,7 +26,7 @@ export interface NotifyDataItem { Status: string } -// FIXME: This should be removed when we stop supporting post-dated updates +// TODO: This should be removed when we stop supporting post-dated updates export interface PostDatedNotifyDataItem extends NotifyDataItem { LastModified: string PostDatedLastModifiedSetAt: string // This is the meta.lastUpdated field from the FHIR resource diff --git a/packages/postDatedLambda/src/businessLogic.ts b/packages/postDatedLambda/src/businessLogic.ts index 3b503e09ac..3681afcaf5 100644 --- a/packages/postDatedLambda/src/businessLogic.ts +++ b/packages/postDatedLambda/src/businessLogic.ts @@ -25,5 +25,8 @@ export async function processMessage( // The existingRecords array contains all records from the DynamoDB table // that match this prescription's PrescriptionID + // NOTE: It is technically possible for the array to be empty if no existing records are found + // This SHOULD never happen in practice, but the code should handle it gracefully just in case + return true } diff --git a/packages/postDatedLambda/src/databaseClient.ts b/packages/postDatedLambda/src/databaseClient.ts index 285202da11..28b36742b9 100644 --- a/packages/postDatedLambda/src/databaseClient.ts +++ b/packages/postDatedLambda/src/databaseClient.ts @@ -135,7 +135,6 @@ export async function fetchExistingRecordsForPrescriptions( error }) // Store empty array on error to allow processing to continue - // TODO: Make sure later, that if existingRecords is empty, we handle that existingRecordsMap.set(lookupKey, []) } }) diff --git a/packages/postDatedLambda/src/orchestration.ts b/packages/postDatedLambda/src/orchestration.ts index 65c45e679b..42db6ae73a 100644 --- a/packages/postDatedLambda/src/orchestration.ts +++ b/packages/postDatedLambda/src/orchestration.ts @@ -6,7 +6,7 @@ import {receivePostDatedSQSMessages, reportQueueStatus, handleProcessedMessages} import {BatchProcessingResult, PostDatedSQSMessage} from "./types" const MAX_QUEUE_RUNTIME = 14 * 60 * 1000 // 14 minutes, to avoid Lambda timeout issues (timeout is 15 minutes) -const BATCH_SIZE = 10 +const MIN_RECEIVED_THRESHOLD = 3 // If fewer than this number of messages are received, consider the queue empty /** * Process a batch of SQS messages. @@ -78,16 +78,19 @@ export async function processPostDatedQueue(logger: Logger): Promise { break } - const {messages, isEmpty} = await receivePostDatedSQSMessages(logger, BATCH_SIZE) - empty = isEmpty + const messages = await receivePostDatedSQSMessages(logger) - if (messages.length === 0) { - logger.info("No messages retrieved from queue") - continue + // break condition + if (messages.length < MIN_RECEIVED_THRESHOLD) { + empty = true + logger.info("Received fewer messages than minimum threshold; considering queue drained", { + receivedMessageCount: messages.length, + minimumThreshold: MIN_RECEIVED_THRESHOLD + }) } // Process messages for this batch const result = await processMessages(messages, logger) - return await handleProcessedMessages(result, logger) + await handleProcessedMessages(result, logger) } } diff --git a/packages/postDatedLambda/src/sqs.ts b/packages/postDatedLambda/src/sqs.ts index fcd2ca7d47..1a6cc48705 100644 --- a/packages/postDatedLambda/src/sqs.ts +++ b/packages/postDatedLambda/src/sqs.ts @@ -10,7 +10,7 @@ import {Logger} from "@aws-lambda-powertools/logger" import {PostDatedNotifyDataItem} from "@psu-common/commonTypes" -import {BatchProcessingResult, PostDatedSQSMessage, ReceivedPostDatedSQSResult} from "./types" +import {BatchProcessingResult, PostDatedSQSMessage} from "./types" const sqs = new SQSClient({region: process.env.AWS_REGION}) @@ -63,106 +63,64 @@ export async function reportQueueStatus(logger: Logger): Promise { } /** - * Pulls up to `maxTotal` messages off the queue (in batches of up to 10) and bundles them together. + * Pulls up to 10 messages from SQS. * * @param logger - The AWS logging object - * @param maxTotal - The maximum number of messages to fetch. Guaranteed to be less than this. - * @param small_batch_threshold - If a batch returns fewer than this number of messages, stop polling further. - * @returns - * - messages: the array of parsed PostDatedSQSMessage - * - isEmpty: true if the last receive returned fewer than 5 messages (or none), - * indicating the queue is effectively drained. + * @returns - The array of parsed PostDatedSQSMessage */ -export async function receivePostDatedSQSMessages( - logger: Logger, - maxTotal = 10, - small_batch_threshold = 5 -): Promise { - // eslint-disable-next-line max-len - // TODO: This is borrowed from the notify lambda, but here it is not necessary to process 100 messages at a time - 10 is enough. Consider refactoring this function away. +export async function receivePostDatedSQSMessages(logger: Logger): Promise> { const sqsUrl = getQueueUrl(logger) + const toFetch = 10 - const allMessages: Array = [] - let receivedSoFar = 0 - let isEmpty = false - let pollingIteration = 0 + const receiveCmd = new ReceiveMessageCommand({ + QueueUrl: sqsUrl, + MaxNumberOfMessages: toFetch, + // Use long polling to avoid getting empty responses when the queue is small + WaitTimeSeconds: 20, + MessageAttributeNames: ["All"] + }) - while (receivedSoFar < maxTotal) { - pollingIteration = pollingIteration + 1 + const {Messages} = await sqs.send(receiveCmd) - const toFetch = Math.min(10, maxTotal - receivedSoFar) - const receiveCmd = new ReceiveMessageCommand({ - QueueUrl: sqsUrl, - MaxNumberOfMessages: toFetch, - // Use long polling to avoid getting empty responses when the queue is small - WaitTimeSeconds: 20, - MessageAttributeNames: ["All"] - }) + if (!Messages || Messages.length === 0) { + logger.info("No messages received; marking queue as empty") + return [] + } - const {Messages} = await sqs.send(receiveCmd) + logger.info("Received some messages from the post-dated queue. Parsing them...", { + MessageIDs: Messages.map((m) => m.MessageId) + }) - // if the queue is now empty, then break the loop - if (!Messages || Messages.length === 0) { - isEmpty = true - logger.info("No messages received; marking queue as empty", {pollingIteration}) - break + const parsedMessages: Array = Messages.flatMap((m) => { + if (!m.Body) { + logger.error( + "Received an invalid SQS message (missing Body) - omitting from processing.", + {offendingMessage: m} + ) + return [] } - - logger.info( - "Received some messages from the post-dated queue. Parsing them...", - { - pollingIteration, - MessageIDs: Messages.map((m) => m.MessageId) - } - ) - - // Parse and validate messages - const parsedMessages: Array = Messages.flatMap((m) => { - if (!m.Body) { - logger.error( - "Received an invalid SQS message (missing Body) - omitting from processing.", - {offendingMessage: m} - ) - return [] - } - try { - const parsedBody: PostDatedNotifyDataItem = JSON.parse(m.Body) - return [ - { - ...m, - prescriptionData: parsedBody - } - ] - } catch (error) { - logger.error( - "Failed to parse SQS message body as JSON - omitting from processing.", - {offendingMessage: m, parseError: error} - ) - return [] - } - }) - - allMessages.push(...parsedMessages) - receivedSoFar += parsedMessages.length - - // if the last batch of messages was small, then break the loop - // This is to prevent a slow-loris style breakdown if the queue has - // barely enough messages to keep the processors alive - if (Messages.length < small_batch_threshold) { - isEmpty = true - logger.info( - "Received a small number of messages. Considering the queue drained.", - {batchLength: Messages.length, small_batch_threshold, pollingIteration} + try { + const parsedBody: PostDatedNotifyDataItem = JSON.parse(m.Body) + return [ + { + ...m, + prescriptionData: parsedBody + } + ] + } catch (error) { + logger.error( + "Failed to parse SQS message body as JSON - omitting from processing.", + {offendingMessage: m, parseError: error} ) - break + return [] } - } + }) - logger.info(`In sum, retrieved ${allMessages.length} messages from post-dated SQS`, - {MessageIDs: allMessages.map(el => el.MessageId)} - ) + logger.info(`In sum, retrieved ${parsedMessages.length} messages from post-dated SQS`, { + MessageIDs: parsedMessages.map((el) => el.MessageId) + }) - return {messages: allMessages, isEmpty} + return parsedMessages } /** @@ -278,6 +236,7 @@ export async function handleProcessedMessages( // Delete matured messages if (maturedPrescriptionUpdates.length > 0) { // TODO: Also need to send messages to the notification queue here (do that first, then delete) + // await sendSQSMessagesToNotificationQueue(logger, maturedPrescriptionUpdates) await removeSQSMessages(logger, maturedPrescriptionUpdates) } diff --git a/packages/postDatedLambda/src/types.ts b/packages/postDatedLambda/src/types.ts index e1d982d47d..037ab09119 100644 --- a/packages/postDatedLambda/src/types.ts +++ b/packages/postDatedLambda/src/types.ts @@ -36,11 +36,3 @@ export interface BatchProcessingResult { maturedPrescriptionUpdates: Array immaturePrescriptionUpdates: Array } - -/** - * Result of draining the queue. - */ -export interface ReceivedPostDatedSQSResult { - messages: Array - isEmpty: boolean -} diff --git a/packages/postDatedLambda/tests/main.test.ts b/packages/postDatedLambda/tests/main.test.ts index b099d6d9e1..6df8c8d468 100644 --- a/packages/postDatedLambda/tests/main.test.ts +++ b/packages/postDatedLambda/tests/main.test.ts @@ -51,4 +51,28 @@ describe("Unit test for post-dated lambda handler", () => { expect(mockProcessPostDatedQueue).toHaveBeenCalledTimes(1) }) + it("Should handle errors from reportQueueStatus", async () => { + mockReportQueueStatus.mockImplementation(() => { + throw new Error("Dynamo error") + }) + mockProcessPostDatedQueue.mockImplementation(() => Promise.resolve()) + + await expect(lambdaHandler(mockEventBridgeEvent)).rejects.toThrow("Dynamo error") + + expect(mockReportQueueStatus).toHaveBeenCalledTimes(1) + expect(mockProcessPostDatedQueue).not.toHaveBeenCalled() + }) + + it("Should handle errors from processPostDatedQueue", async () => { + mockReportQueueStatus.mockImplementation(() => Promise.resolve()) + mockProcessPostDatedQueue.mockImplementation(() => { + throw new Error("Processing error") + }) + + await expect(lambdaHandler(mockEventBridgeEvent)).rejects.toThrow("Processing error") + + expect(mockReportQueueStatus).toHaveBeenCalledTimes(1) + expect(mockProcessPostDatedQueue).toHaveBeenCalledTimes(1) + }) + }) From c7fe17deac3d3e914f8f9559d290da3cf3745a54 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 23 Jan 2026 12:21:23 +0000 Subject: [PATCH 18/75] Minor changes. --- SAMtemplates/messaging/main.yaml | 2 +- packages/common/commonTypes/src/index.ts | 15 +++++++++++++++ .../src/utils/sqsClient.ts | 11 ++--------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/SAMtemplates/messaging/main.yaml b/SAMtemplates/messaging/main.yaml index 0166186af0..c15836cd17 100644 --- a/SAMtemplates/messaging/main.yaml +++ b/SAMtemplates/messaging/main.yaml @@ -113,7 +113,7 @@ Resources: MessageRetentionPeriod: 86400 # 1 day in seconds RedrivePolicy: deadLetterTargetArn: !GetAtt PostDatedNotificationsDeadLetterQueue.Arn - maxReceiveCount: 13 # processed every 6 hours for 3 days, plus one for the first time it's placed on queue. + maxReceiveCount: 166 # 999 hours / 6 hours VisibilityTimeout: 300 PostDatedNotificationsDeadLetterQueue: diff --git a/packages/common/commonTypes/src/index.ts b/packages/common/commonTypes/src/index.ts index 013fd909d3..7ea8fdabc6 100644 --- a/packages/common/commonTypes/src/index.ts +++ b/packages/common/commonTypes/src/index.ts @@ -34,6 +34,21 @@ export interface PostDatedNotifyDataItem extends NotifyDataItem { LineItemID: string } +/** + * The structure of a single SQS message in a batch send. + * I couldn't find this type exported from the SDK, and the Message type that IS exported is for receiving data, + * not sending it. + * + * So, I've just done it myself. These are the core attrtibutes we use. + */ +export interface SQSBatchMessage { + Id: string + MessageBody: string + MessageDeduplicationId: string + MessageGroupId: string + MessageAttributes: {[key: string]: {DataType: string; StringValue: string}} +} + /** * The fields stored in the Notifications table potentially updated by the Notify callback. */ diff --git a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts index 1ef74d0810..9177160ddd 100644 --- a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts +++ b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts @@ -8,19 +8,12 @@ import { NotifyDataItem, PostDatedNotifyDataItem, PSUDataItem, - PSUDataItemWithPrevious + PSUDataItemWithPrevious, + SQSBatchMessage } from "@psu-common/commonTypes" import {checkSiteOrSystemIsNotifyEnabled} from "../validation/notificationSiteAndSystemFilters" -type SQSBatchMessage = { - Id: string - MessageBody: string - MessageDeduplicationId: string - MessageGroupId: string - MessageAttributes: {[key: string]: {DataType: string; StringValue: string}} -} - const sqsUrl: string | undefined = process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL const postDatedSqsUrl: string | undefined = process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL From 13c033add8ede18ac4466b8440dd7f19684e1e1f Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 23 Jan 2026 12:58:24 +0000 Subject: [PATCH 19/75] Refactor the updatePrecriptionStatus SQS interface so that it's a bit easier to handle two queues. Update the test suite accordingly. --- .../src/utils/sqsClient.ts | 172 ++++++++++-------- .../tests/testSqsClient.test.ts | 27 ++- 2 files changed, 109 insertions(+), 90 deletions(-) diff --git a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts index 9177160ddd..10227e89f1 100644 --- a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts +++ b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts @@ -37,6 +37,51 @@ function chunkArray(arr: Array, size: number): Array> { return chunks } +type DeduplicationSource = Pick +type NotificationMessage = NotifyDataItem | PostDatedNotifyDataItem + +function buildSqsBatchEntries( + items: Array, + requestId: string, + sqsSalt: string, + toMessageBody: (item: T) => NotificationMessage +): Array { + return items.map((item, idx) => ({ + Id: idx.toString(), + MessageBody: JSON.stringify(toMessageBody(item)), + MessageDeduplicationId: saltedHash(`${item.PatientNHSNumber}:${item.PharmacyODSCode}`, sqsSalt), + MessageGroupId: requestId, + MessageAttributes: { + RequestId: { + DataType: "String", + StringValue: requestId + } + } + })) +} + +/** + * Sends entries to the SQS queue in batches of 10 + * @param entries + * @param queueUrl + * @param requestId + * @param logger + * @returns An array of the created MessageIds + */ +async function sendEntriesToQueue( + entries: Array, + queueUrl: string, + requestId: string, + logger: Logger +): Promise> { + if (!entries.length) { + return [] + } + + const batches = chunkArray(entries, 10) + return placeBatchInSQS(batches, queueUrl, requestId, logger) +} + /** * Salts and hashes a string. * @@ -60,10 +105,14 @@ export function saltedHash( export async function getSaltValue(logger: Logger): Promise { let sqsSalt: string - if (process.env.SQS_SALT) { + if (!process.env.SQS_SALT) { + // No secret name configured at all, so fall back + sqsSalt = fallbackSalt + } else { try { // grab the secret, expecting JSON like { "salt": "string" } const secretJson = await getSecret(process.env.SQS_SALT, {transform: "json"}) + logger.info("Fetched SQS_SALT from Secrets Manager", {secretJson}) // must be a non‐null object with a string .salt if ( @@ -84,9 +133,6 @@ export async function getSaltValue(logger: Logger): Promise { logger.error("Failed to fetch SQS_SALT from Secrets Manager, using DEV SALT", {error}) sqsSalt = fallbackSalt } - } else { - // No secret name configured at all, so fall back - sqsSalt = fallbackSalt } if (sqsSalt === fallbackSalt) { @@ -98,12 +144,20 @@ export async function getSaltValue(logger: Logger): Promise { return sqsSalt } +/** + * Places batches of messages into SQS + * @param batches + * @param sqsUrl + * @param requestId + * @param logger + * @returns An array of the send MessageIds + */ async function placeBatchInSQS( batches: Array>, sqsUrl: string, requestId: string, logger: Logger -) { +): Promise> { // Used for the return value let out: Array = [] @@ -126,7 +180,7 @@ async function placeBatchInSQS( }) const result = await sqs.send(command) if (result.Successful?.length) { - logger.info("Successfully sent a batch of prescriptions to the notifications SQS", {result}) + logger.info("Successfully sent a batch of prescriptions to the SQS", {result, sqsUrl}) // For each successful message, get its message ID. I don't think there will ever be undefined // actually in here, but the typing suggests that there could be so filter those out @@ -134,10 +188,10 @@ async function placeBatchInSQS( } // Some may succeed, and some may fail. So check for both if (result.Failed?.length) { - throw new Error("Failed to send a batch of prescriptions to the notifications SQS") + throw new Error(`Failed to send a batch of prescriptions to the SQS {sqsUrl}`) } } catch (error) { - logger.error("Failed to send a batch of prescriptions to the notifications SQS", {error}) + logger.error("Failed to send a batch of prescriptions to the SQS", {error, sqsUrl}) throw error } } @@ -152,6 +206,8 @@ function norm(str: string) { /** * Pushes an array of PSUDataItem to the notifications SQS queue * Uses SendMessageBatch to send up to 10 at a time + * Contains the logic for filtering which items should be sent, based on + * which sites/systems are enabled, and which statuses are to be sent * * @param requestId - The x-request-id header from the incoming event * @param data - Array of PSUDataItem to send to SQS @@ -184,8 +240,6 @@ export async function pushPrescriptionToNotificationSQS( norm("ready to collect"), norm("ready to collect - partial") ]) - // Salt for the deduplication hash - const sqsSalt = await getSaltValue(logger) // Get only items which have the correct current statuses const candidates = allowedSitesAndSystemsData.filter( @@ -205,89 +259,47 @@ export async function pushPrescriptionToNotificationSQS( const postDatedItems = changedStatus.filter(item => item.PostDatedLastModifiedSetAt) const nonPostDatedItems = changedStatus.filter(item => !item.PostDatedLastModifiedSetAt) - sendPostDatedItemsToSQS(postDatedItems, requestId, logger) - - // Build SQS batch entries with FIFO parameters - const allEntries: Array = nonPostDatedItems - .map((item, idx) => ({ - Id: idx.toString(), - // Only post the required information to SQS - MessageBody: JSON.stringify(item as NotifyDataItem), - // FIFO - // We dedupe on both nhs number and ods code - MessageDeduplicationId: saltedHash(`${item.PatientNHSNumber}:${item.PharmacyODSCode}`, sqsSalt), - MessageGroupId: requestId, - MessageAttributes: { - RequestId: { - DataType: "String", - StringValue: requestId - } - } - })) - - if (!allEntries.length) { - // Carry on if we have no updates to make. - logger.info("No entries to post to the notifications SQS") - return [] - } + const postDatedMessageIds = sendItemsToSQS(postDatedItems, postDatedSqsUrl, requestId, logger) + const nonPostDatedMessageIds = sendItemsToSQS(nonPostDatedItems, sqsUrl, requestId, logger) logger.info( "The following patients will have prescription update app notifications requested", - {nhsNumbers: allowedSitesAndSystemsData.map(e => e.current.PatientNHSNumber)} + {nhsNumbers: changedStatus.map(e => e.PatientNHSNumber)} ) - // Remove post-dated entries from the normal flow - const currentlyValidEntries = allEntries.filter(entry => { - const body: NotifyDataItem = JSON.parse(entry.MessageBody) - return !("PostDatedLastModifiedSetAt" in body) - }) - - // SQS batch calls are limited to 10 messages per request, so chunk the data - const batches = chunkArray(currentlyValidEntries, 10) - const out = await placeBatchInSQS(batches, sqsUrl, requestId, logger) - - return out + return Promise.all([postDatedMessageIds, nonPostDatedMessageIds]) + .then(results => results.flat()) } -// FIXME: Remove this function once post-dated updates are deprecated -async function sendPostDatedItemsToSQS( - postDatedItems: Array, +/** + * + * @param items + * @param sqsUrl + * @param requestId + * @param logger + * @returns an array of the sent MessageIDs + */ +async function sendItemsToSQS( + items: Array, + sqsUrl: string, requestId: string, logger: Logger -): Promise { - if (postDatedItems.length === 0) { - logger.info("No post-dated items to send to SQS") - return - } - - if (!postDatedSqsUrl) { - logger.error("Post-dated Notifications SQS URL not found in environment variables") - throw new Error("Post-dated Notifications SQS URL not configured") +): Promise> { + if (items.length === 0) { + logger.info("No items to send to SQS", {sqsUrl}) + return [] } - logger.info(`Placing ${postDatedItems.length} post-dated entries into the post-dated SQS queue`) + logger.info(`Placing ${items.length} entries into the SQS queue`, {sqsUrl}) const sqsSalt = await getSaltValue(logger) - // This time, instead of posting NotifyDataItem, we use PostDatedNotifyDataItem - const allEntries: Array = postDatedItems - .map((item, idx) => ({ - Id: idx.toString(), - // Only post the required information to SQS - MessageBody: JSON.stringify(item as PostDatedNotifyDataItem), - // FIFO - // We dedupe on both nhs number and ods code - MessageDeduplicationId: saltedHash(`${item.PatientNHSNumber}:${item.PharmacyODSCode}`, sqsSalt), - MessageGroupId: requestId, - MessageAttributes: { - RequestId: { - DataType: "String", - StringValue: requestId - } - } - })) + const entries = buildSqsBatchEntries( + items, + requestId, + sqsSalt, + item => item as PostDatedNotifyDataItem + ) - // SQS batch calls are limited to 10 messages per request, so chunk the data - const batches = chunkArray(allEntries, 10) - await placeBatchInSQS(batches, postDatedSqsUrl, requestId, logger) + return await sendEntriesToQueue(entries, sqsUrl, requestId, logger) } diff --git a/packages/updatePrescriptionStatus/tests/testSqsClient.test.ts b/packages/updatePrescriptionStatus/tests/testSqsClient.test.ts index 46f73e3815..2ba0ad1379 100644 --- a/packages/updatePrescriptionStatus/tests/testSqsClient.test.ts +++ b/packages/updatePrescriptionStatus/tests/testSqsClient.test.ts @@ -163,8 +163,8 @@ describe("Unit tests for pushPrescriptionToNotificationSQS", () => { }) expect(infoSpy).toHaveBeenCalledWith( - "Successfully sent a batch of prescriptions to the notifications SQS", - {result: {Successful: [{}]}} + "Successfully sent a batch of prescriptions to the SQS", + {result: {Successful: [{}]}, sqsUrl: process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL} ) }) @@ -184,8 +184,8 @@ describe("Unit tests for pushPrescriptionToNotificationSQS", () => { ).rejects.toThrow(testError) expect(errorSpy).toHaveBeenCalledWith( - "Failed to send a batch of prescriptions to the notifications SQS", - {error: testError} + "Failed to send a batch of prescriptions to the SQS", + {error: testError, sqsUrl: process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL} ) }) @@ -195,12 +195,11 @@ describe("Unit tests for pushPrescriptionToNotificationSQS", () => { previous: createMockDataItem({Status: "previous status"}), current: createMockDataItem({Status: "ready to collect"}) } - } - ) + }) mockSend - .mockImplementationOnce(() => Promise.resolve({Successful: Array(10).fill({})})) - .mockImplementationOnce(() => Promise.resolve({Successful: Array(2).fill({})})) + .mockImplementationOnce(() => Promise.resolve({Successful: new Array(10).fill({})})) + .mockImplementationOnce(() => Promise.resolve({Successful: new Array(2).fill({})})) await pushPrescriptionToNotificationSQS("req-111", payload, logger) expect(mockSend).toHaveBeenCalledTimes(2) @@ -208,10 +207,18 @@ describe("Unit tests for pushPrescriptionToNotificationSQS", () => { it("Uses the fallback salt value but logs a warning about it", async () => { mockGetSecret.mockImplementationOnce(async () => { - return "DEV SALT" + return {"salt": "DEV SALT"} + }) + + const payload = Array.from({length: 1}, () => { + return { + previous: createMockDataItem({Status: "previous status"}), + current: createMockDataItem({Status: "ready to collect"}) + } }) + mockSend.mockImplementationOnce(() => Promise.resolve({Successful: new Array(2).fill({})})) - await pushPrescriptionToNotificationSQS("req-123", [], logger) + await pushPrescriptionToNotificationSQS("req-123", payload, logger) expect(warnSpy) .toHaveBeenCalledWith( From e3dcf146261ec209eb65e02149e9a45e51165b10 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 23 Jan 2026 13:19:43 +0000 Subject: [PATCH 20/75] Update updatePrescriptionStatus SQS handling test script to test the post-dated logic --- .../tests/testSqsClient.test.ts | 145 +++++++++++++++++- 1 file changed, 144 insertions(+), 1 deletion(-) diff --git a/packages/updatePrescriptionStatus/tests/testSqsClient.test.ts b/packages/updatePrescriptionStatus/tests/testSqsClient.test.ts index 2ba0ad1379..d8ed468b73 100644 --- a/packages/updatePrescriptionStatus/tests/testSqsClient.test.ts +++ b/packages/updatePrescriptionStatus/tests/testSqsClient.test.ts @@ -86,6 +86,20 @@ describe("Unit tests for pushPrescriptionToNotificationSQS", () => { expect(mockSend).not.toHaveBeenCalled() }) + it("throws if the post-dated SQS URL is not configured", async () => { + process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = undefined + const {pushPrescriptionToNotificationSQS: tempFunc} = await import("../src/utils/sqsClient") + + await expect( + tempFunc("req-123", [], logger) + ).rejects.toThrow("Post-dated Notifications SQS URL not configured") + + expect(warnSpy).toHaveBeenCalledWith( + "Post-dated Notifications SQS URL not found in environment variables" + ) + expect(mockSend).not.toHaveBeenCalled() + }) + it("does nothing when there are no eligible statuses", async () => { const data = [ { @@ -114,6 +128,25 @@ describe("Unit tests for pushPrescriptionToNotificationSQS", () => { expect(mockSend).not.toHaveBeenCalled() }) + it("filters out ready to collect items whose status has not changed", async () => { + const data = [ + { + current: createMockDataItem({Status: "ready to collect"}), + previous: createMockDataItem({Status: "ready to collect"}) + }, + { + current: createMockDataItem({Status: "READY TO COLLECT - PARTIAL"}), + previous: createMockDataItem({Status: "ready to collect - partial"}) + } + ] + + await expect( + pushPrescriptionToNotificationSQS("req-no-change", data, logger) + ).resolves.toEqual([]) + + expect(mockSend).not.toHaveBeenCalled() + }) + it("sends only 'ready to collect' messages and succeeds", async () => { const payload = [ { @@ -143,7 +176,7 @@ describe("Unit tests for pushPrescriptionToNotificationSQS", () => { const sent = mockSend.mock.calls[0][0] expect(sent).toBeInstanceOf(SendMessageBatchCommand) if (!(sent instanceof SendMessageBatchCommand)) { - throw new Error("Expected a SendMessageBatchCommand") + throw new TypeError("Expected a SendMessageBatchCommand") } const entries = sent.input.Entries! @@ -168,6 +201,53 @@ describe("Unit tests for pushPrescriptionToNotificationSQS", () => { ) }) + it("routes post-dated and standard notifications to their respective queues", async () => { + const postDatedCurrent = createMockDataItem({ + Status: "ready to collect", + PatientNHSNumber: "9999999999", + PharmacyODSCode: "JIM123", + PostDatedLastModifiedSetAt: "2100-01-01T00:00:00Z" + }) + const standardCurrent = createMockDataItem({ + Status: "ready to collect - partial", + PatientNHSNumber: "8888888888", + PharmacyODSCode: "JIM123" + }) + const payload = [ + {previous: createMockDataItem({Status: "previous status"}), current: postDatedCurrent}, + {previous: createMockDataItem({Status: "previous status"}), current: standardCurrent} + ] + + mockSend + .mockImplementationOnce(() => Promise.resolve({Successful: [{MessageId: "pd-id"}]})) + .mockImplementationOnce(() => Promise.resolve({Successful: [{MessageId: "std-id"}]})) + + const result = await pushPrescriptionToNotificationSQS("req-mixed", payload, logger) + + expect(result).toEqual(["pd-id", "std-id"]) // Both have been pushed to SQS, so we get their IDs + expect(mockSend).toHaveBeenCalledTimes(2) + + // Check that the send command was called twice, once with each SQS URL + const queueUrls = mockSend.mock.calls.map(call => { + const command = call[0] + expect(command).toBeInstanceOf(SendMessageBatchCommand) + if (!(command instanceof SendMessageBatchCommand)) { + throw new TypeError("Expected a SendMessageBatchCommand") + } + command.input.Entries!.forEach(entry => { + expect(entry.MessageGroupId).toBe("req-mixed") + }) + return command.input.QueueUrl + }) + + expect(queueUrls).toEqual( + expect.arrayContaining([ + process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL, + process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL + ]) + ) + }) + it("rethrows and logs if SendMessageBatchCommand rejects", async () => { const payload = [ { @@ -189,6 +269,69 @@ describe("Unit tests for pushPrescriptionToNotificationSQS", () => { ) }) + it("rejects when the standard queue fails but the post-dated queue succeeds", async () => { + const payload = [ + { + previous: createMockDataItem({Status: "previous status"}), + current: createMockDataItem({ + Status: "ready to collect", + PatientNHSNumber: "444", + PharmacyODSCode: "DDD", + PostDatedLastModifiedSetAt: "2025-05-01T00:00:00Z" + }) + }, + { + previous: createMockDataItem({Status: "previous status"}), + current: createMockDataItem({Status: "ready to collect", PatientNHSNumber: "555", PharmacyODSCode: "EEE"}) + } + ] + const standardQueueError = new Error("Standard queue failure") + + mockSend + .mockImplementationOnce(() => Promise.resolve({Successful: [{MessageId: "pd-ok"}]})) + .mockImplementationOnce(() => Promise.reject(standardQueueError)) + + await expect( + pushPrescriptionToNotificationSQS("req-failure", payload, logger) + ).rejects.toThrow(standardQueueError) + + expect(errorSpy).toHaveBeenCalledWith( + "Failed to send a batch of prescriptions to the SQS", + {error: standardQueueError, sqsUrl: process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL} + ) + }) + + it("Rejects when the post-dated queue fails but the standard queue succeeds", async () => { + const payload = [ + { + previous: createMockDataItem({Status: "previous status"}), + current: createMockDataItem({ + Status: "ready to collect", + PatientNHSNumber: "777", + PharmacyODSCode: "GGG", + PostDatedLastModifiedSetAt: "2100-12-12T00:00:00Z" + }) + }, + { + previous: createMockDataItem({Status: "previous status"}), + current: createMockDataItem({Status: "ready to collect", PatientNHSNumber: "888", PharmacyODSCode: "HHH"}) + } + ] + const postDatedQueueError = new Error("Post-dated queue failure") + + mockSend + .mockImplementationOnce(() => Promise.reject(postDatedQueueError)) + .mockImplementationOnce(() => Promise.resolve({Successful: [{MessageId: "std-ok"}]})) + + await expect( + pushPrescriptionToNotificationSQS("req-failure-2", payload, logger) + ).rejects.toThrow(postDatedQueueError) + expect(errorSpy).toHaveBeenCalledWith( + "Failed to send a batch of prescriptions to the SQS", + {error: postDatedQueueError, sqsUrl: process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL} + ) + }) + it("chunks large payloads into batches of 10", async () => { const payload = Array.from({length: 12}, () => { return { From 8c5fbd6eac3bd95ed27ece22b8639f5b37fe5e7a Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 23 Jan 2026 13:38:15 +0000 Subject: [PATCH 21/75] Add unit tests for the database client --- .../postDatedLambda/src/databaseClient.ts | 2 +- .../tests/testDatabaseClient.test.ts | 255 ++++++++++++++++++ .../tests/{main.test.ts => testMain.test.ts} | 0 packages/postDatedLambda/tests/testUtils.ts | 33 +++ 4 files changed, 289 insertions(+), 1 deletion(-) create mode 100644 packages/postDatedLambda/tests/testDatabaseClient.test.ts rename packages/postDatedLambda/tests/{main.test.ts => testMain.test.ts} (100%) create mode 100644 packages/postDatedLambda/tests/testUtils.ts diff --git a/packages/postDatedLambda/src/databaseClient.ts b/packages/postDatedLambda/src/databaseClient.ts index 28b36742b9..37f21e01dd 100644 --- a/packages/postDatedLambda/src/databaseClient.ts +++ b/packages/postDatedLambda/src/databaseClient.ts @@ -13,7 +13,7 @@ const client = new DynamoDBClient() const tableName = process.env.TABLE_NAME ?? "PrescriptionStatusUpdates" const pharmacyPrescriptionIndexName = "PharmacyODSCodePrescriptionIDIndex" -function createPrescriptionLookupKey(prescriptionID: string, pharmacyODSCode: string): string { +export function createPrescriptionLookupKey(prescriptionID: string, pharmacyODSCode: string): string { return `${prescriptionID.toUpperCase()}#${pharmacyODSCode.toUpperCase()}` } diff --git a/packages/postDatedLambda/tests/testDatabaseClient.test.ts b/packages/postDatedLambda/tests/testDatabaseClient.test.ts new file mode 100644 index 0000000000..412caeffa3 --- /dev/null +++ b/packages/postDatedLambda/tests/testDatabaseClient.test.ts @@ -0,0 +1,255 @@ +import { + expect, + describe, + it, + jest +} from "@jest/globals" + +import * as dynamo from "@aws-sdk/client-dynamodb" + +import {Logger} from "@aws-lambda-powertools/logger" + +import {createMockPostModifiedDataItem} from "./testUtils" + +// Uses unstable jest method to enable mocking while using ESM. To be replaced in future. +export function mockDynamoDBClient() { + const mockSend = jest.fn() + jest.unstable_mockModule("@aws-sdk/client-dynamodb", () => { + return { + ...dynamo, + DynamoDBClient: jest.fn().mockImplementation(() => ({ + send: mockSend + })) + } + }) + return {mockSend} +} + +const {mockSend} = mockDynamoDBClient() +const { + createPrescriptionLookupKey, + getExistingRecordsByPrescriptionID, + fetchExistingRecordsForPrescriptions, + enrichMessagesWithExistingRecords +} = await import("../src/databaseClient") + +const logger = new Logger({serviceName: "postDatedLambdaTEST"}) + +describe("databaseClient", () => { + describe("createPrescriptionLookupKey", () => { + it("should create the correct lookup key", () => { + const prescriptionID = "abc123" + const pharmacyODSCode = "pharm001" + const expectedKey = "ABC123#PHARM001" + + const result = createPrescriptionLookupKey(prescriptionID, pharmacyODSCode) + expect(result).toBe(expectedKey) + }) + }) + + describe("getExistingRecordsByPrescriptionID", () => { + it("should return existing records from DynamoDB", async () => { + const prescriptionID = "testPrescID" + const pharmacyODSCode = "testPharmODS" + + // Mock DynamoDB response + const mockItems = [ + { + PrescriptionID: {S: prescriptionID}, + PharmacyODSCode: {S: pharmacyODSCode}, + Status: {S: "Dispensed"}, + LastModified: {S: "2024-01-01T12:00:00Z"} + }, + { + PrescriptionID: {S: prescriptionID}, + PharmacyODSCode: {S: pharmacyODSCode}, + Status: {S: "ReadyForCollection"}, + LastModified: {S: "2023-12-31T12:00:00Z"} + } + ] + + mockSend.mockReturnValueOnce({ + Items: mockItems, + LastEvaluatedKey: undefined + }) + + const records = await getExistingRecordsByPrescriptionID( + prescriptionID, + pharmacyODSCode, + logger + ) + + expect(records).toHaveLength(2) + expect(records[0].Status).toBe("Dispensed") + expect(records[1].Status).toBe("ReadyForCollection") + }) + + it("Should log and throw an error if the DynamoDB query fails", async () => { + const prescriptionID = "errorPrescID" + const pharmacyODSCode = "errorPharmODS" + + // Mock DynamoDB to throw an error + const mockError = new Error("DynamoDB query failed") + mockSend.mockImplementationOnce(() => { + throw mockError + }) + + await expect( + getExistingRecordsByPrescriptionID( + prescriptionID, + pharmacyODSCode, + logger + ) + ).rejects.toThrow("DynamoDB query failed") + }) + }) + + describe("fetchExistingRecordsForPrescriptions", () => { + it("should fetch existing records for multiple prescriptions", async () => { + const prescriptions = [ + createMockPostModifiedDataItem({PrescriptionID: "presc1", PharmacyODSCode: "pharmA"}), + createMockPostModifiedDataItem({PrescriptionID: "presc2", PharmacyODSCode: "pharmB"}) + ] + + // Mock DynamoDB responses + const mockItemsPresc1 = [ + { + PrescriptionID: {S: "presc1"}, + PharmacyODSCode: {S: "pharmA"}, + Status: {S: "Dispensed"}, + LastModified: {S: "2024-01-01T12:00:00Z"} + } + ] + + const mockItemsPresc2 = [ + { + PrescriptionID: {S: "presc2"}, + PharmacyODSCode: {S: "pharmB"}, + Status: {S: "ReadyForCollection"}, + LastModified: {S: "2024-01-02T12:00:00Z"} + } + ] + + mockSend + .mockReturnValueOnce({ + Items: mockItemsPresc1, + LastEvaluatedKey: undefined + }) + .mockReturnValueOnce({ + Items: mockItemsPresc2, + LastEvaluatedKey: undefined + }) + + const result = await fetchExistingRecordsForPrescriptions( + prescriptions, + logger + ) + + expect(result.length).toBe(2) + expect(result[0].existingRecords.length).toBe(1) + expect(result[0].existingRecords[0].Status).toBe("Dispensed") + expect(result[1].existingRecords.length).toBe(1) + expect(result[1].existingRecords[0].Status).toBe("ReadyForCollection") + }) + + it( + "Should log an error if the fetch fails for one prescription, and set the existingRecords to empty array", + async () => { + const prescriptions = [ + createMockPostModifiedDataItem({PrescriptionID: "presc1", PharmacyODSCode: "pharmA"}), + createMockPostModifiedDataItem({PrescriptionID: "errorPresc", PharmacyODSCode: "errorPharm"}) + ] + + // Mock DynamoDB responses + const mockItemsPresc1 = [ + { + PrescriptionID: {S: "presc1"}, + PharmacyODSCode: {S: "pharmA"}, + Status: {S: "Dispensed"}, + LastModified: {S: "2024-01-01T12:00:00Z"} + } + ] + + mockSend + .mockReturnValueOnce({ + Items: mockItemsPresc1, + LastEvaluatedKey: undefined + }) + .mockImplementationOnce(() => { + throw new Error("DynamoDB query failed") + }) + + const result = await fetchExistingRecordsForPrescriptions( + prescriptions, + logger + ) + + expect(result.length).toBe(2) + expect(result[0].existingRecords.length).toBe(1) + expect(result[0].existingRecords[0].Status).toBe("Dispensed") + expect(result[1].existingRecords.length).toBe(0) + }) + }) + + describe("enrichMessagesWithExistingRecords", () => { + it("should enrich messages with existing records", async () => { + const prescriptions = [ + createMockPostModifiedDataItem({PrescriptionID: "presc1", PharmacyODSCode: "pharmA"}), + createMockPostModifiedDataItem({PrescriptionID: "presc2", PharmacyODSCode: "pharmB"}) + ] + + // Mock DynamoDB responses + const mockItemsPresc1 = [ + { + PrescriptionID: {S: "presc1"}, + PharmacyODSCode: {S: "pharmA"}, + Status: {S: "Dispensed"}, + LastModified: {S: "2024-01-01T12:00:00Z"} + } + ] + + const mockItemsPresc2 = [ + { + PrescriptionID: {S: "presc2"}, + PharmacyODSCode: {S: "pharmB"}, + Status: {S: "ReadyForCollection"}, + LastModified: {S: "2024-01-02T12:00:00Z"} + } + ] + + mockSend + .mockReturnValueOnce({ + Items: mockItemsPresc1, + LastEvaluatedKey: undefined + }) + .mockReturnValueOnce({ + Items: mockItemsPresc2, + LastEvaluatedKey: undefined + }) + + const messages = prescriptions.map((presc) => ({ + prescriptionData: presc + })) + + const enrichedMessages = await enrichMessagesWithExistingRecords( + messages, + logger + ) + + expect(enrichedMessages.length).toBe(2) + expect(enrichedMessages[0].existingRecords.length).toBe(1) + expect(enrichedMessages[0].existingRecords[0].Status).toBe("Dispensed") + expect(enrichedMessages[1].existingRecords.length).toBe(1) + expect(enrichedMessages[1].existingRecords[0].Status).toBe("ReadyForCollection") + }) + }) + + it("Should return empty array when no messages are provided", async () => { + const enrichedMessages = await enrichMessagesWithExistingRecords( + [], + logger + ) + + expect(enrichedMessages).toEqual([]) + }) +}) diff --git a/packages/postDatedLambda/tests/main.test.ts b/packages/postDatedLambda/tests/testMain.test.ts similarity index 100% rename from packages/postDatedLambda/tests/main.test.ts rename to packages/postDatedLambda/tests/testMain.test.ts diff --git a/packages/postDatedLambda/tests/testUtils.ts b/packages/postDatedLambda/tests/testUtils.ts new file mode 100644 index 0000000000..1a8c52209c --- /dev/null +++ b/packages/postDatedLambda/tests/testUtils.ts @@ -0,0 +1,33 @@ +import {PSUDataItem, PostDatedNotifyDataItem} from "packages/common/commonTypes/lib/src" + +export function createMockDataItem(overrides: Partial): PSUDataItem { + return { + LastModified: "2023-01-02T00:00:00Z", + LineItemID: "spamandeggs", + PatientNHSNumber: "0123456789", + PharmacyODSCode: "ABC123", + PrescriptionID: "abcdef-ghijkl-mnopqr", + RequestID: "x-request-id", + Status: "ready to collect", + TaskID: "mnopqr-ghijkl-abcdef", + TerminalStatus: "ready to collect", + ApplicationName: "Internal Test System", + ExpiryTime: 123, + ...overrides + } +} + +export function createMockPostModifiedDataItem(overrides: Partial): PostDatedNotifyDataItem { + return { + LastModified: "2023-01-02T00:00:00Z", + LineItemID: "spamandeggs", + PatientNHSNumber: "0123456789", + PharmacyODSCode: "ABC123", + PrescriptionID: "abcdef-ghijkl-mnopqr", + RequestID: "x-request-id", + Status: "ready to collect", + TaskID: "mnopqr-ghijkl-abcdef", + PostDatedLastModifiedSetAt: "Changed dosage instructions", + ...overrides + } +} From 1d60b95f613895141c56c724ad21a361877edc17 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 23 Jan 2026 13:44:24 +0000 Subject: [PATCH 22/75] Minimal orchestration test setup --- .../tests/testOrchestration.test.ts | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 packages/postDatedLambda/tests/testOrchestration.test.ts diff --git a/packages/postDatedLambda/tests/testOrchestration.test.ts b/packages/postDatedLambda/tests/testOrchestration.test.ts new file mode 100644 index 0000000000..f9c79df10a --- /dev/null +++ b/packages/postDatedLambda/tests/testOrchestration.test.ts @@ -0,0 +1,74 @@ +import { + expect, + describe, + it, + jest +} from "@jest/globals" + +// Mock the imports from local modules +const mockProcessMessage = jest.fn() +jest.unstable_mockModule("../src/businessLogic", () => { + return { + processMessage: mockProcessMessage + } +}) + +const mockEnrichMessagesWithExistingRecords = jest.fn() +jest.unstable_mockModule("../src/databaseClient", () => { + return { + enrichMessagesWithExistingRecords: mockEnrichMessagesWithExistingRecords + } +}) + +const mockReceivePostDatedSQSMessages = jest.fn() +const mockReportQueueStatus = jest.fn() +const mockHandleProcessedMessages = jest.fn() +jest.unstable_mockModule("../src/sqs", () => { + return { + receivePostDatedSQSMessages: mockReceivePostDatedSQSMessages, + reportQueueStatus: mockReportQueueStatus, + handleProcessedMessages: mockHandleProcessedMessages + } +}) + +import {Logger} from "@aws-lambda-powertools/logger" + +import {createMockPostModifiedDataItem} from "./testUtils" +import {PostDatedSQSMessage} from "../src/types" + +// Import the orchestration module after mocking dependencies +const {processMessages} = await import("../src/orchestration") + +const logger = new Logger({serviceName: "postDatedLambdaTEST"}) + +describe("orchestration", () => { + describe("processMessages", () => { + it("should process messages and categorize them correctly", async () => { + const mockMessages: Array = [ + {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostModifiedDataItem({})}, + {MessageId: "2", Body: "Message 2", prescriptionData: createMockPostModifiedDataItem({})} + ] + + // Mock the enrichment function to return the same messages + mockEnrichMessagesWithExistingRecords.mockReturnValueOnce(mockMessages) + + // Mock processMessage to return true for first message and false for second + mockProcessMessage.mockReturnValueOnce(true) + mockProcessMessage.mockReturnValueOnce(false) + + const result = await processMessages(mockMessages, logger) + + expect(result.maturedPrescriptionUpdates).toHaveLength(1) + expect(result.maturedPrescriptionUpdates[0].MessageId).toBe("1") + expect(result.immaturePrescriptionUpdates).toHaveLength(1) + expect(result.immaturePrescriptionUpdates[0].MessageId).toBe("2") + }) + + it("should handle empty message array", async () => { + const result = await processMessages([], logger) + + expect(result.maturedPrescriptionUpdates).toHaveLength(0) + expect(result.immaturePrescriptionUpdates).toHaveLength(0) + }) + }) +}) From 4e73794aebce24f21b02429405dc99ba386f7bd9 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 23 Jan 2026 14:29:05 +0000 Subject: [PATCH 23/75] expand the test coverage of the orchestration script a bit --- packages/postDatedLambda/src/orchestration.ts | 2 +- .../tests/testOrchestration.test.ts | 72 ++++++++++++++++++- 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/packages/postDatedLambda/src/orchestration.ts b/packages/postDatedLambda/src/orchestration.ts index 42db6ae73a..b7a2574def 100644 --- a/packages/postDatedLambda/src/orchestration.ts +++ b/packages/postDatedLambda/src/orchestration.ts @@ -5,7 +5,7 @@ import {enrichMessagesWithExistingRecords} from "./databaseClient" import {receivePostDatedSQSMessages, reportQueueStatus, handleProcessedMessages} from "./sqs" import {BatchProcessingResult, PostDatedSQSMessage} from "./types" -const MAX_QUEUE_RUNTIME = 14 * 60 * 1000 // 14 minutes, to avoid Lambda timeout issues (timeout is 15 minutes) +export const MAX_QUEUE_RUNTIME = 14 * 60 * 1000 // 14 minutes, to avoid Lambda timeout issues (timeout is 15 minutes) const MIN_RECEIVED_THRESHOLD = 3 // If fewer than this number of messages are received, consider the queue empty /** diff --git a/packages/postDatedLambda/tests/testOrchestration.test.ts b/packages/postDatedLambda/tests/testOrchestration.test.ts index f9c79df10a..b17f69b3db 100644 --- a/packages/postDatedLambda/tests/testOrchestration.test.ts +++ b/packages/postDatedLambda/tests/testOrchestration.test.ts @@ -34,10 +34,10 @@ jest.unstable_mockModule("../src/sqs", () => { import {Logger} from "@aws-lambda-powertools/logger" import {createMockPostModifiedDataItem} from "./testUtils" -import {PostDatedSQSMessage} from "../src/types" +import {BatchProcessingResult, PostDatedSQSMessage} from "../src/types" // Import the orchestration module after mocking dependencies -const {processMessages} = await import("../src/orchestration") +const {processMessages, processPostDatedQueue} = await import("../src/orchestration") const logger = new Logger({serviceName: "postDatedLambdaTEST"}) @@ -71,4 +71,72 @@ describe("orchestration", () => { expect(result.immaturePrescriptionUpdates).toHaveLength(0) }) }) + + describe("processPostDatedQueue", () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + it("should process the SQS queue correctly", async () => { + const mockMessages: Array = [ + {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostModifiedDataItem({})}, + {MessageId: "2", Body: "Message 2", prescriptionData: createMockPostModifiedDataItem({})} + ] + + const mockEnrichedMessages = mockMessages.map((message) => ({ + ...message, + existingRecords: [] + })) + + mockReceivePostDatedSQSMessages.mockReturnValueOnce(mockMessages) + mockEnrichMessagesWithExistingRecords.mockReturnValueOnce(mockEnrichedMessages) + mockProcessMessage.mockReturnValue(true) + + await processPostDatedQueue(logger) + + expect(mockReceivePostDatedSQSMessages).toHaveBeenCalledWith(logger) + expect(mockReportQueueStatus).not.toHaveBeenCalled() + expect(mockHandleProcessedMessages).toHaveBeenCalled() + const [res, lg] = + mockHandleProcessedMessages.mock.calls[0] as [BatchProcessingResult, Logger] + expect(lg).toBe(logger) + expect(res.maturedPrescriptionUpdates).toHaveLength(mockMessages.length) + expect(res.immaturePrescriptionUpdates).toHaveLength(0) + expect(res.maturedPrescriptionUpdates.map((message) => message.MessageId)).toEqual( + mockMessages.map((message) => message.MessageId) + ) + expect(mockProcessMessage).toHaveBeenCalledTimes(mockMessages.length) + }) + + it("Should stop processing if the max runtime is exceeded", async () => { + jest.useFakeTimers() + const mockMessages: Array = [ + {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostModifiedDataItem({})}, + {MessageId: "2", Body: "Message 2", prescriptionData: createMockPostModifiedDataItem({})}, + {MessageId: "3", Body: "Message 3", prescriptionData: createMockPostModifiedDataItem({})}, + {MessageId: "4", Body: "Message 4", prescriptionData: createMockPostModifiedDataItem({})}, + {MessageId: "5", Body: "Message 5", prescriptionData: createMockPostModifiedDataItem({})}, + {MessageId: "6", Body: "Message 6", prescriptionData: createMockPostModifiedDataItem({})} + ] + + mockReceivePostDatedSQSMessages.mockReturnValue(mockMessages) + mockEnrichMessagesWithExistingRecords.mockReturnValue( + mockMessages.map((message) => ({ + ...message, + existingRecords: [] + })) + ) + const {MAX_QUEUE_RUNTIME} = await import("../src/orchestration") + mockProcessMessage.mockImplementation(async () => { + // Overrun by a second + jest.advanceTimersByTime(MAX_QUEUE_RUNTIME + 1000) + return true + }) + + await processPostDatedQueue(logger) + + expect(mockReportQueueStatus).toHaveBeenCalled() + jest.useRealTimers() + }) + }) }) From 13474a0ae5042a1bfd39133ab8e3ad53a85e9c0c Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 23 Jan 2026 15:27:27 +0000 Subject: [PATCH 24/75] Imrprove testing coverage of the orchestration suite --- .../tests/testDatabaseClient.test.ts | 2 +- .../tests/testOrchestration.test.ts | 102 +++++++++++++++++- .../postDatedLambda/tests/testUtils.test.ts | 16 +++ packages/postDatedLambda/tests/testUtils.ts | 33 ------ 4 files changed, 118 insertions(+), 35 deletions(-) create mode 100644 packages/postDatedLambda/tests/testUtils.test.ts delete mode 100644 packages/postDatedLambda/tests/testUtils.ts diff --git a/packages/postDatedLambda/tests/testDatabaseClient.test.ts b/packages/postDatedLambda/tests/testDatabaseClient.test.ts index 412caeffa3..d32ac427d6 100644 --- a/packages/postDatedLambda/tests/testDatabaseClient.test.ts +++ b/packages/postDatedLambda/tests/testDatabaseClient.test.ts @@ -9,7 +9,7 @@ import * as dynamo from "@aws-sdk/client-dynamodb" import {Logger} from "@aws-lambda-powertools/logger" -import {createMockPostModifiedDataItem} from "./testUtils" +import {createMockPostModifiedDataItem} from "./testUtils.test" // Uses unstable jest method to enable mocking while using ESM. To be replaced in future. export function mockDynamoDBClient() { diff --git a/packages/postDatedLambda/tests/testOrchestration.test.ts b/packages/postDatedLambda/tests/testOrchestration.test.ts index b17f69b3db..f6ad38418e 100644 --- a/packages/postDatedLambda/tests/testOrchestration.test.ts +++ b/packages/postDatedLambda/tests/testOrchestration.test.ts @@ -33,7 +33,7 @@ jest.unstable_mockModule("../src/sqs", () => { import {Logger} from "@aws-lambda-powertools/logger" -import {createMockPostModifiedDataItem} from "./testUtils" +import {createMockPostModifiedDataItem} from "./testUtils.test" import {BatchProcessingResult, PostDatedSQSMessage} from "../src/types" // Import the orchestration module after mocking dependencies @@ -43,6 +43,10 @@ const logger = new Logger({serviceName: "postDatedLambdaTEST"}) describe("orchestration", () => { describe("processMessages", () => { + beforeEach(() => { + jest.clearAllMocks() + }) + it("should process messages and categorize them correctly", async () => { const mockMessages: Array = [ {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostModifiedDataItem({})}, @@ -69,6 +73,51 @@ describe("orchestration", () => { expect(result.maturedPrescriptionUpdates).toHaveLength(0) expect(result.immaturePrescriptionUpdates).toHaveLength(0) + expect(mockEnrichMessagesWithExistingRecords).not.toHaveBeenCalled() + }) + + it("should log errors and mark messages immature when processing throws", async () => { + const mockMessages: Array = [ + {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostModifiedDataItem({})}, + {MessageId: "2", Body: "Message 2", prescriptionData: createMockPostModifiedDataItem({})} + ] + + mockEnrichMessagesWithExistingRecords.mockReturnValueOnce(mockMessages) + mockProcessMessage + .mockReturnValueOnce(true) + .mockImplementationOnce(async () => { + throw new Error("processing failed") + }) + + const errorSpy = jest.spyOn(logger, "error") + const result = await processMessages(mockMessages, logger) + + expect(result.maturedPrescriptionUpdates).toHaveLength(1) + expect(result.immaturePrescriptionUpdates).toHaveLength(1) + expect(result.immaturePrescriptionUpdates[0].MessageId).toBe("2") + expect(errorSpy).toHaveBeenCalledWith( + "Error processing message", + expect.objectContaining({messageId: "2"}) + ) + errorSpy.mockRestore() + }) + + it("should pass enriched records into processMessage", async () => { + const mockMessages: Array = [ + {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostModifiedDataItem({})} + ] + + const enrichedMessage = { + ...mockMessages[0], + existingRecords: [{prescriptionId: "abc"}] + } + + mockEnrichMessagesWithExistingRecords.mockReturnValueOnce([enrichedMessage]) + mockProcessMessage.mockReturnValue(true) + + await processMessages(mockMessages, logger) + + expect(mockProcessMessage).toHaveBeenCalledWith(logger, enrichedMessage) }) }) @@ -138,5 +187,56 @@ describe("orchestration", () => { expect(mockReportQueueStatus).toHaveBeenCalled() jest.useRealTimers() }) + + it("should continue processing batches until message count drops below threshold", async () => { + const createBatch = (ids: Array) => + ids.map((id) => ({ + MessageId: id, + Body: `Message ${id}`, + prescriptionData: createMockPostModifiedDataItem({}) + })) + + const batch1 = createBatch(["1", "2", "3"]) + const batch2 = createBatch(["4", "5", "6"]) + const batch3 = createBatch(["7"]) + + const enrich = (messages: Array) => + messages.map((message) => ({ + ...message, + existingRecords: [] + })) + + mockReceivePostDatedSQSMessages + .mockReturnValueOnce(batch1) + .mockReturnValueOnce(batch2) + .mockReturnValueOnce(batch3) + mockEnrichMessagesWithExistingRecords + .mockReturnValueOnce(enrich(batch1)) + .mockReturnValueOnce(enrich(batch2)) + .mockReturnValueOnce(enrich(batch3)) + mockProcessMessage.mockReturnValue(true) + + await processPostDatedQueue(logger) + + expect(mockReceivePostDatedSQSMessages).toHaveBeenCalledTimes(3) + expect(mockHandleProcessedMessages).toHaveBeenCalledTimes(3) + expect(mockReportQueueStatus).not.toHaveBeenCalled() + const totalMessages = batch1.length + batch2.length + batch3.length + expect(mockProcessMessage).toHaveBeenCalledTimes(totalMessages) + }) + + it("should treat empty receives as drained batches", async () => { + mockReceivePostDatedSQSMessages.mockReturnValueOnce([]) + + await processPostDatedQueue(logger) + + expect(mockEnrichMessagesWithExistingRecords).not.toHaveBeenCalled() + expect(mockProcessMessage).not.toHaveBeenCalled() + expect(mockHandleProcessedMessages).toHaveBeenCalledTimes(1) + const [result] = mockHandleProcessedMessages.mock.calls[0] as [BatchProcessingResult] + expect(result.maturedPrescriptionUpdates).toHaveLength(0) + expect(result.immaturePrescriptionUpdates).toHaveLength(0) + expect(mockReportQueueStatus).not.toHaveBeenCalled() + }) }) }) diff --git a/packages/postDatedLambda/tests/testUtils.test.ts b/packages/postDatedLambda/tests/testUtils.test.ts new file mode 100644 index 0000000000..5eb0952b7d --- /dev/null +++ b/packages/postDatedLambda/tests/testUtils.test.ts @@ -0,0 +1,16 @@ +import {PostDatedNotifyDataItem} from "packages/common/commonTypes/lib/src" + +export function createMockPostModifiedDataItem(overrides: Partial): PostDatedNotifyDataItem { + return { + LastModified: "2023-01-02T00:00:00Z", + LineItemID: "spamandeggs", + PatientNHSNumber: "0123456789", + PharmacyODSCode: "ABC123", + PrescriptionID: "abcdef-ghijkl-mnopqr", + RequestID: "x-request-id", + Status: "ready to collect", + TaskID: "mnopqr-ghijkl-abcdef", + PostDatedLastModifiedSetAt: "Changed dosage instructions", + ...overrides + } +} diff --git a/packages/postDatedLambda/tests/testUtils.ts b/packages/postDatedLambda/tests/testUtils.ts deleted file mode 100644 index 1a8c52209c..0000000000 --- a/packages/postDatedLambda/tests/testUtils.ts +++ /dev/null @@ -1,33 +0,0 @@ -import {PSUDataItem, PostDatedNotifyDataItem} from "packages/common/commonTypes/lib/src" - -export function createMockDataItem(overrides: Partial): PSUDataItem { - return { - LastModified: "2023-01-02T00:00:00Z", - LineItemID: "spamandeggs", - PatientNHSNumber: "0123456789", - PharmacyODSCode: "ABC123", - PrescriptionID: "abcdef-ghijkl-mnopqr", - RequestID: "x-request-id", - Status: "ready to collect", - TaskID: "mnopqr-ghijkl-abcdef", - TerminalStatus: "ready to collect", - ApplicationName: "Internal Test System", - ExpiryTime: 123, - ...overrides - } -} - -export function createMockPostModifiedDataItem(overrides: Partial): PostDatedNotifyDataItem { - return { - LastModified: "2023-01-02T00:00:00Z", - LineItemID: "spamandeggs", - PatientNHSNumber: "0123456789", - PharmacyODSCode: "ABC123", - PrescriptionID: "abcdef-ghijkl-mnopqr", - RequestID: "x-request-id", - Status: "ready to collect", - TaskID: "mnopqr-ghijkl-abcdef", - PostDatedLastModifiedSetAt: "Changed dosage instructions", - ...overrides - } -} From 82403374b7e7e93e186e2a7b6db780bce33016f9 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 23 Jan 2026 15:40:39 +0000 Subject: [PATCH 25/75] Rename util file. --- .../postDatedLambda/tests/{testUtils.test.ts => testUtils.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/postDatedLambda/tests/{testUtils.test.ts => testUtils.ts} (100%) diff --git a/packages/postDatedLambda/tests/testUtils.test.ts b/packages/postDatedLambda/tests/testUtils.ts similarity index 100% rename from packages/postDatedLambda/tests/testUtils.test.ts rename to packages/postDatedLambda/tests/testUtils.ts From 2f44905e19dd2f6254720db068c1be5ceb454c47 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 23 Jan 2026 15:42:56 +0000 Subject: [PATCH 26/75] Forgot to rename imports --- packages/postDatedLambda/tests/testDatabaseClient.test.ts | 2 +- packages/postDatedLambda/tests/testOrchestration.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/postDatedLambda/tests/testDatabaseClient.test.ts b/packages/postDatedLambda/tests/testDatabaseClient.test.ts index d32ac427d6..412caeffa3 100644 --- a/packages/postDatedLambda/tests/testDatabaseClient.test.ts +++ b/packages/postDatedLambda/tests/testDatabaseClient.test.ts @@ -9,7 +9,7 @@ import * as dynamo from "@aws-sdk/client-dynamodb" import {Logger} from "@aws-lambda-powertools/logger" -import {createMockPostModifiedDataItem} from "./testUtils.test" +import {createMockPostModifiedDataItem} from "./testUtils" // Uses unstable jest method to enable mocking while using ESM. To be replaced in future. export function mockDynamoDBClient() { diff --git a/packages/postDatedLambda/tests/testOrchestration.test.ts b/packages/postDatedLambda/tests/testOrchestration.test.ts index f6ad38418e..90bb7834ef 100644 --- a/packages/postDatedLambda/tests/testOrchestration.test.ts +++ b/packages/postDatedLambda/tests/testOrchestration.test.ts @@ -33,7 +33,7 @@ jest.unstable_mockModule("../src/sqs", () => { import {Logger} from "@aws-lambda-powertools/logger" -import {createMockPostModifiedDataItem} from "./testUtils.test" +import {createMockPostModifiedDataItem} from "./testUtils" import {BatchProcessingResult, PostDatedSQSMessage} from "../src/types" // Import the orchestration module after mocking dependencies From 28974e00b19ad8e914ca108a693ce6b72224ec44 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 23 Jan 2026 15:58:18 +0000 Subject: [PATCH 27/75] Start writing the unit tests for the SQS interface --- packages/postDatedLambda/src/sqs.ts | 2 +- .../postDatedLambda/tests/testSqs.test.ts | 118 ++++++++++++++++++ 2 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 packages/postDatedLambda/tests/testSqs.test.ts diff --git a/packages/postDatedLambda/src/sqs.ts b/packages/postDatedLambda/src/sqs.ts index 1a6cc48705..b60d033a11 100644 --- a/packages/postDatedLambda/src/sqs.ts +++ b/packages/postDatedLambda/src/sqs.ts @@ -18,7 +18,7 @@ const sqs = new SQSClient({region: process.env.AWS_REGION}) * Get the SQS queue URL from environment variables. * Throws an error if not configured. */ -function getQueueUrl(logger: Logger): string { +export function getQueueUrl(logger: Logger): string { const sqsUrl = process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL if (!sqsUrl) { logger.error("Post-dated prescriptions SQS URL not configured") diff --git a/packages/postDatedLambda/tests/testSqs.test.ts b/packages/postDatedLambda/tests/testSqs.test.ts new file mode 100644 index 0000000000..16ad09c8b6 --- /dev/null +++ b/packages/postDatedLambda/tests/testSqs.test.ts @@ -0,0 +1,118 @@ +import { + describe, + it, + expect, + jest +} from "@jest/globals" +import {SpiedFunction} from "jest-mock" + +import {Logger} from "@aws-lambda-powertools/logger" +import {LogItemMessage, LogItemExtraInput} from "@aws-lambda-powertools/logger/lib/cjs/types/Logger" +import * as sqs from "@aws-sdk/client-sqs" + +export function mockSQSClient() { + const mockSend = jest.fn() + jest.unstable_mockModule("@aws-sdk/client-sqs", () => { + return { + ...sqs, + SQSClient: jest.fn().mockImplementation(() => ({ + send: mockSend + })) + } + }) + return {mockSend} +} + +const {mockSend} = mockSQSClient() + +const {getQueueUrl, reportQueueStatus} = await import("../src/sqs") + +const ORIGINAL_ENV = {...process.env} + +describe("sqs", () => { + let logger: Logger + let infoSpy: SpiedFunction<(input: LogItemMessage, ...extraInput: LogItemExtraInput) => void> + let errorSpy: SpiedFunction<(input: LogItemMessage, ...extraInput: LogItemExtraInput) => void> + // let warnSpy: SpiedFunction<(input: LogItemMessage, ...extraInput: LogItemExtraInput) => void> + + beforeEach(() => { + jest.resetModules() + jest.clearAllMocks() + + // Reset environment + process.env = {...ORIGINAL_ENV} + + // Fresh logger and spies + logger = new Logger({serviceName: "test-service"}) + infoSpy = jest.spyOn(logger, "info") + errorSpy = jest.spyOn(logger, "error") + // warnSpy = jest.spyOn(logger, "warn") + }) + + describe("getQueueUrl", () => { + it("Should return the SQS queue URL from environment variables", () => { + const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue" + process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl + + const result = getQueueUrl(logger) + expect(result).toBe(testUrl) + }) + + it("Should throw an error if the SQS queue URL is not configured", () => { + delete process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL + + expect(() => getQueueUrl(logger)).toThrow("POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL not set") + expect(errorSpy).toHaveBeenCalledWith("Post-dated prescriptions SQS URL not configured") + }) + }) + + describe("reportQueueStatus", () => { + it("Should report the current status of the SQS queue", async () => { + const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue" + process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl + + // Mock SQS response + mockSend.mockReturnValueOnce({ + Attributes: { + ApproximateNumberOfMessages: "5", + ApproximateNumberOfMessagesNotVisible: "2", + ApproximateNumberOfMessagesDelayed: "1" + } + }) + + await reportQueueStatus(logger) + + expect(mockSend).toHaveBeenCalledTimes(1) + expect(infoSpy).toHaveBeenCalledWith( + "Current post-dated queue attributes (if a value failed to fetch, it will be reported as -1):", + { + ApproximateNumberOfMessages: 5, + ApproximateNumberOfMessagesNotVisible: 2, + ApproximateNumberOfMessagesDelayed: 1 + } + ) + }) + + it("Should handle missing attributes gracefully", async () => { + const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue" + process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl + + // Mock SQS response with missing attributes + mockSend.mockReturnValueOnce({ + Attributes: {} + }) + + await reportQueueStatus(logger) + + expect(mockSend).toHaveBeenCalledTimes(1) + expect(infoSpy).toHaveBeenCalledWith( + "Current post-dated queue attributes (if a value failed to fetch, it will be reported as -1):", + { + ApproximateNumberOfMessages: -1, + ApproximateNumberOfMessagesNotVisible: -1, + ApproximateNumberOfMessagesDelayed: -1 + } + ) + }) + }) +}) From a5e03b7fd1098f70119ce62d4104458f5aae707f Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 23 Jan 2026 16:18:13 +0000 Subject: [PATCH 28/75] Expand SQS test suite --- packages/postDatedLambda/src/sqs.ts | 24 +- .../postDatedLambda/tests/testSqs.test.ts | 221 +++++++++++++++++- 2 files changed, 236 insertions(+), 9 deletions(-) diff --git a/packages/postDatedLambda/src/sqs.ts b/packages/postDatedLambda/src/sqs.ts index b60d033a11..a9ec00bb89 100644 --- a/packages/postDatedLambda/src/sqs.ts +++ b/packages/postDatedLambda/src/sqs.ts @@ -164,6 +164,8 @@ export async function removeSQSMessages( messageIds: entries.map((e) => e.Id) }) } + + logger.info(`Successfully removed ${delResult.Successful?.length ?? 0} messages from SQS`) } /** @@ -206,15 +208,21 @@ export async function returnMessagesToQueue( QueueUrl: sqsUrl, Entries: entries }) - const result = await sqs.send(changeVisibilityCmd) - if (result.Failed && result.Failed.length > 0) { - logger.error("Some messages failed to have visibility changed in this batch", {failed: result.Failed}) - } else { - logger.info("Successfully returned SQS messages to queue", { - result: result, - messageIds: entries.map((e) => e.Id) - }) + try { + const result = await sqs.send(changeVisibilityCmd) + + if (result.Failed && result.Failed.length > 0) { + logger.error("Some messages failed to have visibility changed in this batch", {failed: result.Failed}) + } else { + logger.info("Successfully returned SQS messages to queue", { + result: result, + messageIds: entries.map((e) => e.Id) + }) + } + } catch (error) { + const message = error instanceof Error ? error.message : "Failed to change SQS message visibility" + logger.error(message, {error}) } } diff --git a/packages/postDatedLambda/tests/testSqs.test.ts b/packages/postDatedLambda/tests/testSqs.test.ts index 16ad09c8b6..21d8acc80c 100644 --- a/packages/postDatedLambda/tests/testSqs.test.ts +++ b/packages/postDatedLambda/tests/testSqs.test.ts @@ -9,6 +9,8 @@ import {SpiedFunction} from "jest-mock" import {Logger} from "@aws-lambda-powertools/logger" import {LogItemMessage, LogItemExtraInput} from "@aws-lambda-powertools/logger/lib/cjs/types/Logger" import * as sqs from "@aws-sdk/client-sqs" +import {BatchProcessingResult, PostDatedSQSMessage} from "../src/types" +import {createMockPostModifiedDataItem} from "./testUtils" export function mockSQSClient() { const mockSend = jest.fn() @@ -25,7 +27,14 @@ export function mockSQSClient() { const {mockSend} = mockSQSClient() -const {getQueueUrl, reportQueueStatus} = await import("../src/sqs") +const { + getQueueUrl, + reportQueueStatus, + receivePostDatedSQSMessages, + removeSQSMessages, + returnMessagesToQueue, + handleProcessedMessages +} = await import("../src/sqs") const ORIGINAL_ENV = {...process.env} @@ -115,4 +124,214 @@ describe("sqs", () => { ) }) }) + + describe("receivePostDatedSQSMessages", () => { + it("Should receive messages from the SQS queue", async () => { + const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue" + process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl + + // Mock SQS response with messages + const mockMessages = [ + { + MessageId: "1", + Body: JSON.stringify({PrescriptionID: "presc1"}) + }, + { + MessageId: "2", + Body: JSON.stringify({PrescriptionID: "presc2"}) + } + ] + mockSend.mockReturnValueOnce({ + Messages: mockMessages + }) + + const result = await receivePostDatedSQSMessages(logger) + + expect(mockSend).toHaveBeenCalledTimes(1) + expect(result).toHaveLength(2) + expect(result[0].MessageId).toBe("1") + expect(result[0].prescriptionData.PrescriptionID).toBe("presc1") + expect(result[1].MessageId).toBe("2") + expect(result[1].prescriptionData.PrescriptionID).toBe("presc2") + }) + + it("Should return an empty array if no messages are received", async () => { + const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue" + process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl + + // Mock SQS response with no messages + mockSend.mockReturnValueOnce({ + Messages: [] + }) + + const result = await receivePostDatedSQSMessages(logger) + + expect(mockSend).toHaveBeenCalledTimes(1) + expect(result).toHaveLength(0) + }) + }) + + describe("removeSQSMessages", () => { + it("Should remove messages from the SQS queue", async () => { + const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue" + process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl + + const messagesToRemove = [ + {MessageId: "1", ReceiptHandle: "handle1"}, + {MessageId: "2", ReceiptHandle: "handle2"} + ] + + // Mock SQS delete response + mockSend.mockReturnValueOnce({ + Successful: messagesToRemove.map((msg) => ({Id: msg.MessageId})), + Failed: [] + }) + + await removeSQSMessages(logger, messagesToRemove) + + expect(mockSend).toHaveBeenCalledTimes(1) + expect(infoSpy).toHaveBeenCalledWith("Successfully removed 2 messages from SQS") + }) + + it("Should handle empty message array gracefully", async () => { + await removeSQSMessages(logger, []) + + expect(mockSend).toHaveBeenCalledTimes(0) + expect(infoSpy).toHaveBeenCalledWith("No messages to delete") + }) + + it("Should log errors but not throw if deletion fails", async () => { + // We don't want to throw on failed deletions, as this would cause + // later batches to be skipped unnecessarily. + // The messages that are failed to delete will become visible and be processed again after the visibility timeout + const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue" + process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl + + const messagesToRemove = [ + {MessageId: "1", ReceiptHandle: "handle1"}, + {MessageId: "2", ReceiptHandle: "handle2"} + ] + + // Mock SQS delete response with failures + mockSend.mockReturnValueOnce({ + Successful: [{Id: "1"}], + Failed: [{Id: "2", Message: "Some error"}] + }) + + await removeSQSMessages(logger, messagesToRemove) + + expect(mockSend).toHaveBeenCalledTimes(1) + expect(errorSpy).toHaveBeenCalledWith("Some messages failed to delete", { + failed: [{Id: "2", Message: "Some error"}] + }) + expect(infoSpy).toHaveBeenCalledWith("Successfully removed 1 messages from SQS") + }) + }) + + describe("returnMessagesToQueue", () => { + it("Should return messages to the SQS queue by updating their visibility timeout", async () => { + const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue" + process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl + + const messagesToReturn = [ + {MessageId: "1", ReceiptHandle: "handle1"}, + {MessageId: "2", ReceiptHandle: "handle2"} + ] + + // Mock SQS change visibility response + mockSend.mockReturnValueOnce({ + // No specific return value needed for ChangeMessageVisibilityBatch + }) + + await returnMessagesToQueue(logger, messagesToReturn) + + expect(mockSend).toHaveBeenCalledTimes(1) + expect(infoSpy).toHaveBeenCalledWith("Returning messages to queue with timeouts", { + numberOfMessages: 2, + idAndTimeouts: [ + {id: "1", visibilityTimeout: 300}, + {id: "2", visibilityTimeout: 300} + ] + }) + expect(errorSpy).not.toHaveBeenCalled() + }) + + it("Should handle empty message array gracefully", async () => { + await returnMessagesToQueue(logger, []) + + expect(mockSend).toHaveBeenCalledTimes(0) + expect(infoSpy).toHaveBeenCalledWith("No messages to return to queue") + }) + + it("should log an error if SQS change visibility fails", async () => { + const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue" + process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl + + const messagesToReturn = [ + {MessageId: "1", ReceiptHandle: "handle1"} + ] + + // Mock SQS change visibility to throw an error + const expectedError = new Error("SQS change visibility failed") + mockSend.mockImplementationOnce(() => { + throw expectedError + }) + + await returnMessagesToQueue(logger, messagesToReturn) + + expect(mockSend).toHaveBeenCalledTimes(1) + expect(errorSpy).toHaveBeenCalledWith("SQS change visibility failed", {error: expectedError}) + }) + }) + + describe("handleProcessedMessages", () => { + it("should remove matured messages and return immature messages to the queue", async () => { + const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue" + process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl + + const maturedMessages: Array = [ + {MessageId: "1", ReceiptHandle: "handle1", prescriptionData: createMockPostModifiedDataItem({})} + ] + const immatureMessages: Array = [ + {MessageId: "2", ReceiptHandle: "handle2", prescriptionData: createMockPostModifiedDataItem({})} + ] + + const batchResult: BatchProcessingResult = { + maturedPrescriptionUpdates: maturedMessages, + immaturePrescriptionUpdates: immatureMessages + } + + // Mock SQS responses + mockSend + .mockReturnValueOnce({ + Successful: [{Id: "1"}], + Failed: [] + }) // For removeSQSMessages + .mockReturnValueOnce({}) // For returnMessagesToQueue + + await handleProcessedMessages(batchResult, logger) + + expect(mockSend).toHaveBeenCalledTimes(2) + expect(infoSpy).toHaveBeenCalledWith("Successfully removed 1 messages from SQS") + expect(infoSpy).toHaveBeenCalledWith("Returning messages to queue with timeouts", { + numberOfMessages: 1, + idAndTimeouts: [ + {id: "2", visibilityTimeout: 300} + ] + }) + }) + + it("should handle empty matured and immature message arrays gracefully", async () => { + const batchResult: BatchProcessingResult = { + maturedPrescriptionUpdates: [], + immaturePrescriptionUpdates: [] + } + + await handleProcessedMessages(batchResult, logger) + + expect(mockSend).toHaveBeenCalledTimes(0) + expect(infoSpy).not.toHaveBeenCalledWith("Successfully removed") + expect(infoSpy).not.toHaveBeenCalledWith("Returning messages to queue with timeouts", expect.anything()) + }) + }) }) From 97a7f2410522d201d5ace7aed53b9650380d1ec6 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 23 Jan 2026 16:37:05 +0000 Subject: [PATCH 29/75] Gate the post-dated lambda behind a feature flag in updatePrescriptionStatus. Enable in in dev, qa, and int, but leave it off in ref and prod --- .github/workflows/ci.yml | 3 +++ .github/workflows/pull_request.yml | 1 + .github/workflows/release.yml | 3 +++ .../workflows/run_release_code_and_api.yml | 4 ++++ SAMtemplates/functions/main.yaml | 5 ++++ SAMtemplates/main_template.yaml | 5 ++++ .../src/utils/sqsClient.ts | 24 +++++++++++++------ 7 files changed, 38 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c265bbdab..c513a493d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,6 +86,7 @@ jobs: ENABLE_BACKUP: "True" ENABLE_NOTIFICATIONS_INTERNAL: true ENABLE_NOTIFICATIONS_EXTERNAL: false + ENABLE_POST_DATED_NOTIFICATIONS: true ENABLED_SYSTEMS: "Internal Test System" BLOCKED_SITE_ODS_CODES: "B3J1Z" NOTIFY_ROUTING_PLAN_ID: "e57fe5cc-0567-4854-abe2-b7dd9014a50c" @@ -126,6 +127,7 @@ jobs: ENABLE_BACKUP: "False" ENABLE_NOTIFICATIONS_INTERNAL: false ENABLE_NOTIFICATIONS_EXTERNAL: false + ENABLE_POST_DATED_NOTIFICATIONS: true ENABLED_SYSTEMS: "Internal Test System" BLOCKED_SITE_ODS_CODES: "B3J1Z" NOTIFY_ROUTING_PLAN_ID: "e57fe5cc-0567-4854-abe2-b7dd9014a50c" @@ -163,6 +165,7 @@ jobs: ENABLE_BACKUP: "False" ENABLE_NOTIFICATIONS_INTERNAL: false ENABLE_NOTIFICATIONS_EXTERNAL: false + ENABLE_POST_DATED_NOTIFICATIONS: true ENABLED_SYSTEMS: "Internal Test System" BLOCKED_SITE_ODS_CODES: "B3J1Z" NOTIFY_ROUTING_PLAN_ID: "e57fe5cc-0567-4854-abe2-b7dd9014a50c" diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 02d6f1337a..24bd8333c7 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -119,6 +119,7 @@ jobs: ENABLE_BACKUP: "False" ENABLE_NOTIFICATIONS_INTERNAL: true ENABLE_NOTIFICATIONS_EXTERNAL: false + ENABLE_POST_DATED_NOTIFICATIONS: true ENABLED_SYSTEMS: "Internal Test System" BLOCKED_SITE_ODS_CODES: "XXXXX" # Workaround empty string handling NOTIFY_ROUTING_PLAN_ID: "e57fe5cc-0567-4854-abe2-b7dd9014a50c" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b9d19e90e3..9ceb32c084 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -84,6 +84,7 @@ jobs: ENABLE_BACKUP: "True" ENABLE_NOTIFICATIONS_INTERNAL: false ENABLE_NOTIFICATIONS_EXTERNAL: false + ENABLE_POST_DATED_NOTIFICATIONS: true ENABLED_SYSTEMS: "Internal Test System" BLOCKED_SITE_ODS_CODES: "XXXXX" # Workaround empty string handling NOTIFY_ROUTING_PLAN_ID: "e57fe5cc-0567-4854-abe2-b7dd9014a50c" @@ -223,6 +224,7 @@ jobs: ENABLE_BACKUP: "False" ENABLE_NOTIFICATIONS_INTERNAL: false ENABLE_NOTIFICATIONS_EXTERNAL: false + ENABLE_POST_DATED_NOTIFICATIONS: true ENABLED_SYSTEMS: "Internal Test System, Apotec Ltd - Apotec CRM - Production, CrxPatientApp, nhsPrescriptionApp, Titan PSU Prod" BLOCKED_SITE_ODS_CODES: "B3J1Z" NOTIFY_ROUTING_PLAN_ID: "e57fe5cc-0567-4854-abe2-b7dd9014a50c" @@ -266,6 +268,7 @@ jobs: ENABLE_BACKUP: "True" ENABLE_NOTIFICATIONS_INTERNAL: true ENABLE_NOTIFICATIONS_EXTERNAL: true + ENABLE_POST_DATED_NOTIFICATIONS: true ENABLED_SYSTEMS: "Internal Test System" # Workaround empty string handling BLOCKED_SITE_ODS_CODES: "XXXXX" # Workaround empty string handling NOTIFY_ROUTING_PLAN_ID: "e57fe5cc-0567-4854-abe2-b7dd9014a50c" diff --git a/.github/workflows/run_release_code_and_api.yml b/.github/workflows/run_release_code_and_api.yml index d5c4c32f48..7b10e52e11 100644 --- a/.github/workflows/run_release_code_and_api.yml +++ b/.github/workflows/run_release_code_and_api.yml @@ -84,6 +84,10 @@ on: required: false type: boolean default: false + ENABLE_POST_DATED_NOTIFICATIONS: + required: false + type: boolean + default: false ENABLED_SYSTEMS: required: true type: string diff --git a/SAMtemplates/functions/main.yaml b/SAMtemplates/functions/main.yaml index 6aa02cef71..601f9f2c43 100644 --- a/SAMtemplates/functions/main.yaml +++ b/SAMtemplates/functions/main.yaml @@ -61,6 +61,10 @@ Parameters: EnableNotificationsInternalParam: Type: AWS::SSM::Parameter::Name + EnablePostDatedNotifications: + Type: AWS::SSM::Parameter::Name + Default: "false" + RequireApplicationName: Type: String Default: false @@ -126,6 +130,7 @@ Resources: TABLE_NAME: !Ref PrescriptionStatusUpdatesTableName NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL: !Ref NHSNotifyPrescriptionsSQSQueueUrl POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL: !Ref PostDatedNotificationsSQSQueueUrl + ENABLE_POST_DATED_NOTIFICATIONS: !Ref EnablePostDatedNotifications SQS_SALT: !Ref SQSSaltSecret ENABLED_SITE_ODS_CODES_PARAM: !Ref EnabledSiteODSCodesParam ENABLED_SYSTEMS_PARAM: !Ref EnabledSystemsParam diff --git a/SAMtemplates/main_template.yaml b/SAMtemplates/main_template.yaml index 7219907136..48a2f6b8d3 100644 --- a/SAMtemplates/main_template.yaml +++ b/SAMtemplates/main_template.yaml @@ -122,6 +122,10 @@ Parameters: Type: String Default: " " + EnablePostDatedNotifications: + Type: String + Default: "false" + NotifyRoutingPlanIDValue: Type: String Default: " " @@ -232,6 +236,7 @@ Resources: PrescriptionNotificationStatesTableName: !GetAtt Tables.Outputs.PrescriptionNotificationStatesTableName NHSNotifyPrescriptionsSQSQueueUrl: !GetAtt Messaging.Outputs.NHSNotifyPrescriptionsSQSQueueUrl PostDatedNotificationsSQSQueueUrl: !GetAtt Messaging.Outputs.PostDatedNotificationsSQSQueueUrl + EnablePostDatedNotifications: !Ref EnablePostDatedNotifications SQSSaltSecret: !GetAtt Secrets.Outputs.SQSSaltSecret EnabledSiteODSCodesParam: !GetAtt Parameters.Outputs.EnabledSiteODSCodesParameterName EnabledSystemsParam: !GetAtt Parameters.Outputs.EnabledSystemsParameterName diff --git a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts index 10227e89f1..f09769c923 100644 --- a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts +++ b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts @@ -14,6 +14,9 @@ import { import {checkSiteOrSystemIsNotifyEnabled} from "../validation/notificationSiteAndSystemFilters" +// eslint-disable-next-line max-len +const ENABLE_POST_DATED_NOTIFICATIONS = (process.env.ENABLE_POST_DATED_NOTIFICATIONS || "false").toLowerCase() === "true" + const sqsUrl: string | undefined = process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL const postDatedSqsUrl: string | undefined = process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL @@ -255,20 +258,27 @@ export async function pushPrescriptionToNotificationSQS( }) .map(({current}) => current) - // Build two arrays, one of all post dated, and one of all non-post-dated - const postDatedItems = changedStatus.filter(item => item.PostDatedLastModifiedSetAt) - const nonPostDatedItems = changedStatus.filter(item => !item.PostDatedLastModifiedSetAt) + let sqsPromises: Promise> + if (ENABLE_POST_DATED_NOTIFICATIONS) { + logger.info("Post-dated notifications are enabled, separating post-dated and non-post-dated items") + // Build two arrays, one of all post dated, and one of all non-post-dated + const postDatedItems = changedStatus.filter(item => item.PostDatedLastModifiedSetAt) + const nonPostDatedItems = changedStatus.filter(item => !item.PostDatedLastModifiedSetAt) - const postDatedMessageIds = sendItemsToSQS(postDatedItems, postDatedSqsUrl, requestId, logger) - const nonPostDatedMessageIds = sendItemsToSQS(nonPostDatedItems, sqsUrl, requestId, logger) + const postDatedMessageIds = sendItemsToSQS(postDatedItems, postDatedSqsUrl, requestId, logger) + const nonPostDatedMessageIds = sendItemsToSQS(nonPostDatedItems, sqsUrl, requestId, logger) + sqsPromises = Promise.all([postDatedMessageIds, nonPostDatedMessageIds]).then(results => results.flat()) + } else { + logger.info("Post-dated notifications are disabled, sending all items to the standard notifications queue") + sqsPromises = sendItemsToSQS(changedStatus, sqsUrl, requestId, logger) + } logger.info( "The following patients will have prescription update app notifications requested", {nhsNumbers: changedStatus.map(e => e.PatientNHSNumber)} ) - return Promise.all([postDatedMessageIds, nonPostDatedMessageIds]) - .then(results => results.flat()) + return sqsPromises } /** From 45941d01e7fe24bf3d5b13a3ca2622d9f554359c Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 23 Jan 2026 16:55:18 +0000 Subject: [PATCH 30/75] When the flag is off, don't require the post dated sqs url. Also, add a comment --- packages/postDatedLambda/src/databaseClient.ts | 1 + .../updatePrescriptionStatus/src/utils/sqsClient.ts | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/postDatedLambda/src/databaseClient.ts b/packages/postDatedLambda/src/databaseClient.ts index 37f21e01dd..0d4f3ca328 100644 --- a/packages/postDatedLambda/src/databaseClient.ts +++ b/packages/postDatedLambda/src/databaseClient.ts @@ -179,6 +179,7 @@ export async function enrichMessagesWithExistingRecords( const postDatedItems = messages.map((m) => m.prescriptionData) const prescriptionsWithRecords = await fetchExistingRecordsForPrescriptions(postDatedItems, logger) + // prescription IDs are unique, even across pharmacies. so we can build a map keyed by prescription ID just fine. const recordsMap = new Map(prescriptionsWithRecords.map((p) => [p.postDatedData.PrescriptionID, p.existingRecords])) const enrichedMessages: Array = messages.map((message) => ({ ...message, diff --git a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts index f09769c923..884e374ba0 100644 --- a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts +++ b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts @@ -230,11 +230,6 @@ export async function pushPrescriptionToNotificationSQS( throw new Error("Notifications SQS URL not configured") } - if (!postDatedSqsUrl) { - logger.warn("Post-dated Notifications SQS URL not found in environment variables") - throw new Error("Post-dated Notifications SQS URL not configured") - } - // Only allow through sites and systems that are allowedSitesAndSystems const allowedSitesAndSystemsData = await checkSiteOrSystemIsNotifyEnabled(data) @@ -261,6 +256,12 @@ export async function pushPrescriptionToNotificationSQS( let sqsPromises: Promise> if (ENABLE_POST_DATED_NOTIFICATIONS) { logger.info("Post-dated notifications are enabled, separating post-dated and non-post-dated items") + + if (!postDatedSqsUrl) { + logger.warn("Post-dated Notifications SQS URL not found in environment variables") + throw new Error("Post-dated Notifications SQS URL not configured") + } + // Build two arrays, one of all post dated, and one of all non-post-dated const postDatedItems = changedStatus.filter(item => item.PostDatedLastModifiedSetAt) const nonPostDatedItems = changedStatus.filter(item => !item.PostDatedLastModifiedSetAt) From 5799bbfa33dd776e0c6c3b85f0b6b2bba7287415 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 23 Jan 2026 16:59:41 +0000 Subject: [PATCH 31/75] Update test env vars --- packages/updatePrescriptionStatus/.jest/setEnvVars.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/updatePrescriptionStatus/.jest/setEnvVars.js b/packages/updatePrescriptionStatus/.jest/setEnvVars.js index a9a92250b6..67d1f3297a 100644 --- a/packages/updatePrescriptionStatus/.jest/setEnvVars.js +++ b/packages/updatePrescriptionStatus/.jest/setEnvVars.js @@ -1,6 +1,7 @@ /* eslint-disable no-undef */ process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL = "dummy_notify_sqs"; process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = "dummy_postdated_sqs"; +process.env.ENABLE_POST_DATED_NOTIFICATIONS = "true"; process.env.AWS_REGION = "eu-west-2"; process.env.SQS_SALT = "the quick brown fox something something" process.env.ENABLED_SITE_ODS_CODES_PARAM = "ENABLED_SITE_ODS_CODES_PARAM" From f1d9d58c408ec7674fa04202625f27f7c5950d82 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 23 Jan 2026 17:23:14 +0000 Subject: [PATCH 32/75] await async call --- packages/postDatedLambda/src/orchestration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/postDatedLambda/src/orchestration.ts b/packages/postDatedLambda/src/orchestration.ts index b7a2574def..b461f63ffe 100644 --- a/packages/postDatedLambda/src/orchestration.ts +++ b/packages/postDatedLambda/src/orchestration.ts @@ -74,7 +74,7 @@ export async function processPostDatedQueue(logger: Logger): Promise { logger.warn("processPostDatedQueue timed out; exiting before queue is empty. Will report queue status", { maxRuntimeMilliseconds: MAX_QUEUE_RUNTIME }) - reportQueueStatus(logger) + await reportQueueStatus(logger) break } From 7b4e5ec9925058f135e8e3f2e223d5d6c1349fe1 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Mon, 26 Jan 2026 10:13:42 +0000 Subject: [PATCH 33/75] Rename PostModifiedDataItem to PostUpdatedDataItem --- packages/common/commonTypes/src/index.ts | 6 ++-- packages/gsul/src/dynamoDBclient.ts | 4 +-- packages/gsul/src/getStatusUpdates.ts | 8 ++--- packages/gsul/src/schema/response.ts | 2 +- packages/gsul/tests/testBuildResult.test.ts | 14 ++++----- .../tests/testDatabaseClient.test.ts | 14 ++++----- .../tests/testOrchestration.test.ts | 30 +++++++++---------- .../postDatedLambda/tests/testSqs.test.ts | 6 ++-- packages/postDatedLambda/tests/testUtils.ts | 4 +-- .../src/updatePrescriptionStatus.ts | 4 +-- .../src/utils/sqsClient.ts | 4 +-- .../tests/testSqsClient.test.ts | 6 ++-- .../testUpdatePrescriptionStatus.test.ts | 4 +-- 13 files changed, 52 insertions(+), 54 deletions(-) diff --git a/packages/common/commonTypes/src/index.ts b/packages/common/commonTypes/src/index.ts index 7ea8fdabc6..b9a9060cb0 100644 --- a/packages/common/commonTypes/src/index.ts +++ b/packages/common/commonTypes/src/index.ts @@ -13,9 +13,7 @@ export interface PSUDataItem { ExpiryTime: number // (Optional, legacy batch-processors only) Indicates that {@link LastModified} is postdated; // contains the ISO 8601 timestamp when the postdated update was set. - // todo: This field needs to be passed to the sqs message for post-dated updates - // FIXME: This should be called PostDatedLastUpdatedSetAt - PostDatedLastModifiedSetAt?: string + PostDatedLastUpdatedSetAt?: string } export interface NotifyDataItem { @@ -29,7 +27,7 @@ export interface NotifyDataItem { // TODO: This should be removed when we stop supporting post-dated updates export interface PostDatedNotifyDataItem extends NotifyDataItem { LastModified: string - PostDatedLastModifiedSetAt: string // This is the meta.lastUpdated field from the FHIR resource + PostDatedLastUpdatedSetAt: string // This is the meta.lastUpdated field from the FHIR resource PrescriptionID: string LineItemID: string } diff --git a/packages/gsul/src/dynamoDBclient.ts b/packages/gsul/src/dynamoDBclient.ts index 1aa871bd39..b3b3a6fc83 100644 --- a/packages/gsul/src/dynamoDBclient.ts +++ b/packages/gsul/src/dynamoDBclient.ts @@ -41,8 +41,8 @@ export async function getItemsUpdatesForPrescription( latestStatus: String(singleUpdate.Status), isTerminalState: String(singleUpdate.TerminalStatus).toLowerCase() === "completed", lastUpdateDateTime: String(singleUpdate.LastModified), - ...(singleUpdate.PostDatedLastModifiedSetAt && { - postDatedLastModifiedSetAt: String(singleUpdate.PostDatedLastModifiedSetAt) + ...(singleUpdate.PostDatedLastUpdatedSetAt && { + postDatedLastUpdatedSetAt: String(singleUpdate.PostDatedLastUpdatedSetAt) }) })) } diff --git a/packages/gsul/src/getStatusUpdates.ts b/packages/gsul/src/getStatusUpdates.ts index abada87188..2bffdbf460 100644 --- a/packages/gsul/src/getStatusUpdates.ts +++ b/packages/gsul/src/getStatusUpdates.ts @@ -61,11 +61,11 @@ export const filterOutFutureReduceToLatestUpdates = ( } const group = itemGroups[item.itemId] - if (item.postDatedLastModifiedSetAt && !group.postDated) { // this is a post-dated update + if (item.postDatedLastUpdatedSetAt && !group.postDated) { // this is a post-dated update group.postDated = item - } else if (item.postDatedLastModifiedSetAt && group.postDated) { // also a post-dated update - const existingTime = Date.parse(group.postDated.postDatedLastModifiedSetAt) - const newTime = Date.parse(item.postDatedLastModifiedSetAt) + } else if (item.postDatedLastUpdatedSetAt && group.postDated) { // also a post-dated update + const existingTime = Date.parse(group.postDated.postDatedLastUpdatedSetAt) + const newTime = Date.parse(item.postDatedLastUpdatedSetAt) if (newTime > existingTime) { group.postDated = item } diff --git a/packages/gsul/src/schema/response.ts b/packages/gsul/src/schema/response.ts index 5aeafb4252..e2b7c2e26f 100644 --- a/packages/gsul/src/schema/response.ts +++ b/packages/gsul/src/schema/response.ts @@ -16,7 +16,7 @@ const itemSchema = { lastUpdateDateTime: { type: "string" }, - postDatedLastModifiedSetAt: { + postDatedLastUpdatedSetAt: { type: "string" } } diff --git a/packages/gsul/tests/testBuildResult.test.ts b/packages/gsul/tests/testBuildResult.test.ts index 918b817bf9..fa2cca7496 100644 --- a/packages/gsul/tests/testBuildResult.test.ts +++ b/packages/gsul/tests/testBuildResult.test.ts @@ -147,7 +147,7 @@ const scenarios: Array = [ latestStatus: "Ready to collect", isTerminalState: false, lastUpdateDateTime: "2030-01-01T00:00:00Z", // Future, no fallback - postDatedLastModifiedSetAt:"1972-01-01T00:00:00Z" + postDatedLastUpdatedSetAt: "1972-01-01T00:00:00Z" } ], expectedResult: { @@ -174,14 +174,14 @@ const scenarios: Array = [ latestStatus: "Ready to collect", isTerminalState: false, lastUpdateDateTime: "1970-01-02T00:00:00Z", - postDatedLastModifiedSetAt: "1970-01-01T00:00:00Z" // first RTC: post-dated and matured + postDatedLastUpdatedSetAt: "1970-01-01T00:00:00Z" // first RTC: post-dated and matured }, { itemId: "item_1", latestStatus: "Ready to collect", isTerminalState: false, lastUpdateDateTime: futureDateTime, - postDatedLastModifiedSetAt: "1970-01-02T00:00:00Z" // second RTC: post-dated and yet to mature + postDatedLastUpdatedSetAt: "1970-01-02T00:00:00Z" // second RTC: post-dated and yet to mature }, { itemId: "item_1", @@ -194,7 +194,7 @@ const scenarios: Array = [ latestStatus: "Ready to collect", isTerminalState: false, lastUpdateDateTime: "1970-01-04T00:00:00Z", - postDatedLastModifiedSetAt: "1970-01-03T00:00:00Z" // third RTC: post-dated and matured + postDatedLastUpdatedSetAt: "1970-01-03T00:00:00Z" // third RTC: post-dated and matured } ], expectedResult: { @@ -212,7 +212,7 @@ const scenarios: Array = [ latestStatus: "Ready to collect", isTerminalState: false, lastUpdateDateTime: "1970-01-04T00:00:00Z", - postDatedLastModifiedSetAt: "1970-01-03T00:00:00Z" + postDatedLastUpdatedSetAt: "1970-01-03T00:00:00Z" } ] } @@ -229,7 +229,7 @@ const scenarios: Array = [ latestStatus: "Ready to collect", isTerminalState: false, lastUpdateDateTime: "1970-01-01T00:00:00Z", - postDatedLastModifiedSetAt: futureDateTime + postDatedLastUpdatedSetAt: futureDateTime } ], expectedResult: { @@ -241,7 +241,7 @@ const scenarios: Array = [ latestStatus: "Ready to collect", isTerminalState: false, lastUpdateDateTime: "1970-01-01T00:00:00Z", - postDatedLastModifiedSetAt: futureDateTime + postDatedLastUpdatedSetAt: futureDateTime } ] } diff --git a/packages/postDatedLambda/tests/testDatabaseClient.test.ts b/packages/postDatedLambda/tests/testDatabaseClient.test.ts index 412caeffa3..9a01da9c52 100644 --- a/packages/postDatedLambda/tests/testDatabaseClient.test.ts +++ b/packages/postDatedLambda/tests/testDatabaseClient.test.ts @@ -9,7 +9,7 @@ import * as dynamo from "@aws-sdk/client-dynamodb" import {Logger} from "@aws-lambda-powertools/logger" -import {createMockPostModifiedDataItem} from "./testUtils" +import {createMockPostUpdatedDataItem} from "./testUtils" // Uses unstable jest method to enable mocking while using ESM. To be replaced in future. export function mockDynamoDBClient() { @@ -107,8 +107,8 @@ describe("databaseClient", () => { describe("fetchExistingRecordsForPrescriptions", () => { it("should fetch existing records for multiple prescriptions", async () => { const prescriptions = [ - createMockPostModifiedDataItem({PrescriptionID: "presc1", PharmacyODSCode: "pharmA"}), - createMockPostModifiedDataItem({PrescriptionID: "presc2", PharmacyODSCode: "pharmB"}) + createMockPostUpdatedDataItem({PrescriptionID: "presc1", PharmacyODSCode: "pharmA"}), + createMockPostUpdatedDataItem({PrescriptionID: "presc2", PharmacyODSCode: "pharmB"}) ] // Mock DynamoDB responses @@ -156,8 +156,8 @@ describe("databaseClient", () => { "Should log an error if the fetch fails for one prescription, and set the existingRecords to empty array", async () => { const prescriptions = [ - createMockPostModifiedDataItem({PrescriptionID: "presc1", PharmacyODSCode: "pharmA"}), - createMockPostModifiedDataItem({PrescriptionID: "errorPresc", PharmacyODSCode: "errorPharm"}) + createMockPostUpdatedDataItem({PrescriptionID: "presc1", PharmacyODSCode: "pharmA"}), + createMockPostUpdatedDataItem({PrescriptionID: "errorPresc", PharmacyODSCode: "errorPharm"}) ] // Mock DynamoDB responses @@ -194,8 +194,8 @@ describe("databaseClient", () => { describe("enrichMessagesWithExistingRecords", () => { it("should enrich messages with existing records", async () => { const prescriptions = [ - createMockPostModifiedDataItem({PrescriptionID: "presc1", PharmacyODSCode: "pharmA"}), - createMockPostModifiedDataItem({PrescriptionID: "presc2", PharmacyODSCode: "pharmB"}) + createMockPostUpdatedDataItem({PrescriptionID: "presc1", PharmacyODSCode: "pharmA"}), + createMockPostUpdatedDataItem({PrescriptionID: "presc2", PharmacyODSCode: "pharmB"}) ] // Mock DynamoDB responses diff --git a/packages/postDatedLambda/tests/testOrchestration.test.ts b/packages/postDatedLambda/tests/testOrchestration.test.ts index 90bb7834ef..0c2073d628 100644 --- a/packages/postDatedLambda/tests/testOrchestration.test.ts +++ b/packages/postDatedLambda/tests/testOrchestration.test.ts @@ -33,7 +33,7 @@ jest.unstable_mockModule("../src/sqs", () => { import {Logger} from "@aws-lambda-powertools/logger" -import {createMockPostModifiedDataItem} from "./testUtils" +import {createMockPostUpdatedDataItem} from "./testUtils" import {BatchProcessingResult, PostDatedSQSMessage} from "../src/types" // Import the orchestration module after mocking dependencies @@ -49,8 +49,8 @@ describe("orchestration", () => { it("should process messages and categorize them correctly", async () => { const mockMessages: Array = [ - {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostModifiedDataItem({})}, - {MessageId: "2", Body: "Message 2", prescriptionData: createMockPostModifiedDataItem({})} + {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostUpdatedDataItem({})}, + {MessageId: "2", Body: "Message 2", prescriptionData: createMockPostUpdatedDataItem({})} ] // Mock the enrichment function to return the same messages @@ -78,8 +78,8 @@ describe("orchestration", () => { it("should log errors and mark messages immature when processing throws", async () => { const mockMessages: Array = [ - {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostModifiedDataItem({})}, - {MessageId: "2", Body: "Message 2", prescriptionData: createMockPostModifiedDataItem({})} + {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostUpdatedDataItem({})}, + {MessageId: "2", Body: "Message 2", prescriptionData: createMockPostUpdatedDataItem({})} ] mockEnrichMessagesWithExistingRecords.mockReturnValueOnce(mockMessages) @@ -104,7 +104,7 @@ describe("orchestration", () => { it("should pass enriched records into processMessage", async () => { const mockMessages: Array = [ - {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostModifiedDataItem({})} + {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostUpdatedDataItem({})} ] const enrichedMessage = { @@ -128,8 +128,8 @@ describe("orchestration", () => { it("should process the SQS queue correctly", async () => { const mockMessages: Array = [ - {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostModifiedDataItem({})}, - {MessageId: "2", Body: "Message 2", prescriptionData: createMockPostModifiedDataItem({})} + {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostUpdatedDataItem({})}, + {MessageId: "2", Body: "Message 2", prescriptionData: createMockPostUpdatedDataItem({})} ] const mockEnrichedMessages = mockMessages.map((message) => ({ @@ -160,12 +160,12 @@ describe("orchestration", () => { it("Should stop processing if the max runtime is exceeded", async () => { jest.useFakeTimers() const mockMessages: Array = [ - {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostModifiedDataItem({})}, - {MessageId: "2", Body: "Message 2", prescriptionData: createMockPostModifiedDataItem({})}, - {MessageId: "3", Body: "Message 3", prescriptionData: createMockPostModifiedDataItem({})}, - {MessageId: "4", Body: "Message 4", prescriptionData: createMockPostModifiedDataItem({})}, - {MessageId: "5", Body: "Message 5", prescriptionData: createMockPostModifiedDataItem({})}, - {MessageId: "6", Body: "Message 6", prescriptionData: createMockPostModifiedDataItem({})} + {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostUpdatedDataItem({})}, + {MessageId: "2", Body: "Message 2", prescriptionData: createMockPostUpdatedDataItem({})}, + {MessageId: "3", Body: "Message 3", prescriptionData: createMockPostUpdatedDataItem({})}, + {MessageId: "4", Body: "Message 4", prescriptionData: createMockPostUpdatedDataItem({})}, + {MessageId: "5", Body: "Message 5", prescriptionData: createMockPostUpdatedDataItem({})}, + {MessageId: "6", Body: "Message 6", prescriptionData: createMockPostUpdatedDataItem({})} ] mockReceivePostDatedSQSMessages.mockReturnValue(mockMessages) @@ -193,7 +193,7 @@ describe("orchestration", () => { ids.map((id) => ({ MessageId: id, Body: `Message ${id}`, - prescriptionData: createMockPostModifiedDataItem({}) + prescriptionData: createMockPostUpdatedDataItem({}) })) const batch1 = createBatch(["1", "2", "3"]) diff --git a/packages/postDatedLambda/tests/testSqs.test.ts b/packages/postDatedLambda/tests/testSqs.test.ts index 21d8acc80c..348f7913a3 100644 --- a/packages/postDatedLambda/tests/testSqs.test.ts +++ b/packages/postDatedLambda/tests/testSqs.test.ts @@ -10,7 +10,7 @@ import {Logger} from "@aws-lambda-powertools/logger" import {LogItemMessage, LogItemExtraInput} from "@aws-lambda-powertools/logger/lib/cjs/types/Logger" import * as sqs from "@aws-sdk/client-sqs" import {BatchProcessingResult, PostDatedSQSMessage} from "../src/types" -import {createMockPostModifiedDataItem} from "./testUtils" +import {createMockPostUpdatedDataItem} from "./testUtils" export function mockSQSClient() { const mockSend = jest.fn() @@ -290,10 +290,10 @@ describe("sqs", () => { process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl const maturedMessages: Array = [ - {MessageId: "1", ReceiptHandle: "handle1", prescriptionData: createMockPostModifiedDataItem({})} + {MessageId: "1", ReceiptHandle: "handle1", prescriptionData: createMockPostUpdatedDataItem({})} ] const immatureMessages: Array = [ - {MessageId: "2", ReceiptHandle: "handle2", prescriptionData: createMockPostModifiedDataItem({})} + {MessageId: "2", ReceiptHandle: "handle2", prescriptionData: createMockPostUpdatedDataItem({})} ] const batchResult: BatchProcessingResult = { diff --git a/packages/postDatedLambda/tests/testUtils.ts b/packages/postDatedLambda/tests/testUtils.ts index 5eb0952b7d..4492efd0f9 100644 --- a/packages/postDatedLambda/tests/testUtils.ts +++ b/packages/postDatedLambda/tests/testUtils.ts @@ -1,6 +1,6 @@ import {PostDatedNotifyDataItem} from "packages/common/commonTypes/lib/src" -export function createMockPostModifiedDataItem(overrides: Partial): PostDatedNotifyDataItem { +export function createMockPostUpdatedDataItem(overrides: Partial): PostDatedNotifyDataItem { return { LastModified: "2023-01-02T00:00:00Z", LineItemID: "spamandeggs", @@ -10,7 +10,7 @@ export function createMockPostModifiedDataItem(overrides: Partial): Promis previousStatus: previousItem.Status, newTerminalStatus: currentItem.TerminalStatus, previousTerminalStatus: previousItem.TerminalStatus, - isPostDated: currentItem.PostDatedLastModifiedSetAt + isPostDated: currentItem.PostDatedLastUpdatedSetAt }) } } catch (e) { diff --git a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts index 884e374ba0..3137891820 100644 --- a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts +++ b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts @@ -263,8 +263,8 @@ export async function pushPrescriptionToNotificationSQS( } // Build two arrays, one of all post dated, and one of all non-post-dated - const postDatedItems = changedStatus.filter(item => item.PostDatedLastModifiedSetAt) - const nonPostDatedItems = changedStatus.filter(item => !item.PostDatedLastModifiedSetAt) + const postDatedItems = changedStatus.filter(item => item.PostDatedLastUpdatedSetAt) + const nonPostDatedItems = changedStatus.filter(item => !item.PostDatedLastUpdatedSetAt) const postDatedMessageIds = sendItemsToSQS(postDatedItems, postDatedSqsUrl, requestId, logger) const nonPostDatedMessageIds = sendItemsToSQS(nonPostDatedItems, sqsUrl, requestId, logger) diff --git a/packages/updatePrescriptionStatus/tests/testSqsClient.test.ts b/packages/updatePrescriptionStatus/tests/testSqsClient.test.ts index d8ed468b73..a3e7b62a5d 100644 --- a/packages/updatePrescriptionStatus/tests/testSqsClient.test.ts +++ b/packages/updatePrescriptionStatus/tests/testSqsClient.test.ts @@ -206,7 +206,7 @@ describe("Unit tests for pushPrescriptionToNotificationSQS", () => { Status: "ready to collect", PatientNHSNumber: "9999999999", PharmacyODSCode: "JIM123", - PostDatedLastModifiedSetAt: "2100-01-01T00:00:00Z" + PostDatedLastUpdatedSetAt: "2100-01-01T00:00:00Z" }) const standardCurrent = createMockDataItem({ Status: "ready to collect - partial", @@ -277,7 +277,7 @@ describe("Unit tests for pushPrescriptionToNotificationSQS", () => { Status: "ready to collect", PatientNHSNumber: "444", PharmacyODSCode: "DDD", - PostDatedLastModifiedSetAt: "2025-05-01T00:00:00Z" + PostDatedLastUpdatedSetAt: "2025-05-01T00:00:00Z" }) }, { @@ -309,7 +309,7 @@ describe("Unit tests for pushPrescriptionToNotificationSQS", () => { Status: "ready to collect", PatientNHSNumber: "777", PharmacyODSCode: "GGG", - PostDatedLastModifiedSetAt: "2100-12-12T00:00:00Z" + PostDatedLastUpdatedSetAt: "2100-12-12T00:00:00Z" }) }, { diff --git a/packages/updatePrescriptionStatus/tests/testUpdatePrescriptionStatus.test.ts b/packages/updatePrescriptionStatus/tests/testUpdatePrescriptionStatus.test.ts index a03d391b60..d873421a61 100644 --- a/packages/updatePrescriptionStatus/tests/testUpdatePrescriptionStatus.test.ts +++ b/packages/updatePrescriptionStatus/tests/testUpdatePrescriptionStatus.test.ts @@ -288,7 +288,7 @@ describe("buildDataItems", () => { expect(dataItems[0].ExpiryTime).toBeGreaterThan(expectedExpiryTime) }) - it("should include PostDatedLastModifiedSetAt in data item when meta.lastUpdated is defined", () => { + it("should include PostDatedLastUpdatedSetAt in data item when meta.lastUpdated is defined", () => { const task = validTask() const lastUpdated = new Date(DEFAULT_DATE.valueOf() - (24 * 60 * 60 * 1000)).toISOString() task.meta = { @@ -302,6 +302,6 @@ describe("buildDataItems", () => { const dataItems = buildDataItems([requestEntry], "", "") const first: any = dataItems[0] - expect(first.PostDatedLastModifiedSetAt).toEqual(lastUpdated) + expect(first.PostDatedLastUpdatedSetAt).toEqual(lastUpdated) }) }) From 59c0496a17b9bf6f2f8f8f1d57b902d620c0f134 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Mon, 26 Jan 2026 11:30:00 +0000 Subject: [PATCH 34/75] Revert "Rename PostModifiedDataItem to PostUpdatedDataItem" This reverts commit 7b4e5ec9925058f135e8e3f2e223d5d6c1349fe1. --- packages/common/commonTypes/src/index.ts | 6 ++-- packages/gsul/src/dynamoDBclient.ts | 4 +-- packages/gsul/src/getStatusUpdates.ts | 8 ++--- packages/gsul/src/schema/response.ts | 2 +- packages/gsul/tests/testBuildResult.test.ts | 14 ++++----- .../tests/testDatabaseClient.test.ts | 14 ++++----- .../tests/testOrchestration.test.ts | 30 +++++++++---------- .../postDatedLambda/tests/testSqs.test.ts | 6 ++-- packages/postDatedLambda/tests/testUtils.ts | 4 +-- .../src/updatePrescriptionStatus.ts | 4 +-- .../src/utils/sqsClient.ts | 4 +-- .../tests/testSqsClient.test.ts | 6 ++-- .../testUpdatePrescriptionStatus.test.ts | 4 +-- 13 files changed, 54 insertions(+), 52 deletions(-) diff --git a/packages/common/commonTypes/src/index.ts b/packages/common/commonTypes/src/index.ts index b9a9060cb0..7ea8fdabc6 100644 --- a/packages/common/commonTypes/src/index.ts +++ b/packages/common/commonTypes/src/index.ts @@ -13,7 +13,9 @@ export interface PSUDataItem { ExpiryTime: number // (Optional, legacy batch-processors only) Indicates that {@link LastModified} is postdated; // contains the ISO 8601 timestamp when the postdated update was set. - PostDatedLastUpdatedSetAt?: string + // todo: This field needs to be passed to the sqs message for post-dated updates + // FIXME: This should be called PostDatedLastUpdatedSetAt + PostDatedLastModifiedSetAt?: string } export interface NotifyDataItem { @@ -27,7 +29,7 @@ export interface NotifyDataItem { // TODO: This should be removed when we stop supporting post-dated updates export interface PostDatedNotifyDataItem extends NotifyDataItem { LastModified: string - PostDatedLastUpdatedSetAt: string // This is the meta.lastUpdated field from the FHIR resource + PostDatedLastModifiedSetAt: string // This is the meta.lastUpdated field from the FHIR resource PrescriptionID: string LineItemID: string } diff --git a/packages/gsul/src/dynamoDBclient.ts b/packages/gsul/src/dynamoDBclient.ts index b3b3a6fc83..1aa871bd39 100644 --- a/packages/gsul/src/dynamoDBclient.ts +++ b/packages/gsul/src/dynamoDBclient.ts @@ -41,8 +41,8 @@ export async function getItemsUpdatesForPrescription( latestStatus: String(singleUpdate.Status), isTerminalState: String(singleUpdate.TerminalStatus).toLowerCase() === "completed", lastUpdateDateTime: String(singleUpdate.LastModified), - ...(singleUpdate.PostDatedLastUpdatedSetAt && { - postDatedLastUpdatedSetAt: String(singleUpdate.PostDatedLastUpdatedSetAt) + ...(singleUpdate.PostDatedLastModifiedSetAt && { + postDatedLastModifiedSetAt: String(singleUpdate.PostDatedLastModifiedSetAt) }) })) } diff --git a/packages/gsul/src/getStatusUpdates.ts b/packages/gsul/src/getStatusUpdates.ts index 2bffdbf460..abada87188 100644 --- a/packages/gsul/src/getStatusUpdates.ts +++ b/packages/gsul/src/getStatusUpdates.ts @@ -61,11 +61,11 @@ export const filterOutFutureReduceToLatestUpdates = ( } const group = itemGroups[item.itemId] - if (item.postDatedLastUpdatedSetAt && !group.postDated) { // this is a post-dated update + if (item.postDatedLastModifiedSetAt && !group.postDated) { // this is a post-dated update group.postDated = item - } else if (item.postDatedLastUpdatedSetAt && group.postDated) { // also a post-dated update - const existingTime = Date.parse(group.postDated.postDatedLastUpdatedSetAt) - const newTime = Date.parse(item.postDatedLastUpdatedSetAt) + } else if (item.postDatedLastModifiedSetAt && group.postDated) { // also a post-dated update + const existingTime = Date.parse(group.postDated.postDatedLastModifiedSetAt) + const newTime = Date.parse(item.postDatedLastModifiedSetAt) if (newTime > existingTime) { group.postDated = item } diff --git a/packages/gsul/src/schema/response.ts b/packages/gsul/src/schema/response.ts index e2b7c2e26f..5aeafb4252 100644 --- a/packages/gsul/src/schema/response.ts +++ b/packages/gsul/src/schema/response.ts @@ -16,7 +16,7 @@ const itemSchema = { lastUpdateDateTime: { type: "string" }, - postDatedLastUpdatedSetAt: { + postDatedLastModifiedSetAt: { type: "string" } } diff --git a/packages/gsul/tests/testBuildResult.test.ts b/packages/gsul/tests/testBuildResult.test.ts index fa2cca7496..918b817bf9 100644 --- a/packages/gsul/tests/testBuildResult.test.ts +++ b/packages/gsul/tests/testBuildResult.test.ts @@ -147,7 +147,7 @@ const scenarios: Array = [ latestStatus: "Ready to collect", isTerminalState: false, lastUpdateDateTime: "2030-01-01T00:00:00Z", // Future, no fallback - postDatedLastUpdatedSetAt: "1972-01-01T00:00:00Z" + postDatedLastModifiedSetAt:"1972-01-01T00:00:00Z" } ], expectedResult: { @@ -174,14 +174,14 @@ const scenarios: Array = [ latestStatus: "Ready to collect", isTerminalState: false, lastUpdateDateTime: "1970-01-02T00:00:00Z", - postDatedLastUpdatedSetAt: "1970-01-01T00:00:00Z" // first RTC: post-dated and matured + postDatedLastModifiedSetAt: "1970-01-01T00:00:00Z" // first RTC: post-dated and matured }, { itemId: "item_1", latestStatus: "Ready to collect", isTerminalState: false, lastUpdateDateTime: futureDateTime, - postDatedLastUpdatedSetAt: "1970-01-02T00:00:00Z" // second RTC: post-dated and yet to mature + postDatedLastModifiedSetAt: "1970-01-02T00:00:00Z" // second RTC: post-dated and yet to mature }, { itemId: "item_1", @@ -194,7 +194,7 @@ const scenarios: Array = [ latestStatus: "Ready to collect", isTerminalState: false, lastUpdateDateTime: "1970-01-04T00:00:00Z", - postDatedLastUpdatedSetAt: "1970-01-03T00:00:00Z" // third RTC: post-dated and matured + postDatedLastModifiedSetAt: "1970-01-03T00:00:00Z" // third RTC: post-dated and matured } ], expectedResult: { @@ -212,7 +212,7 @@ const scenarios: Array = [ latestStatus: "Ready to collect", isTerminalState: false, lastUpdateDateTime: "1970-01-04T00:00:00Z", - postDatedLastUpdatedSetAt: "1970-01-03T00:00:00Z" + postDatedLastModifiedSetAt: "1970-01-03T00:00:00Z" } ] } @@ -229,7 +229,7 @@ const scenarios: Array = [ latestStatus: "Ready to collect", isTerminalState: false, lastUpdateDateTime: "1970-01-01T00:00:00Z", - postDatedLastUpdatedSetAt: futureDateTime + postDatedLastModifiedSetAt: futureDateTime } ], expectedResult: { @@ -241,7 +241,7 @@ const scenarios: Array = [ latestStatus: "Ready to collect", isTerminalState: false, lastUpdateDateTime: "1970-01-01T00:00:00Z", - postDatedLastUpdatedSetAt: futureDateTime + postDatedLastModifiedSetAt: futureDateTime } ] } diff --git a/packages/postDatedLambda/tests/testDatabaseClient.test.ts b/packages/postDatedLambda/tests/testDatabaseClient.test.ts index 9a01da9c52..412caeffa3 100644 --- a/packages/postDatedLambda/tests/testDatabaseClient.test.ts +++ b/packages/postDatedLambda/tests/testDatabaseClient.test.ts @@ -9,7 +9,7 @@ import * as dynamo from "@aws-sdk/client-dynamodb" import {Logger} from "@aws-lambda-powertools/logger" -import {createMockPostUpdatedDataItem} from "./testUtils" +import {createMockPostModifiedDataItem} from "./testUtils" // Uses unstable jest method to enable mocking while using ESM. To be replaced in future. export function mockDynamoDBClient() { @@ -107,8 +107,8 @@ describe("databaseClient", () => { describe("fetchExistingRecordsForPrescriptions", () => { it("should fetch existing records for multiple prescriptions", async () => { const prescriptions = [ - createMockPostUpdatedDataItem({PrescriptionID: "presc1", PharmacyODSCode: "pharmA"}), - createMockPostUpdatedDataItem({PrescriptionID: "presc2", PharmacyODSCode: "pharmB"}) + createMockPostModifiedDataItem({PrescriptionID: "presc1", PharmacyODSCode: "pharmA"}), + createMockPostModifiedDataItem({PrescriptionID: "presc2", PharmacyODSCode: "pharmB"}) ] // Mock DynamoDB responses @@ -156,8 +156,8 @@ describe("databaseClient", () => { "Should log an error if the fetch fails for one prescription, and set the existingRecords to empty array", async () => { const prescriptions = [ - createMockPostUpdatedDataItem({PrescriptionID: "presc1", PharmacyODSCode: "pharmA"}), - createMockPostUpdatedDataItem({PrescriptionID: "errorPresc", PharmacyODSCode: "errorPharm"}) + createMockPostModifiedDataItem({PrescriptionID: "presc1", PharmacyODSCode: "pharmA"}), + createMockPostModifiedDataItem({PrescriptionID: "errorPresc", PharmacyODSCode: "errorPharm"}) ] // Mock DynamoDB responses @@ -194,8 +194,8 @@ describe("databaseClient", () => { describe("enrichMessagesWithExistingRecords", () => { it("should enrich messages with existing records", async () => { const prescriptions = [ - createMockPostUpdatedDataItem({PrescriptionID: "presc1", PharmacyODSCode: "pharmA"}), - createMockPostUpdatedDataItem({PrescriptionID: "presc2", PharmacyODSCode: "pharmB"}) + createMockPostModifiedDataItem({PrescriptionID: "presc1", PharmacyODSCode: "pharmA"}), + createMockPostModifiedDataItem({PrescriptionID: "presc2", PharmacyODSCode: "pharmB"}) ] // Mock DynamoDB responses diff --git a/packages/postDatedLambda/tests/testOrchestration.test.ts b/packages/postDatedLambda/tests/testOrchestration.test.ts index 0c2073d628..90bb7834ef 100644 --- a/packages/postDatedLambda/tests/testOrchestration.test.ts +++ b/packages/postDatedLambda/tests/testOrchestration.test.ts @@ -33,7 +33,7 @@ jest.unstable_mockModule("../src/sqs", () => { import {Logger} from "@aws-lambda-powertools/logger" -import {createMockPostUpdatedDataItem} from "./testUtils" +import {createMockPostModifiedDataItem} from "./testUtils" import {BatchProcessingResult, PostDatedSQSMessage} from "../src/types" // Import the orchestration module after mocking dependencies @@ -49,8 +49,8 @@ describe("orchestration", () => { it("should process messages and categorize them correctly", async () => { const mockMessages: Array = [ - {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostUpdatedDataItem({})}, - {MessageId: "2", Body: "Message 2", prescriptionData: createMockPostUpdatedDataItem({})} + {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostModifiedDataItem({})}, + {MessageId: "2", Body: "Message 2", prescriptionData: createMockPostModifiedDataItem({})} ] // Mock the enrichment function to return the same messages @@ -78,8 +78,8 @@ describe("orchestration", () => { it("should log errors and mark messages immature when processing throws", async () => { const mockMessages: Array = [ - {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostUpdatedDataItem({})}, - {MessageId: "2", Body: "Message 2", prescriptionData: createMockPostUpdatedDataItem({})} + {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostModifiedDataItem({})}, + {MessageId: "2", Body: "Message 2", prescriptionData: createMockPostModifiedDataItem({})} ] mockEnrichMessagesWithExistingRecords.mockReturnValueOnce(mockMessages) @@ -104,7 +104,7 @@ describe("orchestration", () => { it("should pass enriched records into processMessage", async () => { const mockMessages: Array = [ - {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostUpdatedDataItem({})} + {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostModifiedDataItem({})} ] const enrichedMessage = { @@ -128,8 +128,8 @@ describe("orchestration", () => { it("should process the SQS queue correctly", async () => { const mockMessages: Array = [ - {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostUpdatedDataItem({})}, - {MessageId: "2", Body: "Message 2", prescriptionData: createMockPostUpdatedDataItem({})} + {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostModifiedDataItem({})}, + {MessageId: "2", Body: "Message 2", prescriptionData: createMockPostModifiedDataItem({})} ] const mockEnrichedMessages = mockMessages.map((message) => ({ @@ -160,12 +160,12 @@ describe("orchestration", () => { it("Should stop processing if the max runtime is exceeded", async () => { jest.useFakeTimers() const mockMessages: Array = [ - {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostUpdatedDataItem({})}, - {MessageId: "2", Body: "Message 2", prescriptionData: createMockPostUpdatedDataItem({})}, - {MessageId: "3", Body: "Message 3", prescriptionData: createMockPostUpdatedDataItem({})}, - {MessageId: "4", Body: "Message 4", prescriptionData: createMockPostUpdatedDataItem({})}, - {MessageId: "5", Body: "Message 5", prescriptionData: createMockPostUpdatedDataItem({})}, - {MessageId: "6", Body: "Message 6", prescriptionData: createMockPostUpdatedDataItem({})} + {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostModifiedDataItem({})}, + {MessageId: "2", Body: "Message 2", prescriptionData: createMockPostModifiedDataItem({})}, + {MessageId: "3", Body: "Message 3", prescriptionData: createMockPostModifiedDataItem({})}, + {MessageId: "4", Body: "Message 4", prescriptionData: createMockPostModifiedDataItem({})}, + {MessageId: "5", Body: "Message 5", prescriptionData: createMockPostModifiedDataItem({})}, + {MessageId: "6", Body: "Message 6", prescriptionData: createMockPostModifiedDataItem({})} ] mockReceivePostDatedSQSMessages.mockReturnValue(mockMessages) @@ -193,7 +193,7 @@ describe("orchestration", () => { ids.map((id) => ({ MessageId: id, Body: `Message ${id}`, - prescriptionData: createMockPostUpdatedDataItem({}) + prescriptionData: createMockPostModifiedDataItem({}) })) const batch1 = createBatch(["1", "2", "3"]) diff --git a/packages/postDatedLambda/tests/testSqs.test.ts b/packages/postDatedLambda/tests/testSqs.test.ts index 348f7913a3..21d8acc80c 100644 --- a/packages/postDatedLambda/tests/testSqs.test.ts +++ b/packages/postDatedLambda/tests/testSqs.test.ts @@ -10,7 +10,7 @@ import {Logger} from "@aws-lambda-powertools/logger" import {LogItemMessage, LogItemExtraInput} from "@aws-lambda-powertools/logger/lib/cjs/types/Logger" import * as sqs from "@aws-sdk/client-sqs" import {BatchProcessingResult, PostDatedSQSMessage} from "../src/types" -import {createMockPostUpdatedDataItem} from "./testUtils" +import {createMockPostModifiedDataItem} from "./testUtils" export function mockSQSClient() { const mockSend = jest.fn() @@ -290,10 +290,10 @@ describe("sqs", () => { process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl const maturedMessages: Array = [ - {MessageId: "1", ReceiptHandle: "handle1", prescriptionData: createMockPostUpdatedDataItem({})} + {MessageId: "1", ReceiptHandle: "handle1", prescriptionData: createMockPostModifiedDataItem({})} ] const immatureMessages: Array = [ - {MessageId: "2", ReceiptHandle: "handle2", prescriptionData: createMockPostUpdatedDataItem({})} + {MessageId: "2", ReceiptHandle: "handle2", prescriptionData: createMockPostModifiedDataItem({})} ] const batchResult: BatchProcessingResult = { diff --git a/packages/postDatedLambda/tests/testUtils.ts b/packages/postDatedLambda/tests/testUtils.ts index 4492efd0f9..5eb0952b7d 100644 --- a/packages/postDatedLambda/tests/testUtils.ts +++ b/packages/postDatedLambda/tests/testUtils.ts @@ -1,6 +1,6 @@ import {PostDatedNotifyDataItem} from "packages/common/commonTypes/lib/src" -export function createMockPostUpdatedDataItem(overrides: Partial): PostDatedNotifyDataItem { +export function createMockPostModifiedDataItem(overrides: Partial): PostDatedNotifyDataItem { return { LastModified: "2023-01-02T00:00:00Z", LineItemID: "spamandeggs", @@ -10,7 +10,7 @@ export function createMockPostUpdatedDataItem(overrides: Partial): Promis previousStatus: previousItem.Status, newTerminalStatus: currentItem.TerminalStatus, previousTerminalStatus: previousItem.TerminalStatus, - isPostDated: currentItem.PostDatedLastUpdatedSetAt + isPostDated: currentItem.PostDatedLastModifiedSetAt }) } } catch (e) { diff --git a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts index 3137891820..884e374ba0 100644 --- a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts +++ b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts @@ -263,8 +263,8 @@ export async function pushPrescriptionToNotificationSQS( } // Build two arrays, one of all post dated, and one of all non-post-dated - const postDatedItems = changedStatus.filter(item => item.PostDatedLastUpdatedSetAt) - const nonPostDatedItems = changedStatus.filter(item => !item.PostDatedLastUpdatedSetAt) + const postDatedItems = changedStatus.filter(item => item.PostDatedLastModifiedSetAt) + const nonPostDatedItems = changedStatus.filter(item => !item.PostDatedLastModifiedSetAt) const postDatedMessageIds = sendItemsToSQS(postDatedItems, postDatedSqsUrl, requestId, logger) const nonPostDatedMessageIds = sendItemsToSQS(nonPostDatedItems, sqsUrl, requestId, logger) diff --git a/packages/updatePrescriptionStatus/tests/testSqsClient.test.ts b/packages/updatePrescriptionStatus/tests/testSqsClient.test.ts index a3e7b62a5d..d8ed468b73 100644 --- a/packages/updatePrescriptionStatus/tests/testSqsClient.test.ts +++ b/packages/updatePrescriptionStatus/tests/testSqsClient.test.ts @@ -206,7 +206,7 @@ describe("Unit tests for pushPrescriptionToNotificationSQS", () => { Status: "ready to collect", PatientNHSNumber: "9999999999", PharmacyODSCode: "JIM123", - PostDatedLastUpdatedSetAt: "2100-01-01T00:00:00Z" + PostDatedLastModifiedSetAt: "2100-01-01T00:00:00Z" }) const standardCurrent = createMockDataItem({ Status: "ready to collect - partial", @@ -277,7 +277,7 @@ describe("Unit tests for pushPrescriptionToNotificationSQS", () => { Status: "ready to collect", PatientNHSNumber: "444", PharmacyODSCode: "DDD", - PostDatedLastUpdatedSetAt: "2025-05-01T00:00:00Z" + PostDatedLastModifiedSetAt: "2025-05-01T00:00:00Z" }) }, { @@ -309,7 +309,7 @@ describe("Unit tests for pushPrescriptionToNotificationSQS", () => { Status: "ready to collect", PatientNHSNumber: "777", PharmacyODSCode: "GGG", - PostDatedLastUpdatedSetAt: "2100-12-12T00:00:00Z" + PostDatedLastModifiedSetAt: "2100-12-12T00:00:00Z" }) }, { diff --git a/packages/updatePrescriptionStatus/tests/testUpdatePrescriptionStatus.test.ts b/packages/updatePrescriptionStatus/tests/testUpdatePrescriptionStatus.test.ts index d873421a61..a03d391b60 100644 --- a/packages/updatePrescriptionStatus/tests/testUpdatePrescriptionStatus.test.ts +++ b/packages/updatePrescriptionStatus/tests/testUpdatePrescriptionStatus.test.ts @@ -288,7 +288,7 @@ describe("buildDataItems", () => { expect(dataItems[0].ExpiryTime).toBeGreaterThan(expectedExpiryTime) }) - it("should include PostDatedLastUpdatedSetAt in data item when meta.lastUpdated is defined", () => { + it("should include PostDatedLastModifiedSetAt in data item when meta.lastUpdated is defined", () => { const task = validTask() const lastUpdated = new Date(DEFAULT_DATE.valueOf() - (24 * 60 * 60 * 1000)).toISOString() task.meta = { @@ -302,6 +302,6 @@ describe("buildDataItems", () => { const dataItems = buildDataItems([requestEntry], "", "") const first: any = dataItems[0] - expect(first.PostDatedLastUpdatedSetAt).toEqual(lastUpdated) + expect(first.PostDatedLastModifiedSetAt).toEqual(lastUpdated) }) }) From 2554fa579f71764986c8c876cac33566ff7cd189 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Mon, 26 Jan 2026 11:48:13 +0000 Subject: [PATCH 35/75] Refactor SQS to send messages in parallel --- .../src/utils/sqsClient.ts | 105 ++++++++---------- .../tests/testSqsClient.test.ts | 8 +- 2 files changed, 52 insertions(+), 61 deletions(-) diff --git a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts index 884e374ba0..e066ad213c 100644 --- a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts +++ b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts @@ -82,7 +82,53 @@ async function sendEntriesToQueue( } const batches = chunkArray(entries, 10) - return placeBatchInSQS(batches, queueUrl, requestId, logger) + + // Each batch is converted to an array of strings, so we end up with an array of arrays of strings + // (or rather, their promises) + const batchPromises = batches.map(async batch => { + try { + logger.info( + "Pushing a batch of notification requests to SQS", + { + batchLength: batch.length, + deduplicationIds: batch.map(e => e.MessageDeduplicationId), + requestId, + queueUrl + } + ) + + const command = new SendMessageBatchCommand({ + QueueUrl: queueUrl, + Entries: batch + }) + const result = await sqs.send(command) + + let successfulIds: Array = [] + if (result.Successful?.length) { + logger.info("Successfully sent a batch of prescriptions to the SQS", {result, queueUrl}) + + // For each successful message, get its message ID. I don't think there will ever be undefined + // actually in here, but the typing suggests that there could be so filter those out + successfulIds = result.Successful + .map(e => e.MessageId) + .filter((msgId): msgId is string => msgId !== undefined) + } + + // Some may succeed, and some may fail. So check for both + if (result.Failed?.length) { + throw new Error(`Failed to send a batch of prescriptions to the SQS ${queueUrl}`) + } + + return successfulIds + } catch (error) { + logger.error("Failed to send a batch of prescriptions to the SQS", {error, queueUrl}) + throw error + } + }) + + // Flatten the array of arrays of strings into a single array of strings + const batchResults = Promise.all(batchPromises).then(results => results.flat()) + return batchResults } /** @@ -147,61 +193,6 @@ export async function getSaltValue(logger: Logger): Promise { return sqsSalt } -/** - * Places batches of messages into SQS - * @param batches - * @param sqsUrl - * @param requestId - * @param logger - * @returns An array of the send MessageIds - */ -async function placeBatchInSQS( - batches: Array>, - sqsUrl: string, - requestId: string, - logger: Logger -): Promise> { - - // Used for the return value - let out: Array = [] - - for (const batch of batches) { - try { - logger.info( - "Pushing a batch of notification requests to SQS", - { - batchLength: batch.length, - deduplicationIds: batch.map(e => e.MessageDeduplicationId), - requestId, - sqsUrl - } - ) - - const command = new SendMessageBatchCommand({ - QueueUrl: sqsUrl, - Entries: batch - }) - const result = await sqs.send(command) - if (result.Successful?.length) { - logger.info("Successfully sent a batch of prescriptions to the SQS", {result, sqsUrl}) - - // For each successful message, get its message ID. I don't think there will ever be undefined - // actually in here, but the typing suggests that there could be so filter those out - out.push(...result.Successful.map(e => e.MessageId).filter(msg_id => msg_id !== undefined)) - } - // Some may succeed, and some may fail. So check for both - if (result.Failed?.length) { - throw new Error(`Failed to send a batch of prescriptions to the SQS {sqsUrl}`) - } - } catch (error) { - logger.error("Failed to send a batch of prescriptions to the SQS", {error, sqsUrl}) - throw error - } - } - - return out -} - function norm(str: string) { return str.toLowerCase().trim() } @@ -312,5 +303,5 @@ async function sendItemsToSQS( item => item as PostDatedNotifyDataItem ) - return await sendEntriesToQueue(entries, sqsUrl, requestId, logger) + return sendEntriesToQueue(entries, sqsUrl, requestId, logger) } diff --git a/packages/updatePrescriptionStatus/tests/testSqsClient.test.ts b/packages/updatePrescriptionStatus/tests/testSqsClient.test.ts index d8ed468b73..f720533a4e 100644 --- a/packages/updatePrescriptionStatus/tests/testSqsClient.test.ts +++ b/packages/updatePrescriptionStatus/tests/testSqsClient.test.ts @@ -197,7 +197,7 @@ describe("Unit tests for pushPrescriptionToNotificationSQS", () => { expect(infoSpy).toHaveBeenCalledWith( "Successfully sent a batch of prescriptions to the SQS", - {result: {Successful: [{}]}, sqsUrl: process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL} + {result: {Successful: [{}]}, queueUrl: process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL} ) }) @@ -265,7 +265,7 @@ describe("Unit tests for pushPrescriptionToNotificationSQS", () => { expect(errorSpy).toHaveBeenCalledWith( "Failed to send a batch of prescriptions to the SQS", - {error: testError, sqsUrl: process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL} + {error: testError, queueUrl: process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL} ) }) @@ -297,7 +297,7 @@ describe("Unit tests for pushPrescriptionToNotificationSQS", () => { expect(errorSpy).toHaveBeenCalledWith( "Failed to send a batch of prescriptions to the SQS", - {error: standardQueueError, sqsUrl: process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL} + {error: standardQueueError, queueUrl: process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL} ) }) @@ -328,7 +328,7 @@ describe("Unit tests for pushPrescriptionToNotificationSQS", () => { ).rejects.toThrow(postDatedQueueError) expect(errorSpy).toHaveBeenCalledWith( "Failed to send a batch of prescriptions to the SQS", - {error: postDatedQueueError, sqsUrl: process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL} + {error: postDatedQueueError, queueUrl: process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL} ) }) From cf80b916bfd95e2ab5733ce615798cf32e88875a Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Mon, 26 Jan 2026 12:05:01 +0000 Subject: [PATCH 36/75] rename function. Pass notifications SQS into the post dated lambda --- SAMtemplates/functions/main.yaml | 1 + packages/postDatedLambda/src/orchestration.ts | 2 +- packages/postDatedLambda/src/sqs.ts | 19 ++++++++++++++----- .../postDatedLambda/tests/testSqs.test.ts | 8 ++++---- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/SAMtemplates/functions/main.yaml b/SAMtemplates/functions/main.yaml index 601f9f2c43..3adeaed794 100644 --- a/SAMtemplates/functions/main.yaml +++ b/SAMtemplates/functions/main.yaml @@ -587,6 +587,7 @@ Resources: - Fn::ImportValue: !Sub ${StackName}-UseNotificationSQSQueueKMSKeyPolicyArn - Fn::ImportValue: !Sub ${StackName}-WritePostDatedNotificationsSQSQueuePolicyArn - Fn::ImportValue: !Sub ${StackName}-ReadPostDatedNotificationsSQSQueuePolicyArn + - Fn::ImportValue: !Sub ${StackName}-WriteNHSNotifyPrescriptionsSQSQueuePolicyArn # Doesnt need to be able to read, only write. - Fn::ImportValue: !Sub ${StackName}:tables:${PrescriptionStatusUpdatesTableName}:TableWritePolicyArn - Fn::ImportValue: !Sub ${StackName}:tables:${PrescriptionStatusUpdatesTableName}:TableReadPolicyArn - Fn::ImportValue: !Sub ${StackName}:tables:UsePrescriptionStatusUpdatesKMSKeyPolicyArn diff --git a/packages/postDatedLambda/src/orchestration.ts b/packages/postDatedLambda/src/orchestration.ts index b461f63ffe..fdbcc5adfa 100644 --- a/packages/postDatedLambda/src/orchestration.ts +++ b/packages/postDatedLambda/src/orchestration.ts @@ -9,7 +9,7 @@ export const MAX_QUEUE_RUNTIME = 14 * 60 * 1000 // 14 minutes, to avoid Lambda t const MIN_RECEIVED_THRESHOLD = 3 // If fewer than this number of messages are received, consider the queue empty /** - * Process a batch of SQS messages. + * Process a batch of SQS messages. Returns arrays of matured and immature prescription updates. * Messages are enriched with existing records from DynamoDB and processed individually. * Results are tracked for success/failure handling. * diff --git a/packages/postDatedLambda/src/sqs.ts b/packages/postDatedLambda/src/sqs.ts index a9ec00bb89..69251219a8 100644 --- a/packages/postDatedLambda/src/sqs.ts +++ b/packages/postDatedLambda/src/sqs.ts @@ -18,7 +18,7 @@ const sqs = new SQSClient({region: process.env.AWS_REGION}) * Get the SQS queue URL from environment variables. * Throws an error if not configured. */ -export function getQueueUrl(logger: Logger): string { +export function getPostDatedQueueUrl(logger: Logger): string { const sqsUrl = process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL if (!sqsUrl) { logger.error("Post-dated prescriptions SQS URL not configured") @@ -27,13 +27,22 @@ export function getQueueUrl(logger: Logger): string { return sqsUrl } +export function getNotificationQueueUrl(logger: Logger): string { + const sqsUrl = process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL + if (!sqsUrl) { + logger.error("NHS Notify prescriptions SQS URL not configured") + throw new Error("NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL not set") + } + return sqsUrl +} + /** * Report the current status of the post-dated prescriptions SQS queue. * * @param logger - The AWS logging object */ export async function reportQueueStatus(logger: Logger): Promise { - const sqsUrl = getQueueUrl(logger) + const sqsUrl = getPostDatedQueueUrl(logger) const attrsCmd = new GetQueueAttributesCommand({ QueueUrl: sqsUrl, @@ -69,7 +78,7 @@ export async function reportQueueStatus(logger: Logger): Promise { * @returns - The array of parsed PostDatedSQSMessage */ export async function receivePostDatedSQSMessages(logger: Logger): Promise> { - const sqsUrl = getQueueUrl(logger) + const sqsUrl = getPostDatedQueueUrl(logger) const toFetch = 10 const receiveCmd = new ReceiveMessageCommand({ @@ -138,7 +147,7 @@ export async function removeSQSMessages( return } - const sqsUrl = getQueueUrl(logger) + const sqsUrl = getPostDatedQueueUrl(logger) const entries = messages.map((m) => ({ Id: m.MessageId!, @@ -185,7 +194,7 @@ export async function returnMessagesToQueue( return } - const sqsUrl = getQueueUrl(logger) + const sqsUrl = getPostDatedQueueUrl(logger) // TODO: Each message needs to have an appropriate visibility timeout based on when it is due to be retried. // For now, use a fixed 5 minute timeout for all messages. diff --git a/packages/postDatedLambda/tests/testSqs.test.ts b/packages/postDatedLambda/tests/testSqs.test.ts index 21d8acc80c..6ddc0223f1 100644 --- a/packages/postDatedLambda/tests/testSqs.test.ts +++ b/packages/postDatedLambda/tests/testSqs.test.ts @@ -28,7 +28,7 @@ export function mockSQSClient() { const {mockSend} = mockSQSClient() const { - getQueueUrl, + getPostDatedQueueUrl, reportQueueStatus, receivePostDatedSQSMessages, removeSQSMessages, @@ -58,19 +58,19 @@ describe("sqs", () => { // warnSpy = jest.spyOn(logger, "warn") }) - describe("getQueueUrl", () => { + describe("getPostDatedQueueUrl", () => { it("Should return the SQS queue URL from environment variables", () => { const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue" process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl - const result = getQueueUrl(logger) + const result = getPostDatedQueueUrl(logger) expect(result).toBe(testUrl) }) it("Should throw an error if the SQS queue URL is not configured", () => { delete process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL - expect(() => getQueueUrl(logger)).toThrow("POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL not set") + expect(() => getPostDatedQueueUrl(logger)).toThrow("POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL not set") expect(errorSpy).toHaveBeenCalledWith("Post-dated prescriptions SQS URL not configured") }) }) From ebff1ac98c5cd1328ea25da36da4c5565a2ea8ba Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Mon, 26 Jan 2026 12:27:57 +0000 Subject: [PATCH 37/75] Update the psotDatedLambda to forward matured prescriptions to the notifications queue --- packages/postDatedLambda/src/sqs.ts | 129 +++++++++++++++++- .../postDatedLambda/tests/testSqs.test.ts | 80 ++++++++++- 2 files changed, 201 insertions(+), 8 deletions(-) diff --git a/packages/postDatedLambda/src/sqs.ts b/packages/postDatedLambda/src/sqs.ts index 69251219a8..b90b5e6a4f 100644 --- a/packages/postDatedLambda/src/sqs.ts +++ b/packages/postDatedLambda/src/sqs.ts @@ -4,16 +4,112 @@ import { DeleteMessageBatchCommand, ChangeMessageVisibilityBatchCommand, GetQueueAttributesCommand, + SendMessageBatchCommand, Message } from "@aws-sdk/client-sqs" import {Logger} from "@aws-lambda-powertools/logger" -import {PostDatedNotifyDataItem} from "@psu-common/commonTypes" +import {PostDatedNotifyDataItem, SQSBatchMessage} from "@psu-common/commonTypes" import {BatchProcessingResult, PostDatedSQSMessage} from "./types" const sqs = new SQSClient({region: process.env.AWS_REGION}) +// Note that a lot of the code to send an SQS message is copied from the updatePrescriptionStatus lambda, +// and I've NOT moved the code into a shared location for the two. +// This is because I don't want to alter the updatePrescriptionStatus lambda in that way +// for the sake of temporarily supporting post-dated messages. +// - Jim Wild, Jan. 2026 + +function chunkArray(arr: Array, size: number): Array> { + const chunks: Array> = [] + for (let i = 0; i < arr.length; i += size) { + chunks.push(arr.slice(i, i + size)) + } + return chunks +} + +function buildNotificationBatchEntries( + messages: Array, + logger: Logger +): Array { + return messages.map((message, idx) => { + const {prescriptionData} = message + const requestId = prescriptionData.RequestID + + // If we get something with no deduplication ID, then something upstream is wrong and we should fail out + if (!message.Attributes?.MessageDeduplicationId) { + logger.error("Post-dated SQS message is missing MessageDeduplicationId attribute", { + messageId: message.MessageId, message + }) + throw new Error("Missing MessageDeduplicationId in SQS message attributes") + } + + return { + Id: idx.toString(), + MessageBody: JSON.stringify(prescriptionData), + MessageDeduplicationId: message.Attributes?.MessageDeduplicationId, + MessageGroupId: requestId, + MessageAttributes: { + RequestId: { + DataType: "String", + StringValue: requestId + } + } + } + }) +} + +async function sendEntriesToQueue( + entries: Array, + queueUrl: string, + logger: Logger +): Promise> { + if (entries.length === 0) { + return [] + } + + const batches = chunkArray(entries, 10) + + const batchPromises = batches.map(async (batch) => { + try { + logger.info( + "Pushing a batch of notification requests to SQS", + { + batchLength: batch.length, + deduplicationIds: batch.map((entry) => entry.MessageDeduplicationId), + queueUrl + } + ) + + const command = new SendMessageBatchCommand({ + QueueUrl: queueUrl, + Entries: batch + }) + const result = await sqs.send(command) + + let successfulIds: Array = [] + if (result.Successful?.length) { + logger.info("Successfully sent a batch of prescriptions to the SQS", {result, queueUrl}) + successfulIds = result.Successful + .map((entry) => entry.MessageId) + .filter((msgId): msgId is string => msgId !== undefined) + } + + if (result.Failed?.length) { + throw new Error(`Failed to send a batch of prescriptions to the SQS ${queueUrl}`) + } + + return successfulIds + } catch (error) { + logger.error("Failed to send a batch of prescriptions to the SQS", {error, queueUrl}) + throw error + } + }) + + return Promise.all(batchPromises).then((results) => results.flat()) +} + /** * Get the SQS queue URL from environment variables. * Throws an error if not configured. @@ -132,6 +228,32 @@ export async function receivePostDatedSQSMessages(logger: Logger): Promise +): Promise> { + if (messages.length === 0) { + logger.info("No matured post-dated messages to forward to notifications queue") + return [] + } + + const queueUrl = getNotificationQueueUrl(logger) + const entries = buildNotificationBatchEntries(messages, logger) + + const sentMessageIds = await sendEntriesToQueue(entries, queueUrl, logger) + + logger.info("Forwarded matured post-dated messages to notifications queue", { + queueUrl, + forwardedCount: sentMessageIds.length, + sqsMessageIds: sentMessageIds + }) + + return sentMessageIds +} + /** * Delete successfully processed messages from the SQS queue. * @@ -250,10 +372,9 @@ export async function handleProcessedMessages( ): Promise { const {maturedPrescriptionUpdates, immaturePrescriptionUpdates} = result - // Delete matured messages + // Move matured messages to the notification queue and remove them from the post-dated queue if (maturedPrescriptionUpdates.length > 0) { - // TODO: Also need to send messages to the notification queue here (do that first, then delete) - // await sendSQSMessagesToNotificationQueue(logger, maturedPrescriptionUpdates) + await sendSQSMessagesToNotificationQueue(logger, maturedPrescriptionUpdates) await removeSQSMessages(logger, maturedPrescriptionUpdates) } diff --git a/packages/postDatedLambda/tests/testSqs.test.ts b/packages/postDatedLambda/tests/testSqs.test.ts index 6ddc0223f1..e6fbe508ed 100644 --- a/packages/postDatedLambda/tests/testSqs.test.ts +++ b/packages/postDatedLambda/tests/testSqs.test.ts @@ -33,7 +33,8 @@ const { receivePostDatedSQSMessages, removeSQSMessages, returnMessagesToQueue, - handleProcessedMessages + handleProcessedMessages, + sendSQSMessagesToNotificationQueue } = await import("../src/sqs") const ORIGINAL_ENV = {...process.env} @@ -50,6 +51,7 @@ describe("sqs", () => { // Reset environment process.env = {...ORIGINAL_ENV} + delete process.env.SQS_SALT // Fresh logger and spies logger = new Logger({serviceName: "test-service"}) @@ -171,6 +173,63 @@ describe("sqs", () => { }) }) + describe("sendSQSMessagesToNotificationQueue", () => { + it("should send matured post-dated messages to the notifications queue", async () => { + const notifyUrl = "https://sqs.eu-west-2.amazonaws.com/123456789012/notify" + process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL = notifyUrl + + const messages: Array = [ + { + MessageId: "1", + ReceiptHandle: "handle-1", + prescriptionData: createMockPostModifiedDataItem({RequestID: "req-1"}), + Attributes: {MessageDeduplicationId: "dedup-1"} + } + ] + + mockSend.mockReturnValueOnce({ + Successful: [{Id: "0", MessageId: "notify-msg-1"}], + Failed: [] + }) + + const result = await sendSQSMessagesToNotificationQueue(logger, messages) + + expect(mockSend).toHaveBeenCalledTimes(1) + const command = mockSend.mock.calls[0][0] as {input: {QueueUrl: string; Entries: Array<{MessageBody: string}>}} + expect(command.input.QueueUrl).toBe(notifyUrl) + expect(command.input.Entries[0].MessageBody).toBe(JSON.stringify(messages[0].prescriptionData)) + expect(result).toEqual(["notify-msg-1"]) + }) + + it("should short circuit when there are no matured messages", async () => { + process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL = "https://sqs.eu-west-2.amazonaws.com/123456789012/notify" + + const result = await sendSQSMessagesToNotificationQueue(logger, []) + + expect(result).toEqual([]) + expect(mockSend).not.toHaveBeenCalled() + }) + + it("should throw an error if the deduplication ID is missing", async () => { + process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL = "https://sqs.eu-west-2.amazonaws.com/123456789012/notify" + + const messages: Array = [ + { + MessageId: "1", + ReceiptHandle: "handle-1", + prescriptionData: createMockPostModifiedDataItem({RequestID: "req-1"}), + Attributes: {} // Missing MessageDeduplicationId + } + ] + + await expect( + sendSQSMessagesToNotificationQueue(logger, messages) + ).rejects.toThrow("Missing MessageDeduplicationId in SQS message attributes") + + expect(mockSend).not.toHaveBeenCalled() + }) + }) + describe("removeSQSMessages", () => { it("Should remove messages from the SQS queue", async () => { const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue" @@ -288,12 +347,21 @@ describe("sqs", () => { it("should remove matured messages and return immature messages to the queue", async () => { const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue" process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl + process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL = "https://sqs.us-east-1.amazonaws.com/123456789012/notify" const maturedMessages: Array = [ - {MessageId: "1", ReceiptHandle: "handle1", prescriptionData: createMockPostModifiedDataItem({})} + { + MessageId: "1", ReceiptHandle: "handle1", + prescriptionData: createMockPostModifiedDataItem({}), + Attributes: {MessageDeduplicationId: "dedup-1"} + } ] const immatureMessages: Array = [ - {MessageId: "2", ReceiptHandle: "handle2", prescriptionData: createMockPostModifiedDataItem({})} + { + MessageId: "2", ReceiptHandle: "handle2", + prescriptionData: createMockPostModifiedDataItem({}), + Attributes: {MessageDeduplicationId: "dedup-1"} + } ] const batchResult: BatchProcessingResult = { @@ -303,6 +371,10 @@ describe("sqs", () => { // Mock SQS responses mockSend + .mockReturnValueOnce({ + Successful: [{Id: "0", MessageId: "notify-msg"}], + Failed: [] + }) // sendSQSMessagesToNotificationQueue .mockReturnValueOnce({ Successful: [{Id: "1"}], Failed: [] @@ -311,7 +383,7 @@ describe("sqs", () => { await handleProcessedMessages(batchResult, logger) - expect(mockSend).toHaveBeenCalledTimes(2) + expect(mockSend).toHaveBeenCalledTimes(3) expect(infoSpy).toHaveBeenCalledWith("Successfully removed 1 messages from SQS") expect(infoSpy).toHaveBeenCalledWith("Returning messages to queue with timeouts", { numberOfMessages: 1, From 23de0fe8ef7e3ed569c884430b7738c682900c94 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Mon, 26 Jan 2026 13:04:17 +0000 Subject: [PATCH 38/75] Add dynamo dependency that we needed --- package-lock.json | 19953 +++++++++++++++++--------------------------- package.json | 1 + 2 files changed, 7581 insertions(+), 12373 deletions(-) diff --git a/package-lock.json b/package-lock.json index 699e8e1666..f6486b7410 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "packages/common/utilities" ], "dependencies": { + "@aws-sdk/lib-dynamodb": "^3.975.0", "@psu-common/commonTypes": "^1.0.0", "@psu-common/middyErrorHandler": "^1.0.0", "@psu-common/utilities": "^1.0.0", @@ -269,7 +270,214 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-backup/node_modules/@aws-sdk/client-sso": { + "node_modules/@aws-sdk/client-dynamodb": { + "version": "3.975.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.975.0.tgz", + "integrity": "sha512-Cq6oGb8XswG56YhF2kHmxuyEnMNayDpL8xDxp9E4zIUqDeSLCKE6lCaqZzo5zpngzqLluFsJbCC9jrMzZejMAA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.1", + "@aws-sdk/credential-provider-node": "^3.972.1", + "@aws-sdk/dynamodb-codec": "^3.972.2", + "@aws-sdk/middleware-endpoint-discovery": "^3.972.1", + "@aws-sdk/middleware-host-header": "^3.972.1", + "@aws-sdk/middleware-logger": "^3.972.1", + "@aws-sdk/middleware-recursion-detection": "^3.972.1", + "@aws-sdk/middleware-user-agent": "^3.972.2", + "@aws-sdk/region-config-resolver": "^3.972.1", + "@aws-sdk/types": "^3.973.0", + "@aws-sdk/util-endpoints": "3.972.0", + "@aws-sdk/util-user-agent-browser": "^3.972.1", + "@aws-sdk/util-user-agent-node": "^3.972.1", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.21.1", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.11", + "@smithy/middleware-retry": "^4.4.27", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.10.12", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.26", + "@smithy/util-defaults-mode-node": "^4.2.29", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "@smithy/util-waiter": "^4.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-secrets-manager": { + "version": "3.975.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.975.0.tgz", + "integrity": "sha512-KY67ghh2BBBhfaCvOquazOWWTe8CEaEsKOFtNVtECIttRlmm1YAuIDUTk7reaQhTqb+wwuS2xoGsu5z1FZkFyA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.1", + "@aws-sdk/credential-provider-node": "^3.972.1", + "@aws-sdk/middleware-host-header": "^3.972.1", + "@aws-sdk/middleware-logger": "^3.972.1", + "@aws-sdk/middleware-recursion-detection": "^3.972.1", + "@aws-sdk/middleware-user-agent": "^3.972.2", + "@aws-sdk/region-config-resolver": "^3.972.1", + "@aws-sdk/types": "^3.973.0", + "@aws-sdk/util-endpoints": "3.972.0", + "@aws-sdk/util-user-agent-browser": "^3.972.1", + "@aws-sdk/util-user-agent-node": "^3.972.1", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.21.1", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.11", + "@smithy/middleware-retry": "^4.4.27", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.10.12", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.26", + "@smithy/util-defaults-mode-node": "^4.2.29", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs": { + "version": "3.975.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sqs/-/client-sqs-3.975.0.tgz", + "integrity": "sha512-6KS8T24LkEp2QZ/529SKVHQTgvCDUWXE8NtrILUBxZ9e3BiprjC9JSEdMqgh82BUD8s8yv4nnoa4Faiz7lRFpw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.1", + "@aws-sdk/credential-provider-node": "^3.972.1", + "@aws-sdk/middleware-host-header": "^3.972.1", + "@aws-sdk/middleware-logger": "^3.972.1", + "@aws-sdk/middleware-recursion-detection": "^3.972.1", + "@aws-sdk/middleware-sdk-sqs": "^3.972.2", + "@aws-sdk/middleware-user-agent": "^3.972.2", + "@aws-sdk/region-config-resolver": "^3.972.1", + "@aws-sdk/types": "^3.973.0", + "@aws-sdk/util-endpoints": "3.972.0", + "@aws-sdk/util-user-agent-browser": "^3.972.1", + "@aws-sdk/util-user-agent-node": "^3.972.1", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.21.1", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/md5-js": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.11", + "@smithy/middleware-retry": "^4.4.27", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.10.12", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.26", + "@smithy/util-defaults-mode-node": "^4.2.29", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-ssm": { + "version": "3.975.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ssm/-/client-ssm-3.975.0.tgz", + "integrity": "sha512-yCWgQ+8vI9Q1XxcmeCDcwQTS1I39TM+3hJU6ubpEb5nfC29mosskv0+9+rIE+0PIRfBb6GZ9qLSzl0/suWg4Lg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.1", + "@aws-sdk/credential-provider-node": "^3.972.1", + "@aws-sdk/middleware-host-header": "^3.972.1", + "@aws-sdk/middleware-logger": "^3.972.1", + "@aws-sdk/middleware-recursion-detection": "^3.972.1", + "@aws-sdk/middleware-user-agent": "^3.972.2", + "@aws-sdk/region-config-resolver": "^3.972.1", + "@aws-sdk/types": "^3.973.0", + "@aws-sdk/util-endpoints": "3.972.0", + "@aws-sdk/util-user-agent-browser": "^3.972.1", + "@aws-sdk/util-user-agent-node": "^3.972.1", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.21.1", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.11", + "@smithy/middleware-retry": "^4.4.27", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.10.12", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.26", + "@smithy/util-defaults-mode-node": "^4.2.29", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "@smithy/util-waiter": "^4.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { "version": "3.974.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.974.0.tgz", "integrity": "sha512-ci+GiM0c4ULo4D79UMcY06LcOLcfvUfiyt8PzNY0vbt5O8BfCPYf4QomwVgkNcLLCYmroO4ge2Yy1EsLUlcD6g==", @@ -318,7 +526,7 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-backup/node_modules/@aws-sdk/core": { + "node_modules/@aws-sdk/core": { "version": "3.973.1", "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.1.tgz", "integrity": "sha512-Ocubx42QsMyVs9ANSmFpRm0S+hubWljpPLjOi9UFrtcnVJjrVJTzQ51sN0e5g4e8i8QZ7uY73zosLmgYL7kZTQ==", @@ -342,7 +550,7 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-backup/node_modules/@aws-sdk/credential-provider-env": { + "node_modules/@aws-sdk/credential-provider-env": { "version": "3.972.1", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.1.tgz", "integrity": "sha512-/etNHqnx96phy/SjI0HRC588o4vKH5F0xfkZ13yAATV7aNrb+5gYGNE6ePWafP+FuZ3HkULSSlJFj0AxgrAqYw==", @@ -358,7 +566,7 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-backup/node_modules/@aws-sdk/credential-provider-http": { + "node_modules/@aws-sdk/credential-provider-http": { "version": "3.972.2", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.2.tgz", "integrity": "sha512-mXgdaUfe5oM+tWKyeZ7Vh/iQ94FrkMky1uuzwTOmFADiRcSk5uHy/e3boEFedXiT/PRGzgBmqvJVK4F6lUISCg==", @@ -379,7 +587,7 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-backup/node_modules/@aws-sdk/credential-provider-ini": { + "node_modules/@aws-sdk/credential-provider-ini": { "version": "3.972.1", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.1.tgz", "integrity": "sha512-OdbJA3v+XlNDsrYzNPRUwr8l7gw1r/nR8l4r96MDzSBDU8WEo8T6C06SvwaXR8SpzsjO3sq5KMP86wXWg7Rj4g==", @@ -404,7 +612,7 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-backup/node_modules/@aws-sdk/credential-provider-login": { + "node_modules/@aws-sdk/credential-provider-login": { "version": "3.972.1", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.1.tgz", "integrity": "sha512-CccqDGL6ZrF3/EFWZefvKW7QwwRdxlHUO8NVBKNVcNq6womrPDvqB6xc9icACtE0XB0a7PLoSTkAg8bQVkTO2w==", @@ -423,7 +631,7 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-backup/node_modules/@aws-sdk/credential-provider-node": { + "node_modules/@aws-sdk/credential-provider-node": { "version": "3.972.1", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.1.tgz", "integrity": "sha512-DwXPk9GfuU/xG9tmCyXFVkCr6X3W8ZCoL5Ptb0pbltEx1/LCcg7T+PBqDlPiiinNCD6ilIoMJDWsnJ8ikzZA7Q==", @@ -446,7 +654,7 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-backup/node_modules/@aws-sdk/credential-provider-process": { + "node_modules/@aws-sdk/credential-provider-process": { "version": "3.972.1", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.1.tgz", "integrity": "sha512-bi47Zigu3692SJwdBvo8y1dEwE6B61stCwCFnuRWJVTfiM84B+VTSCV661CSWJmIZzmcy7J5J3kWyxL02iHj0w==", @@ -463,7 +671,7 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-backup/node_modules/@aws-sdk/credential-provider-sso": { + "node_modules/@aws-sdk/credential-provider-sso": { "version": "3.972.1", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.1.tgz", "integrity": "sha512-dLZVNhM7wSgVUFsgVYgI5hb5Z/9PUkT46pk/SHrSmUqfx6YDvoV4YcPtaiRqviPpEGGiRtdQMEadyOKIRqulUQ==", @@ -482,7 +690,7 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-backup/node_modules/@aws-sdk/credential-provider-web-identity": { + "node_modules/@aws-sdk/credential-provider-web-identity": { "version": "3.972.1", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.1.tgz", "integrity": "sha512-YMDeYgi0u687Ay0dAq/pFPKuijrlKTgsaB/UATbxCs/FzZfMiG4If5ksywHmmW7MiYUF8VVv+uou3TczvLrN4w==", @@ -500,52 +708,139 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-backup/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.1.tgz", - "integrity": "sha512-/R82lXLPmZ9JaUGSUdKtBp2k/5xQxvBT3zZWyKiBOhyulFotlfvdlrO8TnqstBimsl4lYEYySDL+W6ldFh6ALg==", + "node_modules/@aws-sdk/dynamodb-codec": { + "version": "3.972.2", + "resolved": "https://registry.npmjs.org/@aws-sdk/dynamodb-codec/-/dynamodb-codec-3.972.2.tgz", + "integrity": "sha512-K9CDUrjDSFhVeGibVjdKd+plE25EEo7DMAIl+mYaJ+ySXsQxdPgEmOYDmZq3iq3mpkkV0lG8XWM/oaQ+q+5aCQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/protocol-http": "^5.3.8", + "@aws-sdk/core": "^3.973.1", + "@smithy/core": "^3.21.1", + "@smithy/smithy-client": "^4.10.12", "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" }, "engines": { "node": ">=20.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "3.975.0" } }, - "node_modules/@aws-sdk/client-backup/node_modules/@aws-sdk/middleware-logger": { + "node_modules/@aws-sdk/endpoint-cache": { "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.1.tgz", - "integrity": "sha512-JGgFl6cHg9G2FHu4lyFIzmFN8KESBiRr84gLC3Aeni0Gt1nKm+KxWLBuha/RPcXxJygGXCcMM4AykkIwxor8RA==", + "resolved": "https://registry.npmjs.org/@aws-sdk/endpoint-cache/-/endpoint-cache-3.972.1.tgz", + "integrity": "sha512-w9TVoCUNwPG4njcbnZpSQaOZ1BF2z1Guox8NltoXm7oS1+q/8iHeG8eqY9TlGQsKLNA4KfnKUEAx4rlEc6Qv6w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/types": "^4.12.0", + "mnemonist": "0.38.3", "tslib": "^2.6.2" }, "engines": { "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-backup/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.1.tgz", - "integrity": "sha512-taGzNRe8vPHjnliqXIHp9kBgIemLE/xCaRTMH1NH0cncHeaPcjxtnCroAAM9aOlPuKvBe2CpZESyvM1+D8oI7Q==", + "node_modules/@aws-sdk/lib-dynamodb": { + "version": "3.975.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.975.0.tgz", + "integrity": "sha512-BEhzr8atBFT8mJ8KuFne3OHj81zsqQWLP1Is2zzbzbZy4Mjbm1nRceNEwZ603tp9oFrV2mwTNLagFDPEudJC/g==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.8", + "@aws-sdk/core": "^3.973.1", + "@aws-sdk/util-dynamodb": "3.975.0", + "@smithy/core": "^3.21.1", + "@smithy/smithy-client": "^4.10.12", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-backup/node_modules/@aws-sdk/middleware-user-agent": { + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "3.975.0" + } + }, + "node_modules/@aws-sdk/middleware-endpoint-discovery": { + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.972.1.tgz", + "integrity": "sha512-3d6QaHQAjevuCioG0lZmZM/Nb8mT4JiF2mRmlh/aTM32Fc/YNGxp2Qbri8B8nfeYlfoi8GM12gH7SaIwkihuBQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/endpoint-cache": "^3.972.1", + "@aws-sdk/types": "^3.973.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.1.tgz", + "integrity": "sha512-/R82lXLPmZ9JaUGSUdKtBp2k/5xQxvBT3zZWyKiBOhyulFotlfvdlrO8TnqstBimsl4lYEYySDL+W6ldFh6ALg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.1.tgz", + "integrity": "sha512-JGgFl6cHg9G2FHu4lyFIzmFN8KESBiRr84gLC3Aeni0Gt1nKm+KxWLBuha/RPcXxJygGXCcMM4AykkIwxor8RA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.0", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.1.tgz", + "integrity": "sha512-taGzNRe8vPHjnliqXIHp9kBgIemLE/xCaRTMH1NH0cncHeaPcjxtnCroAAM9aOlPuKvBe2CpZESyvM1+D8oI7Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.0", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-sqs": { + "version": "3.972.2", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sqs/-/middleware-sdk-sqs-3.972.2.tgz", + "integrity": "sha512-LPKsfqdoei7kBJo7JqGKbIM05W0bbcnJNfFtoOPgjXOJa7OpEs0pYj5OHiqbykgUFzkygD22f9sBmEfZkFoZ0g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.0", + "@smithy/smithy-client": "^4.10.12", + "@smithy/types": "^4.12.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { "version": "3.972.2", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.2.tgz", "integrity": "sha512-d+Exq074wy0X6wvShg/kmZVtkah+28vMuqCtuY3cydg8LUZOJBtbAolCpEJizSyb8mJJZF9BjWaTANXL4OYnkg==", @@ -563,7 +858,7 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-backup/node_modules/@aws-sdk/nested-clients": { + "node_modules/@aws-sdk/nested-clients": { "version": "3.974.0", "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.974.0.tgz", "integrity": "sha512-k3dwdo/vOiHMJc9gMnkPl1BA5aQfTrZbz+8fiDkWrPagqAioZgmo5oiaOaeX0grObfJQKDtcpPFR4iWf8cgl8Q==", @@ -612,7 +907,7 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-backup/node_modules/@aws-sdk/region-config-resolver": { + "node_modules/@aws-sdk/region-config-resolver": { "version": "3.972.1", "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.1.tgz", "integrity": "sha512-voIY8RORpxLAEgEkYaTFnkaIuRwVBEc+RjVZYcSSllPV+ZEKAacai6kNhJeE3D70Le+JCfvRb52tng/AVHY+jQ==", @@ -628,7 +923,7 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-backup/node_modules/@aws-sdk/token-providers": { + "node_modules/@aws-sdk/token-providers": { "version": "3.974.0", "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.974.0.tgz", "integrity": "sha512-cBykL0LiccKIgNhGWvQRTPvsBLPZxnmJU3pYxG538jpFX8lQtrCy1L7mmIHNEdxIdIGEPgAEHF8/JQxgBToqUQ==", @@ -646,7 +941,7 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-backup/node_modules/@aws-sdk/types": { + "node_modules/@aws-sdk/types": { "version": "3.973.0", "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.0.tgz", "integrity": "sha512-jYIdB7a7jhRTvyb378nsjyvJh1Si+zVduJ6urMNGpz8RjkmHZ+9vM2H07XaIB2Cfq0GhJRZYOfUCH8uqQhqBkQ==", @@ -659,7 +954,22 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-backup/node_modules/@aws-sdk/util-endpoints": { + "node_modules/@aws-sdk/util-dynamodb": { + "version": "3.975.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.975.0.tgz", + "integrity": "sha512-ZsziF8m5Syn+kA2YJLEG2kk6zfxea8yRl/7SkSFpAls8RFYkt8EUmVUMBhX2hBpGw+nbZL7+AcRi4S2LxAcYWA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "3.975.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { "version": "3.972.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.972.0.tgz", "integrity": "sha512-6JHsl1V/a1ZW8D8AFfd4R52fwZPnZ5H4U6DS8m/bWT8qad72NvbOFAC7U2cDtFs2TShqUO3TEiX/EJibtY3ijg==", @@ -675,7 +985,7 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-backup/node_modules/@aws-sdk/util-endpoints/node_modules/@aws-sdk/types": { + "node_modules/@aws-sdk/util-endpoints/node_modules/@aws-sdk/types": { "version": "3.972.0", "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz", "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==", @@ -688,7 +998,17 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-backup/node_modules/@aws-sdk/util-user-agent-browser": { + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.893.0", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { "version": "3.972.1", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.1.tgz", "integrity": "sha512-IgF55NFmJX8d9Wql9M0nEpk2eYbuD8G4781FN4/fFgwTXBn86DvlZJuRWDCMcMqZymnBVX7HW9r+3r9ylqfW0w==", @@ -700,7 +1020,7 @@ "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-backup/node_modules/@aws-sdk/util-user-agent-node": { + "node_modules/@aws-sdk/util-user-agent-node": { "version": "3.972.1", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.1.tgz", "integrity": "sha512-oIs4JFcADzoZ0c915R83XvK2HltWupxNsXUIuZse2rgk7b97zTpkxaqXiH0h9ylh31qtgo/t8hp4tIqcsMrEbQ==", @@ -724,7 +1044,7 @@ } } }, - "node_modules/@aws-sdk/client-backup/node_modules/@aws-sdk/xml-builder": { + "node_modules/@aws-sdk/xml-builder": { "version": "3.972.1", "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.1.tgz", "integrity": "sha512-6zZGlPOqn7Xb+25MAXGb1JhgvaC5HjZj6GzszuVrnEgbhvzBRFGKYemuHBV4bho+dtqeYKPgaZUv7/e80hIGNg==", @@ -738,10609 +1058,5945 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-dynamodb": { - "version": "3.971.0", + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.3", "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.970.0", - "@aws-sdk/credential-provider-node": "3.971.0", - "@aws-sdk/dynamodb-codec": "3.970.0", - "@aws-sdk/middleware-endpoint-discovery": "3.971.0", - "@aws-sdk/middleware-host-header": "3.969.0", - "@aws-sdk/middleware-logger": "3.969.0", - "@aws-sdk/middleware-recursion-detection": "3.969.0", - "@aws-sdk/middleware-user-agent": "3.970.0", - "@aws-sdk/region-config-resolver": "3.969.0", - "@aws-sdk/types": "3.969.0", - "@aws-sdk/util-endpoints": "3.970.0", - "@aws-sdk/util-user-agent-browser": "3.969.0", - "@aws-sdk/util-user-agent-node": "3.971.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.20.6", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.7", - "@smithy/middleware-retry": "^4.4.23", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.22", - "@smithy/util-defaults-mode-node": "^4.2.25", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "@smithy/util-waiter": "^4.2.8", - "tslib": "^2.6.2" - }, "engines": { - "node": ">=20.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/dynamodb-codec": { - "version": "3.970.0", - "license": "Apache-2.0", + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "3.970.0", - "@smithy/core": "^3.20.6", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "tslib": "^2.6.2" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-dynamodb": "3.970.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/client-secrets-manager": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.975.0.tgz", - "integrity": "sha512-KY67ghh2BBBhfaCvOquazOWWTe8CEaEsKOFtNVtECIttRlmm1YAuIDUTk7reaQhTqb+wwuS2xoGsu5z1FZkFyA==", - "license": "Apache-2.0", + "node_modules/@babel/compat-data": { + "version": "7.28.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.4", + "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/credential-provider-node": "^3.972.1", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.2", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.1", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.11", - "@smithy/middleware-retry": "^4.4.27", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.26", - "@smithy/util-defaults-mode-node": "^4.2.29", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/client-sso": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.974.0.tgz", - "integrity": "sha512-ci+GiM0c4ULo4D79UMcY06LcOLcfvUfiyt8PzNY0vbt5O8BfCPYf4QomwVgkNcLLCYmroO4ge2Yy1EsLUlcD6g==", - "license": "Apache-2.0", + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.10", - "@smithy/middleware-retry": "^4.4.26", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.11", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.25", - "@smithy/util-defaults-mode-node": "^4.2.28", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/core": { - "version": "3.973.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.1.tgz", - "integrity": "sha512-Ocubx42QsMyVs9ANSmFpRm0S+hubWljpPLjOi9UFrtcnVJjrVJTzQ51sN0e5g4e8i8QZ7uY73zosLmgYL7kZTQ==", - "license": "Apache-2.0", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/xml-builder": "^3.972.1", - "@smithy/core": "^3.21.1", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.1.tgz", - "integrity": "sha512-/etNHqnx96phy/SjI0HRC588o4vKH5F0xfkZ13yAATV7aNrb+5gYGNE6ePWafP+FuZ3HkULSSlJFj0AxgrAqYw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.2.tgz", - "integrity": "sha512-mXgdaUfe5oM+tWKyeZ7Vh/iQ94FrkMky1uuzwTOmFADiRcSk5uHy/e3boEFedXiT/PRGzgBmqvJVK4F6lUISCg==", - "license": "Apache-2.0", + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", - "tslib": "^2.6.2" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.1.tgz", - "integrity": "sha512-OdbJA3v+XlNDsrYzNPRUwr8l7gw1r/nR8l4r96MDzSBDU8WEo8T6C06SvwaXR8SpzsjO3sq5KMP86wXWg7Rj4g==", - "license": "Apache-2.0", + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/credential-provider-env": "^3.972.1", - "@aws-sdk/credential-provider-http": "^3.972.1", - "@aws-sdk/credential-provider-login": "^3.972.1", - "@aws-sdk/credential-provider-process": "^3.972.1", - "@aws-sdk/credential-provider-sso": "^3.972.1", - "@aws-sdk/credential-provider-web-identity": "^3.972.1", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.1.tgz", - "integrity": "sha512-CccqDGL6ZrF3/EFWZefvKW7QwwRdxlHUO8NVBKNVcNq6womrPDvqB6xc9icACtE0XB0a7PLoSTkAg8bQVkTO2w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.1.tgz", - "integrity": "sha512-DwXPk9GfuU/xG9tmCyXFVkCr6X3W8ZCoL5Ptb0pbltEx1/LCcg7T+PBqDlPiiinNCD6ilIoMJDWsnJ8ikzZA7Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.1", - "@aws-sdk/credential-provider-http": "^3.972.1", - "@aws-sdk/credential-provider-ini": "^3.972.1", - "@aws-sdk/credential-provider-process": "^3.972.1", - "@aws-sdk/credential-provider-sso": "^3.972.1", - "@aws-sdk/credential-provider-web-identity": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.1.tgz", - "integrity": "sha512-bi47Zigu3692SJwdBvo8y1dEwE6B61stCwCFnuRWJVTfiM84B+VTSCV661CSWJmIZzmcy7J5J3kWyxL02iHj0w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.1.tgz", - "integrity": "sha512-dLZVNhM7wSgVUFsgVYgI5hb5Z/9PUkT46pk/SHrSmUqfx6YDvoV4YcPtaiRqviPpEGGiRtdQMEadyOKIRqulUQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.974.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/token-providers": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.1.tgz", - "integrity": "sha512-YMDeYgi0u687Ay0dAq/pFPKuijrlKTgsaB/UATbxCs/FzZfMiG4If5ksywHmmW7MiYUF8VVv+uou3TczvLrN4w==", - "license": "Apache-2.0", + "node_modules/@babel/helpers": { + "version": "7.28.4", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.1.tgz", - "integrity": "sha512-/R82lXLPmZ9JaUGSUdKtBp2k/5xQxvBT3zZWyKiBOhyulFotlfvdlrO8TnqstBimsl4lYEYySDL+W6ldFh6ALg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "node_modules/@babel/parser": { + "version": "7.28.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.4" + }, + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.0.0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/middleware-logger": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.1.tgz", - "integrity": "sha512-JGgFl6cHg9G2FHu4lyFIzmFN8KESBiRr84gLC3Aeni0Gt1nKm+KxWLBuha/RPcXxJygGXCcMM4AykkIwxor8RA==", - "license": "Apache-2.0", + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.1.tgz", - "integrity": "sha512-taGzNRe8vPHjnliqXIHp9kBgIemLE/xCaRTMH1NH0cncHeaPcjxtnCroAAM9aOlPuKvBe2CpZESyvM1+D8oI7Q==", - "license": "Apache-2.0", + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.2.tgz", - "integrity": "sha512-d+Exq074wy0X6wvShg/kmZVtkah+28vMuqCtuY3cydg8LUZOJBtbAolCpEJizSyb8mJJZF9BjWaTANXL4OYnkg==", - "license": "Apache-2.0", + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@smithy/core": "^3.21.1", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.12.13" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/nested-clients": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.974.0.tgz", - "integrity": "sha512-k3dwdo/vOiHMJc9gMnkPl1BA5aQfTrZbz+8fiDkWrPagqAioZgmo5oiaOaeX0grObfJQKDtcpPFR4iWf8cgl8Q==", - "license": "Apache-2.0", + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.10", - "@smithy/middleware-retry": "^4.4.26", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.11", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.25", - "@smithy/util-defaults-mode-node": "^4.2.28", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.1.tgz", - "integrity": "sha512-voIY8RORpxLAEgEkYaTFnkaIuRwVBEc+RjVZYcSSllPV+ZEKAacai6kNhJeE3D70Le+JCfvRb52tng/AVHY+jQ==", - "license": "Apache-2.0", + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/token-providers": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.974.0.tgz", - "integrity": "sha512-cBykL0LiccKIgNhGWvQRTPvsBLPZxnmJU3pYxG538jpFX8lQtrCy1L7mmIHNEdxIdIGEPgAEHF8/JQxgBToqUQ==", - "license": "Apache-2.0", + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.10.4" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/types": { - "version": "3.973.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.0.tgz", - "integrity": "sha512-jYIdB7a7jhRTvyb378nsjyvJh1Si+zVduJ6urMNGpz8RjkmHZ+9vM2H07XaIB2Cfq0GhJRZYOfUCH8uqQhqBkQ==", - "license": "Apache-2.0", + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/util-endpoints": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.972.0.tgz", - "integrity": "sha512-6JHsl1V/a1ZW8D8AFfd4R52fwZPnZ5H4U6DS8m/bWT8qad72NvbOFAC7U2cDtFs2TShqUO3TEiX/EJibtY3ijg==", - "license": "Apache-2.0", + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/types": "3.972.0", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-endpoints": "^3.2.8", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/util-endpoints/node_modules/@aws-sdk/types": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz", - "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==", - "license": "Apache-2.0", + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.10.4" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.1.tgz", - "integrity": "sha512-IgF55NFmJX8d9Wql9M0nEpk2eYbuD8G4781FN4/fFgwTXBn86DvlZJuRWDCMcMqZymnBVX7HW9r+3r9ylqfW0w==", - "license": "Apache-2.0", + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/types": "^4.12.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.1.tgz", - "integrity": "sha512-oIs4JFcADzoZ0c915R83XvK2HltWupxNsXUIuZse2rgk7b97zTpkxaqXiH0h9ylh31qtgo/t8hp4tIqcsMrEbQ==", - "license": "Apache-2.0", + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/xml-builder": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.1.tgz", - "integrity": "sha512-6zZGlPOqn7Xb+25MAXGb1JhgvaC5HjZj6GzszuVrnEgbhvzBRFGKYemuHBV4bho+dtqeYKPgaZUv7/e80hIGNg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "fast-xml-parser": "5.2.5", - "tslib": "^2.6.2" + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/client-sqs": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sqs/-/client-sqs-3.975.0.tgz", - "integrity": "sha512-6KS8T24LkEp2QZ/529SKVHQTgvCDUWXE8NtrILUBxZ9e3BiprjC9JSEdMqgh82BUD8s8yv4nnoa4Faiz7lRFpw==", - "license": "Apache-2.0", + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/credential-provider-node": "^3.972.1", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-sdk-sqs": "^3.972.2", - "@aws-sdk/middleware-user-agent": "^3.972.2", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.1", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/md5-js": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.11", - "@smithy/middleware-retry": "^4.4.27", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.26", - "@smithy/util-defaults-mode-node": "^4.2.29", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/client-sso": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.974.0.tgz", - "integrity": "sha512-ci+GiM0c4ULo4D79UMcY06LcOLcfvUfiyt8PzNY0vbt5O8BfCPYf4QomwVgkNcLLCYmroO4ge2Yy1EsLUlcD6g==", - "license": "Apache-2.0", + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.10", - "@smithy/middleware-retry": "^4.4.26", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.11", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.25", - "@smithy/util-defaults-mode-node": "^4.2.28", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/core": { - "version": "3.973.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.1.tgz", - "integrity": "sha512-Ocubx42QsMyVs9ANSmFpRm0S+hubWljpPLjOi9UFrtcnVJjrVJTzQ51sN0e5g4e8i8QZ7uY73zosLmgYL7kZTQ==", - "license": "Apache-2.0", + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/xml-builder": "^3.972.1", - "@smithy/core": "^3.21.1", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.1.tgz", - "integrity": "sha512-/etNHqnx96phy/SjI0HRC588o4vKH5F0xfkZ13yAATV7aNrb+5gYGNE6ePWafP+FuZ3HkULSSlJFj0AxgrAqYw==", - "license": "Apache-2.0", + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.2.tgz", - "integrity": "sha512-mXgdaUfe5oM+tWKyeZ7Vh/iQ94FrkMky1uuzwTOmFADiRcSk5uHy/e3boEFedXiT/PRGzgBmqvJVK4F6lUISCg==", - "license": "Apache-2.0", + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.1.tgz", - "integrity": "sha512-OdbJA3v+XlNDsrYzNPRUwr8l7gw1r/nR8l4r96MDzSBDU8WEo8T6C06SvwaXR8SpzsjO3sq5KMP86wXWg7Rj4g==", - "license": "Apache-2.0", + "node_modules/@babel/runtime": { + "version": "7.28.4", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/credential-provider-env": "^3.972.1", - "@aws-sdk/credential-provider-http": "^3.972.1", - "@aws-sdk/credential-provider-login": "^3.972.1", - "@aws-sdk/credential-provider-process": "^3.972.1", - "@aws-sdk/credential-provider-sso": "^3.972.1", - "@aws-sdk/credential-provider-web-identity": "^3.972.1", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.1.tgz", - "integrity": "sha512-CccqDGL6ZrF3/EFWZefvKW7QwwRdxlHUO8NVBKNVcNq6womrPDvqB6xc9icACtE0XB0a7PLoSTkAg8bQVkTO2w==", - "license": "Apache-2.0", + "node_modules/@babel/traverse": { + "version": "7.28.4", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4", + "debug": "^4.3.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.1.tgz", - "integrity": "sha512-DwXPk9GfuU/xG9tmCyXFVkCr6X3W8ZCoL5Ptb0pbltEx1/LCcg7T+PBqDlPiiinNCD6ilIoMJDWsnJ8ikzZA7Q==", - "license": "Apache-2.0", + "node_modules/@babel/types": { + "version": "7.28.4", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.1", - "@aws-sdk/credential-provider-http": "^3.972.1", - "@aws-sdk/credential-provider-ini": "^3.972.1", - "@aws-sdk/credential-provider-process": "^3.972.1", - "@aws-sdk/credential-provider-sso": "^3.972.1", - "@aws-sdk/credential-provider-web-identity": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.1.tgz", - "integrity": "sha512-bi47Zigu3692SJwdBvo8y1dEwE6B61stCwCFnuRWJVTfiM84B+VTSCV661CSWJmIZzmcy7J5J3kWyxL02iHj0w==", - "license": "Apache-2.0", + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@jridgewell/trace-mapping": "0.3.9" }, "engines": { - "node": ">=20.0.0" + "node": ">=12" } }, - "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.1.tgz", - "integrity": "sha512-dLZVNhM7wSgVUFsgVYgI5hb5Z/9PUkT46pk/SHrSmUqfx6YDvoV4YcPtaiRqviPpEGGiRtdQMEadyOKIRqulUQ==", - "license": "Apache-2.0", + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/client-sso": "3.974.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/token-providers": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.1.tgz", - "integrity": "sha512-YMDeYgi0u687Ay0dAq/pFPKuijrlKTgsaB/UATbxCs/FzZfMiG4If5ksywHmmW7MiYUF8VVv+uou3TczvLrN4w==", - "license": "Apache-2.0", + "node_modules/@emnapi/core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" } }, - "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.1.tgz", - "integrity": "sha512-/R82lXLPmZ9JaUGSUdKtBp2k/5xQxvBT3zZWyKiBOhyulFotlfvdlrO8TnqstBimsl4lYEYySDL+W6ldFh6ALg==", - "license": "Apache-2.0", + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "tslib": "^2.4.0" } }, - "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/middleware-logger": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.1.tgz", - "integrity": "sha512-JGgFl6cHg9G2FHu4lyFIzmFN8KESBiRr84gLC3Aeni0Gt1nKm+KxWLBuha/RPcXxJygGXCcMM4AykkIwxor8RA==", - "license": "Apache-2.0", + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "tslib": "^2.4.0" } }, - "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.1.tgz", - "integrity": "sha512-taGzNRe8vPHjnliqXIHp9kBgIemLE/xCaRTMH1NH0cncHeaPcjxtnCroAAM9aOlPuKvBe2CpZESyvM1+D8oI7Q==", - "license": "Apache-2.0", + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.2", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "@emotion/memoize": "^0.8.1" } }, - "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.2.tgz", - "integrity": "sha512-d+Exq074wy0X6wvShg/kmZVtkah+28vMuqCtuY3cydg8LUZOJBtbAolCpEJizSyb8mJJZF9BjWaTANXL4OYnkg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@smithy/core": "^3.21.1", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">=20.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/nested-clients": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.974.0.tgz", - "integrity": "sha512-k3dwdo/vOiHMJc9gMnkPl1BA5aQfTrZbz+8fiDkWrPagqAioZgmo5oiaOaeX0grObfJQKDtcpPFR4iWf8cgl8Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.10", - "@smithy/middleware-retry": "^4.4.26", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.11", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.25", - "@smithy/util-defaults-mode-node": "^4.2.28", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=20.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.1.tgz", - "integrity": "sha512-voIY8RORpxLAEgEkYaTFnkaIuRwVBEc+RjVZYcSSllPV+ZEKAacai6kNhJeE3D70Le+JCfvRb52tng/AVHY+jQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=20.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/token-providers": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.974.0.tgz", - "integrity": "sha512-cBykL0LiccKIgNhGWvQRTPvsBLPZxnmJU3pYxG538jpFX8lQtrCy1L7mmIHNEdxIdIGEPgAEHF8/JQxgBToqUQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/types": { - "version": "3.973.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.0.tgz", - "integrity": "sha512-jYIdB7a7jhRTvyb378nsjyvJh1Si+zVduJ6urMNGpz8RjkmHZ+9vM2H07XaIB2Cfq0GhJRZYOfUCH8uqQhqBkQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/util-endpoints": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.972.0.tgz", - "integrity": "sha512-6JHsl1V/a1ZW8D8AFfd4R52fwZPnZ5H4U6DS8m/bWT8qad72NvbOFAC7U2cDtFs2TShqUO3TEiX/EJibtY3ijg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.972.0", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-endpoints": "^3.2.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/util-endpoints/node_modules/@aws-sdk/types": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz", - "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.1.tgz", - "integrity": "sha512-IgF55NFmJX8d9Wql9M0nEpk2eYbuD8G4781FN4/fFgwTXBn86DvlZJuRWDCMcMqZymnBVX7HW9r+3r9ylqfW0w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/types": "^4.12.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.1.tgz", - "integrity": "sha512-oIs4JFcADzoZ0c915R83XvK2HltWupxNsXUIuZse2rgk7b97zTpkxaqXiH0h9ylh31qtgo/t8hp4tIqcsMrEbQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/client-sqs/node_modules/@aws-sdk/xml-builder": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.1.tgz", - "integrity": "sha512-6zZGlPOqn7Xb+25MAXGb1JhgvaC5HjZj6GzszuVrnEgbhvzBRFGKYemuHBV4bho+dtqeYKPgaZUv7/e80hIGNg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "fast-xml-parser": "5.2.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ssm/-/client-ssm-3.975.0.tgz", - "integrity": "sha512-yCWgQ+8vI9Q1XxcmeCDcwQTS1I39TM+3hJU6ubpEb5nfC29mosskv0+9+rIE+0PIRfBb6GZ9qLSzl0/suWg4Lg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/credential-provider-node": "^3.972.1", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.2", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.1", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.11", - "@smithy/middleware-retry": "^4.4.27", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.26", - "@smithy/util-defaults-mode-node": "^4.2.29", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "@smithy/util-waiter": "^4.2.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/client-sso": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.974.0.tgz", - "integrity": "sha512-ci+GiM0c4ULo4D79UMcY06LcOLcfvUfiyt8PzNY0vbt5O8BfCPYf4QomwVgkNcLLCYmroO4ge2Yy1EsLUlcD6g==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.10", - "@smithy/middleware-retry": "^4.4.26", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.11", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.25", - "@smithy/util-defaults-mode-node": "^4.2.28", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/core": { - "version": "3.973.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.1.tgz", - "integrity": "sha512-Ocubx42QsMyVs9ANSmFpRm0S+hubWljpPLjOi9UFrtcnVJjrVJTzQ51sN0e5g4e8i8QZ7uY73zosLmgYL7kZTQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/xml-builder": "^3.972.1", - "@smithy/core": "^3.21.1", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.1.tgz", - "integrity": "sha512-/etNHqnx96phy/SjI0HRC588o4vKH5F0xfkZ13yAATV7aNrb+5gYGNE6ePWafP+FuZ3HkULSSlJFj0AxgrAqYw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.2.tgz", - "integrity": "sha512-mXgdaUfe5oM+tWKyeZ7Vh/iQ94FrkMky1uuzwTOmFADiRcSk5uHy/e3boEFedXiT/PRGzgBmqvJVK4F6lUISCg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.1.tgz", - "integrity": "sha512-OdbJA3v+XlNDsrYzNPRUwr8l7gw1r/nR8l4r96MDzSBDU8WEo8T6C06SvwaXR8SpzsjO3sq5KMP86wXWg7Rj4g==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/credential-provider-env": "^3.972.1", - "@aws-sdk/credential-provider-http": "^3.972.1", - "@aws-sdk/credential-provider-login": "^3.972.1", - "@aws-sdk/credential-provider-process": "^3.972.1", - "@aws-sdk/credential-provider-sso": "^3.972.1", - "@aws-sdk/credential-provider-web-identity": "^3.972.1", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.1.tgz", - "integrity": "sha512-CccqDGL6ZrF3/EFWZefvKW7QwwRdxlHUO8NVBKNVcNq6womrPDvqB6xc9icACtE0XB0a7PLoSTkAg8bQVkTO2w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.1.tgz", - "integrity": "sha512-DwXPk9GfuU/xG9tmCyXFVkCr6X3W8ZCoL5Ptb0pbltEx1/LCcg7T+PBqDlPiiinNCD6ilIoMJDWsnJ8ikzZA7Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.1", - "@aws-sdk/credential-provider-http": "^3.972.1", - "@aws-sdk/credential-provider-ini": "^3.972.1", - "@aws-sdk/credential-provider-process": "^3.972.1", - "@aws-sdk/credential-provider-sso": "^3.972.1", - "@aws-sdk/credential-provider-web-identity": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.1.tgz", - "integrity": "sha512-bi47Zigu3692SJwdBvo8y1dEwE6B61stCwCFnuRWJVTfiM84B+VTSCV661CSWJmIZzmcy7J5J3kWyxL02iHj0w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.1.tgz", - "integrity": "sha512-dLZVNhM7wSgVUFsgVYgI5hb5Z/9PUkT46pk/SHrSmUqfx6YDvoV4YcPtaiRqviPpEGGiRtdQMEadyOKIRqulUQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.974.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/token-providers": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.1.tgz", - "integrity": "sha512-YMDeYgi0u687Ay0dAq/pFPKuijrlKTgsaB/UATbxCs/FzZfMiG4If5ksywHmmW7MiYUF8VVv+uou3TczvLrN4w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.1.tgz", - "integrity": "sha512-/R82lXLPmZ9JaUGSUdKtBp2k/5xQxvBT3zZWyKiBOhyulFotlfvdlrO8TnqstBimsl4lYEYySDL+W6ldFh6ALg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/middleware-logger": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.1.tgz", - "integrity": "sha512-JGgFl6cHg9G2FHu4lyFIzmFN8KESBiRr84gLC3Aeni0Gt1nKm+KxWLBuha/RPcXxJygGXCcMM4AykkIwxor8RA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.1.tgz", - "integrity": "sha512-taGzNRe8vPHjnliqXIHp9kBgIemLE/xCaRTMH1NH0cncHeaPcjxtnCroAAM9aOlPuKvBe2CpZESyvM1+D8oI7Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.2.tgz", - "integrity": "sha512-d+Exq074wy0X6wvShg/kmZVtkah+28vMuqCtuY3cydg8LUZOJBtbAolCpEJizSyb8mJJZF9BjWaTANXL4OYnkg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@smithy/core": "^3.21.1", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/nested-clients": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.974.0.tgz", - "integrity": "sha512-k3dwdo/vOiHMJc9gMnkPl1BA5aQfTrZbz+8fiDkWrPagqAioZgmo5oiaOaeX0grObfJQKDtcpPFR4iWf8cgl8Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.10", - "@smithy/middleware-retry": "^4.4.26", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.11", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.25", - "@smithy/util-defaults-mode-node": "^4.2.28", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.1.tgz", - "integrity": "sha512-voIY8RORpxLAEgEkYaTFnkaIuRwVBEc+RjVZYcSSllPV+ZEKAacai6kNhJeE3D70Le+JCfvRb52tng/AVHY+jQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/token-providers": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.974.0.tgz", - "integrity": "sha512-cBykL0LiccKIgNhGWvQRTPvsBLPZxnmJU3pYxG538jpFX8lQtrCy1L7mmIHNEdxIdIGEPgAEHF8/JQxgBToqUQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/types": { - "version": "3.973.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.0.tgz", - "integrity": "sha512-jYIdB7a7jhRTvyb378nsjyvJh1Si+zVduJ6urMNGpz8RjkmHZ+9vM2H07XaIB2Cfq0GhJRZYOfUCH8uqQhqBkQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/util-endpoints": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.972.0.tgz", - "integrity": "sha512-6JHsl1V/a1ZW8D8AFfd4R52fwZPnZ5H4U6DS8m/bWT8qad72NvbOFAC7U2cDtFs2TShqUO3TEiX/EJibtY3ijg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.972.0", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-endpoints": "^3.2.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/util-endpoints/node_modules/@aws-sdk/types": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz", - "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.1.tgz", - "integrity": "sha512-IgF55NFmJX8d9Wql9M0nEpk2eYbuD8G4781FN4/fFgwTXBn86DvlZJuRWDCMcMqZymnBVX7HW9r+3r9ylqfW0w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/types": "^4.12.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.1.tgz", - "integrity": "sha512-oIs4JFcADzoZ0c915R83XvK2HltWupxNsXUIuZse2rgk7b97zTpkxaqXiH0h9ylh31qtgo/t8hp4tIqcsMrEbQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/xml-builder": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.1.tgz", - "integrity": "sha512-6zZGlPOqn7Xb+25MAXGb1JhgvaC5HjZj6GzszuVrnEgbhvzBRFGKYemuHBV4bho+dtqeYKPgaZUv7/e80hIGNg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "fast-xml-parser": "5.2.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-sso": { - "version": "3.971.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.970.0", - "@aws-sdk/middleware-host-header": "3.969.0", - "@aws-sdk/middleware-logger": "3.969.0", - "@aws-sdk/middleware-recursion-detection": "3.969.0", - "@aws-sdk/middleware-user-agent": "3.970.0", - "@aws-sdk/region-config-resolver": "3.969.0", - "@aws-sdk/types": "3.969.0", - "@aws-sdk/util-endpoints": "3.970.0", - "@aws-sdk/util-user-agent-browser": "3.969.0", - "@aws-sdk/util-user-agent-node": "3.971.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.20.6", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.7", - "@smithy/middleware-retry": "^4.4.23", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.22", - "@smithy/util-defaults-mode-node": "^4.2.25", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/core": { - "version": "3.970.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.969.0", - "@aws-sdk/xml-builder": "3.969.0", - "@smithy/core": "^3.20.6", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.970.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.970.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.971.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/credential-provider-env": "3.970.0", - "@aws-sdk/credential-provider-http": "3.970.0", - "@aws-sdk/credential-provider-login": "3.971.0", - "@aws-sdk/credential-provider-process": "3.970.0", - "@aws-sdk/credential-provider-sso": "3.971.0", - "@aws-sdk/credential-provider-web-identity": "3.971.0", - "@aws-sdk/nested-clients": "3.971.0", - "@aws-sdk/types": "3.969.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.971.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/nested-clients": "3.971.0", - "@aws-sdk/types": "3.969.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.971.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.970.0", - "@aws-sdk/credential-provider-http": "3.970.0", - "@aws-sdk/credential-provider-ini": "3.971.0", - "@aws-sdk/credential-provider-process": "3.970.0", - "@aws-sdk/credential-provider-sso": "3.971.0", - "@aws-sdk/credential-provider-web-identity": "3.971.0", - "@aws-sdk/types": "3.969.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.970.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.971.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.971.0", - "@aws-sdk/core": "3.970.0", - "@aws-sdk/token-providers": "3.971.0", - "@aws-sdk/types": "3.969.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.971.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/nested-clients": "3.971.0", - "@aws-sdk/types": "3.969.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/endpoint-cache": { - "version": "3.971.0", - "license": "Apache-2.0", - "dependencies": { - "mnemonist": "0.38.3", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-endpoint-discovery": { - "version": "3.971.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/endpoint-cache": "3.971.0", - "@aws-sdk/types": "3.969.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.969.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.969.0", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.969.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.969.0", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.969.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.969.0", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-sdk-sqs": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sqs/-/middleware-sdk-sqs-3.972.2.tgz", - "integrity": "sha512-LPKsfqdoei7kBJo7JqGKbIM05W0bbcnJNfFtoOPgjXOJa7OpEs0pYj5OHiqbykgUFzkygD22f9sBmEfZkFoZ0g==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-sdk-sqs/node_modules/@aws-sdk/types": { - "version": "3.973.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.0.tgz", - "integrity": "sha512-jYIdB7a7jhRTvyb378nsjyvJh1Si+zVduJ6urMNGpz8RjkmHZ+9vM2H07XaIB2Cfq0GhJRZYOfUCH8uqQhqBkQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.970.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@aws-sdk/util-endpoints": "3.970.0", - "@smithy/core": "^3.20.6", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/nested-clients": { - "version": "3.971.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.970.0", - "@aws-sdk/middleware-host-header": "3.969.0", - "@aws-sdk/middleware-logger": "3.969.0", - "@aws-sdk/middleware-recursion-detection": "3.969.0", - "@aws-sdk/middleware-user-agent": "3.970.0", - "@aws-sdk/region-config-resolver": "3.969.0", - "@aws-sdk/types": "3.969.0", - "@aws-sdk/util-endpoints": "3.970.0", - "@aws-sdk/util-user-agent-browser": "3.969.0", - "@aws-sdk/util-user-agent-node": "3.971.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.20.6", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.7", - "@smithy/middleware-retry": "^4.4.23", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.22", - "@smithy/util-defaults-mode-node": "^4.2.25", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.969.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.969.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.971.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/nested-clients": "3.971.0", - "@aws-sdk/types": "3.969.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/types": { - "version": "3.969.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-dynamodb": { - "version": "3.971.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-dynamodb": "3.971.0" - } - }, - "node_modules/@aws-sdk/util-endpoints": { - "version": "3.970.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.969.0", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-endpoints": "^3.2.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-locate-window": { - "version": "3.893.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.969.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.969.0", - "@smithy/types": "^4.12.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.971.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/xml-builder": { - "version": "3.969.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "fast-xml-parser": "5.2.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws/lambda-invoke-store": { - "version": "0.2.3", - "license": "Apache-2.0", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.28.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.28.4", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.4", - "@babel/types": "^7.28.4", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.28.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.3", - "@babel/types": "^7.28.2", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.28.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.28.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.4" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.28.4", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.28.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.28.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@emnapi/core": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", - "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", - "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emotion/is-prop-valid": { - "version": "1.2.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@emotion/memoize": "^0.8.1" - } - }, - "node_modules/@emotion/memoize": { - "version": "0.8.1", - "dev": true, - "license": "MIT" - }, - "node_modules/@emotion/unitless": { - "version": "0.8.1", - "dev": true, - "license": "MIT" - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.2", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.21.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "5.3.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/js": { - "version": "9.39.2", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@exodus/schemasafe": { - "version": "1.3.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@faker-js/faker": { - "version": "10.2.0", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/fakerjs" - } - ], - "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0", - "npm": ">=10" - } - }, - "node_modules/@fluent/syntax": { - "version": "0.18.1", - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/momoa": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@humanwhocodes/momoa/-/momoa-2.0.4.tgz", - "integrity": "sha512-RE815I4arJFtt+FVeU1Tgp9/Xvecacji8w/V6XtXsWWH/wz/eNkNbhb+ny/+PlVZjV0rxQpRSQKNKE3lcktHEA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "30.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/core": { - "version": "30.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "30.2.0", - "@jest/pattern": "30.0.1", - "@jest/reporters": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-changed-files": "30.2.0", - "jest-config": "30.2.0", - "jest-haste-map": "30.2.0", - "jest-message-util": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-resolve-dependencies": "30.2.0", - "jest-runner": "30.2.0", - "jest-runtime": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "jest-watcher": "30.2.0", - "micromatch": "^4.0.8", - "pretty-format": "30.2.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/diff-sequences": { - "version": "30.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/environment": { - "version": "30.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "jest-mock": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "30.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "30.2.0", - "jest-snapshot": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "30.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "30.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "@sinonjs/fake-timers": "^13.0.0", - "@types/node": "*", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-util": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/get-type": { - "version": "30.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "30.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "30.2.0", - "@jest/expect": "30.2.0", - "@jest/types": "30.2.0", - "jest-mock": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/pattern": { - "version": "30.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-regex-util": "30.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "30.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@jridgewell/trace-mapping": "^0.3.25", - "@types/node": "*", - "chalk": "^4.1.2", - "collect-v8-coverage": "^1.0.2", - "exit-x": "^0.2.2", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^5.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "jest-worker": "30.2.0", - "slash": "^3.0.0", - "string-length": "^4.0.2", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/schemas": { - "version": "30.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/snapshot-utils": { - "version": "30.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "natural-compare": "^1.4.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "30.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "callsites": "^3.1.0", - "graceful-fs": "^4.2.11" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "30.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "30.2.0", - "@jest/types": "30.2.0", - "@types/istanbul-lib-coverage": "^2.0.6", - "collect-v8-coverage": "^1.0.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "30.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "30.2.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "30.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.27.4", - "@jest/types": "30.2.0", - "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.1", - "chalk": "^4.1.2", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-util": "30.2.0", - "micromatch": "^4.0.8", - "pirates": "^4.0.7", - "slash": "^3.0.0", - "write-file-atomic": "^5.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/types": { - "version": "30.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.5", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@middy/core": { - "version": "7.0.2", - "license": "MIT", - "engines": { - "node": ">=22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/willfarrell" - }, - "peerDependencies": { - "@aws/durable-execution-sdk-js": "^1.0.0" - }, - "peerDependenciesMeta": { - "@aws/durable-execution-sdk-js": { - "optional": true - } - } - }, - "node_modules/@middy/http-header-normalizer": { - "version": "7.0.2", - "license": "MIT", - "engines": { - "node": ">=22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/willfarrell" - } - }, - "node_modules/@middy/input-output-logger": { - "version": "7.0.2", - "license": "MIT", - "dependencies": { - "@middy/util": "7.0.2" - }, - "engines": { - "node": ">=22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/willfarrell" - } - }, - "node_modules/@middy/input-output-logger/node_modules/@middy/util": { - "version": "7.0.2", - "license": "MIT", - "engines": { - "node": ">=22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/willfarrell" - } - }, - "node_modules/@middy/util": { - "version": "6.4.1", - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/willfarrell" - } - }, - "node_modules/@middy/validator": { - "version": "6.4.1", - "license": "MIT", - "dependencies": { - "@middy/util": "6.4.1", - "ajv": "8.17.1", - "ajv-errors": "3.0.0", - "ajv-formats": "3.0.1", - "ajv-formats-draft2019": "1.6.1", - "ajv-ftl-i18n": "0.1.1", - "ajv-keywords": "5.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/willfarrell" - } - }, - "node_modules/@middy/validator/node_modules/ajv": { - "version": "8.17.1", - "license": "MIT", - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@middy/validator/node_modules/ajv-errors": { - "version": "3.0.0", - "license": "MIT", - "peerDependencies": { - "ajv": "^8.0.1" - } - }, - "node_modules/@middy/validator/node_modules/ajv-keywords": { - "version": "5.1.0", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/@middy/validator/node_modules/json-schema-traverse": { - "version": "1.0.0", - "license": "MIT" - }, - "node_modules/@mswjs/interceptors": { - "version": "0.39.7", - "license": "MIT", - "dependencies": { - "@open-draft/deferred-promise": "^2.2.0", - "@open-draft/logger": "^0.3.0", - "@open-draft/until": "^2.0.0", - "is-node-process": "^1.2.0", - "outvariant": "^1.4.3", - "strict-event-emitter": "^0.5.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" - } - }, - "node_modules/@nhs/fhir-middy-error-handler": { - "version": "2.1.70", - "resolved": "https://registry.npmjs.org/@nhs/fhir-middy-error-handler/-/fhir-middy-error-handler-2.1.70.tgz", - "integrity": "sha512-eK0nuMsEwOBu2UxXj4jOcUDZt1LT+Sgona/E3hObwQWQQa2TFqJiEVj5prGTue8atLom8hQRtk61uZ7s1OucJA==", - "license": "MIT", - "dependencies": { - "@aws-lambda-powertools/logger": "^2.30.2", - "@middy/core": "^7.0.2" - } - }, - "node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@open-draft/deferred-promise": { - "version": "2.2.0", - "license": "MIT" - }, - "node_modules/@open-draft/logger": { - "version": "0.3.0", - "license": "MIT", - "dependencies": { - "is-node-process": "^1.2.0", - "outvariant": "^1.4.0" - } - }, - "node_modules/@open-draft/until": { - "version": "2.1.0", - "license": "MIT" - }, - "node_modules/@opentelemetry/api": { - "version": "1.9.0", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@opentelemetry/api-logs": { - "version": "0.202.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api": "^1.3.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@opentelemetry/context-async-hooks": { - "version": "2.0.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/core": { - "version": "2.0.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-http": { - "version": "0.202.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/otlp-exporter-base": "0.202.0", - "@opentelemetry/otlp-transformer": "0.202.0", - "@opentelemetry/resources": "2.0.1", - "@opentelemetry/sdk-trace-base": "2.0.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/otlp-exporter-base": { - "version": "0.202.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/otlp-transformer": "0.202.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/otlp-transformer": { - "version": "0.202.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.202.0", - "@opentelemetry/core": "2.0.1", - "@opentelemetry/resources": "2.0.1", - "@opentelemetry/sdk-logs": "0.202.0", - "@opentelemetry/sdk-metrics": "2.0.1", - "@opentelemetry/sdk-trace-base": "2.0.1", - "protobufjs": "^7.3.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/resources": { - "version": "2.0.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-logs": { - "version": "0.202.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.202.0", - "@opentelemetry/core": "2.0.1", - "@opentelemetry/resources": "2.0.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.4.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-metrics": { - "version": "2.0.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/resources": "2.0.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.9.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-trace-base": { - "version": "2.0.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.0.1", - "@opentelemetry/resources": "2.0.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-trace-node": { - "version": "2.0.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/context-async-hooks": "2.0.1", - "@opentelemetry/core": "2.0.1", - "@opentelemetry/sdk-trace-base": "2.0.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/semantic-conventions": { - "version": "1.34.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@pkgr/core": { - "version": "0.2.9", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" - } - }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@psu-common/commonTypes": { - "resolved": "packages/common/commonTypes", - "link": true - }, - "node_modules/@psu-common/middyErrorHandler": { - "resolved": "packages/common/middyErrorHandler", - "link": true - }, - "node_modules/@psu-common/testing": { - "resolved": "packages/common/testing", - "link": true - }, - "node_modules/@psu-common/utilities": { - "resolved": "packages/common/utilities", - "link": true - }, - "node_modules/@redocly/ajv": { - "version": "8.17.1", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@redocly/ajv/node_modules/json-schema-traverse": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@redocly/cli": { - "version": "2.14.9", - "resolved": "https://registry.npmjs.org/@redocly/cli/-/cli-2.14.9.tgz", - "integrity": "sha512-eueSFzydep5jwOHxlYW6rFOSRP1b5vY7gVOzQBd2/I6XQJnG31hgrDACqx/heD4vv3hhdpoumopz24erxLe0zQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@opentelemetry/exporter-trace-otlp-http": "0.202.0", - "@opentelemetry/resources": "2.0.1", - "@opentelemetry/sdk-trace-node": "2.0.1", - "@opentelemetry/semantic-conventions": "1.34.0", - "@redocly/openapi-core": "2.14.9", - "@redocly/respect-core": "2.14.9", - "abort-controller": "^3.0.0", - "ajv": "npm:@redocly/ajv@8.17.1", - "ajv-formats": "^3.0.1", - "chokidar": "^3.5.1", - "colorette": "^1.2.0", - "cookie": "^0.7.2", - "dotenv": "16.4.7", - "glob": "^11.0.1", - "handlebars": "^4.7.6", - "https-proxy-agent": "^7.0.5", - "mobx": "^6.0.4", - "pluralize": "^8.0.0", - "react": "^17.0.0 || ^18.2.0 || ^19.2.1", - "react-dom": "^17.0.0 || ^18.2.0 || ^19.2.1", - "redoc": "2.5.1", - "semver": "^7.5.2", - "set-cookie-parser": "^2.3.5", - "simple-websocket": "^9.0.0", - "styled-components": "^6.0.7", - "ulid": "^3.0.1", - "undici": "^6.23.0", - "yargs": "17.0.1" - }, - "bin": { - "openapi": "bin/cli.js", - "redocly": "bin/cli.js" - }, - "engines": { - "node": ">=22.12.0 || >=20.19.0 <21.0.0", - "npm": ">=10" - } - }, - "node_modules/@redocly/cli/node_modules/ajv": { - "name": "@redocly/ajv", - "version": "8.17.1", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@redocly/cli/node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@redocly/cli/node_modules/cliui": { - "version": "7.0.4", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/@redocly/cli/node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@redocly/cli/node_modules/glob": { - "version": "11.1.0", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "foreground-child": "^3.3.1", - "jackspeak": "^4.1.1", - "minimatch": "^10.1.1", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@redocly/cli/node_modules/jackspeak": { - "version": "4.1.1", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@redocly/cli/node_modules/json-schema-traverse": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@redocly/cli/node_modules/lru-cache": { - "version": "11.2.2", - "dev": true, - "license": "ISC", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@redocly/cli/node_modules/minimatch": { - "version": "10.1.1", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@redocly/cli/node_modules/path-scurry": { - "version": "2.0.0", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@redocly/cli/node_modules/string-width": { - "version": "4.2.3", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@redocly/cli/node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@redocly/cli/node_modules/wrap-ansi": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@redocly/cli/node_modules/yargs": { - "version": "17.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@redocly/cli/node_modules/yargs-parser": { - "version": "20.2.9", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/@redocly/config": { - "version": "0.41.2", - "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.41.2.tgz", - "integrity": "sha512-G6muhdTKcEV2TECBFzuT905p4a27OgUtwBqRVnMx1JebO6i8zlm6bPB2H3fD1Hl+MiUpk7Jx2kwGmLVgpz5nIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-schema-to-ts": "2.7.2" - } - }, - "node_modules/@redocly/config/node_modules/json-schema-to-ts": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-2.7.2.tgz", - "integrity": "sha512-R1JfqKqbBR4qE8UyBR56Ms30LL62/nlhoz+1UkfI/VE7p54Awu919FZ6ZUPG8zIa3XB65usPJgr1ONVncUGSaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.18.3", - "@types/json-schema": "^7.0.9", - "ts-algebra": "^1.2.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@redocly/config/node_modules/ts-algebra": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-1.2.2.tgz", - "integrity": "sha512-kloPhf1hq3JbCPOTYoOWDKxebWjNb2o/LKnNfkWhxVVisFFmMJPPdJeGoGmM+iRLyoXAR61e08Pb+vUXINg8aA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@redocly/openapi-core": { - "version": "2.14.9", - "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-2.14.9.tgz", - "integrity": "sha512-PIWVxm7Os3U276XMhLIh+qftaA6TxVwOuoyDaobGSkd609fp25tp57G2k/uzzKjrhUD4g2QzeJ4lfCoaBgEjhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@redocly/ajv": "^8.17.2", - "@redocly/config": "^0.41.2", - "ajv": "npm:@redocly/ajv@8.17.2", - "ajv-formats": "^3.0.1", - "colorette": "^1.2.0", - "js-levenshtein": "^1.1.6", - "js-yaml": "^4.1.0", - "picomatch": "^4.0.3", - "pluralize": "^8.0.0", - "yaml-ast-parser": "0.0.43" - }, - "engines": { - "node": ">=22.12.0 || >=20.19.0 <21.0.0", - "npm": ">=10" - } - }, - "node_modules/@redocly/openapi-core/node_modules/@redocly/ajv": { - "version": "8.17.2", - "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.17.2.tgz", - "integrity": "sha512-rcbDZOfXAgGEJeJ30aWCVVJvxV9ooevb/m1/SFblO2qHs4cqTk178gx7T/vdslf57EA4lTofrwsq5K8rxK9g+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@redocly/openapi-core/node_modules/ajv": { - "name": "@redocly/ajv", - "version": "8.17.2", - "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.17.2.tgz", - "integrity": "sha512-rcbDZOfXAgGEJeJ30aWCVVJvxV9ooevb/m1/SFblO2qHs4cqTk178gx7T/vdslf57EA4lTofrwsq5K8rxK9g+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@redocly/openapi-core/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/@redocly/openapi-core/node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@redocly/openapi-core/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/@redocly/openapi-core/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@redocly/respect-core": { - "version": "2.14.9", - "resolved": "https://registry.npmjs.org/@redocly/respect-core/-/respect-core-2.14.9.tgz", - "integrity": "sha512-kP3rylB04NZV52IoEa1t8ObkSxpRZy3zpkRAxYOiLtggRETOnLdcDbYep4ipbk6t0suZamznOLOgiAeBYHo/ig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@faker-js/faker": "^7.6.0", - "@noble/hashes": "^1.8.0", - "@redocly/ajv": "8.17.1", - "@redocly/openapi-core": "2.14.9", - "ajv": "npm:@redocly/ajv@8.17.1", - "better-ajv-errors": "^1.2.0", - "colorette": "^2.0.20", - "json-pointer": "^0.6.2", - "jsonpath-rfc9535": "1.3.0", - "openapi-sampler": "^1.6.1", - "outdent": "^0.8.0" - }, - "engines": { - "node": ">=22.12.0 || >=20.19.0 <21.0.0", - "npm": ">=10" - } - }, - "node_modules/@redocly/respect-core/node_modules/@faker-js/faker": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-7.6.0.tgz", - "integrity": "sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0", - "npm": ">=6.0.0" - } - }, - "node_modules/@redocly/respect-core/node_modules/ajv": { - "name": "@redocly/ajv", - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-EDtsGZS964mf9zAUXAl9Ew16eYbeyAFWhsPr0fX6oaJxgd8rApYlPBf0joyhnUHz88WxrigyFtTaqqzXNzPgqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@redocly/respect-core/node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@redocly/respect-core/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sinclair/typebox": { - "version": "0.34.41", - "dev": true, - "license": "MIT" - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "13.0.5", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1" - } - }, - "node_modules/@sinonjs/samsam": { - "version": "8.0.3", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "type-detect": "^4.1.0" - } - }, - "node_modules/@sinonjs/samsam/node_modules/type-detect": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@sinonjs/text-encoding": { - "version": "0.7.3", - "dev": true, - "license": "(Unlicense OR Apache-2.0)" - }, - "node_modules/@smithy/abort-controller": { - "version": "4.2.8", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/config-resolver": { - "version": "4.4.6", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/core": { - "version": "3.21.1", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.21.1.tgz", - "integrity": "sha512-NUH8R4O6FkN8HKMojzbGg/5pNjsfTjlMmeFclyPfPaXXUrbr5TzhWgbf7t92wfrpCHRgpjyz7ffASIS3wX28aA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/middleware-serde": "^4.2.9", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-stream": "^4.5.10", - "@smithy/util-utf8": "^4.2.0", - "@smithy/uuid": "^1.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.8", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.9", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.3.8", - "@smithy/querystring-builder": "^4.2.8", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/hash-node": { - "version": "4.2.8", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/invalid-dependency": { - "version": "4.2.8", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/is-array-buffer": { - "version": "4.2.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/md5-js": { - "version": "4.2.8", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-content-length": { - "version": "4.2.8", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-endpoint": { - "version": "4.4.11", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.11.tgz", - "integrity": "sha512-/WqsrycweGGfb9sSzME4CrsuayjJF6BueBmkKlcbeU5q18OhxRrvvKlmfw3tpDsK5ilx2XUJvoukwxHB0nHs/Q==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.21.1", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-middleware": "^4.2.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-retry": { - "version": "4.4.27", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.27.tgz", - "integrity": "sha512-xFUYCGRVsfgiN5EjsJJSzih9+yjStgMTCLANPlf0LVQkPDYCe0hz97qbdTZosFOiYlGBlHYityGRxrQ/hxhfVQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/service-error-classification": "^4.2.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/uuid": "^1.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-serde": { - "version": "4.2.9", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-stack": { - "version": "4.2.8", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/node-config-provider": { - "version": "4.3.8", - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/node-http-handler": { - "version": "4.4.8", - "license": "Apache-2.0", - "dependencies": { - "@smithy/abort-controller": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/querystring-builder": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/property-provider": { - "version": "4.2.8", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/protocol-http": { - "version": "5.3.8", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/querystring-builder": { - "version": "4.2.8", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "@smithy/util-uri-escape": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/querystring-parser": { - "version": "4.2.8", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/service-error-classification": { - "version": "4.2.8", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.3", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/signature-v4": { - "version": "5.3.8", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-uri-escape": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/smithy-client": { - "version": "4.10.12", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.12.tgz", - "integrity": "sha512-VKO/HKoQ5OrSHW6AJUmEnUKeXI1/5LfCwO9cwyao7CmLvGnZeM1i36Lyful3LK1XU7HwTVieTqO1y2C/6t3qtA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.21.1", - "@smithy/middleware-endpoint": "^4.4.11", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/types": { - "version": "4.12.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/url-parser": { - "version": "4.2.8", - "license": "Apache-2.0", - "dependencies": { - "@smithy/querystring-parser": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-base64": { - "version": "4.3.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-body-length-browser": { - "version": "4.2.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-body-length-node": { - "version": "4.2.1", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-buffer-from": { - "version": "4.2.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-config-provider": { - "version": "4.2.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.26", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.26.tgz", - "integrity": "sha512-vva0dzYUTgn7DdE0uaha10uEdAgmdLnNFowKFjpMm6p2R0XDk5FHPX3CBJLzWQkQXuEprsb0hGz9YwbicNWhjw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^4.2.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.29", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.29.tgz", - "integrity": "sha512-c6D7IUBsZt/aNnTBHMTf+OVh+h/JcxUUgfTcIJaWRe6zhOum1X+pNKSZtZ+7fbOn5I99XVFtmrnXKv8yHHErTQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/config-resolver": "^4.4.6", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-endpoints": { - "version": "3.2.8", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-hex-encoding": { - "version": "4.2.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-middleware": { - "version": "4.2.8", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-retry": { - "version": "4.2.8", - "license": "Apache-2.0", - "dependencies": { - "@smithy/service-error-classification": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-stream": { - "version": "4.5.10", - "license": "Apache-2.0", - "dependencies": { - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-uri-escape": { - "version": "4.2.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-utf8": { - "version": "4.2.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-waiter": { - "version": "4.2.8", - "license": "Apache-2.0", - "dependencies": { - "@smithy/abort-controller": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/uuid": { - "version": "1.1.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "dev": true, - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/aws-lambda": { - "version": "8.10.160", - "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.160.tgz", - "integrity": "sha512-uoO4QVQNWFPJMh26pXtmtrRfGshPUSpMZGUyUQY20FhfHEElEBOPKgVmFs1z+kbpyBsRs2JnoOPT7++Z4GA9pA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/fhir": { - "version": "0.0.41", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "30.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^30.0.0", - "pretty-format": "^30.0.0" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "25.0.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz", - "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/sinon": { - "version": "17.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/sinonjs__fake-timers": "*" - } - }, - "node_modules/@types/sinonjs__fake-timers": { - "version": "8.1.5", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/stylis": { - "version": "4.2.5", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/@types/yargs": { - "version": "17.0.33", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz", - "integrity": "sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.53.1", - "@typescript-eslint/type-utils": "8.53.1", - "@typescript-eslint/utils": "8.53.1", - "@typescript-eslint/visitor-keys": "8.53.1", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.53.1", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.1.tgz", - "integrity": "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@typescript-eslint/scope-manager": "8.53.1", - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/typescript-estree": "8.53.1", - "@typescript-eslint/visitor-keys": "8.53.1", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.1.tgz", - "integrity": "sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog==", - "dev": true, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.53.1", - "@typescript-eslint/types": "^8.53.1", - "debug": "^4.4.3" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "node": ">=18" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz", - "integrity": "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==", - "dev": true, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/visitor-keys": "8.53.1" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=18" } }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.1.tgz", - "integrity": "sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA==", - "dev": true, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "node": ">=18" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.53.1.tgz", - "integrity": "sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w==", - "dev": true, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/typescript-estree": "8.53.1", - "@typescript-eslint/utils": "8.53.1", - "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@typescript-eslint/types": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz", - "integrity": "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==", - "dev": true, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=18" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.1.tgz", - "integrity": "sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==", - "dev": true, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.53.1", - "@typescript-eslint/tsconfig-utils": "8.53.1", - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/visitor-keys": "8.53.1", - "debug": "^4.4.3", - "minimatch": "^9.0.5", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "node": ">=18" } }, - "node_modules/@typescript-eslint/utils": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.1.tgz", - "integrity": "sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==", - "dev": true, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.53.1", - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/typescript-estree": "8.53.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "node": ">=18" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.1.tgz", - "integrity": "sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==", - "dev": true, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.53.1", - "eslint-visitor-keys": "^4.2.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=18" } }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=18" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "dev": true, - "license": "ISC" + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", - "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", "cpu": [ - "arm" + "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "android" - ] + "linux" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", - "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", "cpu": [ - "arm64" + "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "android" - ] + "linux" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", - "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", "cpu": [ - "arm64" + "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "darwin" - ] + "linux" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", - "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "darwin" - ] + "linux" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", - "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", "cpu": [ - "x64" + "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "freebsd" - ] + "netbsd" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", - "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", "cpu": [ - "arm" + "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" - ] + "netbsd" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", - "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", "cpu": [ - "arm" + "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" - ] + "openbsd" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", - "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", "cpu": [ - "arm64" + "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" - ] + "openbsd" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", - "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" - ] + "openharmony" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", - "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", "cpu": [ - "ppc64" + "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" - ] + "sunos" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", - "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", "cpu": [ - "riscv64" + "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" - ] + "win32" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", - "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", "cpu": [ - "riscv64" + "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" - ] + "win32" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", - "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", "cpu": [ - "s390x" + "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.11.1", - "cpu": [ - "x64" + "win32" ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": ">= 4" + } }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.11.1", - "cpu": [ - "x64" - ], + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.1", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", - "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", - "cpu": [ - "wasm32" - ], + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", "dev": true, - "license": "MIT", - "optional": true, + "license": "ISC", "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.11" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=14.0.0" + "node": "*" } }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", - "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", - "cpu": [ - "arm64" - ], + "node_modules/@eslint/js": { + "version": "9.39.2", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", - "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", - "cpu": [ - "ia32" - ], + "node_modules/@eslint/object-schema": { + "version": "2.1.7", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", - "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", - "cpu": [ - "x64" - ], + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } }, - "node_modules/abbrev": { - "version": "1.1.1", + "node_modules/@exodus/schemasafe": { + "version": "1.3.0", "dev": true, - "license": "ISC" + "license": "MIT" }, - "node_modules/abort-controller": { - "version": "3.0.0", + "node_modules/@faker-js/faker": { + "version": "10.2.0", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0", + "npm": ">=10" + } + }, + "node_modules/@fluent/syntax": { + "version": "0.18.1", + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "event-target-shim": "^5.0.0" + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { - "node": ">=6.5" + "node": ">=18.18.0" } }, - "node_modules/acorn": { - "version": "8.15.0", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "acorn": "bin/acorn" - }, + "license": "Apache-2.0", "engines": { - "node": ">=0.4.0" + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", + "node_modules/@humanwhocodes/momoa": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@humanwhocodes/momoa/-/momoa-2.0.4.tgz", + "integrity": "sha512-RE815I4arJFtt+FVeU1Tgp9/Xvecacji8w/V6XtXsWWH/wz/eNkNbhb+ny/+PlVZjV0rxQpRSQKNKE3lcktHEA==", "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "license": "Apache-2.0", + "engines": { + "node": ">=10.10.0" } }, - "node_modules/acorn-walk": { - "version": "8.3.4", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, + "license": "Apache-2.0", "engines": { - "node": ">=0.4.0" + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/agent-base": { - "version": "7.1.4", + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", "dev": true, "license": "MIT", "engines": { - "node": ">= 14" + "node": "20 || >=22" } }, - "node_modules/ajv": { - "version": "6.12.6", + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "@isaacs/balanced-match": "^4.0.1" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": "20 || >=22" } }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "license": "MIT", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "dev": true, + "license": "ISC", "dependencies": { - "ajv": "^8.0.0" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, - "peerDependencies": { - "ajv": "^8.0.0" + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "engines": { + "node": ">=8" } }, - "node_modules/ajv-formats-draft2019": { - "version": "1.6.1", + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "dev": true, "license": "MIT", "dependencies": { - "punycode": "^2.1.1", - "schemes": "^1.4.0", - "smtp-address-parser": "^1.0.3", - "uri-js": "^4.4.1" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, - "peerDependencies": { - "ajv": "*" + "engines": { + "node": ">=8" } }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.17.1", + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "p-locate": "^4.1.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">=8" } }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "license": "MIT" - }, - "node_modules/ajv-ftl-i18n": { - "version": "0.1.1", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "dev": true, "license": "MIT", "dependencies": { - "commander": "10.0.0", - "fluent-transpiler": "0.2.1" + "p-try": "^2.0.0" }, - "bin": { - "ajv-ftl": "cli.js" + "engines": { + "node": ">=6" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/willfarrell" - } - }, - "node_modules/ajv-ftl-i18n/node_modules/commander": { - "version": "10.0.0", - "license": "MIT", - "engines": { - "node": ">=14" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-escapes": { - "version": "4.3.2", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", "dev": true, "license": "MIT", "dependencies": { - "type-fest": "^0.21.3" + "p-limit": "^2.2.0" }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-regex": { - "version": "6.2.2", + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", "dev": true, "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "node": ">=8" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", "dev": true, "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/anymatch": { - "version": "3.1.3", + "node_modules/@jest/console": { + "version": "30.2.0", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "slash": "^3.0.0" }, "engines": { - "node": ">= 8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/apim-spec": { - "resolved": "packages/specification", - "link": true - }, - "node_modules/arg": { - "version": "4.1.3", + "node_modules/@jest/core": { + "version": "30.2.0", "dev": true, - "license": "MIT" - }, - "node_modules/argparse": { - "version": "1.0.10", "license": "MIT", "dependencies": { - "sprintf-js": "~1.0.2" + "@jest/console": "30.2.0", + "@jest/pattern": "30.0.1", + "@jest/reporters": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.2.0", + "jest-config": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-resolve-dependencies": "30.2.0", + "jest-runner": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "jest-watcher": "30.2.0", + "micromatch": "^4.0.8", + "pretty-format": "30.2.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/array-find-index": { - "version": "1.0.2", + "node_modules/@jest/diff-sequences": { + "version": "30.0.1", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/asap": { - "version": "2.0.6", + "node_modules/@jest/environment": { + "version": "30.2.0", "dev": true, - "license": "MIT" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "license": "MIT" - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", "license": "MIT", "dependencies": { - "possible-typed-array-names": "^1.0.0" + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-mock": "30.2.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/aws-lambda": { - "version": "1.0.7", + "node_modules/@jest/expect": { + "version": "30.2.0", + "dev": true, "license": "MIT", "dependencies": { - "aws-sdk": "^2.814.0", - "commander": "^3.0.2", - "js-yaml": "^3.14.1", - "watchpack": "^2.0.0-beta.10" + "expect": "30.2.0", + "jest-snapshot": "30.2.0" }, - "bin": { - "lambda": "bin/lambda" + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/aws-sdk": { - "version": "2.1692.0", - "hasInstallScript": true, - "license": "Apache-2.0", + "node_modules/@jest/expect-utils": { + "version": "30.2.0", + "dev": true, + "license": "MIT", "dependencies": { - "buffer": "4.9.2", - "events": "1.1.1", - "ieee754": "1.1.13", - "jmespath": "0.16.0", - "querystring": "0.2.0", - "sax": "1.2.1", - "url": "0.10.3", - "util": "^0.12.4", - "uuid": "8.0.0", - "xml2js": "0.6.2" + "@jest/get-type": "30.1.0" }, "engines": { - "node": ">= 10.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/aws-sdk-client-mock": { - "version": "4.1.0", + "node_modules/@jest/fake-timers": { + "version": "30.2.0", "dev": true, "license": "MIT", "dependencies": { - "@types/sinon": "^17.0.3", - "sinon": "^18.0.1", - "tslib": "^2.1.0" + "@jest/types": "30.2.0", + "@sinonjs/fake-timers": "^13.0.0", + "@types/node": "*", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/axios": { - "version": "1.13.2", + "node_modules/@jest/get-type": { + "version": "30.1.0", + "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/axios-mock-adapter": { - "version": "2.1.0", + "node_modules/@jest/globals": { + "version": "30.2.0", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "is-buffer": "^2.0.5" + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/types": "30.2.0", + "jest-mock": "30.2.0" }, - "peerDependencies": { - "axios": ">= 0.17.0" + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/axios-retry": { - "version": "4.5.0", - "license": "Apache-2.0", + "node_modules/@jest/pattern": { + "version": "30.0.1", + "dev": true, + "license": "MIT", "dependencies": { - "is-retry-allowed": "^2.2.0" + "@types/node": "*", + "jest-regex-util": "30.0.1" }, - "peerDependencies": { - "axios": "0.x || 1.x" + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/babel-jest": { + "node_modules/@jest/reporters": { "version": "30.2.0", "dev": true, "license": "MIT", "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "30.2.0", + "@jest/test-result": "30.2.0", "@jest/transform": "30.2.0", - "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^7.0.1", - "babel-preset-jest": "30.2.0", + "@jest/types": "30.2.0", + "@jridgewell/trace-mapping": "^0.3.25", + "@types/node": "*", "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^10.3.10", "graceful-fs": "^4.2.11", - "slash": "^3.0.0" + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^5.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", + "slash": "^3.0.0", + "string-length": "^4.0.2", + "v8-to-istanbul": "^9.0.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-0" + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/babel-plugin-istanbul": { - "version": "7.0.1", + "node_modules/@jest/schemas": { + "version": "30.0.5", "dev": true, - "license": "BSD-3-Clause", - "workspaces": [ - "test/babel-8" - ], + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-instrument": "^6.0.2", - "test-exclude": "^6.0.0" + "@sinclair/typebox": "^0.34.0" }, "engines": { - "node": ">=12" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/babel-plugin-jest-hoist": { + "node_modules/@jest/snapshot-utils": { "version": "30.2.0", "dev": true, "license": "MIT", "dependencies": { - "@types/babel__core": "^7.20.5" + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.2.0", + "node_modules/@jest/source-map": { + "version": "30.0.1", "dev": true, "license": "MIT", "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" }, - "peerDependencies": { - "@babel/core": "^7.0.0 || ^8.0.0-0" + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/babel-preset-jest": { + "node_modules/@jest/test-result": { "version": "30.2.0", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "30.2.0", - "babel-preset-current-node-syntax": "^1.2.0" + "@jest/console": "30.2.0", + "@jest/types": "30.2.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-beta.1" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.8.9", + "node_modules/@jest/test-sequencer": { + "version": "30.2.0", "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.2.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/better-ajv-errors": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/better-ajv-errors/-/better-ajv-errors-1.2.0.tgz", - "integrity": "sha512-UW+IsFycygIo7bclP9h5ugkNH8EjCSgqyFB/yQ4Hqqa1OEYDtb0uFIkYE0b6+CjkgJYVM5UKI/pJPxjYe9EZlA==", + "node_modules/@jest/transform": { + "version": "30.2.0", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.16.0", - "@humanwhocodes/momoa": "^2.0.2", + "@babel/core": "^7.27.4", + "@jest/types": "30.2.0", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.1", "chalk": "^4.1.2", - "jsonpointer": "^5.0.0", - "leven": "^3.1.0 < 4" + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.2.0", + "micromatch": "^4.0.8", + "pirates": "^4.0.7", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.1" }, "engines": { - "node": ">= 12.13.0" - }, - "peerDependencies": { - "ajv": "4.11.8 - 8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", + "node_modules/@jest/types": { + "version": "30.2.0", "dev": true, "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/bowser": { - "version": "2.12.1", - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "2.0.2", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/braces": { - "version": "3.0.3", + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", "dev": true, "license": "MIT", "dependencies": { - "fill-range": "^7.1.1" - }, + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=6.0.0" } }, - "node_modules/browserslist": { - "version": "4.26.2", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", - "peer": true, "dependencies": { - "baseline-browser-mapping": "^2.8.3", - "caniuse-lite": "^1.0.30001741", - "electron-to-chromium": "^1.5.218", - "node-releases": "^2.0.21", - "update-browserslist-db": "^1.1.3" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@middy/core": { + "version": "7.0.2", + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/willfarrell" }, - "bin": { - "browserslist": "cli.js" + "peerDependencies": { + "@aws/durable-execution-sdk-js": "^1.0.0" }, + "peerDependenciesMeta": { + "@aws/durable-execution-sdk-js": { + "optional": true + } + } + }, + "node_modules/@middy/http-header-normalizer": { + "version": "7.0.2", + "license": "MIT", "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": ">=22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/willfarrell" } }, - "node_modules/bs-logger": { - "version": "0.2.6", - "dev": true, + "node_modules/@middy/input-output-logger": { + "version": "7.0.2", "license": "MIT", "dependencies": { - "fast-json-stable-stringify": "2.x" + "@middy/util": "7.0.2" }, "engines": { - "node": ">= 6" + "node": ">=22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/willfarrell" } }, - "node_modules/bser": { - "version": "2.1.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "node-int64": "^0.4.0" + "node_modules/@middy/input-output-logger/node_modules/@middy/util": { + "version": "7.0.2", + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/willfarrell" } }, - "node_modules/buffer": { - "version": "4.9.2", + "node_modules/@middy/util": { + "version": "6.4.1", "license": "MIT", - "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" + "engines": { + "node": ">=20" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/willfarrell" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/call-bind": { - "version": "1.0.8", + "node_modules/@middy/validator": { + "version": "6.4.1", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" + "@middy/util": "6.4.1", + "ajv": "8.17.1", + "ajv-errors": "3.0.0", + "ajv-formats": "3.0.1", + "ajv-formats-draft2019": "1.6.1", + "ajv-ftl-i18n": "0.1.1", + "ajv-keywords": "5.1.0" }, "engines": { - "node": ">= 0.4" + "node": ">=20" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "github", + "url": "https://github.com/sponsors/willfarrell" } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", + "node_modules/@middy/validator/node_modules/ajv": { + "version": "8.17.1", "license": "MIT", + "peer": true, "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, - "engines": { - "node": ">= 0.4" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/call-bound": { - "version": "1.0.4", + "node_modules/@middy/validator/node_modules/ajv-errors": { + "version": "3.0.0", + "license": "MIT", + "peerDependencies": { + "ajv": "^8.0.1" + } + }, + "node_modules/@middy/validator/node_modules/ajv-keywords": { + "version": "5.1.0", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" + "fast-deep-equal": "^3.1.3" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "ajv": "^8.8.2" } }, - "node_modules/call-me-maybe": { - "version": "1.0.2", - "dev": true, + "node_modules/@middy/validator/node_modules/json-schema-traverse": { + "version": "1.0.0", "license": "MIT" }, - "node_modules/callsites": { - "version": "3.1.0", - "dev": true, + "node_modules/@mswjs/interceptors": { + "version": "0.39.7", "license": "MIT", + "dependencies": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "strict-event-emitter": "^0.5.1" + }, "engines": { - "node": ">=6" + "node": ">=18" } }, - "node_modules/camel-case": { - "version": "4.1.2", + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "dev": true, + "node_modules/@nhs/fhir-middy-error-handler": { + "version": "2.1.70", + "resolved": "https://registry.npmjs.org/@nhs/fhir-middy-error-handler/-/fhir-middy-error-handler-2.1.70.tgz", + "integrity": "sha512-eK0nuMsEwOBu2UxXj4jOcUDZt1LT+Sgona/E3hObwQWQQa2TFqJiEVj5prGTue8atLom8hQRtk61uZ7s1OucJA==", "license": "MIT", - "engines": { - "node": ">=6" + "dependencies": { + "@aws-lambda-powertools/logger": "^2.30.2", + "@middy/core": "^7.0.2" } }, - "node_modules/camelize": { - "version": "1.0.1", + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "dev": true, "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://paulmillr.com/funding/" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001745", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/capabilityStatement": { - "resolved": "packages/capabilityStatement", - "link": true + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "license": "MIT" }, - "node_modules/capital-case": { - "version": "1.0.4", + "node_modules/@open-draft/logger": { + "version": "0.3.0", "license": "MIT", "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3", - "upper-case-first": "^2.0.2" + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" } }, - "node_modules/chalk": { - "version": "4.1.2", + "node_modules/@open-draft/until": { + "version": "2.1.0", + "license": "MIT" + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "license": "Apache-2.0", + "peer": true, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=8.0.0" } }, - "node_modules/change-case": { - "version": "4.1.2", - "license": "MIT", + "node_modules/@opentelemetry/api-logs": { + "version": "0.202.0", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "camel-case": "^4.1.2", - "capital-case": "^1.0.4", - "constant-case": "^3.0.4", - "dot-case": "^3.0.4", - "header-case": "^2.0.4", - "no-case": "^3.0.4", - "param-case": "^3.0.4", - "pascal-case": "^3.1.2", - "path-case": "^3.0.4", - "sentence-case": "^3.0.4", - "snake-case": "^3.0.4", - "tslib": "^2.0.3" + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" } }, - "node_modules/char-regex": { - "version": "1.0.2", + "node_modules/@opentelemetry/context-async-hooks": { + "version": "2.0.1", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">=10" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/checkPrescriptionStatusUpdates": { - "resolved": "packages/checkPrescriptionStatusUpdates", - "link": true - }, - "node_modules/chokidar": { - "version": "3.6.0", + "node_modules/@opentelemetry/core": { + "version": "2.0.1", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "node": "^18.19.0 || >=20.6.0" }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", + "node_modules/@opentelemetry/exporter-trace-otlp-http": { + "version": "0.202.0", "dev": true, - "license": "ISC", + "license": "Apache-2.0", "dependencies": { - "is-glob": "^4.0.1" + "@opentelemetry/core": "2.0.1", + "@opentelemetry/otlp-exporter-base": "0.202.0", + "@opentelemetry/otlp-transformer": "0.202.0", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-trace-base": "2.0.1" }, "engines": { - "node": ">= 6" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/ci-info": { - "version": "4.3.0", + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.202.0", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.0.1", + "@opentelemetry/otlp-transformer": "0.202.0" + }, "engines": { - "node": ">=8" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/cjs-module-lexer": { - "version": "2.1.0", - "dev": true, - "license": "MIT" - }, - "node_modules/classnames": { - "version": "2.5.1", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui": { - "version": "8.0.1", + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.202.0", "dev": true, - "license": "ISC", + "license": "Apache-2.0", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "@opentelemetry/api-logs": "0.202.0", + "@opentelemetry/core": "2.0.1", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-logs": "0.202.0", + "@opentelemetry/sdk-metrics": "2.0.1", + "@opentelemetry/sdk-trace-base": "2.0.1", + "protobufjs": "^7.3.0" }, "engines": { - "node": ">=12" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", + "node_modules/@opentelemetry/resources": { + "version": "2.0.1", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.0.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, "engines": { - "node": ">=8" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", + "node_modules/@opentelemetry/sdk-logs": { + "version": "0.202.0", "dev": true, - "license": "MIT" + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.202.0", + "@opentelemetry/core": "2.0.1", + "@opentelemetry/resources": "2.0.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", + "node_modules/@opentelemetry/sdk-metrics": { + "version": "2.0.1", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "@opentelemetry/core": "2.0.1", + "@opentelemetry/resources": "2.0.1" }, "engines": { - "node": ">=8" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.0.1", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "ansi-regex": "^5.0.1" + "@opentelemetry/core": "2.0.1", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=8" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", + "node_modules/@opentelemetry/sdk-trace-node": { + "version": "2.0.1", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "@opentelemetry/context-async-hooks": "2.0.1", + "@opentelemetry/core": "2.0.1", + "@opentelemetry/sdk-trace-base": "2.0.1" }, "engines": { - "node": ">=10" + "node": "^18.19.0 || >=20.6.0" }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/clsx": { - "version": "2.1.1", + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.34.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", "dev": true, "license": "MIT", + "optional": true, "engines": { - "node": ">=6" + "node": ">=14" } }, - "node_modules/co": { - "version": "4.6.0", + "node_modules/@pkgr/core": { + "version": "0.2.9", "dev": true, "license": "MIT", "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" } }, - "node_modules/collect-v8-coverage": { + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { "version": "1.0.2", "dev": true, - "license": "MIT" + "license": "BSD-3-Clause" }, - "node_modules/color-convert": { - "version": "2.0.1", + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } + "license": "BSD-3-Clause" }, - "node_modules/color-name": { - "version": "1.1.4", + "node_modules/@protobufjs/pool": { + "version": "1.1.0", "dev": true, - "license": "MIT" + "license": "BSD-3-Clause" }, - "node_modules/colorette": { - "version": "1.4.0", + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", "dev": true, - "license": "MIT" + "license": "BSD-3-Clause" }, - "node_modules/combined-stream": { - "version": "1.0.8", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } + "node_modules/@psu-common/commonTypes": { + "resolved": "packages/common/commonTypes", + "link": true }, - "node_modules/commander": { - "version": "3.0.2", - "license": "MIT" + "node_modules/@psu-common/middyErrorHandler": { + "resolved": "packages/common/middyErrorHandler", + "link": true }, - "node_modules/concat-map": { - "version": "0.0.1", - "dev": true, - "license": "MIT" + "node_modules/@psu-common/testing": { + "resolved": "packages/common/testing", + "link": true }, - "node_modules/constant-case": { - "version": "3.0.4", + "node_modules/@psu-common/utilities": { + "resolved": "packages/common/utilities", + "link": true + }, + "node_modules/@redocly/ajv": { + "version": "8.17.1", + "dev": true, "license": "MIT", "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3", - "upper-case": "^2.0.2" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/convert-source-map": { - "version": "2.0.0", + "node_modules/@redocly/ajv/node_modules/json-schema-traverse": { + "version": "1.0.0", "dev": true, "license": "MIT" }, - "node_modules/cookie": { - "version": "0.7.2", + "node_modules/@redocly/cli": { + "version": "2.14.9", + "resolved": "https://registry.npmjs.org/@redocly/cli/-/cli-2.14.9.tgz", + "integrity": "sha512-eueSFzydep5jwOHxlYW6rFOSRP1b5vY7gVOzQBd2/I6XQJnG31hgrDACqx/heD4vv3hhdpoumopz24erxLe0zQ==", "dev": true, "license": "MIT", + "dependencies": { + "@opentelemetry/exporter-trace-otlp-http": "0.202.0", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-trace-node": "2.0.1", + "@opentelemetry/semantic-conventions": "1.34.0", + "@redocly/openapi-core": "2.14.9", + "@redocly/respect-core": "2.14.9", + "abort-controller": "^3.0.0", + "ajv": "npm:@redocly/ajv@8.17.1", + "ajv-formats": "^3.0.1", + "chokidar": "^3.5.1", + "colorette": "^1.2.0", + "cookie": "^0.7.2", + "dotenv": "16.4.7", + "glob": "^11.0.1", + "handlebars": "^4.7.6", + "https-proxy-agent": "^7.0.5", + "mobx": "^6.0.4", + "pluralize": "^8.0.0", + "react": "^17.0.0 || ^18.2.0 || ^19.2.1", + "react-dom": "^17.0.0 || ^18.2.0 || ^19.2.1", + "redoc": "2.5.1", + "semver": "^7.5.2", + "set-cookie-parser": "^2.3.5", + "simple-websocket": "^9.0.0", + "styled-components": "^6.0.7", + "ulid": "^3.0.1", + "undici": "^6.23.0", + "yargs": "17.0.1" + }, + "bin": { + "openapi": "bin/cli.js", + "redocly": "bin/cli.js" + }, "engines": { - "node": ">= 0.6" + "node": ">=22.12.0 || >=20.19.0 <21.0.0", + "npm": ">=10" } }, - "node_modules/cpsuLambda": { - "resolved": "packages/cpsuLambda", - "link": true - }, - "node_modules/create-require": { - "version": "1.1.1", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", + "node_modules/@redocly/cli/node_modules/ajv": { + "name": "@redocly/ajv", + "version": "8.17.1", "dev": true, "license": "MIT", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, - "engines": { - "node": ">= 8" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/css-color-keywords": { - "version": "1.0.0", + "node_modules/@redocly/cli/node_modules/ansi-regex": { + "version": "5.0.1", "dev": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/css-to-react-native": { - "version": "3.2.0", + "node_modules/@redocly/cli/node_modules/cliui": { + "version": "7.0.4", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "camelize": "^1.0.0", - "css-color-keywords": "^1.0.0", - "postcss-value-parser": "^4.0.2" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, - "node_modules/csstype": { - "version": "3.1.3", + "node_modules/@redocly/cli/node_modules/emoji-regex": { + "version": "8.0.0", "dev": true, "license": "MIT" }, - "node_modules/debug": { - "version": "4.4.3", + "node_modules/@redocly/cli/node_modules/glob": { + "version": "11.1.0", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "ms": "^2.1.3" + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": ">=6.0" + "node": "20 || >=22" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/debuglog": { - "version": "1.0.1", + "node_modules/@redocly/cli/node_modules/jackspeak": { + "version": "4.1.1", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, "engines": { - "node": "*" - } - }, - "node_modules/decko": { - "version": "1.2.0", - "dev": true - }, - "node_modules/dedent": { - "version": "1.7.0", - "dev": true, - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" + "node": "20 || >=22" }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/deep-is": { - "version": "0.1.4", + "node_modules/@redocly/cli/node_modules/json-schema-traverse": { + "version": "1.0.0", "dev": true, "license": "MIT" }, - "node_modules/deepmerge": { - "version": "4.3.1", + "node_modules/@redocly/cli/node_modules/lru-cache": { + "version": "11.2.2", "dev": true, - "license": "MIT", + "license": "ISC", "engines": { - "node": ">=0.10.0" + "node": "20 || >=22" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "license": "MIT", + "node_modules/@redocly/cli/node_modules/minimatch": { + "version": "10.1.1", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": ">= 0.4" + "node": "20 || >=22" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "license": "MIT", + "node_modules/@redocly/cli/node_modules/path-scurry": { + "version": "2.0.0", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, "engines": { - "node": ">=0.4.0" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/detect-newline": { - "version": "3.1.0", + "node_modules/@redocly/cli/node_modules/string-width": { + "version": "4.2.3", "dev": true, "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, "engines": { "node": ">=8" } }, - "node_modules/dezalgo": { - "version": "1.0.4", + "node_modules/@redocly/cli/node_modules/strip-ansi": { + "version": "6.0.1", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "dev": true, - "license": "BSD-3-Clause", + "ansi-regex": "^5.0.1" + }, "engines": { - "node": ">=0.3.1" + "node": ">=8" } }, - "node_modules/discontinuous-range": { - "version": "1.0.0", - "license": "MIT" - }, - "node_modules/dompurify": { - "version": "3.2.7", + "node_modules/@redocly/cli/node_modules/wrap-ansi": { + "version": "7.0.0", "dev": true, - "license": "(MPL-2.0 OR Apache-2.0)", - "optionalDependencies": { - "@types/trusted-types": "^2.0.7" - } - }, - "node_modules/dot-case": { - "version": "3.0.4", "license": "MIT", "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/dotenv": { - "version": "16.4.7", - "dev": true, - "license": "BSD-2-Clause", + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { - "url": "https://dotenvx.com" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", + "node_modules/@redocly/cli/node_modules/yargs": { + "version": "17.0.1", + "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" }, "engines": { - "node": ">= 0.4" + "node": ">=12" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", + "node_modules/@redocly/cli/node_modules/yargs-parser": { + "version": "20.2.9", "dev": true, - "license": "MIT" + "license": "ISC", + "engines": { + "node": ">=10" + } }, - "node_modules/electron-to-chromium": { - "version": "1.5.227", + "node_modules/@redocly/config": { + "version": "0.41.2", + "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.41.2.tgz", + "integrity": "sha512-G6muhdTKcEV2TECBFzuT905p4a27OgUtwBqRVnMx1JebO6i8zlm6bPB2H3fD1Hl+MiUpk7Jx2kwGmLVgpz5nIg==", "dev": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "json-schema-to-ts": "2.7.2" + } }, - "node_modules/emittery": { - "version": "0.13.1", + "node_modules/@redocly/config/node_modules/json-schema-to-ts": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-2.7.2.tgz", + "integrity": "sha512-R1JfqKqbBR4qE8UyBR56Ms30LL62/nlhoz+1UkfI/VE7p54Awu919FZ6ZUPG8zIa3XB65usPJgr1ONVncUGSaQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "@babel/runtime": "^7.18.3", + "@types/json-schema": "^7.0.9", + "ts-algebra": "^1.2.0" }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" + "engines": { + "node": ">=16" } }, - "node_modules/emoji-regex": { - "version": "9.2.2", + "node_modules/@redocly/config/node_modules/ts-algebra": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-1.2.2.tgz", + "integrity": "sha512-kloPhf1hq3JbCPOTYoOWDKxebWjNb2o/LKnNfkWhxVVisFFmMJPPdJeGoGmM+iRLyoXAR61e08Pb+vUXINg8aA==", "dev": true, "license": "MIT" }, - "node_modules/error-ex": { - "version": "1.3.4", + "node_modules/@redocly/openapi-core": { + "version": "2.14.9", + "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-2.14.9.tgz", + "integrity": "sha512-PIWVxm7Os3U276XMhLIh+qftaA6TxVwOuoyDaobGSkd609fp25tp57G2k/uzzKjrhUD4g2QzeJ4lfCoaBgEjhg==", "dev": true, "license": "MIT", "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "license": "MIT", + "@redocly/ajv": "^8.17.2", + "@redocly/config": "^0.41.2", + "ajv": "npm:@redocly/ajv@8.17.2", + "ajv-formats": "^3.0.1", + "colorette": "^1.2.0", + "js-levenshtein": "^1.1.6", + "js-yaml": "^4.1.0", + "picomatch": "^4.0.3", + "pluralize": "^8.0.0", + "yaml-ast-parser": "0.0.43" + }, "engines": { - "node": ">= 0.4" + "node": ">=22.12.0 || >=20.19.0 <21.0.0", + "npm": ">=10" } }, - "node_modules/es-errors": { - "version": "1.3.0", + "node_modules/@redocly/openapi-core/node_modules/@redocly/ajv": { + "version": "8.17.2", + "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.17.2.tgz", + "integrity": "sha512-rcbDZOfXAgGEJeJ30aWCVVJvxV9ooevb/m1/SFblO2qHs4cqTk178gx7T/vdslf57EA4lTofrwsq5K8rxK9g+g==", + "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.4" + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/es-object-atoms": { - "version": "1.1.1", + "node_modules/@redocly/openapi-core/node_modules/ajv": { + "name": "@redocly/ajv", + "version": "8.17.2", + "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.17.2.tgz", + "integrity": "sha512-rcbDZOfXAgGEJeJ30aWCVVJvxV9ooevb/m1/SFblO2qHs4cqTk178gx7T/vdslf57EA4lTofrwsq5K8rxK9g+g==", + "dev": true, "license": "MIT", "dependencies": { - "es-errors": "^1.3.0" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, - "engines": { - "node": ">= 0.4" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", + "node_modules/@redocly/openapi-core/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/@redocly/openapi-core/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" + "argparse": "^2.0.1" }, - "engines": { - "node": ">= 0.4" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/es6-promise": { - "version": "3.3.1", + "node_modules/@redocly/openapi-core/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, "license": "MIT" }, - "node_modules/esbuild": { - "version": "0.27.2", - "hasInstallScript": true, + "node_modules/@redocly/openapi-core/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, "engines": { - "node": ">=18" + "node": ">=12" }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/escalade": { - "version": "3.2.0", + "node_modules/@redocly/respect-core": { + "version": "2.14.9", + "resolved": "https://registry.npmjs.org/@redocly/respect-core/-/respect-core-2.14.9.tgz", + "integrity": "sha512-kP3rylB04NZV52IoEa1t8ObkSxpRZy3zpkRAxYOiLtggRETOnLdcDbYep4ipbk6t0suZamznOLOgiAeBYHo/ig==", "dev": true, "license": "MIT", + "dependencies": { + "@faker-js/faker": "^7.6.0", + "@noble/hashes": "^1.8.0", + "@redocly/ajv": "8.17.1", + "@redocly/openapi-core": "2.14.9", + "ajv": "npm:@redocly/ajv@8.17.1", + "better-ajv-errors": "^1.2.0", + "colorette": "^2.0.20", + "json-pointer": "^0.6.2", + "jsonpath-rfc9535": "1.3.0", + "openapi-sampler": "^1.6.1", + "outdent": "^0.8.0" + }, "engines": { - "node": ">=6" + "node": ">=22.12.0 || >=20.19.0 <21.0.0", + "npm": ">=10" } }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", + "node_modules/@redocly/respect-core/node_modules/@faker-js/faker": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-7.6.0.tgz", + "integrity": "sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=14.0.0", + "npm": ">=6.0.0" } }, - "node_modules/eslint": { - "version": "9.39.2", + "node_modules/@redocly/respect-core/node_modules/ajv": { + "name": "@redocly/ajv", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-EDtsGZS964mf9zAUXAl9Ew16eYbeyAFWhsPr0fX6oaJxgd8rApYlPBf0joyhnUHz88WxrigyFtTaqqzXNzPgqw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.2", - "@eslint/plugin-kit": "^0.4.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/eslint-config-prettier": { - "version": "10.1.8", + "node_modules/@redocly/respect-core/node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "funding": { - "url": "https://opencollective.com/eslint-config-prettier" - }, - "peerDependencies": { - "eslint": ">=7.0.0" + "license": "MIT" + }, + "node_modules/@redocly/respect-core/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.41", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" } }, - "node_modules/eslint-plugin-import-newlines": { - "version": "1.4.0", + "node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.3", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "type-detect": "^4.1.0" + } + }, + "node_modules/@sinonjs/samsam/node_modules/type-detect": { + "version": "4.1.0", "dev": true, "license": "MIT", - "bin": { - "import-linter": "lib/index.js" - }, "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "eslint": ">=6.0.0" + "node": ">=4" } }, - "node_modules/eslint-plugin-prettier": { - "version": "5.5.5", + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.3", "dev": true, - "license": "MIT", + "license": "(Unlicense OR Apache-2.0)" + }, + "node_modules/@smithy/abort-controller": { + "version": "4.2.8", + "license": "Apache-2.0", "dependencies": { - "prettier-linter-helpers": "^1.0.1", - "synckit": "^0.11.12" + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" }, "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-plugin-prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", - "prettier": ">=3.0.0" - }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { - "optional": true - } + "node": ">=18.0.0" } }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/@smithy/config-resolver": { + "version": "4.4.6", + "license": "Apache-2.0", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "tslib": "^2.6.2" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.21.1.tgz", + "integrity": "sha512-NUH8R4O6FkN8HKMojzbGg/5pNjsfTjlMmeFclyPfPaXXUrbr5TzhWgbf7t92wfrpCHRgpjyz7ffASIS3wX28aA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.2.9", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-stream": "^4.5.10", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" }, - "funding": { - "url": "https://opencollective.com/eslint" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "dev": true, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.8", "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "tslib": "^2.6.2" + }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.9", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.8", + "@smithy/querystring-builder": "^4.2.8", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" }, - "funding": { - "url": "https://opencollective.com/eslint" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "dev": true, - "license": "MIT", + "node_modules/@smithy/hash-node": { + "version": "4.2.8", + "license": "Apache-2.0", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@smithy/types": "^4.12.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "dev": true, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.8", "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ignore": { - "version": "5.3.2", - "dev": true, - "license": "MIT", "engines": { - "node": ">= 4" + "node": ">=18.0.0" } }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "dev": true, - "license": "ISC", + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "license": "Apache-2.0", "dependencies": { - "brace-expansion": "^1.1.7" + "tslib": "^2.6.2" }, "engines": { - "node": "*" + "node": ">=18.0.0" } }, - "node_modules/espree": { - "version": "10.4.0", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/@smithy/md5-js": { + "version": "4.2.8", + "license": "Apache-2.0", "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" + "@smithy/types": "^4.12.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=18.0.0" } }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "dev": true, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.8", "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "dependencies": { + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" }, - "funding": { - "url": "https://opencollective.com/eslint" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/esprima": { - "version": "4.0.1", - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.11", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.11.tgz", + "integrity": "sha512-/WqsrycweGGfb9sSzME4CrsuayjJF6BueBmkKlcbeU5q18OhxRrvvKlmfw3tpDsK5ilx2XUJvoukwxHB0nHs/Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.21.1", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-middleware": "^4.2.8", + "tslib": "^2.6.2" }, "engines": { - "node": ">=4" + "node": ">=18.0.0" } }, - "node_modules/esquery": { - "version": "1.6.0", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/@smithy/middleware-retry": { + "version": "4.4.27", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.27.tgz", + "integrity": "sha512-xFUYCGRVsfgiN5EjsJJSzih9+yjStgMTCLANPlf0LVQkPDYCe0hz97qbdTZosFOiYlGBlHYityGRxrQ/hxhfVQ==", + "license": "Apache-2.0", "dependencies": { - "estraverse": "^5.1.0" + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/service-error-classification": "^4.2.8", + "@smithy/smithy-client": "^4.10.12", + "@smithy/types": "^4.12.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=0.10" + "node": ">=18.0.0" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/@smithy/middleware-serde": { + "version": "4.2.9", + "license": "Apache-2.0", "dependencies": { - "estraverse": "^5.2.0" + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=4.0" + "node": ">=18.0.0" } }, - "node_modules/estraverse": { - "version": "5.3.0", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/@smithy/middleware-stack": { + "version": "4.2.8", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=4.0" + "node": ">=18.0.0" } }, - "node_modules/esutils": { - "version": "2.0.3", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/@smithy/node-config-provider": { + "version": "4.3.8", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=0.10.0" + "node": ">=18.0.0" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "dev": true, - "license": "MIT", + "node_modules/@smithy/node-http-handler": { + "version": "4.4.8", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/querystring-builder": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6" + "node": ">=18.0.0" } }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/events": { - "version": "1.1.1", - "license": "MIT", + "node_modules/@smithy/property-provider": { + "version": "4.2.8", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=0.4.x" + "node": ">=18.0.0" } }, - "node_modules/execa": { - "version": "5.1.1", - "dev": true, - "license": "MIT", + "node_modules/@smithy/protocol-http": { + "version": "5.3.8", + "license": "Apache-2.0", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "node": ">=18.0.0" } }, - "node_modules/execa/node_modules/get-stream": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" + "node_modules/@smithy/querystring-builder": { + "version": "4.2.8", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/execa/node_modules/signal-exit": { - "version": "3.0.7", - "dev": true, - "license": "ISC" - }, - "node_modules/exit-x": { - "version": "0.2.2", - "dev": true, - "license": "MIT", + "node_modules/@smithy/querystring-parser": { + "version": "4.2.8", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">= 0.8.0" + "node": ">=18.0.0" } }, - "node_modules/expect": { - "version": "30.2.0", - "dev": true, - "license": "MIT", + "node_modules/@smithy/service-error-classification": { + "version": "4.2.8", + "license": "Apache-2.0", "dependencies": { - "@jest/expect-utils": "30.2.0", - "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-util": "30.2.0" + "@smithy/types": "^4.12.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18.0.0" } }, - "node_modules/extend": { - "version": "3.0.2", - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "license": "MIT" - }, - "node_modules/fast-diff": { - "version": "1.3.0", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.0", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fast-xml-parser": { - "version": "5.2.5", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.3", + "license": "Apache-2.0", "dependencies": { - "strnum": "^2.1.0" + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" }, - "bin": { - "fxparser": "src/cli/cli.js" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "dev": true, + "node_modules/@smithy/signature-v4": { + "version": "5.3.8", "license": "Apache-2.0", "dependencies": { - "bser": "2.1.1" + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "dev": true, - "license": "MIT", + "node_modules/@smithy/smithy-client": { + "version": "4.10.12", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.12.tgz", + "integrity": "sha512-VKO/HKoQ5OrSHW6AJUmEnUKeXI1/5LfCwO9cwyao7CmLvGnZeM1i36Lyful3LK1XU7HwTVieTqO1y2C/6t3qtA==", + "license": "Apache-2.0", "dependencies": { - "flat-cache": "^4.0.0" + "@smithy/core": "^3.21.1", + "@smithy/middleware-endpoint": "^4.4.11", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-stream": "^4.5.10", + "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "dev": true, - "license": "MIT", + "node_modules/@smithy/types": { + "version": "4.12.0", + "license": "Apache-2.0", "dependencies": { - "to-regex-range": "^5.0.1" + "tslib": "^2.6.2" }, "engines": { - "node": ">=8" + "node": ">=18.0.0" } }, - "node_modules/find-up": { - "version": "5.0.0", - "dev": true, - "license": "MIT", + "node_modules/@smithy/url-parser": { + "version": "4.2.8", + "license": "Apache-2.0", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "@smithy/querystring-parser": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18.0.0" } }, - "node_modules/flat-cache": { - "version": "4.0.1", - "dev": true, - "license": "MIT", + "node_modules/@smithy/util-base64": { + "version": "4.3.0", + "license": "Apache-2.0", "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18.0.0" } }, - "node_modules/flatted": { - "version": "3.3.3", - "dev": true, - "license": "ISC" - }, - "node_modules/fluent-transpiler": { - "version": "0.2.1", - "license": "MIT", + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "license": "Apache-2.0", "dependencies": { - "@fluent/syntax": "0.18.1", - "change-case": "4.1.2", - "commander": "9.4.0" + "tslib": "^2.6.2" }, - "bin": { - "ftl": "cli.js" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/fluent-transpiler/node_modules/commander": { - "version": "9.4.0", - "license": "MIT", + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.1", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": "^12.20.0 || >=14" + "node": ">=18.0.0" } }, - "node_modules/follow-redirects": { - "version": "1.15.11", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } + "engines": { + "node": ">=18.0.0" } }, - "node_modules/for-each": { - "version": "0.3.5", - "license": "MIT", + "node_modules/@smithy/util-config-provider": { + "version": "4.2.0", + "license": "Apache-2.0", "dependencies": { - "is-callable": "^1.2.7" + "tslib": "^2.6.2" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18.0.0" } }, - "node_modules/foreach": { - "version": "2.0.6", - "dev": true, - "license": "MIT" + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.26", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.26.tgz", + "integrity": "sha512-vva0dzYUTgn7DdE0uaha10uEdAgmdLnNFowKFjpMm6p2R0XDk5FHPX3CBJLzWQkQXuEprsb0hGz9YwbicNWhjw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.8", + "@smithy/smithy-client": "^4.10.12", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "dev": true, - "license": "ISC", + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.29", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.29.tgz", + "integrity": "sha512-c6D7IUBsZt/aNnTBHMTf+OVh+h/JcxUUgfTcIJaWRe6zhOum1X+pNKSZtZ+7fbOn5I99XVFtmrnXKv8yHHErTQ==", + "license": "Apache-2.0", "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" + "@smithy/config-resolver": "^4.4.6", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/smithy-client": "^4.10.12", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=18.0.0" } }, - "node_modules/form-data": { - "version": "4.0.4", - "license": "MIT", + "node_modules/@smithy/util-endpoints": { + "version": "3.2.8", + "license": "Apache-2.0", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 6" + "node": ">=18.0.0" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18.0.0" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "dev": true, - "license": "MIT", + "node_modules/@smithy/util-middleware": { + "version": "4.2.8", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "dev": true, - "license": "ISC", + "node_modules/@smithy/util-retry": { + "version": "4.2.8", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">=18.0.0" } }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "license": "MIT", + "node_modules/@smithy/util-stream": { + "version": "4.5.10", + "license": "Apache-2.0", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18.0.0" } }, - "node_modules/get-package-type": { - "version": "0.1.0", - "dev": true, - "license": "MIT", + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=8.0.0" + "node": ">=18.0.0" } }, - "node_modules/get-proto": { - "version": "1.0.1", - "license": "MIT", + "node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "license": "Apache-2.0", "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 0.4" + "node": ">=18.0.0" } }, - "node_modules/glob": { - "version": "10.5.0", - "dev": true, - "license": "ISC", + "node_modules/@smithy/util-waiter": { + "version": "4.2.8", + "license": "Apache-2.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "@smithy/abort-controller": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/glob-parent": { - "version": "6.0.2", - "dev": true, - "license": "ISC", + "node_modules/@smithy/uuid": { + "version": "1.1.0", + "license": "Apache-2.0", "dependencies": { - "is-glob": "^4.0.3" + "tslib": "^2.6.2" }, "engines": { - "node": ">=10.13.0" + "node": ">=18.0.0" } }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "license": "BSD-2-Clause" - }, - "node_modules/globals": { - "version": "14.0.0", + "node_modules/@tsconfig/node10": { + "version": "1.0.11", "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "license": "MIT" }, - "node_modules/gopd": { - "version": "1.2.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "dev": true, + "license": "MIT" }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "license": "ISC" + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "dev": true, + "license": "MIT" }, - "node_modules/gsul": { - "resolved": "packages/gsul", - "link": true + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "dev": true, + "license": "MIT" }, - "node_modules/handlebars": { - "version": "4.7.8", + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" + "tslib": "^2.4.0" } }, - "node_modules/has-flag": { - "version": "4.0.0", + "node_modules/@types/aws-lambda": { + "version": "8.10.160", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.160.tgz", + "integrity": "sha512-uoO4QVQNWFPJMh26pXtmtrRfGshPUSpMZGUyUQY20FhfHEElEBOPKgVmFs1z+kbpyBsRs2JnoOPT7++Z4GA9pA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "license": "MIT" }, - "node_modules/has-symbols": { - "version": "1.1.0", + "node_modules/@types/babel__core": { + "version": "7.20.5", + "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" } }, - "node_modules/has-tostringtag": { - "version": "1.0.2", + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "dev": true, "license": "MIT", "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "@babel/types": "^7.0.0" } }, - "node_modules/hasown": { - "version": "2.0.2", + "node_modules/@types/babel__template": { + "version": "7.4.4", + "dev": true, "license": "MIT", "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "node_modules/header-case": { - "version": "2.0.4", + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "dev": true, "license": "MIT", "dependencies": { - "capital-case": "^1.0.4", - "tslib": "^2.0.3" + "@babel/types": "^7.28.2" } }, - "node_modules/hosted-git-info": { - "version": "2.8.9", + "node_modules/@types/estree": { + "version": "1.0.8", "dev": true, - "license": "ISC" + "license": "MIT" }, - "node_modules/html-escaper": { - "version": "2.0.2", + "node_modules/@types/fhir": { + "version": "0.0.41", "dev": true, "license": "MIT" }, - "node_modules/http2-client": { - "version": "1.3.5", + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", "dev": true, "license": "MIT" }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" + "@types/istanbul-lib-coverage": "*" } }, - "node_modules/human-signals": { - "version": "2.1.0", + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" } }, - "node_modules/ieee754": { - "version": "1.1.13", - "license": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "7.0.5", + "node_modules/@types/jest": { + "version": "30.0.0", "dev": true, "license": "MIT", - "engines": { - "node": ">= 4" + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" } }, - "node_modules/import-fresh": { - "version": "3.3.1", + "node_modules/@types/json-schema": { + "version": "7.0.15", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.0.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz", + "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "undici-types": "~7.16.0" } }, - "node_modules/import-local": { - "version": "3.2.0", + "node_modules/@types/sinon": { + "version": "17.0.4", "dev": true, "license": "MIT", "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@types/sinonjs__fake-timers": "*" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.5", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } + "license": "MIT" }, - "node_modules/inflight": { - "version": "1.0.6", + "node_modules/@types/stack-utils": { + "version": "2.0.3", "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } + "license": "MIT" }, - "node_modules/inherits": { - "version": "2.0.4", - "license": "ISC" + "node_modules/@types/stylis": { + "version": "4.2.5", + "dev": true, + "license": "MIT" }, - "node_modules/is-arguments": { - "version": "1.2.0", + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "@types/yargs-parser": "*" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", + "node_modules/@types/yargs-parser": { + "version": "21.0.3", "dev": true, "license": "MIT" }, - "node_modules/is-binary-path": { - "version": "2.1.0", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz", + "integrity": "sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==", "dev": true, "license": "MIT", "dependencies": { - "binary-extensions": "^2.0.0" + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.53.1", + "@typescript-eslint/type-utils": "8.53.1", + "@typescript-eslint/utils": "8.53.1", + "@typescript-eslint/visitor-keys": "8.53.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" }, "engines": { - "node": ">=8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.53.1", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/is-buffer": { - "version": "2.0.5", + "node_modules/@typescript-eslint/parser": { + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.1.tgz", + "integrity": "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.53.1", + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/typescript-estree": "8.53.1", + "@typescript-eslint/visitor-keys": "8.53.1", + "debug": "^4.4.3" + }, "engines": { - "node": ">=4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/is-callable": { - "version": "1.2.7", + "node_modules/@typescript-eslint/project-service": { + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.1.tgz", + "integrity": "sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog==", + "dev": true, "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.53.1", + "@typescript-eslint/types": "^8.53.1", + "debug": "^4.4.3" + }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/is-core-module": { - "version": "2.16.1", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz", + "integrity": "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==", "dev": true, "license": "MIT", "dependencies": { - "hasown": "^2.0.2" + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/visitor-keys": "8.53.1" }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/is-extglob": { - "version": "2.1.1", + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.1.tgz", + "integrity": "sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", + "node_modules/@typescript-eslint/type-utils": { + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.53.1.tgz", + "integrity": "sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w==", "dev": true, "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/typescript-estree": "8.53.1", + "@typescript-eslint/utils": "8.53.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, "engines": { - "node": ">=8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/is-generator-fn": { - "version": "2.1.0", + "node_modules/@typescript-eslint/types": { + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz", + "integrity": "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/is-generator-function": { - "version": "1.1.0", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.1.tgz", + "integrity": "sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==", + "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "get-proto": "^1.0.0", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" + "@typescript-eslint/project-service": "8.53.1", + "@typescript-eslint/tsconfig-utils": "8.53.1", + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/visitor-keys": "8.53.1", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/is-glob": { - "version": "4.0.3", + "node_modules/@typescript-eslint/utils": { + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.1.tgz", + "integrity": "sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==", "dev": true, "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.53.1", + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/typescript-estree": "8.53.1" }, "engines": { - "node": ">=0.10.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/is-node-process": { - "version": "1.2.0", - "license": "MIT" - }, - "node_modules/is-number": { - "version": "7.0.0", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.1.tgz", + "integrity": "sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==", "dev": true, "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.53.1", + "eslint-visitor-keys": "^4.2.1" + }, "engines": { - "node": ">=0.12.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/is-regex": { - "version": "1.2.1", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/eslint" } }, - "node_modules/is-retry-allowed": { - "version": "2.2.0", + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "dev": true, + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/is-stream": { - "version": "2.0.1", + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/is-typed-array": { - "version": "1.1.15", + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/isarray": { - "version": "1.0.0", - "license": "MIT" + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/isexe": { - "version": "2.0.0", + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], "dev": true, - "license": "ISC" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "cpu": [ + "x64" + ], "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "cpu": [ + "x64" + ], "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", + "optional": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" + "@napi-rs/wasm-runtime": "^0.2.11" }, "engines": { - "node": ">=10" + "node": ">=14.0.0" } }, - "node_modules/istanbul-reports": { - "version": "3.2.0", + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/jackspeak": { - "version": "3.4.3", + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/jest": { - "version": "30.2.0", + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@jest/core": "30.2.0", - "@jest/types": "30.2.0", - "import-local": "^3.2.0", - "jest-cli": "30.2.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/jest-changed-files": { - "version": "30.2.0", + "node_modules/abbrev": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/abort-controller": { + "version": "3.0.0", "dev": true, "license": "MIT", "dependencies": { - "execa": "^5.1.1", - "jest-util": "30.2.0", - "p-limit": "^3.1.0" + "event-target-shim": "^5.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=6.5" } }, - "node_modules/jest-circus": { - "version": "30.2.0", + "node_modules/acorn": { + "version": "8.15.0", "dev": true, "license": "MIT", - "dependencies": { - "@jest/environment": "30.2.0", - "@jest/expect": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "co": "^4.6.0", - "dedent": "^1.6.0", - "is-generator-fn": "^2.1.0", - "jest-each": "30.2.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-runtime": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", - "p-limit": "^3.1.0", - "pretty-format": "30.2.0", - "pure-rand": "^7.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" + "peer": true, + "bin": { + "acorn": "bin/acorn" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=0.4.0" } }, - "node_modules/jest-cli": { - "version": "30.2.0", + "node_modules/acorn-jsx": { + "version": "5.3.2", "dev": true, "license": "MIT", - "dependencies": { - "@jest/core": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", - "chalk": "^4.1.2", - "exit-x": "^0.2.2", - "import-local": "^3.2.0", - "jest-config": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "yargs": "^17.7.2" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/jest-config": { - "version": "30.2.0", + "node_modules/acorn-walk": { + "version": "8.3.4", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.27.4", - "@jest/get-type": "30.1.0", - "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.2.0", - "@jest/types": "30.2.0", - "babel-jest": "30.2.0", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "deepmerge": "^4.3.1", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "jest-circus": "30.2.0", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-runner": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "micromatch": "^4.0.8", - "parse-json": "^5.2.0", - "pretty-format": "30.2.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" + "acorn": "^8.11.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "esbuild-register": ">=3.4.0", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "esbuild-register": { - "optional": true - }, - "ts-node": { - "optional": true - } + "node": ">=0.4.0" } }, - "node_modules/jest-diff": { - "version": "30.2.0", + "node_modules/agent-base": { + "version": "7.1.4", "dev": true, "license": "MIT", - "dependencies": { - "@jest/diff-sequences": "30.0.1", - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "pretty-format": "30.2.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 14" } }, - "node_modules/jest-docblock": { - "version": "30.2.0", - "dev": true, + "node_modules/ajv": { + "version": "6.12.6", "license": "MIT", + "peer": true, "dependencies": { - "detect-newline": "^3.1.0" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/jest-each": { - "version": "30.2.0", - "dev": true, + "node_modules/ajv-formats": { + "version": "3.0.1", "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.2.0", - "chalk": "^4.1.2", - "jest-util": "30.2.0", - "pretty-format": "30.2.0" + "ajv": "^8.0.0" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, - "node_modules/jest-environment-node": { - "version": "30.2.0", - "dev": true, + "node_modules/ajv-formats-draft2019": { + "version": "1.6.1", "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "jest-mock": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0" + "punycode": "^2.1.1", + "schemes": "^1.4.0", + "smtp-address-parser": "^1.0.3", + "uri-js": "^4.4.1" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "peerDependencies": { + "ajv": "*" } }, - "node_modules/jest-haste-map": { - "version": "30.2.0", - "dev": true, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "anymatch": "^3.1.3", - "fb-watchman": "^2.0.2", - "graceful-fs": "^4.2.11", - "jest-regex-util": "30.0.1", - "jest-util": "30.2.0", - "jest-worker": "30.2.0", - "micromatch": "^4.0.8", - "walker": "^1.0.8" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, - "optionalDependencies": { - "fsevents": "^2.3.3" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/jest-junit": { - "version": "16.0.0", - "dev": true, - "license": "Apache-2.0", + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/ajv-ftl-i18n": { + "version": "0.1.1", + "license": "MIT", "dependencies": { - "mkdirp": "^1.0.4", - "strip-ansi": "^6.0.1", - "uuid": "^8.3.2", - "xml": "^1.0.1" + "commander": "10.0.0", + "fluent-transpiler": "0.2.1" }, - "engines": { - "node": ">=10.12.0" + "bin": { + "ajv-ftl": "cli.js" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/willfarrell" } }, - "node_modules/jest-junit/node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, + "node_modules/ajv-ftl-i18n/node_modules/commander": { + "version": "10.0.0", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14" } }, - "node_modules/jest-junit/node_modules/strip-ansi": { - "version": "6.0.1", + "node_modules/ansi-escapes": { + "version": "4.3.2", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "type-fest": "^0.21.3" }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-junit/node_modules/uuid": { - "version": "8.3.2", + "node_modules/ansi-regex": { + "version": "6.2.2", "dev": true, "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/jest-leak-detector": { - "version": "30.2.0", + "node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", - "pretty-format": "30.2.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-matcher-utils": { - "version": "30.2.0", + "node_modules/anymatch": { + "version": "3.1.3", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "jest-diff": "30.2.0", - "pretty-format": "30.2.0" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 8" } }, - "node_modules/jest-message-util": { - "version": "30.2.0", + "node_modules/apim-spec": { + "resolved": "packages/specification", + "link": true + }, + "node_modules/arg": { + "version": "4.1.3", "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@jest/types": "30.2.0", - "@types/stack-utils": "^2.0.3", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "micromatch": "^4.0.8", - "pretty-format": "30.2.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "sprintf-js": "~1.0.2" } }, - "node_modules/jest-mock": { - "version": "30.2.0", + "node_modules/array-find-index": { + "version": "1.0.2", "dev": true, "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "jest-util": "30.2.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", + "node_modules/asap": { + "version": "2.0.6", "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", "license": "MIT", - "engines": { - "node": ">=6" + "dependencies": { + "possible-typed-array-names": "^1.0.0" }, - "peerDependencies": { - "jest-resolve": "*" + "engines": { + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-regex-util": { - "version": "30.0.1", - "dev": true, + "node_modules/aws-lambda": { + "version": "1.0.7", "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "dependencies": { + "aws-sdk": "^2.814.0", + "commander": "^3.0.2", + "js-yaml": "^3.14.1", + "watchpack": "^2.0.0-beta.10" + }, + "bin": { + "lambda": "bin/lambda" } }, - "node_modules/jest-resolve": { - "version": "30.2.0", - "dev": true, - "license": "MIT", + "node_modules/aws-sdk": { + "version": "2.1692.0", + "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "slash": "^3.0.0", - "unrs-resolver": "^1.7.11" + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.16.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "util": "^0.12.4", + "uuid": "8.0.0", + "xml2js": "0.6.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 10.0.0" } }, - "node_modules/jest-resolve-dependencies": { - "version": "30.2.0", + "node_modules/aws-sdk-client-mock": { + "version": "4.1.0", "dev": true, "license": "MIT", "dependencies": { - "jest-regex-util": "30.0.1", - "jest-snapshot": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "@types/sinon": "^17.0.3", + "sinon": "^18.0.1", + "tslib": "^2.1.0" } }, - "node_modules/jest-runner": { - "version": "30.2.0", - "dev": true, + "node_modules/axios": { + "version": "1.13.2", "license": "MIT", + "peer": true, "dependencies": { - "@jest/console": "30.2.0", - "@jest/environment": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.2.0", - "jest-haste-map": "30.2.0", - "jest-leak-detector": "30.2.0", - "jest-message-util": "30.2.0", - "jest-resolve": "30.2.0", - "jest-runtime": "30.2.0", - "jest-util": "30.2.0", - "jest-watcher": "30.2.0", - "jest-worker": "30.2.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" } }, - "node_modules/jest-runtime": { - "version": "30.2.0", + "node_modules/axios-mock-adapter": { + "version": "2.1.0", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/fake-timers": "30.2.0", - "@jest/globals": "30.2.0", - "@jest/source-map": "30.0.1", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "cjs-module-lexer": "^2.1.0", - "collect-v8-coverage": "^1.0.2", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" + "fast-deep-equal": "^3.1.3", + "is-buffer": "^2.0.5" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "peerDependencies": { + "axios": ">= 0.17.0" } }, - "node_modules/jest-snapshot": { - "version": "30.2.0", - "dev": true, - "license": "MIT", + "node_modules/axios-retry": { + "version": "4.5.0", + "license": "Apache-2.0", "dependencies": { - "@babel/core": "^7.27.4", - "@babel/generator": "^7.27.5", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1", - "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.2.0", - "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "babel-preset-current-node-syntax": "^1.2.0", - "chalk": "^4.1.2", - "expect": "30.2.0", - "graceful-fs": "^4.2.11", - "jest-diff": "30.2.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "pretty-format": "30.2.0", - "semver": "^7.7.2", - "synckit": "^0.11.8" + "is-retry-allowed": "^2.2.0" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "peerDependencies": { + "axios": "0.x || 1.x" } }, - "node_modules/jest-util": { + "node_modules/babel-jest": { "version": "30.2.0", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", + "@jest/transform": "30.2.0", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.2.0", "chalk": "^4.1.2", - "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", - "picomatch": "^4.0.2" + "slash": "^3.0.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-0" } }, - "node_modules/jest-util/node_modules/picomatch": { - "version": "4.0.3", + "node_modules/babel-plugin-istanbul": { + "version": "7.0.1", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^6.0.0" + }, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/jest-validate": { + "node_modules/babel-plugin-jest-hoist": { "version": "30.2.0", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.2.0", - "camelcase": "^6.3.0", - "chalk": "^4.1.2", - "leven": "^3.1.0", - "pretty-format": "30.2.0" + "@types/babel__core": "^7.20.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", "dev": true, "license": "MIT", - "engines": { - "node": ">=10" + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" } }, - "node_modules/jest-watcher": { + "node_modules/babel-preset-jest": { "version": "30.2.0", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "jest-util": "30.2.0", - "string-length": "^4.0.2" + "babel-plugin-jest-hoist": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" } }, - "node_modules/jest-worker": { - "version": "30.2.0", + "node_modules/balanced-match": { + "version": "1.0.2", "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.2.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.1.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.9", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" } }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", + "node_modules/better-ajv-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/better-ajv-errors/-/better-ajv-errors-1.2.0.tgz", + "integrity": "sha512-UW+IsFycygIo7bclP9h5ugkNH8EjCSgqyFB/yQ4Hqqa1OEYDtb0uFIkYE0b6+CjkgJYVM5UKI/pJPxjYe9EZlA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "has-flag": "^4.0.0" + "@babel/code-frame": "^7.16.0", + "@humanwhocodes/momoa": "^2.0.2", + "chalk": "^4.1.2", + "jsonpointer": "^5.0.0", + "leven": "^3.1.0 < 4" }, "engines": { - "node": ">=10" + "node": ">= 12.13.0" }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/jmespath": { - "version": "0.16.0", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/jose": { - "version": "6.1.3", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" + "peerDependencies": { + "ajv": "4.11.8 - 8" } }, - "node_modules/js-levenshtein": { - "version": "1.1.6", + "node_modules/binary-extensions": { + "version": "2.3.0", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "dev": true, + "node_modules/bowser": { + "version": "2.12.1", "license": "MIT" }, - "node_modules/js-yaml": { - "version": "3.14.2", + "node_modules/brace-expansion": { + "version": "2.0.2", + "dev": true, "license": "MIT", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "balanced-match": "^1.0.0" } }, - "node_modules/jsesc": { - "version": "3.1.0", + "node_modules/braces": { + "version": "3.0.3", "dev": true, "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" + "dependencies": { + "fill-range": "^7.1.1" }, "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "dev": true, - "license": "MIT" - }, - "node_modules/json-pointer": { - "version": "0.6.2", + "node_modules/browserslist": { + "version": "4.26.2", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", + "peer": true, "dependencies": { - "foreach": "^2.0.4" - } - }, - "node_modules/json-schema-to-ts": { - "version": "3.1.1", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.18.3", - "ts-algebra": "^2.0.0" + "baseline-browser-mapping": "^2.8.3", + "caniuse-lite": "^1.0.30001741", + "electron-to-chromium": "^1.5.218", + "node-releases": "^2.0.21", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" }, "engines": { - "node": ">=16" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "license": "ISC" - }, - "node_modules/json5": { - "version": "2.2.3", + }, + "node_modules/bs-logger": { + "version": "0.2.6", "dev": true, "license": "MIT", - "bin": { - "json5": "lib/cli.js" + "dependencies": { + "fast-json-stable-stringify": "2.x" }, "engines": { - "node": ">=6" + "node": ">= 6" } }, - "node_modules/jsonpath-rfc9535": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsonpath-rfc9535/-/jsonpath-rfc9535-1.3.0.tgz", - "integrity": "sha512-3jFHya7oZ45aDxIIdx+/zQARahHXxFSMWBkcBUldfXpLS9VCXDJyTKt35kQfEXLqh0K3Ixw/9xFnvcDStaxh7Q==", + "node_modules/bser": { + "version": "2.1.1", "dev": true, "license": "Apache-2.0", - "engines": { - "node": ">=20" + "dependencies": { + "node-int64": "^0.4.0" } }, - "node_modules/jsonpointer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", - "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", - "dev": true, + "node_modules/buffer": { + "version": "4.9.2", "license": "MIT", - "engines": { - "node": ">=0.10.0" + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" } }, - "node_modules/just-extend": { - "version": "6.2.0", + "node_modules/buffer-from": { + "version": "1.1.2", "dev": true, "license": "MIT" }, - "node_modules/keyv": { - "version": "4.5.4", - "dev": true, + "node_modules/call-bind": { + "version": "1.0.8", "license": "MIT", "dependencies": { - "json-buffer": "3.0.1" + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/leven": { - "version": "3.1.0", - "dev": true, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, "engines": { - "node": ">=6" + "node": ">= 0.4" } }, - "node_modules/levn": { - "version": "0.4.1", - "dev": true, + "node_modules/call-bound": { + "version": "1.0.4", "license": "MIT", "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/license-checker": { - "version": "25.0.1", + "node_modules/call-me-maybe": { + "version": "1.0.2", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "chalk": "^2.4.1", - "debug": "^3.1.0", - "mkdirp": "^0.5.1", - "nopt": "^4.0.1", - "read-installed": "~4.0.3", - "semver": "^5.5.0", - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0", - "spdx-satisfies": "^4.0.0", - "treeify": "^1.1.0" - }, - "bin": { - "license-checker": "bin/license-checker" - } + "license": "MIT" }, - "node_modules/license-checker/node_modules/ansi-styles": { - "version": "3.2.1", + "node_modules/callsites": { + "version": "3.1.0", "dev": true, "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, "engines": { - "node": ">=4" + "node": ">=6" } }, - "node_modules/license-checker/node_modules/chalk": { - "version": "2.4.2", - "dev": true, + "node_modules/camel-case": { + "version": "4.1.2", "license": "MIT", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" } }, - "node_modules/license-checker/node_modules/color-convert": { - "version": "1.9.3", + "node_modules/camelcase": { + "version": "5.3.1", "dev": true, "license": "MIT", - "dependencies": { - "color-name": "1.1.3" + "engines": { + "node": ">=6" } }, - "node_modules/license-checker/node_modules/color-name": { - "version": "1.1.3", + "node_modules/camelize": { + "version": "1.0.1", "dev": true, - "license": "MIT" + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/license-checker/node_modules/debug": { - "version": "3.2.7", + "node_modules/caniuse-lite": { + "version": "1.0.30001745", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/capabilityStatement": { + "resolved": "packages/capabilityStatement", + "link": true + }, + "node_modules/capital-case": { + "version": "1.0.4", "license": "MIT", "dependencies": { - "ms": "^2.1.1" + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" } }, - "node_modules/license-checker/node_modules/escape-string-regexp": { - "version": "1.0.5", + "node_modules/chalk": { + "version": "4.1.2", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">=0.8.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/license-checker/node_modules/has-flag": { - "version": "3.0.0", + "node_modules/change-case": { + "version": "4.1.2", + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "capital-case": "^1.0.4", + "constant-case": "^3.0.4", + "dot-case": "^3.0.4", + "header-case": "^2.0.4", + "no-case": "^3.0.4", + "param-case": "^3.0.4", + "pascal-case": "^3.1.2", + "path-case": "^3.0.4", + "sentence-case": "^3.0.4", + "snake-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=10" } }, - "node_modules/license-checker/node_modules/mkdirp": { - "version": "0.5.6", + "node_modules/checkPrescriptionStatusUpdates": { + "resolved": "packages/checkPrescriptionStatusUpdates", + "link": true + }, + "node_modules/chokidar": { + "version": "3.6.0", "dev": true, "license": "MIT", "dependencies": { - "minimist": "^1.2.6" + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" }, - "bin": { - "mkdirp": "bin/cmd.js" + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" } }, - "node_modules/license-checker/node_modules/semver": { - "version": "5.7.2", + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", "dev": true, "license": "ISC", - "bin": { - "semver": "bin/semver" + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" } }, - "node_modules/license-checker/node_modules/supports-color": { - "version": "5.5.0", + "node_modules/ci-info": { + "version": "4.3.0", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/lines-and-columns": { - "version": "1.2.4", + "node_modules/cjs-module-lexer": { + "version": "2.1.0", "dev": true, "license": "MIT" }, - "node_modules/locate-path": { - "version": "6.0.0", + "node_modules/classnames": { + "version": "2.5.1", "dev": true, - "license": "MIT", + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "dev": true, + "license": "ISC", "dependencies": { - "p-locate": "^5.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12" } }, - "node_modules/lodash.memoize": { - "version": "4.1.2", + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/lodash.merge": { - "version": "4.6.2", + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", "dev": true, "license": "MIT" }, - "node_modules/long": { - "version": "5.3.2", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/loose-envify": { - "version": "1.4.0", + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", "dev": true, "license": "MIT", "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lower-case": { - "version": "2.0.2", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.3" + "engines": { + "node": ">=8" } }, - "node_modules/lru-cache": { - "version": "5.1.1", + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "yallist": "^3.0.2" + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/lunr": { - "version": "2.3.9", - "dev": true, - "license": "MIT" - }, - "node_modules/make-dir": { - "version": "4.0.0", + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", "dev": true, "license": "MIT", "dependencies": { - "semver": "^7.5.3" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/make-error": { - "version": "1.3.6", + "node_modules/clsx": { + "version": "2.1.1", "dev": true, - "license": "ISC" + "license": "MIT", + "engines": { + "node": ">=6" + } }, - "node_modules/makeerror": { - "version": "1.0.12", + "node_modules/co": { + "version": "4.6.0", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" } }, - "node_modules/mark.js": { - "version": "8.11.1", + "node_modules/collect-v8-coverage": { + "version": "1.0.2", "dev": true, "license": "MIT" }, - "node_modules/math-intrinsics": { - "version": "1.1.0", + "node_modules/color-convert": { + "version": "2.0.1", + "dev": true, "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, "engines": { - "node": ">= 0.4" + "node": ">=7.0.0" } }, - "node_modules/merge-stream": { - "version": "2.0.0", + "node_modules/color-name": { + "version": "1.1.4", "dev": true, "license": "MIT" }, - "node_modules/micromatch": { - "version": "4.0.8", + "node_modules/colorette": { + "version": "1.4.0", "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", "license": "MIT", "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" + "delayed-stream": "~1.0.0" }, "engines": { - "node": ">=8.6" + "node": ">= 0.8" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } + "node_modules/commander": { + "version": "3.0.2", + "license": "MIT" }, - "node_modules/mime-types": { - "version": "2.1.35", + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/constant-case": { + "version": "3.0.4", "license": "MIT", "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case": "^2.0.2" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", + "node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 0.6" } }, - "node_modules/minimatch": { - "version": "9.0.5", + "node_modules/cpsuLambda": { + "resolved": "packages/cpsuLambda", + "link": true + }, + "node_modules/create-require": { + "version": "1.1.1", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "license": "MIT" }, - "node_modules/minimist": { - "version": "1.2.8", + "node_modules/cross-spawn": { + "version": "7.0.6", "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/minipass": { - "version": "7.1.2", + "node_modules/css-color-keywords": { + "version": "1.0.0", "dev": true, "license": "ISC", "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=4" } }, - "node_modules/mkdirp": { - "version": "1.0.4", + "node_modules/css-to-react-native": { + "version": "3.2.0", "dev": true, "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mnemonist": { - "version": "0.38.3", - "license": "MIT", "dependencies": { - "obliterator": "^1.6.1" + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" } }, - "node_modules/mobx": { - "version": "6.15.0", + "node_modules/csstype": { + "version": "3.1.3", "dev": true, - "license": "MIT", - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mobx" - } + "license": "MIT" }, - "node_modules/mobx-react": { - "version": "9.2.0", + "node_modules/debug": { + "version": "4.4.3", "dev": true, "license": "MIT", "dependencies": { - "mobx-react-lite": "^4.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mobx" + "ms": "^2.1.3" }, - "peerDependencies": { - "mobx": "^6.9.0", - "react": "^16.8.0 || ^17 || ^18 || ^19" + "engines": { + "node": ">=6.0" }, "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { + "supports-color": { "optional": true } } }, - "node_modules/mobx-react-lite": { - "version": "4.1.1", + "node_modules/debuglog": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/decko": { + "version": "1.2.0", + "dev": true + }, + "node_modules/dedent": { + "version": "1.7.0", "dev": true, "license": "MIT", - "dependencies": { - "use-sync-external-store": "^1.4.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mobx" - }, "peerDependencies": { - "mobx": "^6.9.0", - "react": "^16.8.0 || ^17 || ^18 || ^19" + "babel-plugin-macros": "^3.1.0" }, "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { + "babel-plugin-macros": { "optional": true } } }, - "node_modules/moo": { - "version": "0.5.2", - "license": "BSD-3-Clause" - }, - "node_modules/ms": { - "version": "2.1.3", + "node_modules/deep-is": { + "version": "0.1.4", "dev": true, "license": "MIT" }, - "node_modules/nanoid": { - "version": "3.3.11", + "node_modules/deepmerge": { + "version": "4.3.1", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": ">=0.10.0" } }, - "node_modules/napi-postinstall": { - "version": "0.3.3", - "dev": true, + "node_modules/define-data-property": { + "version": "1.1.4", "license": "MIT", - "bin": { - "napi-postinstall": "lib/cli.js" + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/napi-postinstall" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/natural-compare": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/nearley": { - "version": "2.20.1", + "node_modules/delayed-stream": { + "version": "1.0.0", "license": "MIT", - "dependencies": { - "commander": "^2.19.0", - "moo": "^0.5.0", - "railroad-diagrams": "^1.0.0", - "randexp": "0.4.6" - }, - "bin": { - "nearley-railroad": "bin/nearley-railroad.js", - "nearley-test": "bin/nearley-test.js", - "nearley-unparse": "bin/nearley-unparse.js", - "nearleyc": "bin/nearleyc.js" - }, - "funding": { - "type": "individual", - "url": "https://nearley.js.org/#give-to-nearley" + "engines": { + "node": ">=0.4.0" } }, - "node_modules/nearley/node_modules/commander": { - "version": "2.20.3", - "license": "MIT" - }, - "node_modules/neo-async": { - "version": "2.6.2", + "node_modules/detect-newline": { + "version": "3.1.0", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/nhsd-psu-sandbox": { - "resolved": "packages/nhsd-psu-sandbox", - "link": true + "node_modules/dezalgo": { + "version": "1.0.4", + "dev": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } }, - "node_modules/nhsNotifyLambda": { - "resolved": "packages/nhsNotifyLambda", - "link": true + "node_modules/diff": { + "version": "4.0.2", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } }, - "node_modules/nhsNotifyUpdateCallback": { - "resolved": "packages/nhsNotifyUpdateCallback", - "link": true + "node_modules/discontinuous-range": { + "version": "1.0.0", + "license": "MIT" }, - "node_modules/nise": { - "version": "6.1.1", + "node_modules/dompurify": { + "version": "3.2.7", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^13.0.1", - "@sinonjs/text-encoding": "^0.7.3", - "just-extend": "^6.2.0", - "path-to-regexp": "^8.1.0" + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" } }, - "node_modules/no-case": { + "node_modules/dot-case": { "version": "3.0.4", "license": "MIT", "dependencies": { - "lower-case": "^2.0.2", + "no-case": "^3.0.4", "tslib": "^2.0.3" } }, - "node_modules/nock": { - "version": "14.0.10", - "license": "MIT", - "dependencies": { - "@mswjs/interceptors": "^0.39.5", - "json-stringify-safe": "^5.0.1", - "propagate": "^2.0.0" - }, - "engines": { - "node": ">=18.20.0 <20 || >=20.12.1" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", + "node_modules/dotenv": { + "version": "16.4.7", "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, + "license": "BSD-2-Clause", "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" + "node": ">=12" }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "funding": { + "url": "https://dotenvx.com" } }, - "node_modules/node-fetch-h2": { - "version": "2.3.0", - "dev": true, + "node_modules/dunder-proto": { + "version": "1.0.1", "license": "MIT", "dependencies": { - "http2-client": "^1.2.5" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" }, "engines": { - "node": "4.x || >=6.0.0" + "node": ">= 0.4" } }, - "node_modules/node-int64": { - "version": "0.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/node-readfiles": { + "node_modules/eastasianwidth": { "version": "0.2.0", "dev": true, - "license": "MIT", - "dependencies": { - "es6-promise": "^3.2.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.21", - "dev": true, "license": "MIT" }, - "node_modules/nopt": { - "version": "4.0.3", - "dev": true, - "license": "ISC", - "dependencies": { - "abbrev": "1", - "osenv": "^0.1.4" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", + "node_modules/electron-to-chromium": { + "version": "1.5.227", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } + "license": "ISC" }, - "node_modules/normalize-path": { - "version": "3.0.0", + "node_modules/emittery": { + "version": "0.13.1", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" } }, - "node_modules/npm-normalize-package-bin": { - "version": "1.0.1", + "node_modules/emoji-regex": { + "version": "9.2.2", "dev": true, - "license": "ISC" + "license": "MIT" }, - "node_modules/npm-run-path": { - "version": "4.0.1", + "node_modules/error-ex": { + "version": "1.3.4", "dev": true, "license": "MIT", "dependencies": { - "path-key": "^3.0.0" - }, + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/oas-kit-common": { - "version": "1.0.8", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "fast-safe-stringify": "^2.0.7" + "node_modules/es-errors": { + "version": "1.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" } }, - "node_modules/oas-linter": { - "version": "3.2.2", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/es-object-atoms": { + "version": "1.1.1", + "license": "MIT", "dependencies": { - "@exodus/schemasafe": "^1.0.0-rc.2", - "should": "^13.2.1", - "yaml": "^1.10.0" + "es-errors": "^1.3.0" }, - "funding": { - "url": "https://github.com/Mermade/oas-kit?sponsor=1" + "engines": { + "node": ">= 0.4" } }, - "node_modules/oas-resolver": { - "version": "2.5.6", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "license": "MIT", "dependencies": { - "node-fetch-h2": "^2.3.0", - "oas-kit-common": "^1.0.8", - "reftools": "^1.1.9", - "yaml": "^1.10.0", - "yargs": "^17.0.1" - }, - "bin": { - "resolve": "resolve.js" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, - "funding": { - "url": "https://github.com/Mermade/oas-kit?sponsor=1" + "engines": { + "node": ">= 0.4" } }, - "node_modules/oas-schema-walker": { - "version": "1.1.5", + "node_modules/es6-promise": { + "version": "3.3.1", "dev": true, - "license": "BSD-3-Clause", - "funding": { - "url": "https://github.com/Mermade/oas-kit?sponsor=1" - } + "license": "MIT" }, - "node_modules/oas-validator": { - "version": "5.0.8", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "call-me-maybe": "^1.0.1", - "oas-kit-common": "^1.0.8", - "oas-linter": "^3.2.2", - "oas-resolver": "^2.5.6", - "oas-schema-walker": "^1.1.5", - "reftools": "^1.1.9", - "should": "^13.2.1", - "yaml": "^1.10.0" + "node_modules/esbuild": { + "version": "0.27.2", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" }, - "funding": { - "url": "https://github.com/Mermade/oas-kit?sponsor=1" + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" } }, - "node_modules/object-assign": { - "version": "4.1.1", + "node_modules/escalade": { + "version": "3.2.0", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/obliterator": { - "version": "1.6.1", - "license": "MIT" - }, - "node_modules/once": { - "version": "1.4.0", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" + "node": ">=6" } }, - "node_modules/onetime": { - "version": "5.1.2", + "node_modules/escape-string-regexp": { + "version": "4.0.0", "dev": true, "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/openapi-sampler": { - "version": "1.6.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.7", - "fast-xml-parser": "^4.5.0", - "json-pointer": "0.6.2" - } - }, - "node_modules/openapi-sampler/node_modules/fast-xml-parser": { - "version": "4.5.3", + "node_modules/eslint": { + "version": "9.39.2", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], "license": "MIT", + "peer": true, "dependencies": { - "strnum": "^1.1.1" + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" }, "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/openapi-sampler/node_modules/strnum": { - "version": "1.1.2", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true } - ], - "license": "MIT" + } }, - "node_modules/optionator": { - "version": "0.9.4", + "node_modules/eslint-config-prettier": { + "version": "10.1.8", "dev": true, "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" + "peer": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" }, - "engines": { - "node": ">= 0.8.0" + "peerDependencies": { + "eslint": ">=7.0.0" } }, - "node_modules/os-homedir": { - "version": "1.0.2", + "node_modules/eslint-plugin-import-newlines": { + "version": "1.4.0", "dev": true, "license": "MIT", + "bin": { + "import-linter": "lib/index.js" + }, "engines": { - "node": ">=0.10.0" + "node": ">=10.0.0" + }, + "peerDependencies": { + "eslint": ">=6.0.0" } }, - "node_modules/os-tmpdir": { - "version": "1.0.2", + "node_modules/eslint-plugin-prettier": { + "version": "5.5.5", "dev": true, "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.12" + }, "engines": { - "node": ">=0.10.0" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } } }, - "node_modules/osenv": { - "version": "0.1.5", + "node_modules/eslint-scope": { + "version": "8.4.0", "dev": true, - "license": "ISC", + "license": "BSD-2-Clause", "dependencies": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/outdent": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/outdent/-/outdent-0.8.0.tgz", - "integrity": "sha512-KiOAIsdpUTcAXuykya5fnVVT+/5uS0Q1mrkRHcF89tpieSmY33O/tmc54CqwA+bfhbtEfZUNLHaPUiB9X3jt1A==", - "dev": true, - "license": "MIT" - }, - "node_modules/outvariant": { - "version": "1.4.3", - "license": "MIT" - }, - "node_modules/p-limit": { - "version": "3.1.0", + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, + "license": "Apache-2.0", "engines": { - "node": ">=10" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "node_modules/p-locate": { - "version": "5.0.0", + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", "dev": true, "license": "MIT", "dependencies": { - "p-limit": "^3.0.2" - }, + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=10" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "node_modules/p-try": { - "version": "2.2.0", + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 4" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/param-case": { - "version": "3.0.4", - "license": "MIT", + "license": "ISC", "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "node_modules/parent-module": { - "version": "1.0.1", + "node_modules/espree": { + "version": "10.4.0", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "callsites": "^3.0.0" + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": ">=6" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/parse-json": { - "version": "5.2.0", + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, + "license": "Apache-2.0", "engines": { - "node": ">=8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "node_modules/pascal-case": { - "version": "3.1.2", - "license": "MIT", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" + "node_modules/esprima": { + "version": "4.0.1", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" } }, - "node_modules/path-browserify": { - "version": "1.0.1", + "node_modules/esquery": { + "version": "1.6.0", "dev": true, - "license": "MIT" + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } }, - "node_modules/path-case": { - "version": "3.0.4", - "license": "MIT", + "node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" } }, - "node_modules/path-exists": { - "version": "4.0.0", + "node_modules/estraverse": { + "version": "5.3.0", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "engines": { - "node": ">=8" + "node": ">=4.0" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", + "node_modules/esutils": { + "version": "2.0.3", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } }, - "node_modules/path-key": { - "version": "3.1.1", + "node_modules/event-target-shim": { + "version": "5.0.1", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/path-parse": { - "version": "1.0.7", + "node_modules/eventemitter3": { + "version": "5.0.1", "dev": true, "license": "MIT" }, - "node_modules/path-scurry": { - "version": "1.11.1", + "node_modules/events": { + "version": "1.1.1", + "license": "MIT", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", "dev": true, "license": "ISC" }, - "node_modules/path-to-regexp": { - "version": "8.3.0", + "node_modules/exit-x": { + "version": "0.2.2", "dev": true, "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "engines": { + "node": ">= 0.8.0" } }, - "node_modules/perfect-scrollbar": { - "version": "1.5.6", + "node_modules/expect": { + "version": "30.2.0", "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.2.0", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", "license": "MIT" }, - "node_modules/picocolors": { - "version": "1.1.1", + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", "dev": true, - "license": "ISC" + "license": "Apache-2.0" }, - "node_modules/picomatch": { - "version": "2.3.1", + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } + "license": "MIT" }, - "node_modules/pirates": { - "version": "4.0.7", + "node_modules/fast-safe-stringify": { + "version": "2.1.1", "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], "license": "MIT", - "engines": { - "node": ">= 6" + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" } }, - "node_modules/pkg-dir": { - "version": "4.2.0", + "node_modules/fb-watchman": { + "version": "2.0.2", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" + "bser": "2.1.1" } }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", + "node_modules/file-entry-cache": { + "version": "8.0.0", "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "flat-cache": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=16.0.0" } }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", + "node_modules/fill-range": { + "version": "7.1.1", "dev": true, "license": "MIT", "dependencies": { - "p-locate": "^4.1.0" + "to-regex-range": "^5.0.1" }, "engines": { "node": ">=8" } }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", + "node_modules/find-up": { + "version": "5.0.0", "dev": true, "license": "MIT", "dependencies": { - "p-try": "^2.0.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", + "node_modules/flat-cache": { + "version": "4.0.1", "dev": true, "license": "MIT", "dependencies": { - "p-limit": "^2.2.0" + "flatted": "^3.2.9", + "keyv": "^4.5.4" }, "engines": { - "node": ">=8" + "node": ">=16" } }, - "node_modules/pluralize": { - "version": "8.0.0", + "node_modules/flatted": { + "version": "3.3.3", "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } + "license": "ISC" }, - "node_modules/polished": { - "version": "4.3.1", - "dev": true, + "node_modules/fluent-transpiler": { + "version": "0.2.1", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.17.8" + "@fluent/syntax": "0.18.1", + "change-case": "4.1.2", + "commander": "9.4.0" }, - "engines": { - "node": ">=10" + "bin": { + "ftl": "cli.js" } }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", + "node_modules/fluent-transpiler/node_modules/commander": { + "version": "9.4.0", "license": "MIT", "engines": { - "node": ">= 0.4" + "node": "^12.20.0 || >=14" } }, - "node_modules/postcss": { - "version": "8.4.49", - "dev": true, + "node_modules/follow-redirects": { + "version": "1.15.11", "funding": [ { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" } ], "license": "MIT", - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "dev": true, - "license": "MIT" - }, - "node_modules/postDatedLambda": { - "resolved": "packages/postDatedLambda", - "link": true - }, - "node_modules/pratica": { - "version": "2.3.0", - "license": "Apache-2.0" - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "dev": true, - "license": "MIT", "engines": { - "node": ">= 0.8.0" + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } } }, - "node_modules/prettier": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", - "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", - "dev": true, + "node_modules/for-each": { + "version": "0.3.5", "license": "MIT", - "peer": true, - "bin": { - "prettier": "bin/prettier.cjs" + "dependencies": { + "is-callable": "^1.2.7" }, "engines": { - "node": ">=14" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.1", + "node_modules/foreach": { + "version": "2.0.6", "dev": true, - "license": "MIT", + "license": "MIT" + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "dev": true, + "license": "ISC", "dependencies": { - "fast-diff": "^1.1.2" + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" }, "engines": { - "node": ">=6.0.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } - }, - "node_modules/pretty-format": { - "version": "30.2.0", - "dev": true, + }, + "node_modules/form-data": { + "version": "4.0.4", "license": "MIT", "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 6" } }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", + "node_modules/fs.realpath": { + "version": "1.0.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "license": "ISC" }, - "node_modules/prismjs": { - "version": "1.30.0", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/prop-types": { - "version": "15.8.1", - "dev": true, + "node_modules/function-bind": { + "version": "1.1.2", "license": "MIT", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/prop-types/node_modules/react-is": { - "version": "16.13.1", + "node_modules/gensync": { + "version": "1.0.0-beta.2", "dev": true, - "license": "MIT" - }, - "node_modules/propagate": { - "version": "2.0.1", "license": "MIT", "engines": { - "node": ">= 8" + "node": ">=6.9.0" } }, - "node_modules/protobufjs": { - "version": "7.5.4", + "node_modules/get-caller-file": { + "version": "2.0.5", "dev": true, - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, + "license": "ISC", "engines": { - "node": ">=12.0.0" + "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "license": "MIT" - }, - "node_modules/psuRestoreValidationLambda": { - "resolved": "packages/psuRestoreValidationLambda", - "link": true - }, - "node_modules/punycode": { - "version": "2.3.1", + "node_modules/get-intrinsic": { + "version": "1.3.0", "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, "engines": { - "node": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/pure-rand": { - "version": "7.0.1", + "node_modules/get-package-type": { + "version": "0.1.0", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, - "node_modules/querystring": { - "version": "0.2.0", + "license": "MIT", "engines": { - "node": ">=0.4.x" + "node": ">=8.0.0" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/railroad-diagrams": { - "version": "1.0.0", - "license": "CC0-1.0" - }, - "node_modules/randexp": { - "version": "0.4.6", + "node_modules/get-proto": { + "version": "1.0.1", "license": "MIT", "dependencies": { - "discontinuous-range": "1.0.0", - "ret": "~0.1.10" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">=0.12" + "node": ">= 0.4" } }, - "node_modules/randombytes": { - "version": "2.1.0", + "node_modules/glob": { + "version": "10.5.0", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "safe-buffer": "^5.1.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/react": { - "version": "19.2.1", + "node_modules/glob-parent": { + "version": "6.0.2", "dev": true, - "license": "MIT", - "peer": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, "engines": { - "node": ">=0.10.0" + "node": ">=10.13.0" } }, - "node_modules/react-dom": { - "version": "19.2.1", + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "license": "BSD-2-Clause" + }, + "node_modules/globals": { + "version": "14.0.0", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "scheduler": "^0.27.0" + "engines": { + "node": ">=18" }, - "peerDependencies": { - "react": "^19.2.1" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/react-is": { - "version": "18.3.1", - "dev": true, - "license": "MIT" - }, - "node_modules/react-tabs": { - "version": "6.1.0", - "dev": true, + "node_modules/gopd": { + "version": "1.2.0", "license": "MIT", - "dependencies": { - "clsx": "^2.0.0", - "prop-types": "^15.5.0" + "engines": { + "node": ">= 0.4" }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/read-installed": { - "version": "4.0.3", + "node_modules/graceful-fs": { + "version": "4.2.11", + "license": "ISC" + }, + "node_modules/gsul": { + "resolved": "packages/gsul", + "link": true + }, + "node_modules/handlebars": { + "version": "4.7.8", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "debuglog": "^1.0.1", - "read-package-json": "^2.0.0", - "readdir-scoped-modules": "^1.0.0", - "semver": "2 || 3 || 4 || 5", - "slide": "~1.1.3", - "util-extend": "^1.0.1" + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" }, "optionalDependencies": { - "graceful-fs": "^4.1.2" + "uglify-js": "^3.1.4" } }, - "node_modules/read-installed/node_modules/semver": { - "version": "5.7.2", + "node_modules/has-flag": { + "version": "4.0.0", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/read-package-json": { - "version": "2.1.2", - "dev": true, - "license": "ISC", + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "license": "MIT", "dependencies": { - "glob": "^7.1.1", - "json-parse-even-better-errors": "^2.3.0", - "normalize-package-data": "^2.0.0", - "npm-normalize-package-bin": "^1.0.0" + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/read-package-json/node_modules/brace-expansion": { - "version": "1.1.12", - "dev": true, + "node_modules/has-symbols": { + "version": "1.1.0", "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/read-package-json/node_modules/glob": { - "version": "7.2.3", - "dev": true, - "license": "ISC", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "has-symbols": "^1.0.3" }, "engines": { - "node": "*" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/read-package-json/node_modules/minimatch": { - "version": "3.1.2", - "dev": true, - "license": "ISC", + "node_modules/hasown": { + "version": "2.0.2", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "function-bind": "^1.1.2" }, "engines": { - "node": "*" + "node": ">= 0.4" } }, - "node_modules/readdir-scoped-modules": { - "version": "1.1.0", - "dev": true, - "license": "ISC", + "node_modules/header-case": { + "version": "2.0.4", + "license": "MIT", "dependencies": { - "debuglog": "^1.0.1", - "dezalgo": "^1.0.0", - "graceful-fs": "^4.1.2", - "once": "^1.3.0" + "capital-case": "^1.0.4", + "tslib": "^2.0.3" } }, - "node_modules/readdirp": { - "version": "3.6.0", + "node_modules/hosted-git-info": { + "version": "2.8.9", "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } + "license": "ISC" }, - "node_modules/redoc": { - "version": "2.5.1", + "node_modules/html-escaper": { + "version": "2.0.2", "dev": true, - "license": "MIT", - "dependencies": { - "@redocly/openapi-core": "^1.4.0", - "classnames": "^2.3.2", - "decko": "^1.2.0", - "dompurify": "^3.2.4", - "eventemitter3": "^5.0.1", - "json-pointer": "^0.6.2", - "lunr": "^2.3.9", - "mark.js": "^8.11.1", - "marked": "^4.3.0", - "mobx-react": "9.2.0", - "openapi-sampler": "^1.5.0", - "path-browserify": "^1.0.1", - "perfect-scrollbar": "^1.5.5", - "polished": "^4.2.2", - "prismjs": "^1.29.0", - "prop-types": "^15.8.1", - "react-tabs": "^6.0.2", - "slugify": "~1.4.7", - "stickyfill": "^1.1.1", - "swagger2openapi": "^7.0.8", - "url-template": "^2.0.8" - }, - "engines": { - "node": ">=6.9", - "npm": ">=3.0.0" - }, - "peerDependencies": { - "core-js": "^3.1.4", - "mobx": "^6.0.4", - "react": "^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "styled-components": "^4.1.1 || ^5.1.1 || ^6.0.5" - } + "license": "MIT" }, - "node_modules/redoc/node_modules/@redocly/config": { - "version": "0.22.2", + "node_modules/http2-client": { + "version": "1.3.5", "dev": true, "license": "MIT" }, - "node_modules/redoc/node_modules/@redocly/openapi-core": { - "version": "1.34.5", + "node_modules/https-proxy-agent": { + "version": "7.0.6", "dev": true, "license": "MIT", "dependencies": { - "@redocly/ajv": "^8.11.2", - "@redocly/config": "^0.22.0", - "colorette": "^1.2.0", - "https-proxy-agent": "^7.0.5", - "js-levenshtein": "^1.1.6", - "js-yaml": "^4.1.0", - "minimatch": "^5.0.1", - "pluralize": "^8.0.0", - "yaml-ast-parser": "0.0.43" + "agent-base": "^7.1.2", + "debug": "4" }, "engines": { - "node": ">=18.17.0", - "npm": ">=9.5.0" + "node": ">= 14" } }, - "node_modules/redoc/node_modules/argparse": { - "version": "2.0.1", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/redoc/node_modules/js-yaml": { - "version": "4.1.1", + "node_modules/human-signals": { + "version": "2.1.0", "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" } }, - "node_modules/redoc/node_modules/marked": { - "version": "4.3.0", + "node_modules/ieee754": { + "version": "1.1.13", + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "7.0.5", "dev": true, "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, "engines": { - "node": ">= 12" + "node": ">= 4" } }, - "node_modules/redoc/node_modules/minimatch": { - "version": "5.1.6", + "node_modules/import-fresh": { + "version": "3.3.1", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": ">=10" - } - }, - "node_modules/reftools": { - "version": "1.1.9", - "dev": true, - "license": "BSD-3-Clause", + "node": ">=6" + }, "funding": { - "url": "https://github.com/Mermade/oas-kit?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/require-directory": { - "version": "2.1.1", + "node_modules/import-local": { + "version": "3.2.0", "dev": true, "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/require-from-string": { - "version": "2.0.2", + "node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=0.8.19" } }, - "node_modules/resolve": { - "version": "1.22.10", + "node_modules/inflight": { + "version": "1.0.6", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "license": "ISC" + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -11349,52 +7005,60 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-cwd": { - "version": "3.0.0", + "node_modules/is-arrayish": { + "version": "0.2.1", "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", + "node_modules/is-binary-path": { + "version": "2.1.0", "dev": true, "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, "engines": { "node": ">=8" } }, - "node_modules/resolve-from": { - "version": "4.0.0", + "node_modules/is-buffer": { + "version": "2.0.5", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT", "engines": { "node": ">=4" } }, - "node_modules/ret": { - "version": "0.1.15", + "node_modules/is-callable": { + "version": "1.2.7", "license": "MIT", "engines": { - "node": ">=0.12" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safe-buffer": { - "version": "5.1.2", + "node_modules/is-core-module": { + "version": "2.16.1", "dev": true, - "license": "MIT" - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -11403,4756 +7067,4299 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/sax": { - "version": "1.2.1", - "license": "ISC" - }, - "node_modules/scheduler": { - "version": "0.27.0", + "node_modules/is-extglob": { + "version": "2.1.1", "dev": true, - "license": "MIT" - }, - "node_modules/schemes": { - "version": "1.4.0", "license": "MIT", - "dependencies": { - "extend": "^3.0.0" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/semver": { - "version": "7.7.3", + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/sentence-case": { - "version": "3.0.4", + "node_modules/is-generator-fn": { + "version": "2.1.0", + "dev": true, "license": "MIT", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3", - "upper-case-first": "^2.0.2" + "engines": { + "node": ">=6" } }, - "node_modules/set-cookie-parser": { - "version": "2.7.1", - "dev": true, - "license": "MIT" - }, - "node_modules/set-function-length": { - "version": "1.2.2", + "node_modules/is-generator-function": { + "version": "1.1.0", "license": "MIT", "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/shallowequal": { - "version": "1.1.0", - "dev": true, - "license": "MIT" - }, - "node_modules/shebang-command": { - "version": "2.0.0", + "node_modules/is-glob": { + "version": "4.0.3", "dev": true, "license": "MIT", "dependencies": { - "shebang-regex": "^3.0.0" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/shebang-regex": { - "version": "3.0.0", + "node_modules/is-node-process": { + "version": "1.2.0", + "license": "MIT" + }, + "node_modules/is-number": { + "version": "7.0.0", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.12.0" } }, - "node_modules/should": { - "version": "13.2.3", - "dev": true, + "node_modules/is-regex": { + "version": "1.2.1", "license": "MIT", "dependencies": { - "should-equal": "^2.0.0", - "should-format": "^3.0.3", - "should-type": "^1.4.0", - "should-type-adaptors": "^1.0.1", - "should-util": "^1.0.0" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/should-equal": { - "version": "2.0.0", - "dev": true, + "node_modules/is-retry-allowed": { + "version": "2.2.0", "license": "MIT", - "dependencies": { - "should-type": "^1.4.0" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/should-format": { - "version": "3.0.3", + "node_modules/is-stream": { + "version": "2.0.1", "dev": true, "license": "MIT", - "dependencies": { - "should-type": "^1.3.0", - "should-type-adaptors": "^1.0.1" + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/should-type": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/should-type-adaptors": { - "version": "1.1.0", - "dev": true, + "node_modules/is-typed-array": { + "version": "1.1.15", "license": "MIT", "dependencies": { - "should-type": "^1.3.0", - "should-util": "^1.0.0" + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/should-util": { - "version": "1.0.1", - "dev": true, + "node_modules/isarray": { + "version": "1.0.0", "license": "MIT" }, - "node_modules/signal-exit": { - "version": "4.1.0", + "node_modules/isexe": { + "version": "2.0.0", "dev": true, - "license": "ISC", + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=8" } }, - "node_modules/simple-websocket": { - "version": "9.1.0", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "debug": "^4.3.1", - "queue-microtask": "^1.2.2", - "randombytes": "^2.1.0", - "readable-stream": "^3.6.0", - "ws": "^7.4.2" + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" } }, - "node_modules/simple-websocket/node_modules/readable-stream": { - "version": "3.6.2", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">= 6" + "node": ">=10" } }, - "node_modules/sinon": { - "version": "18.0.1", + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "11.2.2", - "@sinonjs/samsam": "^8.0.0", - "diff": "^5.2.0", - "nise": "^6.0.0", - "supports-color": "^7" + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" + "engines": { + "node": ">=10" } }, - "node_modules/sinon/node_modules/@sinonjs/fake-timers": { - "version": "11.2.2", + "node_modules/istanbul-reports": { + "version": "3.2.0", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "@sinonjs/commons": "^3.0.0" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/sinon/node_modules/diff": { - "version": "5.2.0", + "node_modules/jackspeak": { + "version": "3.4.3", "dev": true, - "license": "BSD-3-Clause", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jest": { + "version": "30.2.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/core": "30.2.0", + "@jest/types": "30.2.0", + "import-local": "^3.2.0", + "jest-cli": "30.2.0" + }, + "bin": { + "jest": "bin/jest.js" + }, "engines": { - "node": ">=0.3.1" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/slash": { - "version": "3.0.0", + "node_modules/jest-changed-files": { + "version": "30.2.0", "dev": true, "license": "MIT", + "dependencies": { + "execa": "^5.1.1", + "jest-util": "30.2.0", + "p-limit": "^3.1.0" + }, "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/slide": { - "version": "1.1.6", + "node_modules/jest-circus": { + "version": "30.2.0", "dev": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "co": "^4.6.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "p-limit": "^3.1.0", + "pretty-format": "30.2.0", + "pure-rand": "^7.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, "engines": { - "node": "*" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/slugify": { - "version": "1.4.7", + "node_modules/jest-cli": { + "version": "30.2.0", "dev": true, "license": "MIT", + "dependencies": { + "@jest/core": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "yargs": "^17.7.2" + }, + "bin": { + "jest": "bin/jest.js" + }, "engines": { - "node": ">=8.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/smtp-address-parser": { - "version": "1.1.0", + "node_modules/jest-config": { + "version": "30.2.0", + "dev": true, "license": "MIT", "dependencies": { - "nearley": "^2.20.1" + "@babel/core": "^7.27.4", + "@jest/get-type": "30.1.0", + "@jest/pattern": "30.0.1", + "@jest/test-sequencer": "30.2.0", + "@jest/types": "30.2.0", + "babel-jest": "30.2.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "jest-circus": "30.2.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-runner": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "micromatch": "^4.0.8", + "parse-json": "^5.2.0", + "pretty-format": "30.2.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">=0.10" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "esbuild-register": ">=3.4.0", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "esbuild-register": { + "optional": true + }, + "ts-node": { + "optional": true + } } }, - "node_modules/snake-case": { - "version": "3.0.4", + "node_modules/jest-diff": { + "version": "30.2.0", + "dev": true, "license": "MIT", "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/source-map": { - "version": "0.6.1", + "node_modules/jest-docblock": { + "version": "30.2.0", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", + "dependencies": { + "detect-newline": "^3.1.0" + }, "engines": { - "node": ">=0.10.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/source-map-js": { - "version": "1.2.1", + "node_modules/jest-each": { + "version": "30.2.0", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "jest-util": "30.2.0", + "pretty-format": "30.2.0" + }, "engines": { - "node": ">=0.10.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/source-map-support": { - "version": "0.5.13", + "node_modules/jest-environment-node": { + "version": "30.2.0", "dev": true, "license": "MIT", "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-mock": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/spdx-compare": { - "version": "1.0.0", + "node_modules/jest-haste-map": { + "version": "30.2.0", "dev": true, "license": "MIT", "dependencies": { - "array-find-index": "^1.0.2", - "spdx-expression-parse": "^3.0.0", - "spdx-ranges": "^2.0.0" + "@jest/types": "30.2.0", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", + "micromatch": "^4.0.8", + "walker": "^1.0.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" } }, - "node_modules/spdx-correct": { - "version": "3.2.0", + "node_modules/jest-junit": { + "version": "16.0.0", "dev": true, "license": "Apache-2.0", "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "mkdirp": "^1.0.4", + "strip-ansi": "^6.0.1", + "uuid": "^8.3.2", + "xml": "^1.0.1" + }, + "engines": { + "node": ">=10.12.0" } }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", + "node_modules/jest-junit/node_modules/ansi-regex": { + "version": "5.0.1", "dev": true, - "license": "CC-BY-3.0" + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", + "node_modules/jest-junit/node_modules/strip-ansi": { + "version": "6.0.1", "dev": true, "license": "MIT", "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/spdx-license-ids": { - "version": "3.0.22", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/spdx-ranges": { - "version": "2.1.1", - "dev": true, - "license": "(MIT AND CC-BY-3.0)" - }, - "node_modules/spdx-satisfies": { - "version": "4.0.1", + "node_modules/jest-junit/node_modules/uuid": { + "version": "8.3.2", "dev": true, "license": "MIT", - "dependencies": { - "spdx-compare": "^1.0.0", - "spdx-expression-parse": "^3.0.0", - "spdx-ranges": "^2.0.0" + "bin": { + "uuid": "dist/bin/uuid" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "license": "BSD-3-Clause" - }, - "node_modules/stack-utils": { - "version": "2.0.6", + "node_modules/jest-leak-detector": { + "version": "30.2.0", "dev": true, "license": "MIT", "dependencies": { - "escape-string-regexp": "^2.0.0" + "@jest/get-type": "30.1.0", + "pretty-format": "30.2.0" }, "engines": { - "node": ">=10" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", + "node_modules/jest-matcher-utils": { + "version": "30.2.0", "dev": true, "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.2.0", + "pretty-format": "30.2.0" + }, "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/statusLambda": { - "resolved": "packages/statusLambda", - "link": true - }, - "node_modules/stickyfill": { - "version": "1.1.1", - "dev": true - }, - "node_modules/strict-event-emitter": { - "version": "0.5.1", - "license": "MIT" - }, - "node_modules/string_decoder": { - "version": "1.1.1", + "node_modules/jest-message-util": { + "version": "30.2.0", "dev": true, "license": "MIT", "dependencies": { - "safe-buffer": "~5.1.0" + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.2.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "pretty-format": "30.2.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/string-length": { - "version": "4.0.2", + "node_modules/jest-mock": { + "version": "30.2.0", "dev": true, "license": "MIT", "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-util": "30.2.0" }, "engines": { - "node": ">=10" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/string-length/node_modules/ansi-regex": { - "version": "5.0.1", + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } } }, - "node_modules/string-length/node_modules/strip-ansi": { - "version": "6.0.1", + "node_modules/jest-regex-util": { + "version": "30.0.1", "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/string-width": { - "version": "5.1.2", + "node_modules/jest-resolve": { + "version": "30.2.0", "dev": true, "license": "MIT", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-pnp-resolver": "^1.2.3", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "slash": "^3.0.0", + "unrs-resolver": "^1.7.11" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", + "node_modules/jest-resolve-dependencies": { + "version": "30.2.0", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "jest-regex-util": "30.0.1", + "jest-snapshot": "30.2.0" }, "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", + "node_modules/jest-runner": { + "version": "30.2.0", "dev": true, "license": "MIT", + "dependencies": { + "@jest/console": "30.2.0", + "@jest/environment": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-leak-detector": "30.2.0", + "jest-message-util": "30.2.0", + "jest-resolve": "30.2.0", + "jest-runtime": "30.2.0", + "jest-util": "30.2.0", + "jest-watcher": "30.2.0", + "jest-worker": "30.2.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", + "node_modules/jest-runtime": { + "version": "30.2.0", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/globals": "30.2.0", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.1.0", + "collect-v8-coverage": "^1.0.2", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" }, "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/strip-ansi": { - "version": "7.1.2", + "node_modules/jest-snapshot": { + "version": "30.2.0", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "@babel/core": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/types": "^7.27.3", + "@jest/expect-utils": "30.2.0", + "@jest/get-type": "30.1.0", + "@jest/snapshot-utils": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0", + "chalk": "^4.1.2", + "expect": "30.2.0", + "graceful-fs": "^4.2.11", + "jest-diff": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "pretty-format": "30.2.0", + "semver": "^7.7.2", + "synckit": "^0.11.8" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", + "node_modules/jest-util": { + "version": "30.2.0", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" }, "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/strip-bom": { - "version": "4.0.0", + "node_modules/jest-util/node_modules/picomatch": { + "version": "4.0.3", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/strip-final-newline": { - "version": "2.0.0", + "node_modules/jest-validate": { + "version": "30.2.0", "dev": true, "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.2.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.2.0" + }, "engines": { - "node": ">=6" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strnum": { - "version": "2.1.2", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, - "node_modules/styled-components": { - "version": "6.1.19", + "node_modules/jest-watcher": { + "version": "30.2.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@emotion/is-prop-valid": "1.2.2", - "@emotion/unitless": "0.8.1", - "@types/stylis": "4.2.5", - "css-to-react-native": "3.2.0", - "csstype": "3.1.3", - "postcss": "8.4.49", - "shallowequal": "1.1.0", - "stylis": "4.3.2", - "tslib": "2.6.2" + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "jest-util": "30.2.0", + "string-length": "^4.0.2" }, "engines": { - "node": ">= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/styled-components" - }, - "peerDependencies": { - "react": ">= 16.8.0", - "react-dom": ">= 16.8.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/styled-components/node_modules/tslib": { - "version": "2.6.2", - "dev": true, - "license": "0BSD" - }, - "node_modules/stylis": { - "version": "4.3.2", - "dev": true, - "license": "MIT" - }, - "node_modules/supports-color": { - "version": "7.2.0", + "node_modules/jest-worker": { + "version": "30.2.0", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@types/node": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.2.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.1.1" }, "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", "dev": true, "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/swagger2openapi": { - "version": "7.0.8", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "call-me-maybe": "^1.0.1", - "node-fetch": "^2.6.1", - "node-fetch-h2": "^2.3.0", - "node-readfiles": "^0.2.0", - "oas-kit-common": "^1.0.8", - "oas-resolver": "^2.5.6", - "oas-schema-walker": "^1.1.5", - "oas-validator": "^5.0.8", - "reftools": "^1.1.9", - "yaml": "^1.10.0", - "yargs": "^17.0.1" - }, - "bin": { - "boast": "boast.js", - "oas-validate": "oas-validate.js", - "swagger2openapi": "swagger2openapi.js" - }, - "funding": { - "url": "https://github.com/Mermade/oas-kit?sponsor=1" + "node_modules/jmespath": { + "version": "0.16.0", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.6.0" } }, - "node_modules/synckit": { - "version": "0.11.12", - "dev": true, + "node_modules/jose": { + "version": "6.1.3", "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.2.9" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, "funding": { - "url": "https://opencollective.com/synckit" + "url": "https://github.com/sponsors/panva" } }, - "node_modules/test-exclude": { - "version": "6.0.0", + "node_modules/js-levenshtein": { + "version": "1.1.6", "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.12", + "node_modules/js-tokens": { + "version": "4.0.0", "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.2", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", + "node_modules/jsesc": { + "version": "3.1.0", "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=6" } }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", + "node_modules/json-buffer": { + "version": "3.0.1", "dev": true, - "license": "ISC", + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-pointer": { + "version": "0.6.2", + "dev": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" + "foreach": "^2.0.4" } }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, + "node_modules/json-schema-to-ts": { + "version": "3.1.1", "license": "MIT", "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" }, "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" + "node": ">=16" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } + "license": "MIT" }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "license": "ISC" + }, + "node_modules/json5": { + "version": "2.2.3", "dev": true, "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" + "bin": { + "json5": "lib/cli.js" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "engines": { + "node": ">=6" } }, - "node_modules/tmpl": { - "version": "1.0.5", + "node_modules/jsonpath-rfc9535": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsonpath-rfc9535/-/jsonpath-rfc9535-1.3.0.tgz", + "integrity": "sha512-3jFHya7oZ45aDxIIdx+/zQARahHXxFSMWBkcBUldfXpLS9VCXDJyTKt35kQfEXLqh0K3Ixw/9xFnvcDStaxh7Q==", "dev": true, - "license": "BSD-3-Clause" + "license": "Apache-2.0", + "engines": { + "node": ">=20" + } }, - "node_modules/to-regex-range": { + "node_modules/jsonpointer": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", "dev": true, "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, "engines": { - "node": ">=8.0" + "node": ">=0.10.0" } }, - "node_modules/tr46": { - "version": "0.0.3", + "node_modules/just-extend": { + "version": "6.2.0", "dev": true, "license": "MIT" }, - "node_modules/treeify": { - "version": "1.1.0", + "node_modules/keyv": { + "version": "4.5.4", "dev": true, "license": "MIT", - "engines": { - "node": ">=0.6" + "dependencies": { + "json-buffer": "3.0.1" } }, - "node_modules/ts-algebra": { - "version": "2.0.0", - "license": "MIT" - }, - "node_modules/ts-api-utils": { - "version": "2.4.0", + "node_modules/leven": { + "version": "3.1.0", "dev": true, "license": "MIT", "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" + "node": ">=6" } }, - "node_modules/ts-jest": { - "version": "29.4.6", + "node_modules/levn": { + "version": "0.4.1", "dev": true, "license": "MIT", "dependencies": { - "bs-logger": "^0.2.6", - "fast-json-stable-stringify": "^2.1.0", - "handlebars": "^4.7.8", - "json5": "^2.2.3", - "lodash.memoize": "^4.1.2", - "make-error": "^1.3.6", - "semver": "^7.7.3", - "type-fest": "^4.41.0", - "yargs-parser": "^21.1.1" - }, - "bin": { - "ts-jest": "cli.js" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0 || ^30.0.0", - "@jest/types": "^29.0.0 || ^30.0.0", - "babel-jest": "^29.0.0 || ^30.0.0", - "jest": "^29.0.0 || ^30.0.0", - "jest-util": "^29.0.0 || ^30.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/transform": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jest-util": { - "optional": true - } + "node": ">= 0.8.0" } }, - "node_modules/ts-jest/node_modules/type-fest": { - "version": "4.41.0", + "node_modules/license-checker": { + "version": "25.0.1", "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" + "license": "BSD-3-Clause", + "dependencies": { + "chalk": "^2.4.1", + "debug": "^3.1.0", + "mkdirp": "^0.5.1", + "nopt": "^4.0.1", + "read-installed": "~4.0.3", + "semver": "^5.5.0", + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0", + "spdx-satisfies": "^4.0.0", + "treeify": "^1.1.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "bin": { + "license-checker": "bin/license-checker" } }, - "node_modules/ts-md5": { - "version": "2.0.1", + "node_modules/license-checker/node_modules/ansi-styles": { + "version": "3.2.1", + "dev": true, "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, "engines": { - "node": ">=18" + "node": ">=4" } }, - "node_modules/ts-node": { - "version": "10.9.2", + "node_modules/license-checker/node_modules/chalk": { + "version": "2.4.2", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } + "engines": { + "node": ">=4" } }, - "node_modules/tslib": { - "version": "2.8.1", - "license": "0BSD" + "node_modules/license-checker/node_modules/color-convert": { + "version": "1.9.3", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } }, - "node_modules/type-check": { - "version": "0.4.0", + "node_modules/license-checker/node_modules/color-name": { + "version": "1.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/license-checker/node_modules/debug": { + "version": "3.2.7", "dev": true, "license": "MIT", "dependencies": { - "prelude-ls": "^1.2.1" - }, + "ms": "^2.1.1" + } + }, + "node_modules/license-checker/node_modules/escape-string-regexp": { + "version": "1.0.5", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.8.0" + "node": ">=0.8.0" } }, - "node_modules/type-detect": { - "version": "4.0.8", + "node_modules/license-checker/node_modules/has-flag": { + "version": "3.0.0", "dev": true, "license": "MIT", "engines": { "node": ">=4" } }, - "node_modules/type-fest": { - "version": "0.21.3", + "node_modules/license-checker/node_modules/mkdirp": { + "version": "0.5.6", "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "bin": { + "mkdirp": "bin/cmd.js" } }, - "node_modules/typescript": { - "version": "5.9.3", + "node_modules/license-checker/node_modules/semver": { + "version": "5.7.2", "dev": true, - "license": "Apache-2.0", - "peer": true, + "license": "ISC", "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "semver": "bin/semver" + } + }, + "node_modules/license-checker/node_modules/supports-color": { + "version": "5.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" }, "engines": { - "node": ">=14.17" + "node": ">=4" } }, - "node_modules/typescript-eslint": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.53.1.tgz", - "integrity": "sha512-gB+EVQfP5RDElh9ittfXlhZJdjSU4jUSTyE2+ia8CYyNvet4ElfaLlAIqDvQV9JPknKx0jQH1racTYe/4LaLSg==", + "node_modules/lines-and-columns": { + "version": "1.2.4", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.53.1", - "@typescript-eslint/parser": "8.53.1", - "@typescript-eslint/typescript-estree": "8.53.1", - "@typescript-eslint/utils": "8.53.1" + "p-locate": "^5.0.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/uglify-js": { - "version": "3.19.3", + "node_modules/lodash.memoize": { + "version": "4.1.2", "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } + "license": "MIT" }, - "node_modules/ulid": { - "version": "3.0.2", + "node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/loose-envify": { + "version": "1.4.0", "dev": true, "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, "bin": { - "ulid": "dist/cli.js" + "loose-envify": "cli.js" } }, - "node_modules/undici": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", - "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", - "dev": true, + "node_modules/lower-case": { + "version": "2.0.2", "license": "MIT", - "engines": { - "node": ">=18.17" + "dependencies": { + "tslib": "^2.0.3" } }, - "node_modules/undici-types": { - "version": "7.16.0", + "node_modules/lru-cache": { + "version": "5.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lunr": { + "version": "2.3.9", "dev": true, "license": "MIT" }, - "node_modules/unrs-resolver": { - "version": "1.11.1", + "node_modules/make-dir": { + "version": "4.0.0", "dev": true, - "hasInstallScript": true, "license": "MIT", "dependencies": { - "napi-postinstall": "^0.3.0" + "semver": "^7.5.3" }, - "funding": { - "url": "https://opencollective.com/unrs-resolver" + "engines": { + "node": ">=10" }, - "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.11.1", - "@unrs/resolver-binding-android-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-x64": "1.11.1", - "@unrs/resolver-binding-freebsd-x64": "1.11.1", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", - "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-musl": "1.11.1", - "@unrs/resolver-binding-wasm32-wasi": "1.11.1", - "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", - "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", - "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", + "node_modules/make-error": { + "version": "1.3.6", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" + "tmpl": "1.0.5" } }, - "node_modules/updatePrescriptionStatus": { - "resolved": "packages/updatePrescriptionStatus", - "link": true + "node_modules/mark.js": { + "version": "8.11.1", + "dev": true, + "license": "MIT" }, - "node_modules/upper-case": { - "version": "2.0.2", + "node_modules/math-intrinsics": { + "version": "1.1.0", "license": "MIT", - "dependencies": { - "tslib": "^2.0.3" + "engines": { + "node": ">= 0.4" } }, - "node_modules/upper-case-first": { - "version": "2.0.2", + "node_modules/merge-stream": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.0.3" + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" + "node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, - "node_modules/url": { - "version": "0.10.3", + "node_modules/mime-types": { + "version": "2.1.35", "license": "MIT", "dependencies": { - "punycode": "1.3.2", - "querystring": "0.2.0" + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" } }, - "node_modules/url-template": { - "version": "2.0.8", - "dev": true, - "license": "BSD" - }, - "node_modules/url/node_modules/punycode": { - "version": "1.3.2", - "license": "MIT" - }, - "node_modules/use-sync-external-store": { - "version": "1.5.0", + "node_modules/mimic-fn": { + "version": "2.1.0", "dev": true, "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + "engines": { + "node": ">=6" } }, - "node_modules/util": { - "version": "0.12.5", - "license": "MIT", + "node_modules/minimatch": { + "version": "9.0.5", + "dev": true, + "license": "ISC", "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/util-extend": { - "version": "1.0.3", + "node_modules/minimist": { + "version": "1.2.8", "dev": true, - "license": "MIT" - }, - "node_modules/uuid": { - "version": "8.0.0", "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", + "node_modules/minipass": { + "version": "7.1.2", "dev": true, - "license": "MIT" + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", + "node_modules/mkdirp": { + "version": "1.0.4", "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" }, "engines": { - "node": ">=10.12.0" + "node": ">=10" } }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "dev": true, - "license": "Apache-2.0", + "node_modules/mnemonist": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.3.tgz", + "integrity": "sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw==", + "license": "MIT", "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "obliterator": "^1.6.1" } }, - "node_modules/walker": { - "version": "1.0.8", + "node_modules/mobx": { + "version": "6.15.0", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + } + }, + "node_modules/mobx-react": { + "version": "9.2.0", + "dev": true, + "license": "MIT", "dependencies": { - "makeerror": "1.0.12" + "mobx-react-lite": "^4.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + }, + "peerDependencies": { + "mobx": "^6.9.0", + "react": "^16.8.0 || ^17 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } } }, - "node_modules/watchpack": { - "version": "2.4.4", + "node_modules/mobx-react-lite": { + "version": "4.1.1", + "dev": true, "license": "MIT", "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" + "use-sync-external-store": "^1.4.0" }, - "engines": { - "node": ">=10.13.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + }, + "peerDependencies": { + "mobx": "^6.9.0", + "react": "^16.8.0 || ^17 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", + "node_modules/moo": { + "version": "0.5.2", + "license": "BSD-3-Clause" + }, + "node_modules/ms": { + "version": "2.1.3", "dev": true, - "license": "BSD-2-Clause" + "license": "MIT" }, - "node_modules/whatwg-url": { - "version": "5.0.0", + "node_modules/nanoid": { + "version": "3.3.11", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/which": { - "version": "2.0.2", + "node_modules/napi-postinstall": { + "version": "0.3.3", "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, + "license": "MIT", "bin": { - "node-which": "bin/node-which" + "napi-postinstall": "lib/cli.js" }, "engines": { - "node": ">= 8" + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" } }, - "node_modules/which-typed-array": { - "version": "1.1.19", + "node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/nearley": { + "version": "2.20.1", "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" + "commander": "^2.19.0", + "moo": "^0.5.0", + "railroad-diagrams": "^1.0.0", + "randexp": "0.4.6" }, - "engines": { - "node": ">= 0.4" + "bin": { + "nearley-railroad": "bin/nearley-railroad.js", + "nearley-test": "bin/nearley-test.js", + "nearley-unparse": "bin/nearley-unparse.js", + "nearleyc": "bin/nearleyc.js" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "individual", + "url": "https://nearley.js.org/#give-to-nearley" + } + }, + "node_modules/nearley/node_modules/commander": { + "version": "2.20.3", + "license": "MIT" + }, + "node_modules/neo-async": { + "version": "2.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/nhsd-psu-sandbox": { + "resolved": "packages/nhsd-psu-sandbox", + "link": true + }, + "node_modules/nhsNotifyLambda": { + "resolved": "packages/nhsNotifyLambda", + "link": true + }, + "node_modules/nhsNotifyUpdateCallback": { + "resolved": "packages/nhsNotifyUpdateCallback", + "link": true + }, + "node_modules/nise": { + "version": "6.1.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^13.0.1", + "@sinonjs/text-encoding": "^0.7.3", + "just-extend": "^6.2.0", + "path-to-regexp": "^8.1.0" } }, - "node_modules/word-wrap": { - "version": "1.2.5", - "dev": true, + "node_modules/no-case": { + "version": "3.0.4", "license": "MIT", - "engines": { - "node": ">=0.10.0" + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" } }, - "node_modules/wordwrap": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "dev": true, + "node_modules/nock": { + "version": "14.0.10", "license": "MIT", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "@mswjs/interceptors": "^0.39.5", + "json-stringify-safe": "^5.0.1", + "propagate": "^2.0.0" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=18.20.0 <20 || >=20.12.1" } }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", + "node_modules/node-fetch": { + "version": "2.7.0", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "whatwg-url": "^5.0.0" }, "engines": { - "node": ">=10" + "node": "4.x || >=6.0.0" }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", + "node_modules/node-fetch-h2": { + "version": "2.3.0", "dev": true, "license": "MIT", + "dependencies": { + "http2-client": "^1.2.5" + }, "engines": { - "node": ">=8" + "node": "4.x || >=6.0.0" } }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", + "node_modules/node-int64": { + "version": "0.4.0", "dev": true, "license": "MIT" }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", + "node_modules/node-readfiles": { + "version": "0.2.0", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" + "es6-promise": "^3.2.1" } }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", + "node_modules/node-releases": { + "version": "2.0.21", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", + "node_modules/nopt": { + "version": "4.0.3", "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" + "license": "ISC", + "dependencies": { + "abbrev": "1", + "osenv": "^0.1.4" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "bin": { + "nopt": "bin/nopt.js" } }, - "node_modules/wrappy": { - "version": "1.0.2", + "node_modules/normalize-package-data": { + "version": "2.5.0", "dev": true, - "license": "ISC" + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } }, - "node_modules/write-file-atomic": { - "version": "5.0.1", + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", "dev": true, "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "bin": { + "semver": "bin/semver" } }, - "node_modules/ws": { - "version": "7.5.10", + "node_modules/normalize-path": { + "version": "3.0.0", "dev": true, "license": "MIT", "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "node": ">=0.10.0" } }, - "node_modules/xml": { + "node_modules/npm-normalize-package-bin": { "version": "1.0.1", "dev": true, - "license": "MIT" + "license": "ISC" }, - "node_modules/xml2js": { - "version": "0.6.2", + "node_modules/npm-run-path": { + "version": "4.0.1", + "dev": true, "license": "MIT", "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" + "path-key": "^3.0.0" }, "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xmlbuilder": { - "version": "11.0.1", - "license": "MIT", - "engines": { - "node": ">=4.0" + "node": ">=8" } }, - "node_modules/y18n": { - "version": "5.0.8", + "node_modules/oas-kit-common": { + "version": "1.0.8", "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" + "license": "BSD-3-Clause", + "dependencies": { + "fast-safe-stringify": "^2.0.7" } }, - "node_modules/yallist": { - "version": "3.1.1", - "dev": true, - "license": "ISC" - }, - "node_modules/yaml": { - "version": "1.10.2", + "node_modules/oas-linter": { + "version": "3.2.2", "dev": true, - "license": "ISC", - "engines": { - "node": ">= 6" + "license": "BSD-3-Clause", + "dependencies": { + "@exodus/schemasafe": "^1.0.0-rc.2", + "should": "^13.2.1", + "yaml": "^1.10.0" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" } }, - "node_modules/yaml-ast-parser": { - "version": "0.0.43", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/yargs": { - "version": "17.7.2", + "node_modules/oas-resolver": { + "version": "2.5.6", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "node-fetch-h2": "^2.3.0", + "oas-kit-common": "^1.0.8", + "reftools": "^1.1.9", + "yaml": "^1.10.0", + "yargs": "^17.0.1" }, - "engines": { - "node": ">=12" + "bin": { + "resolve": "resolve.js" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" } }, - "node_modules/yargs-parser": { - "version": "21.1.1", + "node_modules/oas-schema-walker": { + "version": "1.1.5", "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" + "license": "BSD-3-Clause", + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-validator": { + "version": "5.0.8", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "call-me-maybe": "^1.0.1", + "oas-kit-common": "^1.0.8", + "oas-linter": "^3.2.2", + "oas-resolver": "^2.5.6", + "oas-schema-walker": "^1.1.5", + "reftools": "^1.1.9", + "should": "^13.2.1", + "yaml": "^1.10.0" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" } }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", + "node_modules/object-assign": { + "version": "4.1.1", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, + "node_modules/obliterator": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-1.6.1.tgz", + "integrity": "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==", "license": "MIT" }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", + "node_modules/once": { + "version": "1.4.0", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" + "wrappy": "1" } }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", + "node_modules/onetime": { + "version": "5.1.2", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "mimic-fn": "^2.1.0" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "dev": true, - "license": "MIT", "engines": { "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/capabilityStatement": { - "version": "1.0.0", + "node_modules/openapi-sampler": { + "version": "1.6.1", + "dev": true, "license": "MIT", "dependencies": { - "@aws-lambda-powertools/commons": "^2.30.2", - "@aws-lambda-powertools/logger": "^2.30.2", - "@middy/core": "^7.0.2", - "@middy/input-output-logger": "^7.0.2", - "@nhs/fhir-middy-error-handler": "^2.1.70" - }, - "devDependencies": { - "@psu-common/testing": "^1.0.0" + "@types/json-schema": "^7.0.7", + "fast-xml-parser": "^4.5.0", + "json-pointer": "0.6.2" } }, - "packages/checkPrescriptionStatusUpdates": { - "version": "1.0.0", + "node_modules/openapi-sampler/node_modules/fast-xml-parser": { + "version": "4.5.3", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], "license": "MIT", "dependencies": { - "@aws-lambda-powertools/commons": "^2.30.2", - "@aws-lambda-powertools/logger": "^2.30.2", - "@aws-lambda-powertools/parameters": "^2.30.2", - "@aws-sdk/client-dynamodb": "^3.958.0", - "@aws-sdk/lib-dynamodb": "^3.975.0", - "@middy/core": "^7.0.2", - "@middy/http-header-normalizer": "^7.0.2", - "@middy/input-output-logger": "^7.0.2", - "@psu-common/middyErrorHandler": "^1.0.0" - }, - "devDependencies": { - "@psu-common/testing": "^1.0.0" - } - }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/client-dynamodb": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.975.0.tgz", - "integrity": "sha512-Cq6oGb8XswG56YhF2kHmxuyEnMNayDpL8xDxp9E4zIUqDeSLCKE6lCaqZzo5zpngzqLluFsJbCC9jrMzZejMAA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/credential-provider-node": "^3.972.1", - "@aws-sdk/dynamodb-codec": "^3.972.2", - "@aws-sdk/middleware-endpoint-discovery": "^3.972.1", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.2", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.1", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.11", - "@smithy/middleware-retry": "^4.4.27", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.26", - "@smithy/util-defaults-mode-node": "^4.2.29", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "@smithy/util-waiter": "^4.2.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/client-sso": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.974.0.tgz", - "integrity": "sha512-ci+GiM0c4ULo4D79UMcY06LcOLcfvUfiyt8PzNY0vbt5O8BfCPYf4QomwVgkNcLLCYmroO4ge2Yy1EsLUlcD6g==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.10", - "@smithy/middleware-retry": "^4.4.26", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.11", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.25", - "@smithy/util-defaults-mode-node": "^4.2.28", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/core": { - "version": "3.973.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.1.tgz", - "integrity": "sha512-Ocubx42QsMyVs9ANSmFpRm0S+hubWljpPLjOi9UFrtcnVJjrVJTzQ51sN0e5g4e8i8QZ7uY73zosLmgYL7kZTQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/xml-builder": "^3.972.1", - "@smithy/core": "^3.21.1", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" + "strnum": "^1.1.1" }, - "engines": { - "node": ">=20.0.0" + "bin": { + "fxparser": "src/cli/cli.js" } }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.1.tgz", - "integrity": "sha512-/etNHqnx96phy/SjI0HRC588o4vKH5F0xfkZ13yAATV7aNrb+5gYGNE6ePWafP+FuZ3HkULSSlJFj0AxgrAqYw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } + "node_modules/openapi-sampler/node_modules/strnum": { + "version": "1.1.2", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.2.tgz", - "integrity": "sha512-mXgdaUfe5oM+tWKyeZ7Vh/iQ94FrkMky1uuzwTOmFADiRcSk5uHy/e3boEFedXiT/PRGzgBmqvJVK4F6lUISCg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", - "tslib": "^2.6.2" + "node_modules/optionator": { + "version": "0.9.4", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, "engines": { - "node": ">=20.0.0" + "node": ">= 0.8.0" } }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.1.tgz", - "integrity": "sha512-OdbJA3v+XlNDsrYzNPRUwr8l7gw1r/nR8l4r96MDzSBDU8WEo8T6C06SvwaXR8SpzsjO3sq5KMP86wXWg7Rj4g==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/credential-provider-env": "^3.972.1", - "@aws-sdk/credential-provider-http": "^3.972.1", - "@aws-sdk/credential-provider-login": "^3.972.1", - "@aws-sdk/credential-provider-process": "^3.972.1", - "@aws-sdk/credential-provider-sso": "^3.972.1", - "@aws-sdk/credential-provider-web-identity": "^3.972.1", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/os-homedir": { + "version": "1.0.2", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=0.10.0" } }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.1.tgz", - "integrity": "sha512-CccqDGL6ZrF3/EFWZefvKW7QwwRdxlHUO8NVBKNVcNq6womrPDvqB6xc9icACtE0XB0a7PLoSTkAg8bQVkTO2w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=0.10.0" } }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.1.tgz", - "integrity": "sha512-DwXPk9GfuU/xG9tmCyXFVkCr6X3W8ZCoL5Ptb0pbltEx1/LCcg7T+PBqDlPiiinNCD6ilIoMJDWsnJ8ikzZA7Q==", - "license": "Apache-2.0", + "node_modules/osenv": { + "version": "0.1.5", + "dev": true, + "license": "ISC", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.1", - "@aws-sdk/credential-provider-http": "^3.972.1", - "@aws-sdk/credential-provider-ini": "^3.972.1", - "@aws-sdk/credential-provider-process": "^3.972.1", - "@aws-sdk/credential-provider-sso": "^3.972.1", - "@aws-sdk/credential-provider-web-identity": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" } }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.1.tgz", - "integrity": "sha512-bi47Zigu3692SJwdBvo8y1dEwE6B61stCwCFnuRWJVTfiM84B+VTSCV661CSWJmIZzmcy7J5J3kWyxL02iHj0w==", - "license": "Apache-2.0", + "node_modules/outdent": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/outdent/-/outdent-0.8.0.tgz", + "integrity": "sha512-KiOAIsdpUTcAXuykya5fnVVT+/5uS0Q1mrkRHcF89tpieSmY33O/tmc54CqwA+bfhbtEfZUNLHaPUiB9X3jt1A==", + "dev": true, + "license": "MIT" + }, + "node_modules/outvariant": { + "version": "1.4.3", + "license": "MIT" + }, + "node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.1.tgz", - "integrity": "sha512-dLZVNhM7wSgVUFsgVYgI5hb5Z/9PUkT46pk/SHrSmUqfx6YDvoV4YcPtaiRqviPpEGGiRtdQMEadyOKIRqulUQ==", - "license": "Apache-2.0", + "node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/client-sso": "3.974.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/token-providers": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "p-limit": "^3.0.2" }, "engines": { - "node": ">=20.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.1.tgz", - "integrity": "sha512-YMDeYgi0u687Ay0dAq/pFPKuijrlKTgsaB/UATbxCs/FzZfMiG4If5ksywHmmW7MiYUF8VVv+uou3TczvLrN4w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/p-try": { + "version": "2.2.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=6" } }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/dynamodb-codec": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/dynamodb-codec/-/dynamodb-codec-3.972.2.tgz", - "integrity": "sha512-K9CDUrjDSFhVeGibVjdKd+plE25EEo7DMAIl+mYaJ+ySXsQxdPgEmOYDmZq3iq3mpkkV0lG8XWM/oaQ+q+5aCQ==", - "license": "Apache-2.0", + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/param-case": { + "version": "3.0.4", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@smithy/core": "^3.21.1", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-dynamodb": "3.975.0" + "dot-case": "^3.0.4", + "tslib": "^2.0.3" } }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/endpoint-cache": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/endpoint-cache/-/endpoint-cache-3.972.1.tgz", - "integrity": "sha512-w9TVoCUNwPG4njcbnZpSQaOZ1BF2z1Guox8NltoXm7oS1+q/8iHeG8eqY9TlGQsKLNA4KfnKUEAx4rlEc6Qv6w==", - "license": "Apache-2.0", + "node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "license": "MIT", "dependencies": { - "mnemonist": "0.38.3", - "tslib": "^2.6.2" + "callsites": "^3.0.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=6" } }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/lib-dynamodb": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.975.0.tgz", - "integrity": "sha512-BEhzr8atBFT8mJ8KuFne3OHj81zsqQWLP1Is2zzbzbZy4Mjbm1nRceNEwZ603tp9oFrV2mwTNLagFDPEudJC/g==", - "license": "Apache-2.0", + "node_modules/parse-json": { + "version": "5.2.0", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/util-dynamodb": "3.975.0", - "@smithy/core": "^3.21.1", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" }, "engines": { - "node": ">=20.0.0" + "node": ">=8" }, - "peerDependencies": { - "@aws-sdk/client-dynamodb": "3.975.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/middleware-endpoint-discovery": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.972.1.tgz", - "integrity": "sha512-3d6QaHQAjevuCioG0lZmZM/Nb8mT4JiF2mRmlh/aTM32Fc/YNGxp2Qbri8B8nfeYlfoi8GM12gH7SaIwkihuBQ==", - "license": "Apache-2.0", + "node_modules/pascal-case": { + "version": "3.1.2", + "license": "MIT", "dependencies": { - "@aws-sdk/endpoint-cache": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "no-case": "^3.0.4", + "tslib": "^2.0.3" } }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.1.tgz", - "integrity": "sha512-/R82lXLPmZ9JaUGSUdKtBp2k/5xQxvBT3zZWyKiBOhyulFotlfvdlrO8TnqstBimsl4lYEYySDL+W6ldFh6ALg==", - "license": "Apache-2.0", + "node_modules/path-browserify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/path-case": { + "version": "3.0.4", + "license": "MIT", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=8" } }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/middleware-logger": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.1.tgz", - "integrity": "sha512-JGgFl6cHg9G2FHu4lyFIzmFN8KESBiRr84gLC3Aeni0Gt1nKm+KxWLBuha/RPcXxJygGXCcMM4AykkIwxor8RA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=0.10.0" } }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.1.tgz", - "integrity": "sha512-taGzNRe8vPHjnliqXIHp9kBgIemLE/xCaRTMH1NH0cncHeaPcjxtnCroAAM9aOlPuKvBe2CpZESyvM1+D8oI7Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=8" } }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.2.tgz", - "integrity": "sha512-d+Exq074wy0X6wvShg/kmZVtkah+28vMuqCtuY3cydg8LUZOJBtbAolCpEJizSyb8mJJZF9BjWaTANXL4OYnkg==", - "license": "Apache-2.0", + "node_modules/path-parse": { + "version": "1.0.7", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@smithy/core": "^3.21.1", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/nested-clients": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.974.0.tgz", - "integrity": "sha512-k3dwdo/vOiHMJc9gMnkPl1BA5aQfTrZbz+8fiDkWrPagqAioZgmo5oiaOaeX0grObfJQKDtcpPFR4iWf8cgl8Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.10", - "@smithy/middleware-retry": "^4.4.26", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.11", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.25", - "@smithy/util-defaults-mode-node": "^4.2.28", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "dev": true, + "license": "ISC" + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.1.tgz", - "integrity": "sha512-voIY8RORpxLAEgEkYaTFnkaIuRwVBEc+RjVZYcSSllPV+ZEKAacai6kNhJeE3D70Le+JCfvRb52tng/AVHY+jQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/perfect-scrollbar": { + "version": "1.5.6", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/token-providers": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.974.0.tgz", - "integrity": "sha512-cBykL0LiccKIgNhGWvQRTPvsBLPZxnmJU3pYxG538jpFX8lQtrCy1L7mmIHNEdxIdIGEPgAEHF8/JQxgBToqUQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/pirates": { + "version": "4.0.7", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">= 6" } }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/types": { - "version": "3.973.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.0.tgz", - "integrity": "sha512-jYIdB7a7jhRTvyb378nsjyvJh1Si+zVduJ6urMNGpz8RjkmHZ+9vM2H07XaIB2Cfq0GhJRZYOfUCH8uqQhqBkQ==", - "license": "Apache-2.0", + "node_modules/pkg-dir": { + "version": "4.2.0", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "find-up": "^4.0.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=8" } }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/util-dynamodb": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.975.0.tgz", - "integrity": "sha512-ZsziF8m5Syn+kA2YJLEG2kk6zfxea8yRl/7SkSFpAls8RFYkt8EUmVUMBhX2hBpGw+nbZL7+AcRi4S2LxAcYWA==", - "license": "Apache-2.0", + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-dynamodb": "3.975.0" + "node": ">=8" } }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/util-endpoints": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.972.0.tgz", - "integrity": "sha512-6JHsl1V/a1ZW8D8AFfd4R52fwZPnZ5H4U6DS8m/bWT8qad72NvbOFAC7U2cDtFs2TShqUO3TEiX/EJibtY3ijg==", - "license": "Apache-2.0", + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/types": "3.972.0", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-endpoints": "^3.2.8", - "tslib": "^2.6.2" + "p-locate": "^4.1.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=8" } }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/util-endpoints/node_modules/@aws-sdk/types": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz", - "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==", - "license": "Apache-2.0", + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "p-try": "^2.0.0" }, "engines": { - "node": ">=20.0.0" - } - }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.1.tgz", - "integrity": "sha512-IgF55NFmJX8d9Wql9M0nEpk2eYbuD8G4781FN4/fFgwTXBn86DvlZJuRWDCMcMqZymnBVX7HW9r+3r9ylqfW0w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/types": "^4.12.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.1.tgz", - "integrity": "sha512-oIs4JFcADzoZ0c915R83XvK2HltWupxNsXUIuZse2rgk7b97zTpkxaqXiH0h9ylh31qtgo/t8hp4tIqcsMrEbQ==", - "license": "Apache-2.0", + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "p-limit": "^2.2.0" }, "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } + "node": ">=8" } }, - "packages/checkPrescriptionStatusUpdates/node_modules/@aws-sdk/xml-builder": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.1.tgz", - "integrity": "sha512-6zZGlPOqn7Xb+25MAXGb1JhgvaC5HjZj6GzszuVrnEgbhvzBRFGKYemuHBV4bho+dtqeYKPgaZUv7/e80hIGNg==", - "license": "Apache-2.0", + "node_modules/pluralize": { + "version": "8.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/polished": { + "version": "4.3.1", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^4.12.0", - "fast-xml-parser": "5.2.5", - "tslib": "^2.6.2" + "@babel/runtime": "^7.17.8" }, "engines": { - "node": ">=20.0.0" + "node": ">=10" } }, - "packages/common/commonTypes": { - "name": "@psu-common/commonTypes", - "version": "1.0.0", + "node_modules/possible-typed-array-names": { + "version": "1.1.0", "license": "MIT", - "devDependencies": {} + "engines": { + "node": ">= 0.4" + } }, - "packages/common/middyErrorHandler": { - "name": "@psu-common/middyErrorHandler", - "version": "1.0.0", + "node_modules/postcss": { + "version": "8.4.49", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "@aws-lambda-powertools/logger": "^2.30.2" + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, - "devDependencies": { - "@middy/core": "^7.0.2", - "@psu-common/testing": "^1.0.0" + "engines": { + "node": "^10 || ^12 || >=14" } }, - "packages/common/testing": { - "name": "@psu-common/testing", - "version": "1.0.0", + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "dev": true, "license": "MIT" }, - "packages/common/utilities": { - "name": "@psu-common/utilities", - "version": "1.0.0", + "node_modules/postDatedLambda": { + "resolved": "packages/postDatedLambda", + "link": true + }, + "node_modules/pratica": { + "version": "2.3.0", + "license": "Apache-2.0" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, "license": "MIT", - "devDependencies": {} + "engines": { + "node": ">= 0.8.0" + } }, - "packages/cpsuLambda": { - "version": "1.0.0", + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, "license": "MIT", - "dependencies": { - "@aws-lambda-powertools/commons": "^2.30.2", - "@aws-lambda-powertools/logger": "^2.30.2", - "@aws-sdk/client-dynamodb": "^3.958.0", - "@aws-sdk/util-dynamodb": "^3.975.0", - "@middy/core": "^7.0.2", - "@middy/http-header-normalizer": "^7.0.2", - "@middy/input-output-logger": "^7.0.2", - "@middy/validator": "6.4.1", - "json-schema-to-ts": "^3.1.1", - "pratica": "^2.3.0", - "ts-md5": "^2.0.1", - "uuid": "^13.0.0" + "peer": true, + "bin": { + "prettier": "bin/prettier.cjs" }, - "devDependencies": { - "@faker-js/faker": "^10.2.0", - "@types/fhir": "^0.0.41", - "aws-sdk-client-mock": "^4.1.0" + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/client-dynamodb": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.975.0.tgz", - "integrity": "sha512-Cq6oGb8XswG56YhF2kHmxuyEnMNayDpL8xDxp9E4zIUqDeSLCKE6lCaqZzo5zpngzqLluFsJbCC9jrMzZejMAA==", - "license": "Apache-2.0", - "peer": true, + "node_modules/prettier-linter-helpers": { + "version": "1.0.1", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/credential-provider-node": "^3.972.1", - "@aws-sdk/dynamodb-codec": "^3.972.2", - "@aws-sdk/middleware-endpoint-discovery": "^3.972.1", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.2", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.1", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.11", - "@smithy/middleware-retry": "^4.4.27", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.26", - "@smithy/util-defaults-mode-node": "^4.2.29", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "@smithy/util-waiter": "^4.2.8", - "tslib": "^2.6.2" + "fast-diff": "^1.1.2" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.0.0" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/client-sso": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.974.0.tgz", - "integrity": "sha512-ci+GiM0c4ULo4D79UMcY06LcOLcfvUfiyt8PzNY0vbt5O8BfCPYf4QomwVgkNcLLCYmroO4ge2Yy1EsLUlcD6g==", - "license": "Apache-2.0", + "node_modules/pretty-format": { + "version": "30.2.0", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.10", - "@smithy/middleware-retry": "^4.4.26", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.11", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.25", - "@smithy/util-defaults-mode-node": "^4.2.28", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "dev": true, + "license": "MIT" + }, + "node_modules/propagate": { + "version": "2.0.1", + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">= 8" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/core": { - "version": "3.973.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.1.tgz", - "integrity": "sha512-Ocubx42QsMyVs9ANSmFpRm0S+hubWljpPLjOi9UFrtcnVJjrVJTzQ51sN0e5g4e8i8QZ7uY73zosLmgYL7kZTQ==", - "license": "Apache-2.0", + "node_modules/protobufjs": { + "version": "7.5.4", + "dev": true, + "hasInstallScript": true, + "license": "BSD-3-Clause", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/xml-builder": "^3.972.1", - "@smithy/core": "^3.21.1", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=12.0.0" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.1.tgz", - "integrity": "sha512-/etNHqnx96phy/SjI0HRC588o4vKH5F0xfkZ13yAATV7aNrb+5gYGNE6ePWafP+FuZ3HkULSSlJFj0AxgrAqYw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/psuRestoreValidationLambda": { + "resolved": "packages/psuRestoreValidationLambda", + "link": true + }, + "node_modules/punycode": { + "version": "2.3.1", + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=6" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.2.tgz", - "integrity": "sha512-mXgdaUfe5oM+tWKyeZ7Vh/iQ94FrkMky1uuzwTOmFADiRcSk5uHy/e3boEFedXiT/PRGzgBmqvJVK4F6lUISCg==", - "license": "Apache-2.0", + "node_modules/pure-rand": { + "version": "7.0.1", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/querystring": { + "version": "0.2.0", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/railroad-diagrams": { + "version": "1.0.0", + "license": "CC0-1.0" + }, + "node_modules/randexp": { + "version": "0.4.6", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", - "tslib": "^2.6.2" + "discontinuous-range": "1.0.0", + "ret": "~0.1.10" }, "engines": { - "node": ">=20.0.0" + "node": ">=0.12" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.1.tgz", - "integrity": "sha512-OdbJA3v+XlNDsrYzNPRUwr8l7gw1r/nR8l4r96MDzSBDU8WEo8T6C06SvwaXR8SpzsjO3sq5KMP86wXWg7Rj4g==", - "license": "Apache-2.0", + "node_modules/randombytes": { + "version": "2.1.0", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/credential-provider-env": "^3.972.1", - "@aws-sdk/credential-provider-http": "^3.972.1", - "@aws-sdk/credential-provider-login": "^3.972.1", - "@aws-sdk/credential-provider-process": "^3.972.1", - "@aws-sdk/credential-provider-sso": "^3.972.1", - "@aws-sdk/credential-provider-web-identity": "^3.972.1", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "safe-buffer": "^5.1.0" + } + }, + "node_modules/react": { + "version": "19.2.1", + "dev": true, + "license": "MIT", + "peer": true, "engines": { - "node": ">=20.0.0" + "node": ">=0.10.0" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.1.tgz", - "integrity": "sha512-CccqDGL6ZrF3/EFWZefvKW7QwwRdxlHUO8NVBKNVcNq6womrPDvqB6xc9icACtE0XB0a7PLoSTkAg8bQVkTO2w==", - "license": "Apache-2.0", + "node_modules/react-dom": { + "version": "19.2.1", + "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "scheduler": "^0.27.0" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "react": "^19.2.1" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.1.tgz", - "integrity": "sha512-DwXPk9GfuU/xG9tmCyXFVkCr6X3W8ZCoL5Ptb0pbltEx1/LCcg7T+PBqDlPiiinNCD6ilIoMJDWsnJ8ikzZA7Q==", - "license": "Apache-2.0", + "node_modules/react-is": { + "version": "18.3.1", + "dev": true, + "license": "MIT" + }, + "node_modules/react-tabs": { + "version": "6.1.0", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.1", - "@aws-sdk/credential-provider-http": "^3.972.1", - "@aws-sdk/credential-provider-ini": "^3.972.1", - "@aws-sdk/credential-provider-process": "^3.972.1", - "@aws-sdk/credential-provider-sso": "^3.972.1", - "@aws-sdk/credential-provider-web-identity": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "clsx": "^2.0.0", + "prop-types": "^15.5.0" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.1.tgz", - "integrity": "sha512-bi47Zigu3692SJwdBvo8y1dEwE6B61stCwCFnuRWJVTfiM84B+VTSCV661CSWJmIZzmcy7J5J3kWyxL02iHj0w==", - "license": "Apache-2.0", + "node_modules/read-installed": { + "version": "4.0.3", + "dev": true, + "license": "ISC", "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "debuglog": "^1.0.1", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "slide": "~1.1.3", + "util-extend": "^1.0.1" }, - "engines": { - "node": ">=20.0.0" + "optionalDependencies": { + "graceful-fs": "^4.1.2" + } + }, + "node_modules/read-installed/node_modules/semver": { + "version": "5.7.2", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.1.tgz", - "integrity": "sha512-dLZVNhM7wSgVUFsgVYgI5hb5Z/9PUkT46pk/SHrSmUqfx6YDvoV4YcPtaiRqviPpEGGiRtdQMEadyOKIRqulUQ==", - "license": "Apache-2.0", + "node_modules/read-package-json": { + "version": "2.1.2", + "dev": true, + "license": "ISC", "dependencies": { - "@aws-sdk/client-sso": "3.974.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/token-providers": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "glob": "^7.1.1", + "json-parse-even-better-errors": "^2.3.0", + "normalize-package-data": "^2.0.0", + "npm-normalize-package-bin": "^1.0.0" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.1.tgz", - "integrity": "sha512-YMDeYgi0u687Ay0dAq/pFPKuijrlKTgsaB/UATbxCs/FzZfMiG4If5ksywHmmW7MiYUF8VVv+uou3TczvLrN4w==", - "license": "Apache-2.0", + "node_modules/read-package-json/node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/dynamodb-codec": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/dynamodb-codec/-/dynamodb-codec-3.972.2.tgz", - "integrity": "sha512-K9CDUrjDSFhVeGibVjdKd+plE25EEo7DMAIl+mYaJ+ySXsQxdPgEmOYDmZq3iq3mpkkV0lG8XWM/oaQ+q+5aCQ==", - "license": "Apache-2.0", + "node_modules/read-package-json/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@smithy/core": "^3.21.1", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "tslib": "^2.6.2" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=20.0.0" + "node": "*" }, - "peerDependencies": { - "@aws-sdk/client-dynamodb": "3.975.0" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/endpoint-cache": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/endpoint-cache/-/endpoint-cache-3.972.1.tgz", - "integrity": "sha512-w9TVoCUNwPG4njcbnZpSQaOZ1BF2z1Guox8NltoXm7oS1+q/8iHeG8eqY9TlGQsKLNA4KfnKUEAx4rlEc6Qv6w==", - "license": "Apache-2.0", + "node_modules/read-package-json/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", "dependencies": { - "mnemonist": "0.38.3", - "tslib": "^2.6.2" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=20.0.0" + "node": "*" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/middleware-endpoint-discovery": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.972.1.tgz", - "integrity": "sha512-3d6QaHQAjevuCioG0lZmZM/Nb8mT4JiF2mRmlh/aTM32Fc/YNGxp2Qbri8B8nfeYlfoi8GM12gH7SaIwkihuBQ==", - "license": "Apache-2.0", + "node_modules/readdir-scoped-modules": { + "version": "1.1.0", + "dev": true, + "license": "ISC", "dependencies": { - "@aws-sdk/endpoint-cache": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.1.tgz", - "integrity": "sha512-/R82lXLPmZ9JaUGSUdKtBp2k/5xQxvBT3zZWyKiBOhyulFotlfvdlrO8TnqstBimsl4lYEYySDL+W6ldFh6ALg==", - "license": "Apache-2.0", + "node_modules/readdirp": { + "version": "3.6.0", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "picomatch": "^2.2.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=8.10.0" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/middleware-logger": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.1.tgz", - "integrity": "sha512-JGgFl6cHg9G2FHu4lyFIzmFN8KESBiRr84gLC3Aeni0Gt1nKm+KxWLBuha/RPcXxJygGXCcMM4AykkIwxor8RA==", - "license": "Apache-2.0", + "node_modules/redoc": { + "version": "2.5.1", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@redocly/openapi-core": "^1.4.0", + "classnames": "^2.3.2", + "decko": "^1.2.0", + "dompurify": "^3.2.4", + "eventemitter3": "^5.0.1", + "json-pointer": "^0.6.2", + "lunr": "^2.3.9", + "mark.js": "^8.11.1", + "marked": "^4.3.0", + "mobx-react": "9.2.0", + "openapi-sampler": "^1.5.0", + "path-browserify": "^1.0.1", + "perfect-scrollbar": "^1.5.5", + "polished": "^4.2.2", + "prismjs": "^1.29.0", + "prop-types": "^15.8.1", + "react-tabs": "^6.0.2", + "slugify": "~1.4.7", + "stickyfill": "^1.1.1", + "swagger2openapi": "^7.0.8", + "url-template": "^2.0.8" }, "engines": { - "node": ">=20.0.0" - } - }, - "packages/cpsuLambda/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.1.tgz", - "integrity": "sha512-taGzNRe8vPHjnliqXIHp9kBgIemLE/xCaRTMH1NH0cncHeaPcjxtnCroAAM9aOlPuKvBe2CpZESyvM1+D8oI7Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "node": ">=6.9", + "npm": ">=3.0.0" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "core-js": "^3.1.4", + "mobx": "^6.0.4", + "react": "^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "styled-components": "^4.1.1 || ^5.1.1 || ^6.0.5" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.2.tgz", - "integrity": "sha512-d+Exq074wy0X6wvShg/kmZVtkah+28vMuqCtuY3cydg8LUZOJBtbAolCpEJizSyb8mJJZF9BjWaTANXL4OYnkg==", - "license": "Apache-2.0", + "node_modules/redoc/node_modules/@redocly/config": { + "version": "0.22.2", + "dev": true, + "license": "MIT" + }, + "node_modules/redoc/node_modules/@redocly/openapi-core": { + "version": "1.34.5", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@smithy/core": "^3.21.1", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@redocly/ajv": "^8.11.2", + "@redocly/config": "^0.22.0", + "colorette": "^1.2.0", + "https-proxy-agent": "^7.0.5", + "js-levenshtein": "^1.1.6", + "js-yaml": "^4.1.0", + "minimatch": "^5.0.1", + "pluralize": "^8.0.0", + "yaml-ast-parser": "0.0.43" }, "engines": { - "node": ">=20.0.0" + "node": ">=18.17.0", + "npm": ">=9.5.0" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/nested-clients": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.974.0.tgz", - "integrity": "sha512-k3dwdo/vOiHMJc9gMnkPl1BA5aQfTrZbz+8fiDkWrPagqAioZgmo5oiaOaeX0grObfJQKDtcpPFR4iWf8cgl8Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.10", - "@smithy/middleware-retry": "^4.4.26", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.11", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.25", - "@smithy/util-defaults-mode-node": "^4.2.28", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" + "node_modules/redoc/node_modules/argparse": { + "version": "2.0.1", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/redoc/node_modules/js-yaml": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" }, - "engines": { - "node": ">=20.0.0" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.1.tgz", - "integrity": "sha512-voIY8RORpxLAEgEkYaTFnkaIuRwVBEc+RjVZYcSSllPV+ZEKAacai6kNhJeE3D70Le+JCfvRb52tng/AVHY+jQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "node_modules/redoc/node_modules/marked": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" }, "engines": { - "node": ">=20.0.0" + "node": ">= 12" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/token-providers": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.974.0.tgz", - "integrity": "sha512-cBykL0LiccKIgNhGWvQRTPvsBLPZxnmJU3pYxG538jpFX8lQtrCy1L7mmIHNEdxIdIGEPgAEHF8/JQxgBToqUQ==", - "license": "Apache-2.0", + "node_modules/redoc/node_modules/minimatch": { + "version": "5.1.6", + "dev": true, + "license": "ISC", "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=10" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/types": { - "version": "3.973.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.0.tgz", - "integrity": "sha512-jYIdB7a7jhRTvyb378nsjyvJh1Si+zVduJ6urMNGpz8RjkmHZ+9vM2H07XaIB2Cfq0GhJRZYOfUCH8uqQhqBkQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/reftools": { + "version": "1.1.9", + "dev": true, + "license": "BSD-3-Clause", + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=0.10.0" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/util-dynamodb": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.975.0.tgz", - "integrity": "sha512-ZsziF8m5Syn+kA2YJLEG2kk6zfxea8yRl/7SkSFpAls8RFYkt8EUmVUMBhX2hBpGw+nbZL7+AcRi4S2LxAcYWA==", - "license": "Apache-2.0", + "node_modules/require-from-string": { + "version": "2.0.2", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" }, "engines": { - "node": ">=20.0.0" + "node": ">= 0.4" }, - "peerDependencies": { - "@aws-sdk/client-dynamodb": "3.975.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/util-endpoints": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.972.0.tgz", - "integrity": "sha512-6JHsl1V/a1ZW8D8AFfd4R52fwZPnZ5H4U6DS8m/bWT8qad72NvbOFAC7U2cDtFs2TShqUO3TEiX/EJibtY3ijg==", - "license": "Apache-2.0", + "node_modules/resolve-cwd": { + "version": "3.0.0", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/types": "3.972.0", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-endpoints": "^3.2.8", - "tslib": "^2.6.2" + "resolve-from": "^5.0.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=8" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/util-endpoints/node_modules/@aws-sdk/types": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz", - "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=8" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.1.tgz", - "integrity": "sha512-IgF55NFmJX8d9Wql9M0nEpk2eYbuD8G4781FN4/fFgwTXBn86DvlZJuRWDCMcMqZymnBVX7HW9r+3r9ylqfW0w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/types": "^4.12.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" + "node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.1.tgz", - "integrity": "sha512-oIs4JFcADzoZ0c915R83XvK2HltWupxNsXUIuZse2rgk7b97zTpkxaqXiH0h9ylh31qtgo/t8hp4tIqcsMrEbQ==", - "license": "Apache-2.0", + "node_modules/ret": { + "version": "0.1.15", + "license": "MIT", + "engines": { + "node": ">=0.12" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "license": "MIT", "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" }, "engines": { - "node": ">=20.0.0" + "node": ">= 0.4" }, - "peerDependencies": { - "aws-crt": ">=1.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sax": { + "version": "1.2.1", + "license": "ISC" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "dev": true, + "license": "MIT" + }, + "node_modules/schemes": { + "version": "1.4.0", + "license": "MIT", + "dependencies": { + "extend": "^3.0.0" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } + "engines": { + "node": ">=10" } }, - "packages/cpsuLambda/node_modules/@aws-sdk/xml-builder": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.1.tgz", - "integrity": "sha512-6zZGlPOqn7Xb+25MAXGb1JhgvaC5HjZj6GzszuVrnEgbhvzBRFGKYemuHBV4bho+dtqeYKPgaZUv7/e80hIGNg==", - "license": "Apache-2.0", + "node_modules/sentence-case": { + "version": "3.0.4", + "license": "MIT", "dependencies": { - "@smithy/types": "^4.12.0", - "fast-xml-parser": "5.2.5", - "tslib": "^2.6.2" + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "dev": true, + "license": "MIT" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" }, "engines": { - "node": ">=20.0.0" + "node": ">= 0.4" } }, - "packages/cpsuLambda/node_modules/uuid": { - "version": "13.0.0", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], + "node_modules/shallowequal": { + "version": "1.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, "license": "MIT", - "bin": { - "uuid": "dist-node/bin/uuid" + "engines": { + "node": ">=8" } }, - "packages/gsul": { - "version": "1.0.0", + "node_modules/should": { + "version": "13.2.3", + "dev": true, "license": "MIT", "dependencies": { - "@aws-lambda-powertools/commons": "^2.30.2", - "@aws-lambda-powertools/logger": "^2.30.2", - "@aws-sdk/client-dynamodb": "^3.958.0", - "@aws-sdk/lib-dynamodb": "^3.975.0", - "@aws-sdk/util-dynamodb": "^3.975.0", - "@middy/core": "^7.0.2", - "@middy/input-output-logger": "^7.0.2", - "@middy/validator": "6.4.1", - "@psu-common/middyErrorHandler": "^1.0.0", - "json-schema-to-ts": "^3.1.1" + "should-equal": "^2.0.0", + "should-format": "^3.0.3", + "should-type": "^1.4.0", + "should-type-adaptors": "^1.0.1", + "should-util": "^1.0.0" } }, - "packages/gsul/node_modules/@aws-sdk/client-dynamodb": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.975.0.tgz", - "integrity": "sha512-Cq6oGb8XswG56YhF2kHmxuyEnMNayDpL8xDxp9E4zIUqDeSLCKE6lCaqZzo5zpngzqLluFsJbCC9jrMzZejMAA==", - "license": "Apache-2.0", - "peer": true, + "node_modules/should-equal": { + "version": "2.0.0", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/credential-provider-node": "^3.972.1", - "@aws-sdk/dynamodb-codec": "^3.972.2", - "@aws-sdk/middleware-endpoint-discovery": "^3.972.1", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.2", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.1", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.11", - "@smithy/middleware-retry": "^4.4.27", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.26", - "@smithy/util-defaults-mode-node": "^4.2.29", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "@smithy/util-waiter": "^4.2.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "should-type": "^1.4.0" } }, - "packages/gsul/node_modules/@aws-sdk/client-sso": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.974.0.tgz", - "integrity": "sha512-ci+GiM0c4ULo4D79UMcY06LcOLcfvUfiyt8PzNY0vbt5O8BfCPYf4QomwVgkNcLLCYmroO4ge2Yy1EsLUlcD6g==", - "license": "Apache-2.0", + "node_modules/should-format": { + "version": "3.0.3", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.10", - "@smithy/middleware-retry": "^4.4.26", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.11", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.25", - "@smithy/util-defaults-mode-node": "^4.2.28", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "should-type": "^1.3.0", + "should-type-adaptors": "^1.0.1" } }, - "packages/gsul/node_modules/@aws-sdk/core": { - "version": "3.973.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.1.tgz", - "integrity": "sha512-Ocubx42QsMyVs9ANSmFpRm0S+hubWljpPLjOi9UFrtcnVJjrVJTzQ51sN0e5g4e8i8QZ7uY73zosLmgYL7kZTQ==", - "license": "Apache-2.0", + "node_modules/should-type": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/should-type-adaptors": { + "version": "1.1.0", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/xml-builder": "^3.972.1", - "@smithy/core": "^3.21.1", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "should-type": "^1.3.0", + "should-util": "^1.0.0" } }, - "packages/gsul/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.1.tgz", - "integrity": "sha512-/etNHqnx96phy/SjI0HRC588o4vKH5F0xfkZ13yAATV7aNrb+5gYGNE6ePWafP+FuZ3HkULSSlJFj0AxgrAqYw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/should-util": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "license": "ISC", "engines": { - "node": ">=20.0.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "packages/gsul/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.2.tgz", - "integrity": "sha512-mXgdaUfe5oM+tWKyeZ7Vh/iQ94FrkMky1uuzwTOmFADiRcSk5uHy/e3boEFedXiT/PRGzgBmqvJVK4F6lUISCg==", - "license": "Apache-2.0", + "node_modules/simple-websocket": { + "version": "9.1.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "debug": "^4.3.1", + "queue-microtask": "^1.2.2", + "randombytes": "^2.1.0", + "readable-stream": "^3.6.0", + "ws": "^7.4.2" } }, - "packages/gsul/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.1.tgz", - "integrity": "sha512-OdbJA3v+XlNDsrYzNPRUwr8l7gw1r/nR8l4r96MDzSBDU8WEo8T6C06SvwaXR8SpzsjO3sq5KMP86wXWg7Rj4g==", - "license": "Apache-2.0", + "node_modules/simple-websocket/node_modules/readable-stream": { + "version": "3.6.2", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/credential-provider-env": "^3.972.1", - "@aws-sdk/credential-provider-http": "^3.972.1", - "@aws-sdk/credential-provider-login": "^3.972.1", - "@aws-sdk/credential-provider-process": "^3.972.1", - "@aws-sdk/credential-provider-sso": "^3.972.1", - "@aws-sdk/credential-provider-web-identity": "^3.972.1", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">=20.0.0" + "node": ">= 6" } }, - "packages/gsul/node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.1.tgz", - "integrity": "sha512-CccqDGL6ZrF3/EFWZefvKW7QwwRdxlHUO8NVBKNVcNq6womrPDvqB6xc9icACtE0XB0a7PLoSTkAg8bQVkTO2w==", - "license": "Apache-2.0", + "node_modules/sinon": { + "version": "18.0.1", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.2.0", + "nise": "^6.0.0", + "supports-color": "^7" }, - "engines": { - "node": ">=20.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" } }, - "packages/gsul/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.1.tgz", - "integrity": "sha512-DwXPk9GfuU/xG9tmCyXFVkCr6X3W8ZCoL5Ptb0pbltEx1/LCcg7T+PBqDlPiiinNCD6ilIoMJDWsnJ8ikzZA7Q==", - "license": "Apache-2.0", + "node_modules/sinon/node_modules/@sinonjs/fake-timers": { + "version": "11.2.2", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.1", - "@aws-sdk/credential-provider-http": "^3.972.1", - "@aws-sdk/credential-provider-ini": "^3.972.1", - "@aws-sdk/credential-provider-process": "^3.972.1", - "@aws-sdk/credential-provider-sso": "^3.972.1", - "@aws-sdk/credential-provider-web-identity": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "5.2.0", + "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">=20.0.0" + "node": ">=0.3.1" } }, - "packages/gsul/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.1.tgz", - "integrity": "sha512-bi47Zigu3692SJwdBvo8y1dEwE6B61stCwCFnuRWJVTfiM84B+VTSCV661CSWJmIZzmcy7J5J3kWyxL02iHj0w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/slash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=8" } }, - "packages/gsul/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.1.tgz", - "integrity": "sha512-dLZVNhM7wSgVUFsgVYgI5hb5Z/9PUkT46pk/SHrSmUqfx6YDvoV4YcPtaiRqviPpEGGiRtdQMEadyOKIRqulUQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.974.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/token-providers": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/slide": { + "version": "1.1.6", + "dev": true, + "license": "ISC", "engines": { - "node": ">=20.0.0" + "node": "*" } }, - "packages/gsul/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.1.tgz", - "integrity": "sha512-YMDeYgi0u687Ay0dAq/pFPKuijrlKTgsaB/UATbxCs/FzZfMiG4If5ksywHmmW7MiYUF8VVv+uou3TczvLrN4w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/slugify": { + "version": "1.4.7", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=8.0.0" } }, - "packages/gsul/node_modules/@aws-sdk/dynamodb-codec": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/dynamodb-codec/-/dynamodb-codec-3.972.2.tgz", - "integrity": "sha512-K9CDUrjDSFhVeGibVjdKd+plE25EEo7DMAIl+mYaJ+ySXsQxdPgEmOYDmZq3iq3mpkkV0lG8XWM/oaQ+q+5aCQ==", - "license": "Apache-2.0", + "node_modules/smtp-address-parser": { + "version": "1.1.0", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@smithy/core": "^3.21.1", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "tslib": "^2.6.2" + "nearley": "^2.20.1" }, "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-dynamodb": "3.975.0" + "node": ">=0.10" } }, - "packages/gsul/node_modules/@aws-sdk/endpoint-cache": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/endpoint-cache/-/endpoint-cache-3.972.1.tgz", - "integrity": "sha512-w9TVoCUNwPG4njcbnZpSQaOZ1BF2z1Guox8NltoXm7oS1+q/8iHeG8eqY9TlGQsKLNA4KfnKUEAx4rlEc6Qv6w==", - "license": "Apache-2.0", + "node_modules/snake-case": { + "version": "3.0.4", + "license": "MIT", "dependencies": { - "mnemonist": "0.38.3", - "tslib": "^2.6.2" - }, + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">=20.0.0" + "node": ">=0.10.0" } }, - "packages/gsul/node_modules/@aws-sdk/lib-dynamodb": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.975.0.tgz", - "integrity": "sha512-BEhzr8atBFT8mJ8KuFne3OHj81zsqQWLP1Is2zzbzbZy4Mjbm1nRceNEwZ603tp9oFrV2mwTNLagFDPEudJC/g==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/util-dynamodb": "3.975.0", - "@smithy/core": "^3.21.1", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/source-map-js": { + "version": "1.2.1", + "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-dynamodb": "3.975.0" + "node": ">=0.10.0" } }, - "packages/gsul/node_modules/@aws-sdk/middleware-endpoint-discovery": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.972.1.tgz", - "integrity": "sha512-3d6QaHQAjevuCioG0lZmZM/Nb8mT4JiF2mRmlh/aTM32Fc/YNGxp2Qbri8B8nfeYlfoi8GM12gH7SaIwkihuBQ==", - "license": "Apache-2.0", + "node_modules/source-map-support": { + "version": "0.5.13", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/endpoint-cache": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "packages/gsul/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.1.tgz", - "integrity": "sha512-/R82lXLPmZ9JaUGSUdKtBp2k/5xQxvBT3zZWyKiBOhyulFotlfvdlrO8TnqstBimsl4lYEYySDL+W6ldFh6ALg==", - "license": "Apache-2.0", + "node_modules/spdx-compare": { + "version": "1.0.0", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "array-find-index": "^1.0.2", + "spdx-expression-parse": "^3.0.0", + "spdx-ranges": "^2.0.0" } }, - "packages/gsul/node_modules/@aws-sdk/middleware-logger": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.1.tgz", - "integrity": "sha512-JGgFl6cHg9G2FHu4lyFIzmFN8KESBiRr84gLC3Aeni0Gt1nKm+KxWLBuha/RPcXxJygGXCcMM4AykkIwxor8RA==", + "node_modules/spdx-correct": { + "version": "3.2.0", + "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.22", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/spdx-ranges": { + "version": "2.1.1", + "dev": true, + "license": "(MIT AND CC-BY-3.0)" + }, + "node_modules/spdx-satisfies": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-compare": "^1.0.0", + "spdx-expression-parse": "^3.0.0", + "spdx-ranges": "^2.0.0" } }, - "packages/gsul/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.1.tgz", - "integrity": "sha512-taGzNRe8vPHjnliqXIHp9kBgIemLE/xCaRTMH1NH0cncHeaPcjxtnCroAAM9aOlPuKvBe2CpZESyvM1+D8oI7Q==", - "license": "Apache-2.0", + "node_modules/sprintf-js": { + "version": "1.0.3", + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "escape-string-regexp": "^2.0.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=10" } }, - "packages/gsul/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.2.tgz", - "integrity": "sha512-d+Exq074wy0X6wvShg/kmZVtkah+28vMuqCtuY3cydg8LUZOJBtbAolCpEJizSyb8mJJZF9BjWaTANXL4OYnkg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@smithy/core": "^3.21.1", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=8" } }, - "packages/gsul/node_modules/@aws-sdk/nested-clients": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.974.0.tgz", - "integrity": "sha512-k3dwdo/vOiHMJc9gMnkPl1BA5aQfTrZbz+8fiDkWrPagqAioZgmo5oiaOaeX0grObfJQKDtcpPFR4iWf8cgl8Q==", - "license": "Apache-2.0", + "node_modules/statusLambda": { + "resolved": "packages/statusLambda", + "link": true + }, + "node_modules/stickyfill": { + "version": "1.1.1", + "dev": true + }, + "node_modules/strict-event-emitter": { + "version": "0.5.1", + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.10", - "@smithy/middleware-retry": "^4.4.26", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.11", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.25", - "@smithy/util-defaults-mode-node": "^4.2.28", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "safe-buffer": "~5.1.0" } }, - "packages/gsul/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.1.tgz", - "integrity": "sha512-voIY8RORpxLAEgEkYaTFnkaIuRwVBEc+RjVZYcSSllPV+ZEKAacai6kNhJeE3D70Le+JCfvRb52tng/AVHY+jQ==", - "license": "Apache-2.0", + "node_modules/string-length": { + "version": "4.0.2", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=10" } }, - "packages/gsul/node_modules/@aws-sdk/token-providers": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.974.0.tgz", - "integrity": "sha512-cBykL0LiccKIgNhGWvQRTPvsBLPZxnmJU3pYxG538jpFX8lQtrCy1L7mmIHNEdxIdIGEPgAEHF8/JQxgBToqUQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/string-length/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=8" } }, - "packages/gsul/node_modules/@aws-sdk/types": { - "version": "3.973.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.0.tgz", - "integrity": "sha512-jYIdB7a7jhRTvyb378nsjyvJh1Si+zVduJ6urMNGpz8RjkmHZ+9vM2H07XaIB2Cfq0GhJRZYOfUCH8uqQhqBkQ==", - "license": "Apache-2.0", + "node_modules/string-length/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=8" } }, - "packages/gsul/node_modules/@aws-sdk/util-dynamodb": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.975.0.tgz", - "integrity": "sha512-ZsziF8m5Syn+kA2YJLEG2kk6zfxea8yRl/7SkSFpAls8RFYkt8EUmVUMBhX2hBpGw+nbZL7+AcRi4S2LxAcYWA==", - "license": "Apache-2.0", + "node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=12" }, - "peerDependencies": { - "@aws-sdk/client-dynamodb": "3.975.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/gsul/node_modules/@aws-sdk/util-endpoints": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.972.0.tgz", - "integrity": "sha512-6JHsl1V/a1ZW8D8AFfd4R52fwZPnZ5H4U6DS8m/bWT8qad72NvbOFAC7U2cDtFs2TShqUO3TEiX/EJibtY3ijg==", - "license": "Apache-2.0", + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/types": "3.972.0", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-endpoints": "^3.2.8", - "tslib": "^2.6.2" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=8" } }, - "packages/gsul/node_modules/@aws-sdk/util-endpoints/node_modules/@aws-sdk/types": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz", - "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" - } - }, - "packages/gsul/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.1.tgz", - "integrity": "sha512-IgF55NFmJX8d9Wql9M0nEpk2eYbuD8G4781FN4/fFgwTXBn86DvlZJuRWDCMcMqZymnBVX7HW9r+3r9ylqfW0w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/types": "^4.12.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" + "node": ">=8" } }, - "packages/gsul/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.1.tgz", - "integrity": "sha512-oIs4JFcADzoZ0c915R83XvK2HltWupxNsXUIuZse2rgk7b97zTpkxaqXiH0h9ylh31qtgo/t8hp4tIqcsMrEbQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" }, - "packages/gsul/node_modules/@aws-sdk/xml-builder": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.1.tgz", - "integrity": "sha512-6zZGlPOqn7Xb+25MAXGb1JhgvaC5HjZj6GzszuVrnEgbhvzBRFGKYemuHBV4bho+dtqeYKPgaZUv7/e80hIGNg==", - "license": "Apache-2.0", + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^4.12.0", - "fast-xml-parser": "5.2.5", - "tslib": "^2.6.2" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=8" } }, - "packages/nhsd-psu-sandbox": { - "version": "1.0.0", + "node_modules/strip-ansi": { + "version": "7.1.2", + "dev": true, "license": "MIT", "dependencies": { - "@aws-lambda-powertools/logger": "^2.30.2", - "@middy/core": "^7.0.2", - "@middy/input-output-logger": "^7.0.2", - "@nhs/fhir-middy-error-handler": "^2.1.70" + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "packages/nhsNotifyLambda": { - "version": "1.0.0", + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "dev": true, "license": "MIT", "dependencies": { - "@aws-lambda-powertools/commons": "^2.30.2", - "@aws-lambda-powertools/logger": "^2.30.2", - "@aws-lambda-powertools/parameters": "^2.30.2", - "@aws-sdk/client-ssm": "^3.975.0", - "@middy/core": "^7.0.2", - "@middy/input-output-logger": "^7.0.2", - "@nhs/fhir-middy-error-handler": "^2.1.70", - "axios": "^1.13.2", - "axios-retry": "^4.5.0", - "jose": "^6.1.3", - "nock": "^14.0.10" + "ansi-regex": "^5.0.1" }, - "devDependencies": { - "@psu-common/testing": "^1.0.0", - "axios-mock-adapter": "^2.1.0" + "engines": { + "node": ">=8" } }, - "packages/nhsNotifyUpdateCallback": { - "version": "1.0.0", + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, "license": "MIT", - "dependencies": { - "@aws-lambda-powertools/commons": "^2.30.2", - "@aws-lambda-powertools/logger": "^2.30.2", - "@aws-lambda-powertools/parameters": "^2.30.2", - "@aws-sdk/client-secrets-manager": "^3.975.0", - "@middy/core": "^7.0.2", - "@middy/input-output-logger": "^7.0.2", - "@nhs/fhir-middy-error-handler": "^2.1.70", - "axios": "^1.13.2" - }, - "devDependencies": { - "@psu-common/testing": "^1.0.0", - "axios-mock-adapter": "^2.1.0" + "engines": { + "node": ">=8" } }, - "packages/postDatedLambda": { - "version": "1.0.0", + "node_modules/strip-bom": { + "version": "4.0.0", + "dev": true, "license": "MIT", - "dependencies": { - "@aws-lambda-powertools/logger": "^2.30.0", - "@aws-sdk/client-dynamodb": "^3.817.0", - "@aws-sdk/client-sqs": "^3.817.0", - "@aws-sdk/util-dynamodb": "^3.817.0", - "@middy/core": "^6.4.5", - "@middy/input-output-logger": "^6.4.5", - "@nhs/fhir-middy-error-handler": "^2.1.65", - "@psu-common/commonTypes": "^1.0.0" - }, - "devDependencies": { - "@psu-common/testing": "^1.0.0" + "engines": { + "node": ">=8" } }, - "packages/postDatedLambda/node_modules/@middy/core": { - "version": "6.4.5", + "node_modules/strip-final-newline": { + "version": "2.0.0", + "dev": true, "license": "MIT", "engines": { - "node": ">=20" + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/willfarrell" + "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/postDatedLambda/node_modules/@middy/input-output-logger": { - "version": "6.4.5", + "node_modules/strnum": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", + "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/styled-components": { + "version": "6.1.19", + "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "@emotion/is-prop-valid": "1.2.2", + "@emotion/unitless": "0.8.1", + "@types/stylis": "4.2.5", + "css-to-react-native": "3.2.0", + "csstype": "3.1.3", + "postcss": "8.4.49", + "shallowequal": "1.1.0", + "stylis": "4.3.2", + "tslib": "2.6.2" + }, "engines": { - "node": ">=20" + "node": ">= 16" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/willfarrell" + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" } }, - "packages/psuRestoreValidationLambda": { + "node_modules/styled-components/node_modules/tslib": { + "version": "2.6.2", + "dev": true, + "license": "0BSD" + }, + "node_modules/stylis": { + "version": "4.3.2", + "dev": true, + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", + "dev": true, "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/swagger2openapi": { + "version": "7.0.8", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@aws-lambda-powertools/logger": "^2.30.2", - "@aws-sdk/client-backup": "^3.975.0", - "@aws-sdk/client-dynamodb": "^3.958.0", - "@aws-sdk/lib-dynamodb": "^3.975.0", - "@middy/input-output-logger": "^7.0.2", - "@psu-common/middyErrorHandler": "^1.0.0", - "aws-lambda": "^1.0.7" + "call-me-maybe": "^1.0.1", + "node-fetch": "^2.6.1", + "node-fetch-h2": "^2.3.0", + "node-readfiles": "^0.2.0", + "oas-kit-common": "^1.0.8", + "oas-resolver": "^2.5.6", + "oas-schema-walker": "^1.1.5", + "oas-validator": "^5.0.8", + "reftools": "^1.1.9", + "yaml": "^1.10.0", + "yargs": "^17.0.1" }, - "devDependencies": { - "@middy/core": "^7.0.2", - "@psu-common/testing": "^1.0.0" + "bin": { + "boast": "boast.js", + "oas-validate": "oas-validate.js", + "swagger2openapi": "swagger2openapi.js" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/client-dynamodb": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.975.0.tgz", - "integrity": "sha512-Cq6oGb8XswG56YhF2kHmxuyEnMNayDpL8xDxp9E4zIUqDeSLCKE6lCaqZzo5zpngzqLluFsJbCC9jrMzZejMAA==", - "license": "Apache-2.0", - "peer": true, + "node_modules/synckit": { + "version": "0.11.12", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/credential-provider-node": "^3.972.1", - "@aws-sdk/dynamodb-codec": "^3.972.2", - "@aws-sdk/middleware-endpoint-discovery": "^3.972.1", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.2", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.1", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.11", - "@smithy/middleware-retry": "^4.4.27", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.26", - "@smithy/util-defaults-mode-node": "^4.2.29", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "@smithy/util-waiter": "^4.2.8", - "tslib": "^2.6.2" + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" }, "engines": { - "node": ">=20.0.0" + "node": ">=8" } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/client-sso": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.974.0.tgz", - "integrity": "sha512-ci+GiM0c4ULo4D79UMcY06LcOLcfvUfiyt8PzNY0vbt5O8BfCPYf4QomwVgkNcLLCYmroO4ge2Yy1EsLUlcD6g==", - "license": "Apache-2.0", + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.10", - "@smithy/middleware-retry": "^4.4.26", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.11", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.25", - "@smithy/util-defaults-mode-node": "^4.2.28", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=20.0.0" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/core": { - "version": "3.973.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.1.tgz", - "integrity": "sha512-Ocubx42QsMyVs9ANSmFpRm0S+hubWljpPLjOi9UFrtcnVJjrVJTzQ51sN0e5g4e8i8QZ7uY73zosLmgYL7kZTQ==", - "license": "Apache-2.0", + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/xml-builder": "^3.972.1", - "@smithy/core": "^3.21.1", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=20.0.0" + "node": "*" } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.1.tgz", - "integrity": "sha512-/etNHqnx96phy/SjI0HRC588o4vKH5F0xfkZ13yAATV7aNrb+5gYGNE6ePWafP+FuZ3HkULSSlJFj0AxgrAqYw==", - "license": "Apache-2.0", + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { - "node": ">=20.0.0" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.2.tgz", - "integrity": "sha512-mXgdaUfe5oM+tWKyeZ7Vh/iQ94FrkMky1uuzwTOmFADiRcSk5uHy/e3boEFedXiT/PRGzgBmqvJVK4F6lUISCg==", - "license": "Apache-2.0", + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", - "tslib": "^2.6.2" + "is-number": "^7.0.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=8.0" } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.1.tgz", - "integrity": "sha512-OdbJA3v+XlNDsrYzNPRUwr8l7gw1r/nR8l4r96MDzSBDU8WEo8T6C06SvwaXR8SpzsjO3sq5KMP86wXWg7Rj4g==", - "license": "Apache-2.0", + "node_modules/tr46": { + "version": "0.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/treeify": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/ts-algebra": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-jest": { + "version": "29.4.6", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/credential-provider-env": "^3.972.1", - "@aws-sdk/credential-provider-http": "^3.972.1", - "@aws-sdk/credential-provider-login": "^3.972.1", - "@aws-sdk/credential-provider-process": "^3.972.1", - "@aws-sdk/credential-provider-sso": "^3.972.1", - "@aws-sdk/credential-provider-web-identity": "^3.972.1", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.3", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <6" }, - "engines": { - "node": ">=20.0.0" + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.1.tgz", - "integrity": "sha512-CccqDGL6ZrF3/EFWZefvKW7QwwRdxlHUO8NVBKNVcNq6womrPDvqB6xc9icACtE0XB0a7PLoSTkAg8bQVkTO2w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=20.0.0" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.1.tgz", - "integrity": "sha512-DwXPk9GfuU/xG9tmCyXFVkCr6X3W8ZCoL5Ptb0pbltEx1/LCcg7T+PBqDlPiiinNCD6ilIoMJDWsnJ8ikzZA7Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.1", - "@aws-sdk/credential-provider-http": "^3.972.1", - "@aws-sdk/credential-provider-ini": "^3.972.1", - "@aws-sdk/credential-provider-process": "^3.972.1", - "@aws-sdk/credential-provider-sso": "^3.972.1", - "@aws-sdk/credential-provider-web-identity": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/ts-md5": { + "version": "2.0.1", + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=18" } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.1.tgz", - "integrity": "sha512-bi47Zigu3692SJwdBvo8y1dEwE6B61stCwCFnuRWJVTfiM84B+VTSCV661CSWJmIZzmcy7J5J3kWyxL02iHj0w==", - "license": "Apache-2.0", + "node_modules/ts-node": { + "version": "10.9.2", + "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" }, - "engines": { - "node": ">=20.0.0" + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.1.tgz", - "integrity": "sha512-dLZVNhM7wSgVUFsgVYgI5hb5Z/9PUkT46pk/SHrSmUqfx6YDvoV4YcPtaiRqviPpEGGiRtdQMEadyOKIRqulUQ==", - "license": "Apache-2.0", + "node_modules/tslib": { + "version": "2.8.1", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/client-sso": "3.974.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/token-providers": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "prelude-ls": "^1.2.1" }, "engines": { - "node": ">=20.0.0" + "node": ">= 0.8.0" } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.1.tgz", - "integrity": "sha512-YMDeYgi0u687Ay0dAq/pFPKuijrlKTgsaB/UATbxCs/FzZfMiG4If5ksywHmmW7MiYUF8VVv+uou3TczvLrN4w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/type-detect": { + "version": "4.0.8", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=4" } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/dynamodb-codec": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/dynamodb-codec/-/dynamodb-codec-3.972.2.tgz", - "integrity": "sha512-K9CDUrjDSFhVeGibVjdKd+plE25EEo7DMAIl+mYaJ+ySXsQxdPgEmOYDmZq3iq3mpkkV0lG8XWM/oaQ+q+5aCQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@smithy/core": "^3.21.1", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "tslib": "^2.6.2" - }, + "node_modules/type-fest": { + "version": "0.21.3", + "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=20.0.0" + "node": ">=10" }, - "peerDependencies": { - "@aws-sdk/client-dynamodb": "3.975.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/endpoint-cache": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/endpoint-cache/-/endpoint-cache-3.972.1.tgz", - "integrity": "sha512-w9TVoCUNwPG4njcbnZpSQaOZ1BF2z1Guox8NltoXm7oS1+q/8iHeG8eqY9TlGQsKLNA4KfnKUEAx4rlEc6Qv6w==", + "node_modules/typescript": { + "version": "5.9.3", + "dev": true, "license": "Apache-2.0", - "dependencies": { - "mnemonist": "0.38.3", - "tslib": "^2.6.2" + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" }, "engines": { - "node": ">=20.0.0" + "node": ">=14.17" } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/lib-dynamodb": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.975.0.tgz", - "integrity": "sha512-BEhzr8atBFT8mJ8KuFne3OHj81zsqQWLP1Is2zzbzbZy4Mjbm1nRceNEwZ603tp9oFrV2mwTNLagFDPEudJC/g==", - "license": "Apache-2.0", + "node_modules/typescript-eslint": { + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.53.1.tgz", + "integrity": "sha512-gB+EVQfP5RDElh9ittfXlhZJdjSU4jUSTyE2+ia8CYyNvet4ElfaLlAIqDvQV9JPknKx0jQH1racTYe/4LaLSg==", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/util-dynamodb": "3.975.0", - "@smithy/core": "^3.21.1", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@typescript-eslint/eslint-plugin": "8.53.1", + "@typescript-eslint/parser": "8.53.1", + "@typescript-eslint/typescript-estree": "8.53.1", + "@typescript-eslint/utils": "8.53.1" }, "engines": { - "node": ">=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "peerDependencies": { - "@aws-sdk/client-dynamodb": "3.975.0" - } - }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/middleware-endpoint-discovery": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.972.1.tgz", - "integrity": "sha512-3d6QaHQAjevuCioG0lZmZM/Nb8mT4JiF2mRmlh/aTM32Fc/YNGxp2Qbri8B8nfeYlfoi8GM12gH7SaIwkihuBQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/endpoint-cache": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.1.tgz", - "integrity": "sha512-/R82lXLPmZ9JaUGSUdKtBp2k/5xQxvBT3zZWyKiBOhyulFotlfvdlrO8TnqstBimsl4lYEYySDL+W6ldFh6ALg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "node_modules/uglify-js": { + "version": "3.19.3", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" }, "engines": { - "node": ">=20.0.0" + "node": ">=0.8.0" } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/middleware-logger": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.1.tgz", - "integrity": "sha512-JGgFl6cHg9G2FHu4lyFIzmFN8KESBiRr84gLC3Aeni0Gt1nKm+KxWLBuha/RPcXxJygGXCcMM4AykkIwxor8RA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "node_modules/ulid": { + "version": "3.0.2", + "dev": true, + "license": "MIT", + "bin": { + "ulid": "dist/cli.js" } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.1.tgz", - "integrity": "sha512-taGzNRe8vPHjnliqXIHp9kBgIemLE/xCaRTMH1NH0cncHeaPcjxtnCroAAM9aOlPuKvBe2CpZESyvM1+D8oI7Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/undici": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", + "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=18.17" } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.2.tgz", - "integrity": "sha512-d+Exq074wy0X6wvShg/kmZVtkah+28vMuqCtuY3cydg8LUZOJBtbAolCpEJizSyb8mJJZF9BjWaTANXL4OYnkg==", - "license": "Apache-2.0", + "node_modules/undici-types": { + "version": "7.16.0", + "dev": true, + "license": "MIT" + }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "dev": true, + "hasInstallScript": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@smithy/core": "^3.21.1", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "napi-postinstall": "^0.3.0" }, - "engines": { - "node": ">=20.0.0" + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/nested-clients": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.974.0.tgz", - "integrity": "sha512-k3dwdo/vOiHMJc9gMnkPl1BA5aQfTrZbz+8fiDkWrPagqAioZgmo5oiaOaeX0grObfJQKDtcpPFR4iWf8cgl8Q==", - "license": "Apache-2.0", + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.10", - "@smithy/middleware-retry": "^4.4.26", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.11", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.25", - "@smithy/util-defaults-mode-node": "^4.2.28", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, - "engines": { - "node": ">=20.0.0" + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.1.tgz", - "integrity": "sha512-voIY8RORpxLAEgEkYaTFnkaIuRwVBEc+RjVZYcSSllPV+ZEKAacai6kNhJeE3D70Le+JCfvRb52tng/AVHY+jQ==", - "license": "Apache-2.0", + "node_modules/updatePrescriptionStatus": { + "resolved": "packages/updatePrescriptionStatus", + "link": true + }, + "node_modules/upper-case": { + "version": "2.0.2", + "license": "MIT", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "tslib": "^2.0.3" } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/token-providers": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.974.0.tgz", - "integrity": "sha512-cBykL0LiccKIgNhGWvQRTPvsBLPZxnmJU3pYxG538jpFX8lQtrCy1L7mmIHNEdxIdIGEPgAEHF8/JQxgBToqUQ==", - "license": "Apache-2.0", + "node_modules/upper-case-first": { + "version": "2.0.2", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "tslib": "^2.0.3" } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/types": { - "version": "3.973.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.0.tgz", - "integrity": "sha512-jYIdB7a7jhRTvyb378nsjyvJh1Si+zVduJ6urMNGpz8RjkmHZ+9vM2H07XaIB2Cfq0GhJRZYOfUCH8uqQhqBkQ==", - "license": "Apache-2.0", + "node_modules/uri-js": { + "version": "4.4.1", + "license": "BSD-2-Clause", "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "punycode": "^2.1.0" } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/util-dynamodb": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.975.0.tgz", - "integrity": "sha512-ZsziF8m5Syn+kA2YJLEG2kk6zfxea8yRl/7SkSFpAls8RFYkt8EUmVUMBhX2hBpGw+nbZL7+AcRi4S2LxAcYWA==", - "license": "Apache-2.0", + "node_modules/url": { + "version": "0.10.3", + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - }, + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/url-template": { + "version": "2.0.8", + "dev": true, + "license": "BSD" + }, + "node_modules/url/node_modules/punycode": { + "version": "1.3.2", + "license": "MIT" + }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "dev": true, + "license": "MIT", "peerDependencies": { - "@aws-sdk/client-dynamodb": "3.975.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util": { + "version": "0.12.5", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/util-endpoints": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.972.0.tgz", - "integrity": "sha512-6JHsl1V/a1ZW8D8AFfd4R52fwZPnZ5H4U6DS8m/bWT8qad72NvbOFAC7U2cDtFs2TShqUO3TEiX/EJibtY3ijg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.972.0", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-endpoints": "^3.2.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "node_modules/util-deprecate": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/util-extend": { + "version": "1.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/uuid": { + "version": "8.0.0", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/util-endpoints/node_modules/@aws-sdk/types": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz", - "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==", - "license": "Apache-2.0", + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "dev": true, + "license": "ISC", "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=10.12.0" } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.1.tgz", - "integrity": "sha512-IgF55NFmJX8d9Wql9M0nEpk2eYbuD8G4781FN4/fFgwTXBn86DvlZJuRWDCMcMqZymnBVX7HW9r+3r9ylqfW0w==", + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/types": "^4.12.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.1.tgz", - "integrity": "sha512-oIs4JFcADzoZ0c915R83XvK2HltWupxNsXUIuZse2rgk7b97zTpkxaqXiH0h9ylh31qtgo/t8hp4tIqcsMrEbQ==", + "node_modules/walker": { + "version": "1.0.8", + "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } + "makeerror": "1.0.12" } }, - "packages/psuRestoreValidationLambda/node_modules/@aws-sdk/xml-builder": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.1.tgz", - "integrity": "sha512-6zZGlPOqn7Xb+25MAXGb1JhgvaC5HjZj6GzszuVrnEgbhvzBRFGKYemuHBV4bho+dtqeYKPgaZUv7/e80hIGNg==", - "license": "Apache-2.0", + "node_modules/watchpack": { + "version": "2.4.4", + "license": "MIT", "dependencies": { - "@smithy/types": "^4.12.0", - "fast-xml-parser": "5.2.5", - "tslib": "^2.6.2" + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" }, "engines": { - "node": ">=20.0.0" + "node": ">=10.13.0" } }, - "packages/specification": { - "name": "apim-spec", - "version": "0.0.1", - "license": "MIT", - "devDependencies": { - "@redocly/cli": "^2.14.9" - }, - "engines": { - "node": "24.12.x", - "npm": "11.6.x" - } + "node_modules/webidl-conversions": { + "version": "3.0.1", + "dev": true, + "license": "BSD-2-Clause" }, - "packages/statusLambda": { - "version": "1.0.0", + "node_modules/whatwg-url": { + "version": "5.0.0", + "dev": true, "license": "MIT", "dependencies": { - "@aws-lambda-powertools/commons": "^2.30.2", - "@aws-lambda-powertools/logger": "^2.30.2", - "@aws-lambda-powertools/parameters": "^2.30.2", - "@middy/core": "^7.0.2", - "@middy/input-output-logger": "^7.0.2", - "@nhs/fhir-middy-error-handler": "^2.1.70", - "axios": "^1.13.2" - }, - "devDependencies": { - "@psu-common/testing": "^1.0.0", - "axios-mock-adapter": "^2.1.0" + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" } }, - "packages/updatePrescriptionStatus": { - "version": "1.0.0", - "license": "MIT", + "node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", "dependencies": { - "@aws-lambda-powertools/commons": "^2.30.2", - "@aws-lambda-powertools/logger": "^2.30.2", - "@aws-sdk/client-dynamodb": "^3.958.0", - "@aws-sdk/client-sqs": "^3.975.0", - "@aws-sdk/util-dynamodb": "^3.975.0", - "@middy/core": "^7.0.2", - "@middy/http-header-normalizer": "^7.0.2", - "@middy/input-output-logger": "^7.0.2", - "@nhs/fhir-middy-error-handler": "^2.1.70" + "isexe": "^2.0.0" }, - "devDependencies": { - "@faker-js/faker": "^10.2.0", - "@types/fhir": "^0.0.41", - "aws-sdk-client-mock": "^4.1.0" + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/client-dynamodb": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.975.0.tgz", - "integrity": "sha512-Cq6oGb8XswG56YhF2kHmxuyEnMNayDpL8xDxp9E4zIUqDeSLCKE6lCaqZzo5zpngzqLluFsJbCC9jrMzZejMAA==", - "license": "Apache-2.0", - "peer": true, + "node_modules/which-typed-array": { + "version": "1.1.19", + "license": "MIT", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/credential-provider-node": "^3.972.1", - "@aws-sdk/dynamodb-codec": "^3.972.2", - "@aws-sdk/middleware-endpoint-discovery": "^3.972.1", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.2", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.1", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.11", - "@smithy/middleware-retry": "^4.4.27", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.26", - "@smithy/util-defaults-mode-node": "^4.2.29", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "@smithy/util-waiter": "^4.2.8", - "tslib": "^2.6.2" + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">=20.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/client-sso": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.974.0.tgz", - "integrity": "sha512-ci+GiM0c4ULo4D79UMcY06LcOLcfvUfiyt8PzNY0vbt5O8BfCPYf4QomwVgkNcLLCYmroO4ge2Yy1EsLUlcD6g==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.10", - "@smithy/middleware-retry": "^4.4.26", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.11", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.25", - "@smithy/util-defaults-mode-node": "^4.2.28", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, + "node_modules/word-wrap": { + "version": "1.2.5", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=0.10.0" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/core": { - "version": "3.973.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.1.tgz", - "integrity": "sha512-Ocubx42QsMyVs9ANSmFpRm0S+hubWljpPLjOi9UFrtcnVJjrVJTzQ51sN0e5g4e8i8QZ7uY73zosLmgYL7kZTQ==", - "license": "Apache-2.0", + "node_modules/wordwrap": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/xml-builder": "^3.972.1", - "@smithy/core": "^3.21.1", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.1.tgz", - "integrity": "sha512-/etNHqnx96phy/SjI0HRC588o4vKH5F0xfkZ13yAATV7aNrb+5gYGNE6ePWafP+FuZ3HkULSSlJFj0AxgrAqYw==", - "license": "Apache-2.0", + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.2.tgz", - "integrity": "sha512-mXgdaUfe5oM+tWKyeZ7Vh/iQ94FrkMky1uuzwTOmFADiRcSk5uHy/e3boEFedXiT/PRGzgBmqvJVK4F6lUISCg==", - "license": "Apache-2.0", + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", - "tslib": "^2.6.2" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=8" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.1.tgz", - "integrity": "sha512-OdbJA3v+XlNDsrYzNPRUwr8l7gw1r/nR8l4r96MDzSBDU8WEo8T6C06SvwaXR8SpzsjO3sq5KMP86wXWg7Rj4g==", - "license": "Apache-2.0", + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/credential-provider-env": "^3.972.1", - "@aws-sdk/credential-provider-http": "^3.972.1", - "@aws-sdk/credential-provider-login": "^3.972.1", - "@aws-sdk/credential-provider-process": "^3.972.1", - "@aws-sdk/credential-provider-sso": "^3.972.1", - "@aws-sdk/credential-provider-web-identity": "^3.972.1", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=8" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.1.tgz", - "integrity": "sha512-CccqDGL6ZrF3/EFWZefvKW7QwwRdxlHUO8NVBKNVcNq6womrPDvqB6xc9icACtE0XB0a7PLoSTkAg8bQVkTO2w==", - "license": "Apache-2.0", + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "dev": true, + "license": "ISC", "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" }, "engines": { - "node": ">=20.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.1.tgz", - "integrity": "sha512-DwXPk9GfuU/xG9tmCyXFVkCr6X3W8ZCoL5Ptb0pbltEx1/LCcg7T+PBqDlPiiinNCD6ilIoMJDWsnJ8ikzZA7Q==", - "license": "Apache-2.0", + "node_modules/ws": { + "version": "7.5.10", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/xml2js": { + "version": "0.6.2", + "license": "MIT", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.1", - "@aws-sdk/credential-provider-http": "^3.972.1", - "@aws-sdk/credential-provider-ini": "^3.972.1", - "@aws-sdk/credential-provider-process": "^3.972.1", - "@aws-sdk/credential-provider-sso": "^3.972.1", - "@aws-sdk/credential-provider-web-identity": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=4.0.0" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.1.tgz", - "integrity": "sha512-bi47Zigu3692SJwdBvo8y1dEwE6B61stCwCFnuRWJVTfiM84B+VTSCV661CSWJmIZzmcy7J5J3kWyxL02iHj0w==", - "license": "Apache-2.0", + "node_modules/xmlbuilder": { + "version": "11.0.1", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "1.10.2", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yaml-ast-parser": { + "version": "0.0.43", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/yargs": { + "version": "17.7.2", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=12" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.1.tgz", - "integrity": "sha512-dLZVNhM7wSgVUFsgVYgI5hb5Z/9PUkT46pk/SHrSmUqfx6YDvoV4YcPtaiRqviPpEGGiRtdQMEadyOKIRqulUQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.974.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/token-providers": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "dev": true, + "license": "ISC", "engines": { - "node": ">=20.0.0" + "node": ">=12" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.1.tgz", - "integrity": "sha512-YMDeYgi0u687Ay0dAq/pFPKuijrlKTgsaB/UATbxCs/FzZfMiG4If5ksywHmmW7MiYUF8VVv+uou3TczvLrN4w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=8" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/dynamodb-codec": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/dynamodb-codec/-/dynamodb-codec-3.972.2.tgz", - "integrity": "sha512-K9CDUrjDSFhVeGibVjdKd+plE25EEo7DMAIl+mYaJ+ySXsQxdPgEmOYDmZq3iq3mpkkV0lG8XWM/oaQ+q+5aCQ==", - "license": "Apache-2.0", + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@smithy/core": "^3.21.1", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "tslib": "^2.6.2" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-dynamodb": "3.975.0" + "node": ">=8" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/endpoint-cache": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/endpoint-cache/-/endpoint-cache-3.972.1.tgz", - "integrity": "sha512-w9TVoCUNwPG4njcbnZpSQaOZ1BF2z1Guox8NltoXm7oS1+q/8iHeG8eqY9TlGQsKLNA4KfnKUEAx4rlEc6Qv6w==", - "license": "Apache-2.0", + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", "dependencies": { - "mnemonist": "0.38.3", - "tslib": "^2.6.2" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=8" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/middleware-endpoint-discovery": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.972.1.tgz", - "integrity": "sha512-3d6QaHQAjevuCioG0lZmZM/Nb8mT4JiF2mRmlh/aTM32Fc/YNGxp2Qbri8B8nfeYlfoi8GM12gH7SaIwkihuBQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/endpoint-cache": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/yn": { + "version": "3.1.1", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=6" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.1.tgz", - "integrity": "sha512-/R82lXLPmZ9JaUGSUdKtBp2k/5xQxvBT3zZWyKiBOhyulFotlfvdlrO8TnqstBimsl4lYEYySDL+W6ldFh6ALg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/middleware-logger": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.1.tgz", - "integrity": "sha512-JGgFl6cHg9G2FHu4lyFIzmFN8KESBiRr84gLC3Aeni0Gt1nKm+KxWLBuha/RPcXxJygGXCcMM4AykkIwxor8RA==", - "license": "Apache-2.0", + "packages/capabilityStatement": { + "version": "1.0.0", + "license": "MIT", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@aws-lambda-powertools/commons": "^2.30.2", + "@aws-lambda-powertools/logger": "^2.30.2", + "@middy/core": "^7.0.2", + "@middy/input-output-logger": "^7.0.2", + "@nhs/fhir-middy-error-handler": "^2.1.70" }, - "engines": { - "node": ">=20.0.0" + "devDependencies": { + "@psu-common/testing": "^1.0.0" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.1.tgz", - "integrity": "sha512-taGzNRe8vPHjnliqXIHp9kBgIemLE/xCaRTMH1NH0cncHeaPcjxtnCroAAM9aOlPuKvBe2CpZESyvM1+D8oI7Q==", - "license": "Apache-2.0", + "packages/checkPrescriptionStatusUpdates": { + "version": "1.0.0", + "license": "MIT", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@aws-lambda-powertools/commons": "^2.30.2", + "@aws-lambda-powertools/logger": "^2.30.2", + "@aws-lambda-powertools/parameters": "^2.30.2", + "@aws-sdk/client-dynamodb": "^3.958.0", + "@aws-sdk/lib-dynamodb": "^3.975.0", + "@middy/core": "^7.0.2", + "@middy/http-header-normalizer": "^7.0.2", + "@middy/input-output-logger": "^7.0.2", + "@psu-common/middyErrorHandler": "^1.0.0" }, - "engines": { - "node": ">=20.0.0" + "devDependencies": { + "@psu-common/testing": "^1.0.0" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.2.tgz", - "integrity": "sha512-d+Exq074wy0X6wvShg/kmZVtkah+28vMuqCtuY3cydg8LUZOJBtbAolCpEJizSyb8mJJZF9BjWaTANXL4OYnkg==", - "license": "Apache-2.0", + "packages/common/commonTypes": { + "name": "@psu-common/commonTypes", + "version": "1.0.0", + "license": "MIT", + "devDependencies": {} + }, + "packages/common/middyErrorHandler": { + "name": "@psu-common/middyErrorHandler", + "version": "1.0.0", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@smithy/core": "^3.21.1", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@aws-lambda-powertools/logger": "^2.30.2" }, - "engines": { - "node": ">=20.0.0" + "devDependencies": { + "@middy/core": "^7.0.2", + "@psu-common/testing": "^1.0.0" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/nested-clients": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.974.0.tgz", - "integrity": "sha512-k3dwdo/vOiHMJc9gMnkPl1BA5aQfTrZbz+8fiDkWrPagqAioZgmo5oiaOaeX0grObfJQKDtcpPFR4iWf8cgl8Q==", - "license": "Apache-2.0", + "packages/common/testing": { + "name": "@psu-common/testing", + "version": "1.0.0", + "license": "MIT" + }, + "packages/common/utilities": { + "name": "@psu-common/utilities", + "version": "1.0.0", + "license": "MIT", + "devDependencies": {} + }, + "packages/cpsuLambda": { + "version": "1.0.0", + "license": "MIT", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.10", - "@smithy/middleware-retry": "^4.4.26", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.11", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.25", - "@smithy/util-defaults-mode-node": "^4.2.28", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" + "@aws-lambda-powertools/commons": "^2.30.2", + "@aws-lambda-powertools/logger": "^2.30.2", + "@aws-sdk/client-dynamodb": "^3.958.0", + "@aws-sdk/util-dynamodb": "^3.975.0", + "@middy/core": "^7.0.2", + "@middy/http-header-normalizer": "^7.0.2", + "@middy/input-output-logger": "^7.0.2", + "@middy/validator": "6.4.1", + "json-schema-to-ts": "^3.1.1", + "pratica": "^2.3.0", + "ts-md5": "^2.0.1", + "uuid": "^13.0.0" }, - "engines": { - "node": ">=20.0.0" + "devDependencies": { + "@faker-js/faker": "^10.2.0", + "@types/fhir": "^0.0.41", + "aws-sdk-client-mock": "^4.1.0" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.1.tgz", - "integrity": "sha512-voIY8RORpxLAEgEkYaTFnkaIuRwVBEc+RjVZYcSSllPV+ZEKAacai6kNhJeE3D70Le+JCfvRb52tng/AVHY+jQ==", - "license": "Apache-2.0", + "packages/cpsuLambda/node_modules/uuid": { + "version": "13.0.0", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "packages/gsul": { + "version": "1.0.0", + "license": "MIT", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "@aws-lambda-powertools/commons": "^2.30.2", + "@aws-lambda-powertools/logger": "^2.30.2", + "@aws-sdk/client-dynamodb": "^3.958.0", + "@aws-sdk/lib-dynamodb": "^3.975.0", + "@aws-sdk/util-dynamodb": "^3.975.0", + "@middy/core": "^7.0.2", + "@middy/input-output-logger": "^7.0.2", + "@middy/validator": "6.4.1", + "@psu-common/middyErrorHandler": "^1.0.0", + "json-schema-to-ts": "^3.1.1" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/token-providers": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.974.0.tgz", - "integrity": "sha512-cBykL0LiccKIgNhGWvQRTPvsBLPZxnmJU3pYxG538jpFX8lQtrCy1L7mmIHNEdxIdIGEPgAEHF8/JQxgBToqUQ==", - "license": "Apache-2.0", + "packages/nhsd-psu-sandbox": { + "version": "1.0.0", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "@aws-lambda-powertools/logger": "^2.30.2", + "@middy/core": "^7.0.2", + "@middy/input-output-logger": "^7.0.2", + "@nhs/fhir-middy-error-handler": "^2.1.70" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/types": { - "version": "3.973.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.0.tgz", - "integrity": "sha512-jYIdB7a7jhRTvyb378nsjyvJh1Si+zVduJ6urMNGpz8RjkmHZ+9vM2H07XaIB2Cfq0GhJRZYOfUCH8uqQhqBkQ==", - "license": "Apache-2.0", + "packages/nhsNotifyLambda": { + "version": "1.0.0", + "license": "MIT", "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@aws-lambda-powertools/commons": "^2.30.2", + "@aws-lambda-powertools/logger": "^2.30.2", + "@aws-lambda-powertools/parameters": "^2.30.2", + "@aws-sdk/client-ssm": "^3.975.0", + "@middy/core": "^7.0.2", + "@middy/input-output-logger": "^7.0.2", + "@nhs/fhir-middy-error-handler": "^2.1.70", + "axios": "^1.13.2", + "axios-retry": "^4.5.0", + "jose": "^6.1.3", + "nock": "^14.0.10" }, - "engines": { - "node": ">=20.0.0" + "devDependencies": { + "@psu-common/testing": "^1.0.0", + "axios-mock-adapter": "^2.1.0" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/util-dynamodb": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.975.0.tgz", - "integrity": "sha512-ZsziF8m5Syn+kA2YJLEG2kk6zfxea8yRl/7SkSFpAls8RFYkt8EUmVUMBhX2hBpGw+nbZL7+AcRi4S2LxAcYWA==", - "license": "Apache-2.0", + "packages/nhsNotifyUpdateCallback": { + "version": "1.0.0", + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "@aws-lambda-powertools/commons": "^2.30.2", + "@aws-lambda-powertools/logger": "^2.30.2", + "@aws-lambda-powertools/parameters": "^2.30.2", + "@aws-sdk/client-secrets-manager": "^3.975.0", + "@middy/core": "^7.0.2", + "@middy/input-output-logger": "^7.0.2", + "@nhs/fhir-middy-error-handler": "^2.1.70", + "axios": "^1.13.2" }, - "peerDependencies": { - "@aws-sdk/client-dynamodb": "3.975.0" + "devDependencies": { + "@psu-common/testing": "^1.0.0", + "axios-mock-adapter": "^2.1.0" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/util-endpoints": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.972.0.tgz", - "integrity": "sha512-6JHsl1V/a1ZW8D8AFfd4R52fwZPnZ5H4U6DS8m/bWT8qad72NvbOFAC7U2cDtFs2TShqUO3TEiX/EJibtY3ijg==", - "license": "Apache-2.0", + "packages/postDatedLambda": { + "version": "1.0.0", + "license": "MIT", "dependencies": { - "@aws-sdk/types": "3.972.0", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-endpoints": "^3.2.8", - "tslib": "^2.6.2" + "@aws-lambda-powertools/logger": "^2.30.0", + "@aws-sdk/client-dynamodb": "^3.817.0", + "@aws-sdk/client-sqs": "^3.817.0", + "@aws-sdk/util-dynamodb": "^3.817.0", + "@middy/core": "^6.4.5", + "@middy/input-output-logger": "^6.4.5", + "@nhs/fhir-middy-error-handler": "^2.1.65", + "@psu-common/commonTypes": "^1.0.0" }, - "engines": { - "node": ">=20.0.0" + "devDependencies": { + "@psu-common/testing": "^1.0.0" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/util-endpoints/node_modules/@aws-sdk/types": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz", - "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "packages/postDatedLambda/node_modules/@middy/core": { + "version": "6.4.5", + "license": "MIT", + "engines": { + "node": ">=20" }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/willfarrell" + } + }, + "packages/postDatedLambda/node_modules/@middy/input-output-logger": { + "version": "6.4.5", + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=20" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/willfarrell" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.1.tgz", - "integrity": "sha512-IgF55NFmJX8d9Wql9M0nEpk2eYbuD8G4781FN4/fFgwTXBn86DvlZJuRWDCMcMqZymnBVX7HW9r+3r9ylqfW0w==", - "license": "Apache-2.0", + "packages/psuRestoreValidationLambda": { + "version": "1.0.0", + "license": "MIT", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/types": "^4.12.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" + "@aws-lambda-powertools/logger": "^2.30.2", + "@aws-sdk/client-backup": "^3.975.0", + "@aws-sdk/client-dynamodb": "^3.958.0", + "@aws-sdk/lib-dynamodb": "^3.975.0", + "@middy/input-output-logger": "^7.0.2", + "@psu-common/middyErrorHandler": "^1.0.0", + "aws-lambda": "^1.0.7" + }, + "devDependencies": { + "@middy/core": "^7.0.2", + "@psu-common/testing": "^1.0.0" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.1.tgz", - "integrity": "sha512-oIs4JFcADzoZ0c915R83XvK2HltWupxNsXUIuZse2rgk7b97zTpkxaqXiH0h9ylh31qtgo/t8hp4tIqcsMrEbQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "packages/specification": { + "name": "apim-spec", + "version": "0.0.1", + "license": "MIT", + "devDependencies": { + "@redocly/cli": "^2.14.9" }, "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" + "node": "24.12.x", + "npm": "11.6.x" + } + }, + "packages/statusLambda": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@aws-lambda-powertools/commons": "^2.30.2", + "@aws-lambda-powertools/logger": "^2.30.2", + "@aws-lambda-powertools/parameters": "^2.30.2", + "@middy/core": "^7.0.2", + "@middy/input-output-logger": "^7.0.2", + "@nhs/fhir-middy-error-handler": "^2.1.70", + "axios": "^1.13.2" }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } + "devDependencies": { + "@psu-common/testing": "^1.0.0", + "axios-mock-adapter": "^2.1.0" } }, - "packages/updatePrescriptionStatus/node_modules/@aws-sdk/xml-builder": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.1.tgz", - "integrity": "sha512-6zZGlPOqn7Xb+25MAXGb1JhgvaC5HjZj6GzszuVrnEgbhvzBRFGKYemuHBV4bho+dtqeYKPgaZUv7/e80hIGNg==", - "license": "Apache-2.0", + "packages/updatePrescriptionStatus": { + "version": "1.0.0", + "license": "MIT", "dependencies": { - "@smithy/types": "^4.12.0", - "fast-xml-parser": "5.2.5", - "tslib": "^2.6.2" + "@aws-lambda-powertools/commons": "^2.30.2", + "@aws-lambda-powertools/logger": "^2.30.2", + "@aws-sdk/client-dynamodb": "^3.958.0", + "@aws-sdk/client-sqs": "^3.975.0", + "@aws-sdk/util-dynamodb": "^3.975.0", + "@middy/core": "^7.0.2", + "@middy/http-header-normalizer": "^7.0.2", + "@middy/input-output-logger": "^7.0.2", + "@nhs/fhir-middy-error-handler": "^2.1.70" }, - "engines": { - "node": ">=20.0.0" + "devDependencies": { + "@faker-js/faker": "^10.2.0", + "@types/fhir": "^0.0.41", + "aws-sdk-client-mock": "^4.1.0" } } } diff --git a/package.json b/package.json index b2d23517ed..90b83e8c48 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "typescript-eslint": "^8.53.1" }, "dependencies": { + "@aws-sdk/lib-dynamodb": "^3.975.0", "@psu-common/commonTypes": "^1.0.0", "@psu-common/middyErrorHandler": "^1.0.0", "@psu-common/utilities": "^1.0.0", From 902d99e402ee182c79fc50d1a0ecbe841dcccec6 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Mon, 26 Jan 2026 13:18:44 +0000 Subject: [PATCH 39/75] Fix SAM template --- SAMtemplates/functions/main.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/SAMtemplates/functions/main.yaml b/SAMtemplates/functions/main.yaml index 3adeaed794..601f9f2c43 100644 --- a/SAMtemplates/functions/main.yaml +++ b/SAMtemplates/functions/main.yaml @@ -587,7 +587,6 @@ Resources: - Fn::ImportValue: !Sub ${StackName}-UseNotificationSQSQueueKMSKeyPolicyArn - Fn::ImportValue: !Sub ${StackName}-WritePostDatedNotificationsSQSQueuePolicyArn - Fn::ImportValue: !Sub ${StackName}-ReadPostDatedNotificationsSQSQueuePolicyArn - - Fn::ImportValue: !Sub ${StackName}-WriteNHSNotifyPrescriptionsSQSQueuePolicyArn # Doesnt need to be able to read, only write. - Fn::ImportValue: !Sub ${StackName}:tables:${PrescriptionStatusUpdatesTableName}:TableWritePolicyArn - Fn::ImportValue: !Sub ${StackName}:tables:${PrescriptionStatusUpdatesTableName}:TableReadPolicyArn - Fn::ImportValue: !Sub ${StackName}:tables:UsePrescriptionStatusUpdatesKMSKeyPolicyArn From 34c895efeb3c2b9a575b93d986f65e1a9974b8ee Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Mon, 26 Jan 2026 14:20:39 +0000 Subject: [PATCH 40/75] Forgot to pass through feature flag --- .github/workflows/run_release_code_and_api.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/run_release_code_and_api.yml b/.github/workflows/run_release_code_and_api.yml index 2638233c0a..ebe7724c13 100644 --- a/.github/workflows/run_release_code_and_api.yml +++ b/.github/workflows/run_release_code_and_api.yml @@ -219,6 +219,7 @@ jobs: ENABLE_BACKUP: ${{ inputs.ENABLE_BACKUP }} ENABLE_NOTIFICATIONS_INTERNAL: ${{ inputs.ENABLE_NOTIFICATIONS_INTERNAL }} ENABLE_NOTIFICATIONS_EXTERNAL: ${{ inputs.ENABLE_NOTIFICATIONS_EXTERNAL }} + ENABLE_POST_DATED_NOTIFICATIONS: ${{ inputs.ENABLE_POST_DATED_NOTIFICATIONS }} REQUIRE_APPLICATION_NAME: ${{ inputs.REQUIRE_APPLICATION_NAME }} ENABLED_SITE_ODS_CODES: ${{ steps.read.outputs.ods_csv }} ENABLED_SYSTEMS: ${{ inputs.ENABLED_SYSTEMS }} From 76a31656c6eb0dc6ab3b1da06197ba0926734172 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Mon, 26 Jan 2026 14:47:03 +0000 Subject: [PATCH 41/75] Receive dedupe IDs, and pass them through when forwarding SQS messages --- packages/postDatedLambda/src/sqs.ts | 17 ++++++++++++----- packages/postDatedLambda/tests/testSqs.test.ts | 17 ++++++++++++----- .../src/utils/sqsClient.ts | 2 +- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/packages/postDatedLambda/src/sqs.ts b/packages/postDatedLambda/src/sqs.ts index b90b5e6a4f..6f6eb244c6 100644 --- a/packages/postDatedLambda/src/sqs.ts +++ b/packages/postDatedLambda/src/sqs.ts @@ -35,25 +35,31 @@ function buildNotificationBatchEntries( ): Array { return messages.map((message, idx) => { const {prescriptionData} = message - const requestId = prescriptionData.RequestID // If we get something with no deduplication ID, then something upstream is wrong and we should fail out if (!message.Attributes?.MessageDeduplicationId) { logger.error("Post-dated SQS message is missing MessageDeduplicationId attribute", { - messageId: message.MessageId, message + messageId: message.MessageId, messageContents: message }) throw new Error("Missing MessageDeduplicationId in SQS message attributes") } + // Same for group ID + if (!message.Attributes?.MessageGroupId) { + logger.error("Post-dated SQS message is missing MessageGroupId attribute", { + messageId: message.MessageId, messageContents: message + }) + throw new Error("Missing MessageGroupId in SQS message attributes") + } return { Id: idx.toString(), MessageBody: JSON.stringify(prescriptionData), MessageDeduplicationId: message.Attributes?.MessageDeduplicationId, - MessageGroupId: requestId, + MessageGroupId: message.Attributes?.MessageGroupId, MessageAttributes: { RequestId: { DataType: "String", - StringValue: requestId + StringValue: message.Attributes?.MessageGroupId } } } @@ -182,7 +188,8 @@ export async function receivePostDatedSQSMessages(logger: Logger): Promise { const mockMessages = [ { MessageId: "1", - Body: JSON.stringify({PrescriptionID: "presc1"}) + Body: JSON.stringify({PrescriptionID: "presc1"}), + Attributes: {MessageDeduplicationId: "dedup1", MessageGroupId: "group1"} }, { MessageId: "2", - Body: JSON.stringify({PrescriptionID: "presc2"}) + Body: JSON.stringify({PrescriptionID: "presc2"}), + Attributes: {MessageDeduplicationId: "dedup2", MessageGroupId: "group2"} } ] mockSend.mockReturnValueOnce({ @@ -150,6 +152,11 @@ describe("sqs", () => { const result = await receivePostDatedSQSMessages(logger) expect(mockSend).toHaveBeenCalledTimes(1) + const receiveCommand = mockSend.mock.calls[0][0] as { + input: {MessageAttributeNames?: Array; MessageSystemAttributeNames?: Array} + } + expect(receiveCommand.input.MessageSystemAttributeNames).toEqual(["MessageDeduplicationId", "MessageGroupId"]) + expect(receiveCommand.input.MessageAttributeNames).toEqual(["All"]) expect(result).toHaveLength(2) expect(result[0].MessageId).toBe("1") expect(result[0].prescriptionData.PrescriptionID).toBe("presc1") @@ -183,7 +190,7 @@ describe("sqs", () => { MessageId: "1", ReceiptHandle: "handle-1", prescriptionData: createMockPostModifiedDataItem({RequestID: "req-1"}), - Attributes: {MessageDeduplicationId: "dedup-1"} + Attributes: {MessageDeduplicationId: "dedup1", MessageGroupId: "group1"} } ] @@ -353,14 +360,14 @@ describe("sqs", () => { { MessageId: "1", ReceiptHandle: "handle1", prescriptionData: createMockPostModifiedDataItem({}), - Attributes: {MessageDeduplicationId: "dedup-1"} + Attributes: {MessageDeduplicationId: "dedup1", MessageGroupId: "group1"} } ] const immatureMessages: Array = [ { MessageId: "2", ReceiptHandle: "handle2", prescriptionData: createMockPostModifiedDataItem({}), - Attributes: {MessageDeduplicationId: "dedup-1"} + Attributes: {MessageDeduplicationId: "dedup2", MessageGroupId: "group2"} } ] diff --git a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts index e066ad213c..29c3fd7de3 100644 --- a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts +++ b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts @@ -300,7 +300,7 @@ async function sendItemsToSQS( items, requestId, sqsSalt, - item => item as PostDatedNotifyDataItem + item => item as PostDatedNotifyDataItem // TODO: When we sunset PostDated, change this to NotifyDataItem ) return sendEntriesToQueue(entries, sqsUrl, requestId, logger) From 53c7535b8ab3eb3953f1f5d3f11810a3a3af0b52 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Mon, 26 Jan 2026 16:01:55 +0000 Subject: [PATCH 42/75] Include postdatedlastmodifiedsetat in dynamo attributes --- SAMtemplates/tables/main.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/SAMtemplates/tables/main.yaml b/SAMtemplates/tables/main.yaml index a32ea6a626..61f9ce7b97 100644 --- a/SAMtemplates/tables/main.yaml +++ b/SAMtemplates/tables/main.yaml @@ -184,6 +184,7 @@ Resources: - TerminalStatus - LastModified - Status + - PostDatedLastModifiedSetAt # TODO: Remove this when we deprecate post modified prescriptions. ProjectionType: INCLUDE ProvisionedThroughput: !If - EnableDynamoDBAutoScalingCondition From a99be635dc3ae3d2b3754d269d1c700afa7bdf84 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Mon, 26 Jan 2026 16:32:15 +0000 Subject: [PATCH 43/75] Define a new GSI for post-dated enrichment --- SAMtemplates/tables/main.yaml | 22 ++++++++++++++++++- .../postDatedLambda/src/databaseClient.ts | 2 +- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/SAMtemplates/tables/main.yaml b/SAMtemplates/tables/main.yaml index 61f9ce7b97..b0da7592dd 100644 --- a/SAMtemplates/tables/main.yaml +++ b/SAMtemplates/tables/main.yaml @@ -184,7 +184,6 @@ Resources: - TerminalStatus - LastModified - Status - - PostDatedLastModifiedSetAt # TODO: Remove this when we deprecate post modified prescriptions. ProjectionType: INCLUDE ProvisionedThroughput: !If - EnableDynamoDBAutoScalingCondition @@ -202,6 +201,27 @@ Resources: - ReadCapacityUnits: 1 WriteCapacityUnits: !Ref MinWritePrescriptionStatusUpdatesCapacity - !Ref "AWS::NoValue" + # TODO: Remove this when we deprecate post modified prescriptions. + - IndexName: PharmacyODSCodePrescriptionIDIndexPostDatedIndex + KeySchema: + - AttributeName: PharmacyODSCode + KeyType: HASH + - AttributeName: PrescriptionID + KeyType: RANGE + Projection: + NonKeyAttributes: + - PatientNHSNumber + - LineItemID + - TerminalStatus + - LastModified + - Status + - PostDatedLastModifiedSetAt + ProjectionType: INCLUDE + ProvisionedThroughput: !If + - EnableDynamoDBAutoScalingCondition + - ReadCapacityUnits: 1 + WriteCapacityUnits: !Ref MinWritePrescriptionStatusUpdatesCapacity + - !Ref "AWS::NoValue" Tags: - Key: "EPS-Tablename" Value: "PrescriptionStatusUpdates" diff --git a/packages/postDatedLambda/src/databaseClient.ts b/packages/postDatedLambda/src/databaseClient.ts index 0d4f3ca328..2980e46526 100644 --- a/packages/postDatedLambda/src/databaseClient.ts +++ b/packages/postDatedLambda/src/databaseClient.ts @@ -11,7 +11,7 @@ import { const client = new DynamoDBClient() const tableName = process.env.TABLE_NAME ?? "PrescriptionStatusUpdates" -const pharmacyPrescriptionIndexName = "PharmacyODSCodePrescriptionIDIndex" +const pharmacyPrescriptionIndexName = "PharmacyODSCodePrescriptionIDIndexPostDatedIndex" export function createPrescriptionLookupKey(prescriptionID: string, pharmacyODSCode: string): string { return `${prescriptionID.toUpperCase()}#${pharmacyODSCode.toUpperCase()}` From 4c65bd4f0113f823c17d2024618e1eb7ea49b774 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Tue, 27 Jan 2026 10:08:22 +0000 Subject: [PATCH 44/75] Extend the NotifyDataItem to include the prescription ID, rather than creating a whole new type --- SAMtemplates/functions/main.yaml | 2 ++ packages/common/commonTypes/src/index.ts | 10 ++-------- packages/nhsNotifyLambda/tests/notify.test.ts | 2 ++ packages/nhsNotifyLambda/tests/testUtils.test.ts | 14 ++++++++++++++ packages/postDatedLambda/src/databaseClient.ts | 4 ++-- packages/postDatedLambda/src/sqs.ts | 4 ++-- packages/postDatedLambda/src/types.ts | 6 +++--- packages/postDatedLambda/tests/testUtils.ts | 9 +++------ .../src/utils/sqsClient.ts | 16 +++++----------- 9 files changed, 35 insertions(+), 32 deletions(-) diff --git a/SAMtemplates/functions/main.yaml b/SAMtemplates/functions/main.yaml index 601f9f2c43..e25cc55107 100644 --- a/SAMtemplates/functions/main.yaml +++ b/SAMtemplates/functions/main.yaml @@ -541,6 +541,8 @@ Resources: LOG_LEVEL: !Ref LogLevel NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL: !Ref NHSNotifyPrescriptionsSQSQueueUrl POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL: !Ref PostDatedNotificationsSQSQueueUrl + POST_DATED_OVERRIDE: false + POST_DATED_OVERRIDE_VALUE: false # By default, override never matures post dated prescriptions TABLE_NAME: !Ref PrescriptionStatusUpdatesTableName Events: ScheduleEvent: diff --git a/packages/common/commonTypes/src/index.ts b/packages/common/commonTypes/src/index.ts index 7ea8fdabc6..8f22b3e6e2 100644 --- a/packages/common/commonTypes/src/index.ts +++ b/packages/common/commonTypes/src/index.ts @@ -24,14 +24,8 @@ export interface NotifyDataItem { RequestID: string TaskID: string Status: string -} - -// TODO: This should be removed when we stop supporting post-dated updates -export interface PostDatedNotifyDataItem extends NotifyDataItem { - LastModified: string - PostDatedLastModifiedSetAt: string // This is the meta.lastUpdated field from the FHIR resource - PrescriptionID: string - LineItemID: string + // TODO: This should be removed when we stop supporting post-dated updates + PrescriptionID: string // Needed to query NPPTS } /** diff --git a/packages/nhsNotifyLambda/tests/notify.test.ts b/packages/nhsNotifyLambda/tests/notify.test.ts index 36aa726e51..61a200a0a5 100644 --- a/packages/nhsNotifyLambda/tests/notify.test.ts +++ b/packages/nhsNotifyLambda/tests/notify.test.ts @@ -46,6 +46,7 @@ describe("logNotificationRequest", () => { const dataItem1: NotifyDataItemMessage = { messageReference: "msg-ref-1", PSUDataItem: { + PrescriptionID: "presc-1", PatientNHSNumber: "9453740578", PharmacyODSCode: "FA566", RequestID: "req-1", @@ -57,6 +58,7 @@ describe("logNotificationRequest", () => { const dataItem2: NotifyDataItemMessage = { messageReference: "msg-ref-2", PSUDataItem: { + PrescriptionID: "presc-2", PatientNHSNumber: "9912003071", PharmacyODSCode: "A83008", RequestID: "req-2", diff --git a/packages/nhsNotifyLambda/tests/testUtils.test.ts b/packages/nhsNotifyLambda/tests/testUtils.test.ts index 9029db622b..b3c2c3355d 100644 --- a/packages/nhsNotifyLambda/tests/testUtils.test.ts +++ b/packages/nhsNotifyLambda/tests/testUtils.test.ts @@ -611,6 +611,7 @@ describe("NHS notify lambda helper functions", () => { const data = [ constructPSUDataItemMessage({ PSUDataItem: { + PrescriptionID: "prescription-id", RequestID: "r1", PatientNHSNumber: "n1", PharmacyODSCode: "o1", @@ -620,6 +621,7 @@ describe("NHS notify lambda helper functions", () => { }), constructPSUDataItemMessage({ PSUDataItem: { + PrescriptionID: "prescription-id", RequestID: "r2", PatientNHSNumber: "n2", PharmacyODSCode: "o2", @@ -688,6 +690,7 @@ describe("NHS notify lambda helper functions", () => { const data = [ constructPSUDataItemMessage({ PSUDataItem: { + PrescriptionID: "prescription-id", RequestID: "rA", PatientNHSNumber: "nx", PharmacyODSCode: "ox", @@ -734,6 +737,7 @@ describe("NHS notify lambda helper functions", () => { const data = [ constructPSUDataItemMessage({ PSUDataItem: { + PrescriptionID: "prescription-id", RequestID: "rX", PatientNHSNumber: "ny", PharmacyODSCode: "oy", @@ -743,6 +747,7 @@ describe("NHS notify lambda helper functions", () => { }), constructPSUDataItemMessage({ PSUDataItem: { + PrescriptionID: "prescription-id", RequestID: "rY", PatientNHSNumber: "nz", PharmacyODSCode: "oz", @@ -782,6 +787,7 @@ describe("NHS notify lambda helper functions", () => { const data = Array.from({length: 7}, (_, i) => constructPSUDataItemMessage({ PSUDataItem: { + PrescriptionID: "prescription-id", RequestID: `r${i}`, PatientNHSNumber: `n${i}`, PharmacyODSCode: `o${i}`, @@ -829,6 +835,7 @@ describe("NHS notify lambda helper functions", () => { const data = [ constructPSUDataItemMessage({ PSUDataItem: { + PrescriptionID: "prescription-id", RequestID: "r1", PatientNHSNumber: "n1", PharmacyODSCode: "o1", @@ -838,6 +845,7 @@ describe("NHS notify lambda helper functions", () => { }), constructPSUDataItemMessage({ PSUDataItem: { + PrescriptionID: "prescription-id", RequestID: "r2", PatientNHSNumber: "n2", PharmacyODSCode: "o2", @@ -887,6 +895,7 @@ describe("NHS notify lambda helper functions", () => { const data = [ constructPSUDataItemMessage({ PSUDataItem: { + PrescriptionID: "prescription-id", RequestID: "r1", PatientNHSNumber: "n1", PharmacyODSCode: "o1", @@ -896,6 +905,7 @@ describe("NHS notify lambda helper functions", () => { }), constructPSUDataItemMessage({ PSUDataItem: { + PrescriptionID: "prescription-id", RequestID: "r2", PatientNHSNumber: "n2", PharmacyODSCode: "o2", @@ -964,6 +974,7 @@ describe("NHS notify lambda helper functions", () => { const dataWithMixed = [ constructPSUDataItemMessage({ PSUDataItem: { + PrescriptionID: "prescription-id", RequestID: "r1", PatientNHSNumber: "n1", PharmacyODSCode: "o1", @@ -973,6 +984,7 @@ describe("NHS notify lambda helper functions", () => { }), constructPSUDataItemMessage({ PSUDataItem: { + PrescriptionID: "prescription-id", RequestID: "r2", PatientNHSNumber: "n2", PharmacyODSCode: "o2", @@ -1004,6 +1016,7 @@ describe("NHS notify lambda helper functions", () => { const data = [constructPSUDataItemMessage({ PSUDataItem: { + PrescriptionID: "prescription-id", RequestID: "r1", PatientNHSNumber: "n1", PharmacyODSCode: "o1", @@ -1026,6 +1039,7 @@ describe("NHS notify lambda helper functions", () => { const data = [constructPSUDataItemMessage({ PSUDataItem: { + PrescriptionID: "prescription-id", RequestID: "r1", PatientNHSNumber: "n1", PharmacyODSCode: "o1", diff --git a/packages/postDatedLambda/src/databaseClient.ts b/packages/postDatedLambda/src/databaseClient.ts index 2980e46526..6849cc4547 100644 --- a/packages/postDatedLambda/src/databaseClient.ts +++ b/packages/postDatedLambda/src/databaseClient.ts @@ -2,7 +2,7 @@ import {Logger} from "@aws-lambda-powertools/logger" import {DynamoDBClient, QueryCommand, QueryCommandInput} from "@aws-sdk/client-dynamodb" import {unmarshall} from "@aws-sdk/util-dynamodb" -import {PSUDataItem, PostDatedNotifyDataItem} from "@psu-common/commonTypes" +import {PSUDataItem, NotifyDataItem} from "@psu-common/commonTypes" import { PostDatedPrescriptionWithExistingRecords, PostDatedSQSMessage, @@ -102,7 +102,7 @@ export async function getExistingRecordsByPrescriptionID( * Existing records are sorted by LastModified descending. */ export async function fetchExistingRecordsForPrescriptions( - postDatedItems: Array, + postDatedItems: Array, logger: Logger ): Promise> { logger.info("Fetching existing records for post-dated prescriptions", { diff --git a/packages/postDatedLambda/src/sqs.ts b/packages/postDatedLambda/src/sqs.ts index 6f6eb244c6..5de3c03d49 100644 --- a/packages/postDatedLambda/src/sqs.ts +++ b/packages/postDatedLambda/src/sqs.ts @@ -9,7 +9,7 @@ import { } from "@aws-sdk/client-sqs" import {Logger} from "@aws-lambda-powertools/logger" -import {PostDatedNotifyDataItem, SQSBatchMessage} from "@psu-common/commonTypes" +import {NotifyDataItem, SQSBatchMessage} from "@psu-common/commonTypes" import {BatchProcessingResult, PostDatedSQSMessage} from "./types" @@ -212,7 +212,7 @@ export async function receivePostDatedSQSMessages(logger: Logger): Promise } diff --git a/packages/postDatedLambda/tests/testUtils.ts b/packages/postDatedLambda/tests/testUtils.ts index 5eb0952b7d..2f172efa8b 100644 --- a/packages/postDatedLambda/tests/testUtils.ts +++ b/packages/postDatedLambda/tests/testUtils.ts @@ -1,16 +1,13 @@ -import {PostDatedNotifyDataItem} from "packages/common/commonTypes/lib/src" +import {NotifyDataItem} from "packages/common/commonTypes/lib/src" -export function createMockPostModifiedDataItem(overrides: Partial): PostDatedNotifyDataItem { +export function createMockPostModifiedDataItem(overrides: Partial): NotifyDataItem { return { - LastModified: "2023-01-02T00:00:00Z", - LineItemID: "spamandeggs", PatientNHSNumber: "0123456789", PharmacyODSCode: "ABC123", - PrescriptionID: "abcdef-ghijkl-mnopqr", RequestID: "x-request-id", Status: "ready to collect", TaskID: "mnopqr-ghijkl-abcdef", - PostDatedLastModifiedSetAt: "Changed dosage instructions", + PrescriptionID: "abcdef-ghijkl-mnopqr", ...overrides } } diff --git a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts index 29c3fd7de3..ca86669a9a 100644 --- a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts +++ b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts @@ -6,7 +6,6 @@ import {createHmac} from "node:crypto" import { NotifyDataItem, - PostDatedNotifyDataItem, PSUDataItem, PSUDataItemWithPrevious, SQSBatchMessage @@ -40,18 +39,14 @@ function chunkArray(arr: Array, size: number): Array> { return chunks } -type DeduplicationSource = Pick -type NotificationMessage = NotifyDataItem | PostDatedNotifyDataItem - -function buildSqsBatchEntries( - items: Array, +function buildSqsBatchEntries( + items: Array, requestId: string, - sqsSalt: string, - toMessageBody: (item: T) => NotificationMessage + sqsSalt: string ): Array { return items.map((item, idx) => ({ Id: idx.toString(), - MessageBody: JSON.stringify(toMessageBody(item)), + MessageBody: JSON.stringify(item as NotifyDataItem), MessageDeduplicationId: saltedHash(`${item.PatientNHSNumber}:${item.PharmacyODSCode}`, sqsSalt), MessageGroupId: requestId, MessageAttributes: { @@ -299,8 +294,7 @@ async function sendItemsToSQS( const entries = buildSqsBatchEntries( items, requestId, - sqsSalt, - item => item as PostDatedNotifyDataItem // TODO: When we sunset PostDated, change this to NotifyDataItem + sqsSalt ) return sendEntriesToQueue(entries, sqsUrl, requestId, logger) From 4f473137965b7842c49c4b4c3f78437c309aae59 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Tue, 27 Jan 2026 10:08:57 +0000 Subject: [PATCH 45/75] Add an override flag for the post-dated business logic, for testing later --- packages/postDatedLambda/src/businessLogic.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/postDatedLambda/src/businessLogic.ts b/packages/postDatedLambda/src/businessLogic.ts index 3681afcaf5..4b79ce34da 100644 --- a/packages/postDatedLambda/src/businessLogic.ts +++ b/packages/postDatedLambda/src/businessLogic.ts @@ -2,6 +2,9 @@ import {Logger} from "@aws-lambda-powertools/logger" import {PostDatedSQSMessageWithExistingRecords} from "./types" +const POST_DATED_OVERRIDE = process.env.POST_DATED_OVERRIDE === "true" +const POST_DATED_OVERRIDE_VALUE = process.env.POST_DATED_OVERRIDE_VALUE === "true" + /** * Process a single post-dated prescription message. * This is a placeholder function that I'll implement properly later. @@ -20,6 +23,12 @@ export async function processMessage( existingRecordsCount: message.existingRecords.length, existingRecordTaskIds: message.existingRecords.map((r) => r.TaskID) }) + if (POST_DATED_OVERRIDE) { + logger.info("Post-dated override is enabled, returning override value", { + overrideValue: POST_DATED_OVERRIDE_VALUE + }) + return POST_DATED_OVERRIDE_VALUE + } // TODO: Implement actual business logic for post-dated prescription processing // The existingRecords array contains all records from the DynamoDB table @@ -28,5 +37,17 @@ export async function processMessage( // NOTE: It is technically possible for the array to be empty if no existing records are found // This SHOULD never happen in practice, but the code should handle it gracefully just in case + const mostRecentRecord = message.existingRecords.reduce((latest, record) => { + return new Date(record.LastModified) > new Date(latest.LastModified) ? record : latest + }, message.existingRecords[0]) + const mostRecentLastModified = new Date(mostRecentRecord.LastModified) + const desiredTransitionTime = new Date(mostRecentRecord.PostDatedLastModifiedSetAt as string) + const currentTime = new Date() + logger.info("Post-dated prescription timing details", { + mostRecentLastModified: mostRecentLastModified.toISOString(), + desiredTransitionTime: desiredTransitionTime.toISOString(), + currentTime: currentTime.toISOString() + }) + return true } From f0d010f90389e57727adb5dd570ac76e1c8da452 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Tue, 27 Jan 2026 11:38:52 +0000 Subject: [PATCH 46/75] refactor the db client --- .../postDatedLambda/src/databaseClient.ts | 88 +++++++++++-------- 1 file changed, 49 insertions(+), 39 deletions(-) diff --git a/packages/postDatedLambda/src/databaseClient.ts b/packages/postDatedLambda/src/databaseClient.ts index 6849cc4547..8461838987 100644 --- a/packages/postDatedLambda/src/databaseClient.ts +++ b/packages/postDatedLambda/src/databaseClient.ts @@ -13,6 +13,12 @@ const client = new DynamoDBClient() const tableName = process.env.TABLE_NAME ?? "PrescriptionStatusUpdates" const pharmacyPrescriptionIndexName = "PharmacyODSCodePrescriptionIDIndexPostDatedIndex" +type PrescriptionLookupRequest = { + lookupKey: string + prescriptionID: string + pharmacyODSCode: string +} + export function createPrescriptionLookupKey(prescriptionID: string, pharmacyODSCode: string): string { return `${prescriptionID.toUpperCase()}#${pharmacyODSCode.toUpperCase()}` } @@ -106,25 +112,54 @@ export async function fetchExistingRecordsForPrescriptions( logger: Logger ): Promise> { logger.info("Fetching existing records for post-dated prescriptions", { - prescriptionCount: postDatedItems.length + prescriptionCount: postDatedItems.length, + prescriptionIDs: postDatedItems.map((p) => p.PrescriptionID) }) + const lookupRequests = buildLookupRequests(postDatedItems) + const existingRecordsMap = await buildExistingRecordsMap(lookupRequests, logger) + + // Map each post-dated item to its corresponding existing records + const results: Array = postDatedItems.map( + (postDatedData) => { + const lookupKey = createPrescriptionLookupKey(postDatedData.PrescriptionID, postDatedData.PharmacyODSCode) + const existingRecords = existingRecordsMap.get(lookupKey) ?? [] + + return { + postDatedData, + existingRecords + } + }) + + return results +} + +function buildLookupRequests(postDatedItems: Array): Array { + // Run though a map to deduplicate lookups + const lookups = new Map() - // The data table is indexed by both PrescriptionID and PharmacyODSCode, so build a map keyed by these - // in combination. Should avoid duplicate queries this way. - const uniquePrescriptionLookups = new Map() for (const item of postDatedItems) { const lookupKey = createPrescriptionLookupKey(item.PrescriptionID, item.PharmacyODSCode) - if (!uniquePrescriptionLookups.has(lookupKey)) { - uniquePrescriptionLookups.set(lookupKey, { - prescriptionID: item.PrescriptionID, - pharmacyODSCode: item.PharmacyODSCode - }) - } + + // dont worry about overwriting entries, since they'll be identical + lookups.set(lookupKey, { + lookupKey, + prescriptionID: item.PrescriptionID, + pharmacyODSCode: item.PharmacyODSCode + }) } + return Array.from(lookups.values()) +} + +async function buildExistingRecordsMap( + lookupRequests: Array, + logger: Logger +): Promise>> { const existingRecordsMap = new Map>() + + // await all lookups in parallel await Promise.all( - Array.from(uniquePrescriptionLookups.entries()).map(async ([lookupKey, {prescriptionID, pharmacyODSCode}]) => { + lookupRequests.map(async ({lookupKey, prescriptionID, pharmacyODSCode}) => { try { const records = await getExistingRecordsByPrescriptionID(prescriptionID, pharmacyODSCode, logger) existingRecordsMap.set(lookupKey, records) @@ -134,30 +169,12 @@ export async function fetchExistingRecordsForPrescriptions( pharmacyODSCode, error }) - // Store empty array on error to allow processing to continue - existingRecordsMap.set(lookupKey, []) + existingRecordsMap.set(lookupKey, []) // Continue processing other prescriptions even when one fails } }) ) - // Map each post-dated item to its corresponding existing records - const results: Array = postDatedItems.map( - (postDatedData) => { - const lookupKey = createPrescriptionLookupKey(postDatedData.PrescriptionID, postDatedData.PharmacyODSCode) - const existingRecords = existingRecordsMap.get(lookupKey) ?? [] - - return { - postDatedData, - existingRecords - } - }) - - logger.info("fetched existing prescription update records for all post-dated prescription IDs", { - totalPrescriptions: postDatedItems.length, - uniquePrescriptionLookups: existingRecordsMap.size - }) - - return results + return existingRecordsMap } /** @@ -172,10 +189,6 @@ export async function enrichMessagesWithExistingRecords( messages: Array, logger: Logger ): Promise> { - if (messages.length === 0) { - return [] - } - const postDatedItems = messages.map((m) => m.prescriptionData) const prescriptionsWithRecords = await fetchExistingRecordsForPrescriptions(postDatedItems, logger) @@ -189,10 +202,7 @@ export async function enrichMessagesWithExistingRecords( for (const msg of enrichedMessages) { logger.info("Prescription and most recent existing record", { prescriptionID: msg.prescriptionData.PrescriptionID, - existingRecordCount: msg.existingRecords.length, - mostRecentExistingRecord: msg.existingRecords.length > 0 - ? msg.existingRecords[0] - : null + existingRecordCount: msg.existingRecords.length }) } From 46dd8ec1cf58dab7c4ab76698e60f17e45ee2636 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Tue, 27 Jan 2026 11:39:20 +0000 Subject: [PATCH 47/75] Partially implement the business logic --- packages/postDatedLambda/src/businessLogic.ts | 90 ++++++++++++++++--- packages/postDatedLambda/src/orchestration.ts | 21 +++-- packages/postDatedLambda/src/sqs.ts | 20 +++-- packages/postDatedLambda/src/types.ts | 8 ++ 4 files changed, 105 insertions(+), 34 deletions(-) diff --git a/packages/postDatedLambda/src/businessLogic.ts b/packages/postDatedLambda/src/businessLogic.ts index 4b79ce34da..c142178e6f 100644 --- a/packages/postDatedLambda/src/businessLogic.ts +++ b/packages/postDatedLambda/src/businessLogic.ts @@ -1,9 +1,40 @@ import {Logger} from "@aws-lambda-powertools/logger" -import {PostDatedSQSMessageWithExistingRecords} from "./types" +import {PSUDataItem} from "@psu-common/commonTypes" +import {PostDatedSQSMessageWithExistingRecords, PostDatedProcessingResult} from "./types" + +// defaults to false const POST_DATED_OVERRIDE = process.env.POST_DATED_OVERRIDE === "true" -const POST_DATED_OVERRIDE_VALUE = process.env.POST_DATED_OVERRIDE_VALUE === "true" + +// set from environment variable POST_DATED_OVERRIDE_VALUE +const POST_DATED_OVERRIDE_VALUE_ENV = process.env.POST_DATED_OVERRIDE_VALUE ?? "ignore" +let POST_DATED_OVERRIDE_VALUE: PostDatedProcessingResult +switch (POST_DATED_OVERRIDE_VALUE_ENV.toLowerCase()) { + case "matured": + POST_DATED_OVERRIDE_VALUE = PostDatedProcessingResult.MATURED + break + case "immature": + POST_DATED_OVERRIDE_VALUE = PostDatedProcessingResult.IMMATURE + break + default: + POST_DATED_OVERRIDE_VALUE = PostDatedProcessingResult.IGNORE + break +} + +export function getMostRecentRecord( + existingRecords: Array +): PSUDataItem { + return existingRecords.reduce((latest, record) => { + const latestTimestamp = latest.PostDatedLastModifiedSetAt + ? new Date(latest.PostDatedLastModifiedSetAt) + : new Date(latest.LastModified) + const recordTimestamp = record.PostDatedLastModifiedSetAt + ? new Date(record.PostDatedLastModifiedSetAt) + : new Date(record.LastModified) + return recordTimestamp > latestTimestamp ? record : latest + }, existingRecords[0]) +} /** * Process a single post-dated prescription message. @@ -13,15 +44,14 @@ const POST_DATED_OVERRIDE_VALUE = process.env.POST_DATED_OVERRIDE_VALUE === "tru * @param message - The SQS message containing post-dated prescription data and existing records * @returns Promise - true if the post-dated prescription has matured, and false otherwise */ -export async function processMessage( +export function processMessage( logger: Logger, message: PostDatedSQSMessageWithExistingRecords -): Promise { - logger.info("Processing post-dated prescription message (dummy)", { +): string { + logger.info("Processing post-dated prescription message", { messageId: message.MessageId, prescriptionData: message.prescriptionData, - existingRecordsCount: message.existingRecords.length, - existingRecordTaskIds: message.existingRecords.map((r) => r.TaskID) + existingRecords: message.existingRecords }) if (POST_DATED_OVERRIDE) { logger.info("Post-dated override is enabled, returning override value", { @@ -30,18 +60,50 @@ export async function processMessage( return POST_DATED_OVERRIDE_VALUE } - // TODO: Implement actual business logic for post-dated prescription processing // The existingRecords array contains all records from the DynamoDB table // that match this prescription's PrescriptionID // NOTE: It is technically possible for the array to be empty if no existing records are found - // This SHOULD never happen in practice, but the code should handle it gracefully just in case + // This SHOULD never happen in practice, but catch it anyway + if (message.existingRecords.length === 0) { + logger.error("No existing records found for post-dated prescription, cannot process", { + badMessage: message + }) + + // throw new Error("No existing records found for post-dated prescription") // maybe? + return PostDatedProcessingResult.IGNORE + } + + // We only care about the most recent submission for this prescription + // If PostDatedLastModifiedSetAt IS set, it is the timestamp we received the submission + // If it is NOT set, then LastModified is the timestamp we received the submission + const mostRecentRecord = getMostRecentRecord(message.existingRecords) + + logger.info("Most recent NPPTS record for post-dated processing", { + mostRecentRecord + }) + + // Is it post-dated? + if (!mostRecentRecord.PostDatedLastModifiedSetAt) { + logger.info( + "Most recent record is not marked as post-dated, and will have been processed " + + "by the standard logic already. Marking as to be ignored by the post-dated notifications lambda." + ) + return PostDatedProcessingResult.IGNORE + } + + // Is it still RTC? + const mostRecentStatus = mostRecentRecord.Status.toLowerCase() + const notifiableStatuses: Array = ["ready to collect", "ready to collect - partial"] + if (!notifiableStatuses.includes(mostRecentStatus)) { + logger.info("Most recent status in the NPPTS data store is not a notifiable status, so will be ignored", { + mostRecentStatus: mostRecentStatus + }) + return PostDatedProcessingResult.IGNORE + } - const mostRecentRecord = message.existingRecords.reduce((latest, record) => { - return new Date(record.LastModified) > new Date(latest.LastModified) ? record : latest - }, message.existingRecords[0]) const mostRecentLastModified = new Date(mostRecentRecord.LastModified) - const desiredTransitionTime = new Date(mostRecentRecord.PostDatedLastModifiedSetAt as string) + const desiredTransitionTime = new Date(mostRecentRecord.PostDatedLastModifiedSetAt) const currentTime = new Date() logger.info("Post-dated prescription timing details", { mostRecentLastModified: mostRecentLastModified.toISOString(), @@ -49,5 +111,5 @@ export async function processMessage( currentTime: currentTime.toISOString() }) - return true + return PostDatedProcessingResult.MATURED } diff --git a/packages/postDatedLambda/src/orchestration.ts b/packages/postDatedLambda/src/orchestration.ts index fdbcc5adfa..2ff63ae528 100644 --- a/packages/postDatedLambda/src/orchestration.ts +++ b/packages/postDatedLambda/src/orchestration.ts @@ -3,7 +3,7 @@ import {Logger} from "@aws-lambda-powertools/logger" import {processMessage} from "./businessLogic" import {enrichMessagesWithExistingRecords} from "./databaseClient" import {receivePostDatedSQSMessages, reportQueueStatus, handleProcessedMessages} from "./sqs" -import {BatchProcessingResult, PostDatedSQSMessage} from "./types" +import {BatchProcessingResult, PostDatedProcessingResult, PostDatedSQSMessage} from "./types" export const MAX_QUEUE_RUNTIME = 14 * 60 * 1000 // 14 minutes, to avoid Lambda timeout issues (timeout is 15 minutes) const MIN_RECEIVED_THRESHOLD = 3 // If fewer than this number of messages are received, consider the queue empty @@ -21,24 +21,22 @@ export async function processMessages( messages: Array, logger: Logger ): Promise { - if (messages.length === 0) { - logger.info("No messages to process in batch") - return {maturedPrescriptionUpdates: [], immaturePrescriptionUpdates: []} - } - // Enrich messages with existing records from DynamoDB const enrichedMessages = await enrichMessagesWithExistingRecords(messages, logger) const maturedPrescriptionUpdates: Array = [] const immaturePrescriptionUpdates: Array = [] + const ignoredPrescriptionUpdates: Array = [] for (const message of enrichedMessages) { try { - const success = await processMessage(logger, message) - if (success) { + const action = processMessage(logger, message) + if (action === PostDatedProcessingResult.MATURED) { maturedPrescriptionUpdates.push(message) - } else { + } else if (action === PostDatedProcessingResult.IMMATURE) { immaturePrescriptionUpdates.push(message) + } else { + ignoredPrescriptionUpdates.push(message) } } catch (error) { logger.error("Error processing message", { @@ -52,10 +50,11 @@ export async function processMessages( logger.info("Batch processing complete", { totalMessages: messages.length, maturedPrescriptionUpdatesCount: maturedPrescriptionUpdates.length, - immaturePrescriptionUpdatesCount: immaturePrescriptionUpdates.length + immaturePrescriptionUpdatesCount: immaturePrescriptionUpdates.length, + ignoredPrescriptionUpdatesCount: ignoredPrescriptionUpdates.length }) - return {maturedPrescriptionUpdates, immaturePrescriptionUpdates} + return {maturedPrescriptionUpdates, immaturePrescriptionUpdates, ignoredPrescriptionUpdates} } /** diff --git a/packages/postDatedLambda/src/sqs.ts b/packages/postDatedLambda/src/sqs.ts index 5de3c03d49..90a72952cd 100644 --- a/packages/postDatedLambda/src/sqs.ts +++ b/packages/postDatedLambda/src/sqs.ts @@ -243,7 +243,8 @@ export async function sendSQSMessagesToNotificationQueue( messages: Array ): Promise> { if (messages.length === 0) { - logger.info("No matured post-dated messages to forward to notifications queue") + // exit early so we don't send a SendMessageBatch with no entries + logger.info("No messages to forward to notifications queue") return [] } @@ -272,6 +273,7 @@ export async function removeSQSMessages( messages: Array ): Promise { if (messages.length === 0) { + // exit early so we don't send a DeleteMessageBatch with no entries logger.info("No messages to delete") return } @@ -319,6 +321,7 @@ export async function returnMessagesToQueue( messages: Array ): Promise { if (messages.length === 0) { + // exit early so we don't send a ChangeMessageVisibilityBatch with no entries logger.info("No messages to return to queue") return } @@ -377,16 +380,15 @@ export async function handleProcessedMessages( result: BatchProcessingResult, logger: Logger ): Promise { - const {maturedPrescriptionUpdates, immaturePrescriptionUpdates} = result + const {maturedPrescriptionUpdates, immaturePrescriptionUpdates, ignoredPrescriptionUpdates} = result // Move matured messages to the notification queue and remove them from the post-dated queue - if (maturedPrescriptionUpdates.length > 0) { - await sendSQSMessagesToNotificationQueue(logger, maturedPrescriptionUpdates) - await removeSQSMessages(logger, maturedPrescriptionUpdates) - } + await sendSQSMessagesToNotificationQueue(logger, maturedPrescriptionUpdates) + await removeSQSMessages(logger, maturedPrescriptionUpdates) // Return failed messages to the queue - if (immaturePrescriptionUpdates.length > 0) { - await returnMessagesToQueue(logger, immaturePrescriptionUpdates) - } + await returnMessagesToQueue(logger, immaturePrescriptionUpdates) + + // Remove ignored messages from the queue, so they are not reprocessed + await removeSQSMessages(logger, ignoredPrescriptionUpdates) } diff --git a/packages/postDatedLambda/src/types.ts b/packages/postDatedLambda/src/types.ts index 4c0ebe1851..092a44e17a 100644 --- a/packages/postDatedLambda/src/types.ts +++ b/packages/postDatedLambda/src/types.ts @@ -29,10 +29,18 @@ export interface PostDatedSQSMessageWithExistingRecords extends PostDatedSQSMess existingRecords: Array } +// Enum of strings, "matured", "immature", "ignore" +export enum PostDatedProcessingResult { + MATURED = "matured", + IMMATURE = "immature", + IGNORE = "ignore" +} + /** * Result of processing a batch of messages. */ export interface BatchProcessingResult { maturedPrescriptionUpdates: Array immaturePrescriptionUpdates: Array + ignoredPrescriptionUpdates: Array } From 1d12b94967f1523b70aa300ca61a62ee41c1c7e0 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Tue, 27 Jan 2026 12:50:37 +0000 Subject: [PATCH 48/75] Fix unit tests --- .../tests/testOrchestration.test.ts | 62 +++++++++++-------- .../postDatedLambda/tests/testSqs.test.ts | 41 +++++++++++- 2 files changed, 74 insertions(+), 29 deletions(-) diff --git a/packages/postDatedLambda/tests/testOrchestration.test.ts b/packages/postDatedLambda/tests/testOrchestration.test.ts index 90bb7834ef..1ec4156503 100644 --- a/packages/postDatedLambda/tests/testOrchestration.test.ts +++ b/packages/postDatedLambda/tests/testOrchestration.test.ts @@ -34,13 +34,29 @@ jest.unstable_mockModule("../src/sqs", () => { import {Logger} from "@aws-lambda-powertools/logger" import {createMockPostModifiedDataItem} from "./testUtils" -import {BatchProcessingResult, PostDatedSQSMessage} from "../src/types" +import {BatchProcessingResult, PostDatedProcessingResult, PostDatedSQSMessage} from "../src/types" // Import the orchestration module after mocking dependencies const {processMessages, processPostDatedQueue} = await import("../src/orchestration") const logger = new Logger({serviceName: "postDatedLambdaTEST"}) +// I needed to move these functions out of the describe block since it was too deeply nested. +function createBatch(ids: Array): Array { + return ids.map((id) => ({ + MessageId: id, + Body: `Message ${id}`, + prescriptionData: createMockPostModifiedDataItem({}) + })) +} + +function enrich(messages: Array) { + return messages.map((message) => ({ + ...message, + existingRecords: [] + })) +} + describe("orchestration", () => { describe("processMessages", () => { beforeEach(() => { @@ -50,15 +66,18 @@ describe("orchestration", () => { it("should process messages and categorize them correctly", async () => { const mockMessages: Array = [ {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostModifiedDataItem({})}, - {MessageId: "2", Body: "Message 2", prescriptionData: createMockPostModifiedDataItem({})} + {MessageId: "2", Body: "Message 2", prescriptionData: createMockPostModifiedDataItem({})}, + {MessageId: "3", Body: "Message 3", prescriptionData: createMockPostModifiedDataItem({})} ] // Mock the enrichment function to return the same messages mockEnrichMessagesWithExistingRecords.mockReturnValueOnce(mockMessages) // Mock processMessage to return true for first message and false for second - mockProcessMessage.mockReturnValueOnce(true) - mockProcessMessage.mockReturnValueOnce(false) + mockProcessMessage + .mockReturnValueOnce(PostDatedProcessingResult.MATURED) + .mockReturnValueOnce(PostDatedProcessingResult.IMMATURE) + .mockReturnValueOnce(PostDatedProcessingResult.IGNORE) const result = await processMessages(mockMessages, logger) @@ -66,14 +85,17 @@ describe("orchestration", () => { expect(result.maturedPrescriptionUpdates[0].MessageId).toBe("1") expect(result.immaturePrescriptionUpdates).toHaveLength(1) expect(result.immaturePrescriptionUpdates[0].MessageId).toBe("2") + expect(result.ignoredPrescriptionUpdates).toHaveLength(1) + expect(result.ignoredPrescriptionUpdates[0].MessageId).toBe("3") }) it("should handle empty message array", async () => { + mockEnrichMessagesWithExistingRecords.mockReturnValueOnce([]) const result = await processMessages([], logger) expect(result.maturedPrescriptionUpdates).toHaveLength(0) expect(result.immaturePrescriptionUpdates).toHaveLength(0) - expect(mockEnrichMessagesWithExistingRecords).not.toHaveBeenCalled() + expect(result.ignoredPrescriptionUpdates).toHaveLength(0) }) it("should log errors and mark messages immature when processing throws", async () => { @@ -84,8 +106,8 @@ describe("orchestration", () => { mockEnrichMessagesWithExistingRecords.mockReturnValueOnce(mockMessages) mockProcessMessage - .mockReturnValueOnce(true) - .mockImplementationOnce(async () => { + .mockReturnValueOnce(PostDatedProcessingResult.MATURED) + .mockImplementationOnce(() => { throw new Error("processing failed") }) @@ -94,6 +116,7 @@ describe("orchestration", () => { expect(result.maturedPrescriptionUpdates).toHaveLength(1) expect(result.immaturePrescriptionUpdates).toHaveLength(1) + expect(result.ignoredPrescriptionUpdates).toHaveLength(0) expect(result.immaturePrescriptionUpdates[0].MessageId).toBe("2") expect(errorSpy).toHaveBeenCalledWith( "Error processing message", @@ -113,7 +136,7 @@ describe("orchestration", () => { } mockEnrichMessagesWithExistingRecords.mockReturnValueOnce([enrichedMessage]) - mockProcessMessage.mockReturnValue(true) + mockProcessMessage.mockReturnValue(PostDatedProcessingResult.MATURED) await processMessages(mockMessages, logger) @@ -139,7 +162,7 @@ describe("orchestration", () => { mockReceivePostDatedSQSMessages.mockReturnValueOnce(mockMessages) mockEnrichMessagesWithExistingRecords.mockReturnValueOnce(mockEnrichedMessages) - mockProcessMessage.mockReturnValue(true) + mockProcessMessage.mockReturnValue(PostDatedProcessingResult.MATURED) await processPostDatedQueue(logger) @@ -179,7 +202,7 @@ describe("orchestration", () => { mockProcessMessage.mockImplementation(async () => { // Overrun by a second jest.advanceTimersByTime(MAX_QUEUE_RUNTIME + 1000) - return true + return PostDatedProcessingResult.MATURED }) await processPostDatedQueue(logger) @@ -189,23 +212,10 @@ describe("orchestration", () => { }) it("should continue processing batches until message count drops below threshold", async () => { - const createBatch = (ids: Array) => - ids.map((id) => ({ - MessageId: id, - Body: `Message ${id}`, - prescriptionData: createMockPostModifiedDataItem({}) - })) - const batch1 = createBatch(["1", "2", "3"]) const batch2 = createBatch(["4", "5", "6"]) const batch3 = createBatch(["7"]) - const enrich = (messages: Array) => - messages.map((message) => ({ - ...message, - existingRecords: [] - })) - mockReceivePostDatedSQSMessages .mockReturnValueOnce(batch1) .mockReturnValueOnce(batch2) @@ -214,7 +224,7 @@ describe("orchestration", () => { .mockReturnValueOnce(enrich(batch1)) .mockReturnValueOnce(enrich(batch2)) .mockReturnValueOnce(enrich(batch3)) - mockProcessMessage.mockReturnValue(true) + mockProcessMessage.mockReturnValue(PostDatedProcessingResult.MATURED) await processPostDatedQueue(logger) @@ -227,16 +237,14 @@ describe("orchestration", () => { it("should treat empty receives as drained batches", async () => { mockReceivePostDatedSQSMessages.mockReturnValueOnce([]) + mockEnrichMessagesWithExistingRecords.mockReturnValueOnce([]) await processPostDatedQueue(logger) - expect(mockEnrichMessagesWithExistingRecords).not.toHaveBeenCalled() - expect(mockProcessMessage).not.toHaveBeenCalled() expect(mockHandleProcessedMessages).toHaveBeenCalledTimes(1) const [result] = mockHandleProcessedMessages.mock.calls[0] as [BatchProcessingResult] expect(result.maturedPrescriptionUpdates).toHaveLength(0) expect(result.immaturePrescriptionUpdates).toHaveLength(0) - expect(mockReportQueueStatus).not.toHaveBeenCalled() }) }) }) diff --git a/packages/postDatedLambda/tests/testSqs.test.ts b/packages/postDatedLambda/tests/testSqs.test.ts index 97ee17e213..947193ddf2 100644 --- a/packages/postDatedLambda/tests/testSqs.test.ts +++ b/packages/postDatedLambda/tests/testSqs.test.ts @@ -373,7 +373,8 @@ describe("sqs", () => { const batchResult: BatchProcessingResult = { maturedPrescriptionUpdates: maturedMessages, - immaturePrescriptionUpdates: immatureMessages + immaturePrescriptionUpdates: immatureMessages, + ignoredPrescriptionUpdates: [] } // Mock SQS responses @@ -403,7 +404,8 @@ describe("sqs", () => { it("should handle empty matured and immature message arrays gracefully", async () => { const batchResult: BatchProcessingResult = { maturedPrescriptionUpdates: [], - immaturePrescriptionUpdates: [] + immaturePrescriptionUpdates: [], + ignoredPrescriptionUpdates: [] } await handleProcessedMessages(batchResult, logger) @@ -412,5 +414,40 @@ describe("sqs", () => { expect(infoSpy).not.toHaveBeenCalledWith("Successfully removed") expect(infoSpy).not.toHaveBeenCalledWith("Returning messages to queue with timeouts", expect.anything()) }) + + it("should remove ignored messages from the queue so they are not reprocessed", async () => { + const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue" + process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl + + const ignoredMessages: Array = [ + { + MessageId: "ignored-1", + ReceiptHandle: "ignored-handle-1", + prescriptionData: createMockPostModifiedDataItem({}), + Attributes: {MessageDeduplicationId: "dedup-ignored", MessageGroupId: "group-ignored"} + } + ] + + const batchResult: BatchProcessingResult = { + maturedPrescriptionUpdates: [], + immaturePrescriptionUpdates: [], + ignoredPrescriptionUpdates: ignoredMessages + } + + mockSend.mockReturnValueOnce({ + Successful: ignoredMessages.map((msg) => ({Id: msg.MessageId})), + Failed: [] + }) + + await handleProcessedMessages(batchResult, logger) + + expect(mockSend).toHaveBeenCalledTimes(1) + + const deleteCommand = mockSend.mock.calls[0][0] as {input: {Entries: Array<{Id: string; ReceiptHandle: string}>}} + expect(deleteCommand.input.Entries).toEqual([ + {Id: "ignored-1", ReceiptHandle: "ignored-handle-1"} + ]) + expect(infoSpy).toHaveBeenCalledWith("Successfully removed 1 messages from SQS") + }) }) }) From f7529b926502ec8f0533a7a735fde55954695429 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Tue, 27 Jan 2026 14:03:33 +0000 Subject: [PATCH 49/75] Implement business logic --- .../docs/mature_prescription_check.md | 17 +++++++++++++ packages/postDatedLambda/src/businessLogic.ts | 25 +++++++++++++++---- 2 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 packages/postDatedLambda/docs/mature_prescription_check.md diff --git a/packages/postDatedLambda/docs/mature_prescription_check.md b/packages/postDatedLambda/docs/mature_prescription_check.md new file mode 100644 index 0000000000..8b3f3dd5aa --- /dev/null +++ b/packages/postDatedLambda/docs/mature_prescription_check.md @@ -0,0 +1,17 @@ +```mermaid +flowchart TD + A["Start `processMessage()`"] --> C{"POST_DATED_OVERRIDE?"} + C -- Yes --> D["Log override + return override value"] + C -- No --> E{"existingRecords empty?"} + E -- Yes --> F["Log error + return IGNORE"] + E -- No --> G["Fetch most recently submitted NPPTS record"] + G --> H{"PostDatedLastModifiedSetAt present?"} + H -- No --> I["Log non post-dated + return IGNORE"] + H -- Yes --> K{"NPPTS Status in [RTC, RTC-partial]?"} + K -- No --> L["Log non-notifiable + return IGNORE"] + K -- Yes --> M["mostRecentLastModified = Date(LastModified)"] + M --> N["currentTime = new Date()"] + N --> O{"mostRecentLastModified AFTER currentTime?"} + O -- Yes --> P["return IMMATURE"] + O -- No --> Q["return MATURED"] +``` diff --git a/packages/postDatedLambda/src/businessLogic.ts b/packages/postDatedLambda/src/businessLogic.ts index c142178e6f..0d58edd0df 100644 --- a/packages/postDatedLambda/src/businessLogic.ts +++ b/packages/postDatedLambda/src/businessLogic.ts @@ -38,7 +38,7 @@ export function getMostRecentRecord( /** * Process a single post-dated prescription message. - * This is a placeholder function that I'll implement properly later. + * A flow diagram of this logic is available at ../docs/mature_prescription_check.md * * @param logger - The AWS Lambda Powertools logger instance * @param message - The SQS message containing post-dated prescription data and existing records @@ -102,12 +102,27 @@ export function processMessage( return PostDatedProcessingResult.IGNORE } + // We know that we have a recent, post-dated prescription status update. + // Check if its LastModified time is in the future. + + // Stored as YYYY-MM-DDTHH:mm:ss.sssZ const mostRecentLastModified = new Date(mostRecentRecord.LastModified) - const desiredTransitionTime = new Date(mostRecentRecord.PostDatedLastModifiedSetAt) const currentTime = new Date() - logger.info("Post-dated prescription timing details", { - mostRecentLastModified: mostRecentLastModified.toISOString(), - desiredTransitionTime: desiredTransitionTime.toISOString(), + logger.info("Most recent NPPTS record is Post-dated. Checking if the post-dated prescription has matured", { + LastModified: mostRecentLastModified.toISOString(), + currentTime: currentTime.toISOString() + }) + + if (mostRecentLastModified > currentTime) { + logger.info("Post-dated prescription is still immature (LastModified is in the future)", { + lastModified: mostRecentLastModified.toISOString(), + currentTime: currentTime.toISOString() + }) + return PostDatedProcessingResult.IMMATURE + } + + logger.info("Post-dated prescription has matured (LastModified is in the past)", { + lastModified: mostRecentLastModified.toISOString(), currentTime: currentTime.toISOString() }) From 719b1b21ebb6f41738a19f2fa2c7a4ca6039f8d7 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Wed, 28 Jan 2026 11:55:33 +0000 Subject: [PATCH 50/75] Update doc --- .../docs/mature_prescription_check.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/postDatedLambda/docs/mature_prescription_check.md b/packages/postDatedLambda/docs/mature_prescription_check.md index 8b3f3dd5aa..2603a09df4 100644 --- a/packages/postDatedLambda/docs/mature_prescription_check.md +++ b/packages/postDatedLambda/docs/mature_prescription_check.md @@ -15,3 +15,17 @@ flowchart TD O -- Yes --> P["return IMMATURE"] O -- No --> Q["return MATURED"] ``` + + +```mermaid +flowchart TB + PSU[PSU] -- "Post dated" --> Qp["Post-dated SQS queue"] + PSU -- "Contemporary" --> Qn[Notifications SQS queue] + + Qp --> lp["Post-dated lambda"] + Qn --> ln["Notifications lambda"] + + lp -- MATURE --> Qn + lp -- IMMATURE --> Qp + lp -- IGNORE --> X((Delete)) +``` From 08e306591caaa4b1b05ce66cee20cdf2d6a367d4 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Wed, 28 Jan 2026 11:56:41 +0000 Subject: [PATCH 51/75] Logging and function name change --- packages/postDatedLambda/src/businessLogic.ts | 2 +- packages/postDatedLambda/src/databaseClient.ts | 7 ------- packages/postDatedLambda/src/sqs.ts | 4 ++-- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/postDatedLambda/src/businessLogic.ts b/packages/postDatedLambda/src/businessLogic.ts index 0d58edd0df..942e4d3154 100644 --- a/packages/postDatedLambda/src/businessLogic.ts +++ b/packages/postDatedLambda/src/businessLogic.ts @@ -66,7 +66,7 @@ export function processMessage( // NOTE: It is technically possible for the array to be empty if no existing records are found // This SHOULD never happen in practice, but catch it anyway if (message.existingRecords.length === 0) { - logger.error("No existing records found for post-dated prescription, cannot process", { + logger.error("No existing records found for post-dated prescription, cannot process. Ignoring this message", { badMessage: message }) diff --git a/packages/postDatedLambda/src/databaseClient.ts b/packages/postDatedLambda/src/databaseClient.ts index 8461838987..ff4096a1b9 100644 --- a/packages/postDatedLambda/src/databaseClient.ts +++ b/packages/postDatedLambda/src/databaseClient.ts @@ -199,12 +199,5 @@ export async function enrichMessagesWithExistingRecords( existingRecords: recordsMap.get(message.prescriptionData.PrescriptionID) ?? [] })) - for (const msg of enrichedMessages) { - logger.info("Prescription and most recent existing record", { - prescriptionID: msg.prescriptionData.PrescriptionID, - existingRecordCount: msg.existingRecords.length - }) - } - return enrichedMessages } diff --git a/packages/postDatedLambda/src/sqs.ts b/packages/postDatedLambda/src/sqs.ts index 90a72952cd..b3983e62b3 100644 --- a/packages/postDatedLambda/src/sqs.ts +++ b/packages/postDatedLambda/src/sqs.ts @@ -238,7 +238,7 @@ export async function receivePostDatedSQSMessages(logger: Logger): Promise ): Promise> { @@ -383,7 +383,7 @@ export async function handleProcessedMessages( const {maturedPrescriptionUpdates, immaturePrescriptionUpdates, ignoredPrescriptionUpdates} = result // Move matured messages to the notification queue and remove them from the post-dated queue - await sendSQSMessagesToNotificationQueue(logger, maturedPrescriptionUpdates) + await forwardSQSMessagesToNotificationQueue(logger, maturedPrescriptionUpdates) await removeSQSMessages(logger, maturedPrescriptionUpdates) // Return failed messages to the queue From 52b087804110c434f5f2d9247e7253b73c19e5ad Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Wed, 28 Jan 2026 11:57:17 +0000 Subject: [PATCH 52/75] Update logic to actually compute the time until the prescription matures, and pass it to the SQS --- packages/postDatedLambda/src/businessLogic.ts | 17 ++ packages/postDatedLambda/src/orchestration.ts | 4 +- packages/postDatedLambda/src/sqs.ts | 9 +- packages/postDatedLambda/src/types.ts | 1 + .../tests/testBusinessLogic.test.ts | 268 ++++++++++++++++++ .../tests/testOrchestration.test.ts | 4 +- .../postDatedLambda/tests/testSqs.test.ts | 12 +- 7 files changed, 302 insertions(+), 13 deletions(-) create mode 100644 packages/postDatedLambda/tests/testBusinessLogic.test.ts diff --git a/packages/postDatedLambda/src/businessLogic.ts b/packages/postDatedLambda/src/businessLogic.ts index 942e4d3154..0987b8f845 100644 --- a/packages/postDatedLambda/src/businessLogic.ts +++ b/packages/postDatedLambda/src/businessLogic.ts @@ -128,3 +128,20 @@ export function processMessage( return PostDatedProcessingResult.MATURED } + +/** + * returns time in seconds until maturity, or undefined if cannot be determined + */ +export function computeTimeUntilMaturity( + data: PostDatedSQSMessageWithExistingRecords +): number | undefined { + const prescriptionRecord = getMostRecentRecord(data.existingRecords) + if (!prescriptionRecord.PostDatedLastModifiedSetAt) { + return undefined + } + + const lastModified = new Date(prescriptionRecord.LastModified) + const currentTime = new Date() + + return lastModified.getTime() - currentTime.getTime() +} diff --git a/packages/postDatedLambda/src/orchestration.ts b/packages/postDatedLambda/src/orchestration.ts index 2ff63ae528..a1001864ae 100644 --- a/packages/postDatedLambda/src/orchestration.ts +++ b/packages/postDatedLambda/src/orchestration.ts @@ -1,6 +1,6 @@ import {Logger} from "@aws-lambda-powertools/logger" -import {processMessage} from "./businessLogic" +import {computeTimeUntilMaturity, processMessage} from "./businessLogic" import {enrichMessagesWithExistingRecords} from "./databaseClient" import {receivePostDatedSQSMessages, reportQueueStatus, handleProcessedMessages} from "./sqs" import {BatchProcessingResult, PostDatedProcessingResult, PostDatedSQSMessage} from "./types" @@ -34,6 +34,8 @@ export async function processMessages( if (action === PostDatedProcessingResult.MATURED) { maturedPrescriptionUpdates.push(message) } else if (action === PostDatedProcessingResult.IMMATURE) { + // Set visibility timeout to time until maturity, or default if calculation fails + message.visibilityTimeoutSeconds = computeTimeUntilMaturity(message) immaturePrescriptionUpdates.push(message) } else { ignoredPrescriptionUpdates.push(message) diff --git a/packages/postDatedLambda/src/sqs.ts b/packages/postDatedLambda/src/sqs.ts index b3983e62b3..5d357037df 100644 --- a/packages/postDatedLambda/src/sqs.ts +++ b/packages/postDatedLambda/src/sqs.ts @@ -15,6 +15,8 @@ import {BatchProcessingResult, PostDatedSQSMessage} from "./types" const sqs = new SQSClient({region: process.env.AWS_REGION}) +const DEFAULT_VISIBILITY_TIMEOUT_SECONDS = 300 // 5 minutes + // Note that a lot of the code to send an SQS message is copied from the updatePrescriptionStatus lambda, // and I've NOT moved the code into a shared location for the two. // This is because I don't want to alter the updatePrescriptionStatus lambda in that way @@ -318,7 +320,7 @@ export async function removeSQSMessages( */ export async function returnMessagesToQueue( logger: Logger, - messages: Array + messages: Array ): Promise { if (messages.length === 0) { // exit early so we don't send a ChangeMessageVisibilityBatch with no entries @@ -328,13 +330,10 @@ export async function returnMessagesToQueue( const sqsUrl = getPostDatedQueueUrl(logger) - // TODO: Each message needs to have an appropriate visibility timeout based on when it is due to be retried. - // For now, use a fixed 5 minute timeout for all messages. - const visibilityTimeoutSeconds = 300 const entries = messages.map((m) => ({ Id: m.MessageId!, ReceiptHandle: m.ReceiptHandle!, - VisibilityTimeout: visibilityTimeoutSeconds + VisibilityTimeout: m.visibilityTimeoutSeconds || DEFAULT_VISIBILITY_TIMEOUT_SECONDS })) logger.info( diff --git a/packages/postDatedLambda/src/types.ts b/packages/postDatedLambda/src/types.ts index 092a44e17a..63fca0ae55 100644 --- a/packages/postDatedLambda/src/types.ts +++ b/packages/postDatedLambda/src/types.ts @@ -7,6 +7,7 @@ import {NotifyDataItem, PSUDataItem} from "@psu-common/commonTypes" */ export interface PostDatedSQSMessage extends Message { prescriptionData: NotifyDataItem + visibilityTimeoutSeconds?: number } /** diff --git a/packages/postDatedLambda/tests/testBusinessLogic.test.ts b/packages/postDatedLambda/tests/testBusinessLogic.test.ts new file mode 100644 index 0000000000..d3b86f98df --- /dev/null +++ b/packages/postDatedLambda/tests/testBusinessLogic.test.ts @@ -0,0 +1,268 @@ +import { + expect, + describe, + it, + afterEach, + jest +} from "@jest/globals" + +import {Logger} from "@aws-lambda-powertools/logger" + +import {PSUDataItem} from "@psu-common/commonTypes" + +import {PostDatedProcessingResult, PostDatedSQSMessageWithExistingRecords} from "../src/types" +import {createMockPostModifiedDataItem} from "./testUtils" + +type BusinessLogicModule = typeof import("../src/businessLogic") + +const ORIGINAL_ENV = {...process.env} + +async function loadBusinessLogic( + envOverrides = {} +): Promise { + // Makes sure that the environment is set before import each time + jest.resetModules() + process.env = {...ORIGINAL_ENV, ...envOverrides} + return import("../src/businessLogic") +} + +function createPSURecord(overrides: Partial = {}): PSUDataItem { + const baseRecord: PSUDataItem = { + LastModified: new Date("2026-01-01T00:00:00.000Z").toISOString(), + LineItemID: "line-item", + PatientNHSNumber: "0123456789", + PharmacyODSCode: "ABC123", + PrescriptionID: "RX123", + RepeatNo: 0, + RequestID: "req-123", + Status: "ready to collect", + TaskID: "task-123", + TerminalStatus: "terminal", + ApplicationName: "post-dated-tests", + ExpiryTime: 0, + PostDatedLastModifiedSetAt: "2026-01-01T00:00:00.000Z" + } + + return { + ...baseRecord, + ...overrides + } +} + +function createMessage( + overrides: Partial = {} +): PostDatedSQSMessageWithExistingRecords { + const prescData = createMockPostModifiedDataItem({}) + const baseMessage: PostDatedSQSMessageWithExistingRecords = { + MessageId: "msg-123", + ReceiptHandle: "receipt-123", + Body: JSON.stringify(prescData), + Attributes: { + + }, + prescriptionData: prescData, + // In theory, this should contain the record corresponding to prescData, but for testing purposes it's fine + existingRecords: [createPSURecord()] + } + + return { + ...baseMessage, + ...overrides, + prescriptionData: overrides.prescriptionData ?? baseMessage.prescriptionData, + existingRecords: overrides.existingRecords ?? baseMessage.existingRecords + } +} + +afterEach(() => { + process.env = {...ORIGINAL_ENV} + jest.useRealTimers() +}) + +describe("businessLogic", () => { + describe("getMostRecentRecord", () => { + it("should return the record with the latest timestamp, when all records are post-dated", async () => { + const {getMostRecentRecord} = await loadBusinessLogic() + const records = [ + createPSURecord({ + LineItemID: "line-old", + PostDatedLastModifiedSetAt: "2026-01-01T00:00:00.000Z", + LastModified: "2026-01-02T00:00:00.000Z" // The time it's scheduled to mature + }), + createPSURecord({ + LineItemID: "line-new", + PostDatedLastModifiedSetAt: "2026-01-01T12:00:00.000Z", // submitted 12 hours later + LastModified: "2026-01-01T18:00:00.000Z" // but last modified is earlier + }) + ] + + const result = getMostRecentRecord(records) + + expect(result.LineItemID).toBe("line-new") + }) + + it("Should return the latest record when only one record is post-dated", async () => { + const {getMostRecentRecord} = await loadBusinessLogic() + const records = [ + createPSURecord({ // post-dated record submitted first + LineItemID: "line-old", + PostDatedLastModifiedSetAt: "2026-01-01T00:00:00.000Z", + LastModified: "2026-01-15T00:00:00.000Z" // The time it's scheduled to mature + }), + createPSURecord({ // contemporary record submitted second + LineItemID: "line-new", + PostDatedLastModifiedSetAt: undefined, + LastModified: "2026-01-02T00:00:00.000Z" // The time the prescription was actually ready to collect + }) + ] + + const result = getMostRecentRecord(records) + + expect(result.LineItemID).toBe("line-new") + }) + + it("should return the latest record when no records are post-dated", async () => { + const {getMostRecentRecord} = await loadBusinessLogic() + const records = [ + createPSURecord({ + LineItemID: "line-old", + PostDatedLastModifiedSetAt: undefined, + LastModified: "2026-01-01T00:00:00.000Z" + }), + createPSURecord({ + LineItemID: "line-new", + PostDatedLastModifiedSetAt: undefined, + LastModified: "2026-02-01T00:00:00.000Z" + }) + ] + + const result = getMostRecentRecord(records) + + expect(result.LineItemID).toBe("line-new") + }) + }) + + describe("processMessage", () => { + it("should return the override value when override mode is enabled", async () => { + const {processMessage} = await loadBusinessLogic({ + POST_DATED_OVERRIDE: "true", + POST_DATED_OVERRIDE_VALUE: "immature" + }) + const logger = new Logger({serviceName: "post-dated-tests"}) + + const result = processMessage(logger, createMessage()) + + expect(result).toBe(PostDatedProcessingResult.IMMATURE) + }) + + it("should ignore messages that have no existing records", async () => { + const {processMessage} = await loadBusinessLogic() + const logger = new Logger({serviceName: "post-dated-tests"}) + const message = createMessage({existingRecords: []}) + + const result = processMessage(logger, message) + + expect(result).toBe(PostDatedProcessingResult.IGNORE) + }) + + it("should ignore messages when the most recent record is not post-dated", async () => { + const {processMessage} = await loadBusinessLogic() + const logger = new Logger({serviceName: "post-dated-tests"}) + const message = createMessage({ + existingRecords: [ + createPSURecord({ + LineItemID: "line-no-post-date", + PostDatedLastModifiedSetAt: undefined + }) + ] + }) + + const result = processMessage(logger, message) + + expect(result).toBe(PostDatedProcessingResult.IGNORE) + }) + + it("should ignore messages when the status is not notifiable", async () => { + const {processMessage} = await loadBusinessLogic() + const logger = new Logger({serviceName: "post-dated-tests"}) + const message = createMessage({ + existingRecords: [ + createPSURecord({ + Status: "dispensed", + LineItemID: "line-not-notifiable" + }) + ] + }) + + const result = processMessage(logger, message) + + expect(result).toBe(PostDatedProcessingResult.IGNORE) + }) + + it("should classify a message as immature when LastModified is in the future", async () => { + jest.useFakeTimers() + jest.setSystemTime(new Date("2026-01-01T12:00:00.000Z")) + const {processMessage} = await loadBusinessLogic() + const logger = new Logger({serviceName: "post-dated-tests"}) + const message = createMessage({ + existingRecords: [ + createPSURecord({ + LastModified: "2026-01-02T12:00:00.000Z", + PostDatedLastModifiedSetAt: "2026-01-02T12:00:00.000Z", + LineItemID: "line-future" + }) + ] + }) + + const result = processMessage(logger, message) + + expect(result).toBe(PostDatedProcessingResult.IMMATURE) + }) + + it("should classify a message as matured when LastModified is in the past", async () => { + jest.useFakeTimers() + jest.setSystemTime(new Date("2026-01-03T12:00:00.000Z")) + const {processMessage} = await loadBusinessLogic() + const logger = new Logger({serviceName: "post-dated-tests"}) + const message = createMessage({ + existingRecords: [ + createPSURecord({ + LastModified: "2026-01-02T12:00:00.000Z", + PostDatedLastModifiedSetAt: "2026-01-02T12:00:00.000Z", + LineItemID: "line-past" + }) + ] + }) + + const result = processMessage(logger, message) + + expect(result).toBe(PostDatedProcessingResult.MATURED) + }) + + it("should use the most recent record when determining maturity", async () => { + jest.useFakeTimers() + jest.setSystemTime(new Date("2026-01-05T12:00:00.000Z")) + const {processMessage} = await loadBusinessLogic() + const logger = new Logger({serviceName: "post-dated-tests"}) + const message = createMessage({ + existingRecords: [ + createPSURecord({ + LineItemID: "line-old", + LastModified: "2026-01-01T12:00:00.000Z", + PostDatedLastModifiedSetAt: "2026-01-01T12:00:00.000Z", + Status: "ready to collect - partial" + }), + createPSURecord({ + LineItemID: "line-new", + LastModified: "2026-01-06T12:00:00.000Z", + PostDatedLastModifiedSetAt: "2026-01-06T12:00:00.000Z", + Status: "ready to collect" + }) + ] + }) + + const result = processMessage(logger, message) + + expect(result).toBe(PostDatedProcessingResult.IMMATURE) + }) + }) +}) diff --git a/packages/postDatedLambda/tests/testOrchestration.test.ts b/packages/postDatedLambda/tests/testOrchestration.test.ts index 1ec4156503..728ab5f923 100644 --- a/packages/postDatedLambda/tests/testOrchestration.test.ts +++ b/packages/postDatedLambda/tests/testOrchestration.test.ts @@ -7,9 +7,11 @@ import { // Mock the imports from local modules const mockProcessMessage = jest.fn() +const mockComputeTimeUntilMaturity = jest.fn().mockReturnValue(300) jest.unstable_mockModule("../src/businessLogic", () => { return { - processMessage: mockProcessMessage + processMessage: mockProcessMessage, + computeTimeUntilMaturity: mockComputeTimeUntilMaturity } }) diff --git a/packages/postDatedLambda/tests/testSqs.test.ts b/packages/postDatedLambda/tests/testSqs.test.ts index 947193ddf2..80adb58055 100644 --- a/packages/postDatedLambda/tests/testSqs.test.ts +++ b/packages/postDatedLambda/tests/testSqs.test.ts @@ -34,7 +34,7 @@ const { removeSQSMessages, returnMessagesToQueue, handleProcessedMessages, - sendSQSMessagesToNotificationQueue + forwardSQSMessagesToNotificationQueue: sendSQSMessagesToNotificationQueue } = await import("../src/sqs") const ORIGINAL_ENV = {...process.env} @@ -299,9 +299,9 @@ describe("sqs", () => { const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue" process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl - const messagesToReturn = [ - {MessageId: "1", ReceiptHandle: "handle1"}, - {MessageId: "2", ReceiptHandle: "handle2"} + const messagesToReturn: Array = [ + {MessageId: "1", ReceiptHandle: "handle1", prescriptionData: createMockPostModifiedDataItem({})}, + {MessageId: "2", ReceiptHandle: "handle2", prescriptionData: createMockPostModifiedDataItem({})} ] // Mock SQS change visibility response @@ -333,8 +333,8 @@ describe("sqs", () => { const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue" process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl - const messagesToReturn = [ - {MessageId: "1", ReceiptHandle: "handle1"} + const messagesToReturn: Array = [ + {MessageId: "1", ReceiptHandle: "handle1", prescriptionData: createMockPostModifiedDataItem({})} ] // Mock SQS change visibility to throw an error From c44f2ef49927dd355741c17394f9fd4acae5d11e Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Wed, 28 Jan 2026 13:27:39 +0000 Subject: [PATCH 53/75] Add a filter so that prior post-dated updates do not block notifications of contemporary ones --- packages/updatePrescriptionStatus/src/utils/sqsClient.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts index ca86669a9a..b42515a540 100644 --- a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts +++ b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts @@ -233,6 +233,9 @@ export async function pushPrescriptionToNotificationSQS( // we don't want items that have gone from "ready to collect" to "ready to collect" // So chuck those out. const changedStatus = candidates + // In this check, don't consider previous updates that have a defined PostDatedLastModifiedSetAt + // This is because if the last one was post-dated, and the current one ISN'T, we DO want to send a notification + .filter(item => item.previous?.PostDatedLastModifiedSetAt === undefined) .filter(({current, previous}) => { if (!previous) return true // no previous item (or hit an error getting one) -> treat as changed return norm(current.Status) !== norm(previous.Status) From 9090dfcaee5c696a7d61f3d5a3d44bf9b813df13 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Wed, 28 Jan 2026 13:33:57 +0000 Subject: [PATCH 54/75] Correct the check - logic was backwards --- packages/updatePrescriptionStatus/src/utils/sqsClient.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts index b42515a540..578eee7056 100644 --- a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts +++ b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts @@ -233,11 +233,9 @@ export async function pushPrescriptionToNotificationSQS( // we don't want items that have gone from "ready to collect" to "ready to collect" // So chuck those out. const changedStatus = candidates - // In this check, don't consider previous updates that have a defined PostDatedLastModifiedSetAt - // This is because if the last one was post-dated, and the current one ISN'T, we DO want to send a notification - .filter(item => item.previous?.PostDatedLastModifiedSetAt === undefined) .filter(({current, previous}) => { if (!previous) return true // no previous item (or hit an error getting one) -> treat as changed + if (previous.PostDatedLastModifiedSetAt) return true // previous was post-dated -> treat as changed return norm(current.Status) !== norm(previous.Status) }) .map(({current}) => current) From 07de0b6e1ab62c9e6c5c2736bf3cb9512532c104 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Wed, 28 Jan 2026 13:38:54 +0000 Subject: [PATCH 55/75] Thought of another edge case --- packages/updatePrescriptionStatus/src/utils/sqsClient.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts index 578eee7056..1ee67d57d8 100644 --- a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts +++ b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts @@ -236,6 +236,7 @@ export async function pushPrescriptionToNotificationSQS( .filter(({current, previous}) => { if (!previous) return true // no previous item (or hit an error getting one) -> treat as changed if (previous.PostDatedLastModifiedSetAt) return true // previous was post-dated -> treat as changed + if (current.PostDatedLastModifiedSetAt) return true // current is post-dated -> treat as changed return norm(current.Status) !== norm(previous.Status) }) .map(({current}) => current) From 6f6ae5517c656b807f60689204cd2a43478c0f44 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Wed, 28 Jan 2026 14:14:46 +0000 Subject: [PATCH 56/75] convert milliseconds to seconds --- packages/postDatedLambda/src/businessLogic.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/postDatedLambda/src/businessLogic.ts b/packages/postDatedLambda/src/businessLogic.ts index 0987b8f845..9d00eef8b6 100644 --- a/packages/postDatedLambda/src/businessLogic.ts +++ b/packages/postDatedLambda/src/businessLogic.ts @@ -143,5 +143,5 @@ export function computeTimeUntilMaturity( const lastModified = new Date(prescriptionRecord.LastModified) const currentTime = new Date() - return lastModified.getTime() - currentTime.getTime() + return (lastModified.getTime() - currentTime.getTime()) / 1000 } From d645e62177937b1bdfa4ac95d83eb31a12aec4eb Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 30 Jan 2026 12:00:00 +0000 Subject: [PATCH 57/75] Dont query by ODS code --- packages/postDatedLambda/src/databaseClient.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/postDatedLambda/src/databaseClient.ts b/packages/postDatedLambda/src/databaseClient.ts index ff4096a1b9..b424613aac 100644 --- a/packages/postDatedLambda/src/databaseClient.ts +++ b/packages/postDatedLambda/src/databaseClient.ts @@ -39,15 +39,13 @@ export async function getExistingRecordsByPrescriptionID( logger: Logger ): Promise> { const normalizedPrescriptionID = prescriptionID.toUpperCase() - const normalizedPharmacyODSCode = pharmacyODSCode.toUpperCase() // Use the GSI to query by PharmacyODSCode and PrescriptionID const query: QueryCommandInput = { TableName: tableName, IndexName: pharmacyPrescriptionIndexName, - KeyConditionExpression: "PharmacyODSCode = :ods AND PrescriptionID = :pid", + KeyConditionExpression: "PrescriptionID = :pid", ExpressionAttributeValues: { - ":ods": {S: normalizedPharmacyODSCode}, ":pid": {S: normalizedPrescriptionID} } } @@ -63,7 +61,6 @@ export async function getExistingRecordsByPrescriptionID( logger.info("Querying DynamoDB for existing prescription records", { prescriptionID: normalizedPrescriptionID, - pharmacyODSCode: normalizedPharmacyODSCode, tableName, indexName: pharmacyPrescriptionIndexName }) @@ -80,7 +77,6 @@ export async function getExistingRecordsByPrescriptionID( logger.info("Retrieved existing prescription records from DynamoDB", { prescriptionID: normalizedPrescriptionID, - pharmacyODSCode: normalizedPharmacyODSCode, recordCount: items.length }) @@ -91,7 +87,6 @@ export async function getExistingRecordsByPrescriptionID( } catch (err) { logger.error("Error querying DynamoDB for existing prescription records", { prescriptionID: normalizedPrescriptionID, - pharmacyODSCode: normalizedPharmacyODSCode, error: err }) throw err From f501651b1b0b4cb555c1670ed7ef3b7eea12defb Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 30 Jan 2026 12:05:59 +0000 Subject: [PATCH 58/75] Write a little prose to help understand the business logic. forgot to cap the visibility timeout --- packages/postDatedLambda/docs/mature_prescription_check.md | 6 +++++- packages/postDatedLambda/src/sqs.ts | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/postDatedLambda/docs/mature_prescription_check.md b/packages/postDatedLambda/docs/mature_prescription_check.md index 2603a09df4..a2ef90e748 100644 --- a/packages/postDatedLambda/docs/mature_prescription_check.md +++ b/packages/postDatedLambda/docs/mature_prescription_check.md @@ -1,3 +1,7 @@ +This is the business logic used for checking if a prescription ID has matured or not. It needs to tolerate a mixture of post-dated, and contemporary prescription updates; e.g. "ready to collect", post-dated, followed by "with pharmacy" non-post-dated. + +The business logic's `processMessage()` function accepts the SQS message for this prescription ID, enriched with the results of querying the NPPTS data store for this prescription ID (this is the `existingRecords` attribute. Then, it inspects only the most recently submitted update for this prescription ID, and checks if it is a) actually post dated (a contemporary update may have come in since the post-dated one that triggered this SQS message), and b) has matured from "will be ready to collect" to "is now ready to collect". The decision of if a notification needs to be sent to this patient is handled by the notifications lambda, still. + ```mermaid flowchart TD A["Start `processMessage()`"] --> C{"POST_DATED_OVERRIDE?"} @@ -16,7 +20,7 @@ flowchart TD O -- No --> Q["return MATURED"] ``` - +Immature prescriptions will be re-processed after some delay, by being updated on SQS. Some prescriptions may no longer need to be considered (e.g. if there has been a subsequent PSU request marking it as "with pharmacy" again), so are deleted from the post-dated SQS without being forwarded to the notifications SQS. Mature prescriptions are forwarded to the notifications SQS. ```mermaid flowchart TB PSU[PSU] -- "Post dated" --> Qp["Post-dated SQS queue"] diff --git a/packages/postDatedLambda/src/sqs.ts b/packages/postDatedLambda/src/sqs.ts index 5d357037df..103189dca6 100644 --- a/packages/postDatedLambda/src/sqs.ts +++ b/packages/postDatedLambda/src/sqs.ts @@ -16,6 +16,7 @@ import {BatchProcessingResult, PostDatedSQSMessage} from "./types" const sqs = new SQSClient({region: process.env.AWS_REGION}) const DEFAULT_VISIBILITY_TIMEOUT_SECONDS = 300 // 5 minutes +const MAXIMUM_VISIBILITY_TIMEOUT_SECONDS = 10 * 60 * 60 // 10 hours // Note that a lot of the code to send an SQS message is copied from the updatePrescriptionStatus lambda, // and I've NOT moved the code into a shared location for the two. @@ -333,7 +334,9 @@ export async function returnMessagesToQueue( const entries = messages.map((m) => ({ Id: m.MessageId!, ReceiptHandle: m.ReceiptHandle!, - VisibilityTimeout: m.visibilityTimeoutSeconds || DEFAULT_VISIBILITY_TIMEOUT_SECONDS + VisibilityTimeout: Math.min( + m.visibilityTimeoutSeconds || DEFAULT_VISIBILITY_TIMEOUT_SECONDS, + MAXIMUM_VISIBILITY_TIMEOUT_SECONDS) // Cap at the max })) logger.info( From 026665dace7ed2dac2c1e69807386bbca4b0f71e Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 30 Jan 2026 12:17:45 +0000 Subject: [PATCH 59/75] Refactor out limiting the NPPTS query by ODS code --- SAMtemplates/tables/main.yaml | 4 +--- packages/postDatedLambda/src/databaseClient.ts | 13 ++++--------- .../tests/testDatabaseClient.test.ts | 18 ------------------ 3 files changed, 5 insertions(+), 30 deletions(-) diff --git a/SAMtemplates/tables/main.yaml b/SAMtemplates/tables/main.yaml index b0da7592dd..ac6700a0b2 100644 --- a/SAMtemplates/tables/main.yaml +++ b/SAMtemplates/tables/main.yaml @@ -202,10 +202,8 @@ Resources: WriteCapacityUnits: !Ref MinWritePrescriptionStatusUpdatesCapacity - !Ref "AWS::NoValue" # TODO: Remove this when we deprecate post modified prescriptions. - - IndexName: PharmacyODSCodePrescriptionIDIndexPostDatedIndex + - IndexName: PrescriptionIDPostDatedIndex KeySchema: - - AttributeName: PharmacyODSCode - KeyType: HASH - AttributeName: PrescriptionID KeyType: RANGE Projection: diff --git a/packages/postDatedLambda/src/databaseClient.ts b/packages/postDatedLambda/src/databaseClient.ts index b424613aac..6ca31ddc61 100644 --- a/packages/postDatedLambda/src/databaseClient.ts +++ b/packages/postDatedLambda/src/databaseClient.ts @@ -11,7 +11,7 @@ import { const client = new DynamoDBClient() const tableName = process.env.TABLE_NAME ?? "PrescriptionStatusUpdates" -const pharmacyPrescriptionIndexName = "PharmacyODSCodePrescriptionIDIndexPostDatedIndex" +const pharmacyPrescriptionIndexName = "PrescriptionIDPostDatedIndex" type PrescriptionLookupRequest = { lookupKey: string @@ -19,10 +19,6 @@ type PrescriptionLookupRequest = { pharmacyODSCode: string } -export function createPrescriptionLookupKey(prescriptionID: string, pharmacyODSCode: string): string { - return `${prescriptionID.toUpperCase()}#${pharmacyODSCode.toUpperCase()}` -} - /** * Query the PrescriptionStatusUpdates table for all records matching a given prescription ID and ODS code. * There should always be at least one result, but there may be multiple if the prescription has been @@ -35,7 +31,6 @@ export function createPrescriptionLookupKey(prescriptionID: string, pharmacyODSC */ export async function getExistingRecordsByPrescriptionID( prescriptionID: string, - pharmacyODSCode: string, logger: Logger ): Promise> { const normalizedPrescriptionID = prescriptionID.toUpperCase() @@ -116,7 +111,7 @@ export async function fetchExistingRecordsForPrescriptions( // Map each post-dated item to its corresponding existing records const results: Array = postDatedItems.map( (postDatedData) => { - const lookupKey = createPrescriptionLookupKey(postDatedData.PrescriptionID, postDatedData.PharmacyODSCode) + const lookupKey = postDatedData.PrescriptionID.toUpperCase() // Case insensitive const existingRecords = existingRecordsMap.get(lookupKey) ?? [] return { @@ -133,7 +128,7 @@ function buildLookupRequests(postDatedItems: Array): Array() for (const item of postDatedItems) { - const lookupKey = createPrescriptionLookupKey(item.PrescriptionID, item.PharmacyODSCode) + const lookupKey = item.PrescriptionID.toUpperCase() // Case insensitive // dont worry about overwriting entries, since they'll be identical lookups.set(lookupKey, { @@ -156,7 +151,7 @@ async function buildExistingRecordsMap( await Promise.all( lookupRequests.map(async ({lookupKey, prescriptionID, pharmacyODSCode}) => { try { - const records = await getExistingRecordsByPrescriptionID(prescriptionID, pharmacyODSCode, logger) + const records = await getExistingRecordsByPrescriptionID(prescriptionID, logger) existingRecordsMap.set(lookupKey, records) } catch (error) { logger.error("Failed to fetch existing records for prescription", { diff --git a/packages/postDatedLambda/tests/testDatabaseClient.test.ts b/packages/postDatedLambda/tests/testDatabaseClient.test.ts index 412caeffa3..3ccf298c16 100644 --- a/packages/postDatedLambda/tests/testDatabaseClient.test.ts +++ b/packages/postDatedLambda/tests/testDatabaseClient.test.ts @@ -27,7 +27,6 @@ export function mockDynamoDBClient() { const {mockSend} = mockDynamoDBClient() const { - createPrescriptionLookupKey, getExistingRecordsByPrescriptionID, fetchExistingRecordsForPrescriptions, enrichMessagesWithExistingRecords @@ -36,33 +35,19 @@ const { const logger = new Logger({serviceName: "postDatedLambdaTEST"}) describe("databaseClient", () => { - describe("createPrescriptionLookupKey", () => { - it("should create the correct lookup key", () => { - const prescriptionID = "abc123" - const pharmacyODSCode = "pharm001" - const expectedKey = "ABC123#PHARM001" - - const result = createPrescriptionLookupKey(prescriptionID, pharmacyODSCode) - expect(result).toBe(expectedKey) - }) - }) - describe("getExistingRecordsByPrescriptionID", () => { it("should return existing records from DynamoDB", async () => { const prescriptionID = "testPrescID" - const pharmacyODSCode = "testPharmODS" // Mock DynamoDB response const mockItems = [ { PrescriptionID: {S: prescriptionID}, - PharmacyODSCode: {S: pharmacyODSCode}, Status: {S: "Dispensed"}, LastModified: {S: "2024-01-01T12:00:00Z"} }, { PrescriptionID: {S: prescriptionID}, - PharmacyODSCode: {S: pharmacyODSCode}, Status: {S: "ReadyForCollection"}, LastModified: {S: "2023-12-31T12:00:00Z"} } @@ -75,7 +60,6 @@ describe("databaseClient", () => { const records = await getExistingRecordsByPrescriptionID( prescriptionID, - pharmacyODSCode, logger ) @@ -86,7 +70,6 @@ describe("databaseClient", () => { it("Should log and throw an error if the DynamoDB query fails", async () => { const prescriptionID = "errorPrescID" - const pharmacyODSCode = "errorPharmODS" // Mock DynamoDB to throw an error const mockError = new Error("DynamoDB query failed") @@ -97,7 +80,6 @@ describe("databaseClient", () => { await expect( getExistingRecordsByPrescriptionID( prescriptionID, - pharmacyODSCode, logger ) ).rejects.toThrow("DynamoDB query failed") From 313105913da67544709d7c3a90d32fcd7012adbb Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 30 Jan 2026 12:38:45 +0000 Subject: [PATCH 60/75] missed a few ODS code bits --- packages/postDatedLambda/src/databaseClient.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/postDatedLambda/src/databaseClient.ts b/packages/postDatedLambda/src/databaseClient.ts index 6ca31ddc61..1db656dc74 100644 --- a/packages/postDatedLambda/src/databaseClient.ts +++ b/packages/postDatedLambda/src/databaseClient.ts @@ -16,16 +16,14 @@ const pharmacyPrescriptionIndexName = "PrescriptionIDPostDatedIndex" type PrescriptionLookupRequest = { lookupKey: string prescriptionID: string - pharmacyODSCode: string } /** - * Query the PrescriptionStatusUpdates table for all records matching a given prescription ID and ODS code. + * Query the PrescriptionStatusUpdates table for all records matching a given prescription ID. * There should always be at least one result, but there may be multiple if the prescription has been * updated multiple times. * * @param prescriptionID - The prescription ID to query for - * @param pharmacyODSCode - The pharmacy ODS code to query for * @param logger - The AWS Lambda Powertools logger instance * @returns Array of PSUDataItem records matching the prescription ID. Sorted by LastModified descending. */ @@ -35,7 +33,7 @@ export async function getExistingRecordsByPrescriptionID( ): Promise> { const normalizedPrescriptionID = prescriptionID.toUpperCase() - // Use the GSI to query by PharmacyODSCode and PrescriptionID + // Use the GSI to query by PrescriptionID const query: QueryCommandInput = { TableName: tableName, IndexName: pharmacyPrescriptionIndexName, @@ -133,8 +131,7 @@ function buildLookupRequests(postDatedItems: Array): Array { + lookupRequests.map(async ({lookupKey, prescriptionID}) => { try { const records = await getExistingRecordsByPrescriptionID(prescriptionID, logger) existingRecordsMap.set(lookupKey, records) } catch (error) { logger.error("Failed to fetch existing records for prescription", { prescriptionID, - pharmacyODSCode, error }) existingRecordsMap.set(lookupKey, []) // Continue processing other prescriptions even when one fails From ef7610fd2cc635086c3f429dd30b78123a52c12f Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 30 Jan 2026 13:52:07 +0000 Subject: [PATCH 61/75] key schema attribute needs to be hash, not range --- SAMtemplates/tables/main.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAMtemplates/tables/main.yaml b/SAMtemplates/tables/main.yaml index ac6700a0b2..75e77e2ac2 100644 --- a/SAMtemplates/tables/main.yaml +++ b/SAMtemplates/tables/main.yaml @@ -205,7 +205,7 @@ Resources: - IndexName: PrescriptionIDPostDatedIndex KeySchema: - AttributeName: PrescriptionID - KeyType: RANGE + KeyType: HASH Projection: NonKeyAttributes: - PatientNHSNumber From 40d2751951aeab91abbe18432febf3a35a88c888 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 30 Jan 2026 14:59:39 +0000 Subject: [PATCH 62/75] Flesh out dynamo tests a bit more, and stop sorting in the dynamo client --- .../postDatedLambda/src/databaseClient.ts | 3 - .../tests/testDatabaseClient.test.ts | 118 ++++++++++++++---- 2 files changed, 97 insertions(+), 24 deletions(-) diff --git a/packages/postDatedLambda/src/databaseClient.ts b/packages/postDatedLambda/src/databaseClient.ts index 1db656dc74..640dc80bdd 100644 --- a/packages/postDatedLambda/src/databaseClient.ts +++ b/packages/postDatedLambda/src/databaseClient.ts @@ -73,9 +73,6 @@ export async function getExistingRecordsByPrescriptionID( recordCount: items.length }) - // Sort by LastModified ascending so most recent is first - items.sort((a, b) => new Date(b.LastModified).valueOf() - new Date(a.LastModified).valueOf()) - return items } catch (err) { logger.error("Error querying DynamoDB for existing prescription records", { diff --git a/packages/postDatedLambda/tests/testDatabaseClient.test.ts b/packages/postDatedLambda/tests/testDatabaseClient.test.ts index 3ccf298c16..23df0473fb 100644 --- a/packages/postDatedLambda/tests/testDatabaseClient.test.ts +++ b/packages/postDatedLambda/tests/testDatabaseClient.test.ts @@ -35,6 +35,10 @@ const { const logger = new Logger({serviceName: "postDatedLambdaTEST"}) describe("databaseClient", () => { + beforeEach(() => { + mockSend.mockReset() + }) + describe("getExistingRecordsByPrescriptionID", () => { it("should return existing records from DynamoDB", async () => { const prescriptionID = "testPrescID" @@ -43,12 +47,12 @@ describe("databaseClient", () => { const mockItems = [ { PrescriptionID: {S: prescriptionID}, - Status: {S: "Dispensed"}, + Status: {S: "With pharmacy"}, LastModified: {S: "2024-01-01T12:00:00Z"} }, { PrescriptionID: {S: prescriptionID}, - Status: {S: "ReadyForCollection"}, + Status: {S: "Ready to collect"}, LastModified: {S: "2023-12-31T12:00:00Z"} } ] @@ -64,8 +68,21 @@ describe("databaseClient", () => { ) expect(records).toHaveLength(2) - expect(records[0].Status).toBe("Dispensed") - expect(records[1].Status).toBe("ReadyForCollection") + expect(records[0].Status).toBe("With pharmacy") + expect(records[1].Status).toBe("Ready to collect") + }) + + it("should return an empty array when DynamoDB returns no items", async () => { + const prescriptionID = "noRecordsPrescID" + + mockSend.mockReturnValueOnce({ + Items: [], + LastEvaluatedKey: undefined + }) + + const records = await getExistingRecordsByPrescriptionID(prescriptionID, logger) + + expect(records).toEqual([]) }) it("Should log and throw an error if the DynamoDB query fails", async () => { @@ -84,6 +101,45 @@ describe("databaseClient", () => { ) ).rejects.toThrow("DynamoDB query failed") }) + + it("should paginate through multiple DynamoDB result pages", async () => { + const prescriptionID = "pagedPrescID" + + const firstPageItems = [ + { + PrescriptionID: {S: prescriptionID}, + Status: {S: "Ready to collect"}, + LastModified: {S: "2024-01-01T12:00:00Z"} + } + ] + + const secondPageItems = [ + { + PrescriptionID: {S: prescriptionID}, + Status: {S: "With pharmacy"}, + LastModified: {S: "2024-01-02T12:00:00Z"} + } + ] + + mockSend + .mockReturnValueOnce({ + Items: firstPageItems, + LastEvaluatedKey: { + PrescriptionID: {S: prescriptionID} + } + }) + .mockReturnValueOnce({ + Items: secondPageItems, + LastEvaluatedKey: undefined + }) + + const records = await getExistingRecordsByPrescriptionID(prescriptionID, logger) + + expect(mockSend).toHaveBeenCalledTimes(2) + expect(records).toHaveLength(2) + expect(records[0].Status).toBe("Ready to collect") + expect(records[1].Status).toBe("With pharmacy") + }) }) describe("fetchExistingRecordsForPrescriptions", () => { @@ -98,7 +154,7 @@ describe("databaseClient", () => { { PrescriptionID: {S: "presc1"}, PharmacyODSCode: {S: "pharmA"}, - Status: {S: "Dispensed"}, + Status: {S: "With pharmacy"}, LastModified: {S: "2024-01-01T12:00:00Z"} } ] @@ -107,7 +163,7 @@ describe("databaseClient", () => { { PrescriptionID: {S: "presc2"}, PharmacyODSCode: {S: "pharmB"}, - Status: {S: "ReadyForCollection"}, + Status: {S: "Ready to collect"}, LastModified: {S: "2024-01-02T12:00:00Z"} } ] @@ -129,9 +185,9 @@ describe("databaseClient", () => { expect(result.length).toBe(2) expect(result[0].existingRecords.length).toBe(1) - expect(result[0].existingRecords[0].Status).toBe("Dispensed") + expect(result[0].existingRecords[0].Status).toBe("With pharmacy") expect(result[1].existingRecords.length).toBe(1) - expect(result[1].existingRecords[0].Status).toBe("ReadyForCollection") + expect(result[1].existingRecords[0].Status).toBe("Ready to collect") }) it( @@ -147,7 +203,7 @@ describe("databaseClient", () => { { PrescriptionID: {S: "presc1"}, PharmacyODSCode: {S: "pharmA"}, - Status: {S: "Dispensed"}, + Status: {S: "With pharmacy"}, LastModified: {S: "2024-01-01T12:00:00Z"} } ] @@ -168,9 +224,32 @@ describe("databaseClient", () => { expect(result.length).toBe(2) expect(result[0].existingRecords.length).toBe(1) - expect(result[0].existingRecords[0].Status).toBe("Dispensed") + expect(result[0].existingRecords[0].Status).toBe("With pharmacy") expect(result[1].existingRecords.length).toBe(0) }) + + it("should return prescriptions with empty existingRecords when DynamoDB has no matches", async () => { + const prescriptions = [ + createMockPostModifiedDataItem({PrescriptionID: "noPresc1", PharmacyODSCode: "pharmA"}), + createMockPostModifiedDataItem({PrescriptionID: "noPresc2", PharmacyODSCode: "pharmB"}) + ] + + mockSend + .mockReturnValueOnce({ + Items: [], + LastEvaluatedKey: undefined + }) + .mockReturnValueOnce({ + Items: [], + LastEvaluatedKey: undefined + }) + + const result = await fetchExistingRecordsForPrescriptions(prescriptions, logger) + + expect(result).toHaveLength(2) + expect(result[0].existingRecords).toEqual([]) + expect(result[1].existingRecords).toEqual([]) + }) }) describe("enrichMessagesWithExistingRecords", () => { @@ -185,7 +264,7 @@ describe("databaseClient", () => { { PrescriptionID: {S: "presc1"}, PharmacyODSCode: {S: "pharmA"}, - Status: {S: "Dispensed"}, + Status: {S: "With pharmacy"}, LastModified: {S: "2024-01-01T12:00:00Z"} } ] @@ -194,7 +273,7 @@ describe("databaseClient", () => { { PrescriptionID: {S: "presc2"}, PharmacyODSCode: {S: "pharmB"}, - Status: {S: "ReadyForCollection"}, + Status: {S: "Ready to collect"}, LastModified: {S: "2024-01-02T12:00:00Z"} } ] @@ -220,18 +299,15 @@ describe("databaseClient", () => { expect(enrichedMessages.length).toBe(2) expect(enrichedMessages[0].existingRecords.length).toBe(1) - expect(enrichedMessages[0].existingRecords[0].Status).toBe("Dispensed") + expect(enrichedMessages[0].existingRecords[0].Status).toBe("With pharmacy") expect(enrichedMessages[1].existingRecords.length).toBe(1) - expect(enrichedMessages[1].existingRecords[0].Status).toBe("ReadyForCollection") + expect(enrichedMessages[1].existingRecords[0].Status).toBe("Ready to collect") }) - }) - it("Should return empty array when no messages are provided", async () => { - const enrichedMessages = await enrichMessagesWithExistingRecords( - [], - logger - ) + it("should return an empty array when no messages are provided", async () => { + const enrichedMessages = await enrichMessagesWithExistingRecords([], logger) - expect(enrichedMessages).toEqual([]) + expect(enrichedMessages).toEqual([]) + }) }) }) From 1ba92ddab18af94e5136974483d58579d0fd23c2 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Tue, 3 Feb 2026 15:24:39 +0000 Subject: [PATCH 63/75] Initial implementation of the internal testing tool. --- .github/scripts/release_code.sh | 1 + .github/workflows/pull_request.yml | 2 + .../workflows/run_release_code_and_api.yml | 4 + ...scription-status-update-api.code-workspace | 14 +- Makefile | 6 + SAMtemplates/apis/main.yaml | 66 +++++++- SAMtemplates/functions/main.yaml | 69 ++++++++ SAMtemplates/main_template.yaml | 8 + package-lock.json | 22 +++ package.json | 1 + packages/common/commonTypes/src/index.ts | 1 + packages/nhsNotifyLambda/src/utils/dynamo.ts | 1 + .../jest.config.ts | 9 + .../notificationsReportingLambda/package.json | 29 ++++ .../src/notificationsReportingLambda.ts | 98 +++++++++++ .../src/notificationsRepository.ts | 156 ++++++++++++++++++ .../notificationsReportingLambda.test.ts | 78 +++++++++ .../tests/notificationsRepository.test.ts | 104 ++++++++++++ .../tsconfig.json | 20 +++ sonar-project.properties | 5 + tsconfig.build.json | 3 + 21 files changed, 693 insertions(+), 4 deletions(-) create mode 100644 packages/notificationsReportingLambda/jest.config.ts create mode 100644 packages/notificationsReportingLambda/package.json create mode 100644 packages/notificationsReportingLambda/src/notificationsReportingLambda.ts create mode 100644 packages/notificationsReportingLambda/src/notificationsRepository.ts create mode 100644 packages/notificationsReportingLambda/tests/notificationsReportingLambda.test.ts create mode 100644 packages/notificationsReportingLambda/tests/notificationsRepository.test.ts create mode 100644 packages/notificationsReportingLambda/tsconfig.json diff --git a/.github/scripts/release_code.sh b/.github/scripts/release_code.sh index bb8bc8b425..77dd7e2559 100755 --- a/.github/scripts/release_code.sh +++ b/.github/scripts/release_code.sh @@ -61,6 +61,7 @@ sam deploy \ LogRetentionInDays="$LOG_RETENTION_DAYS" \ Environment="$TARGET_ENVIRONMENT" \ DeployCheckPrescriptionStatusUpdate="$DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE" \ + DeployNotificationsReporting="$DEPLOY_NOTIFICATIONS_REPORTING" \ EnableAlerts="$ENABLE_ALERTS" \ StateMachineLogLevel="$STATE_MACHINE_LOG_LEVEL" \ EnableNotificationsInternal="$ENABLE_NOTIFICATIONS_INTERNAL" \ diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 610f7250ac..a04241f220 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -131,6 +131,7 @@ jobs: TEST_PRESCRIPTIONS_3: ${{ vars.TEST_PRESCRIPTIONS_3_VALUES }} TEST_PRESCRIPTIONS_4: ${{ vars.TEST_PRESCRIPTIONS_4_VALUES }} FORWARD_CSOC_LOGS: false + DEPLOY_NOTIFICATIONS_REPORTING_ENDPOINT: true secrets: CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} PROXYGEN_ROLE: ${{ secrets.PROXYGEN_PTL_ROLE }} @@ -166,6 +167,7 @@ jobs: MTLS_KEY: psu-mtls-1 IS_PULL_REQUEST: true FORWARD_CSOC_LOGS: false + DEPLOY_NOTIFICATIONS_REPORTING_ENDPOINT: true secrets: CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} PROXYGEN_ROLE: ${{ secrets.PROXYGEN_PTL_ROLE }} diff --git a/.github/workflows/run_release_code_and_api.yml b/.github/workflows/run_release_code_and_api.yml index ebe7724c13..7ad702138b 100644 --- a/.github/workflows/run_release_code_and_api.yml +++ b/.github/workflows/run_release_code_and_api.yml @@ -124,6 +124,9 @@ on: FORWARD_CSOC_LOGS: required: true type: boolean + DEPLOY_NOTIFICATIONS_REPORTING_ENDPOINT: + type: boolean + default: false secrets: CLOUD_FORMATION_DEPLOY_ROLE: required: true @@ -231,6 +234,7 @@ jobs: TEST_PRESCRIPTIONS_3: ${{ inputs.TEST_PRESCRIPTIONS_3 || 'noval' }} TEST_PRESCRIPTIONS_4: ${{ inputs.TEST_PRESCRIPTIONS_4 || 'noval' }} FORWARD_CSOC_LOGS: ${{ inputs.FORWARD_CSOC_LOGS }} + DEPLOY_NOTIFICATIONS_REPORTING: ${{ inputs.DEPLOY_NOTIFICATIONS_REPORTING_ENDPOINT }} run: ./release_code.sh - name: get mtls secrets diff --git a/.vscode/eps-prescription-status-update-api.code-workspace b/.vscode/eps-prescription-status-update-api.code-workspace index 06664a3e91..31cbdef70d 100644 --- a/.vscode/eps-prescription-status-update-api.code-workspace +++ b/.vscode/eps-prescription-status-update-api.code-workspace @@ -32,6 +32,10 @@ "name": "packages/nhsNotifyLambda", "path": "../packages/nhsNotifyLambda" }, + { + "name": "packages/notificationsReportingLambda", + "path": "../packages/notificationsReportingLambda" + }, { "name": "packages/postDatedLambda", "path": "../packages/postDatedLambda" @@ -156,8 +160,14 @@ "versionable", "whens" ], - "cSpell.dictionaries": ["en-GB"], - "cSpell.ignorePaths": ["package-lock.json", "node_modules", ".vscode"], + "cSpell.dictionaries": [ + "en-GB" + ], + "cSpell.ignorePaths": [ + "package-lock.json", + "node_modules", + ".vscode" + ], "jest.jestCommandLine": "NODE_OPTIONS=--experimental-vm-modules node_modules/.bin/jest --no-cache", "jest.nodeEnv": { "POWERTOOLS_DEV": true diff --git a/Makefile b/Makefile index f8c02a4413..b3326ea011 100644 --- a/Makefile +++ b/Makefile @@ -34,6 +34,7 @@ sam-sync: guard-AWS_DEFAULT_PROFILE guard-stack_name compile --parameter-overrides \ EnableSplunk=false \ DeployCheckPrescriptionStatusUpdate=true \ + DeployNotificationsReporting=true \ EnableAlerts=false sam-deploy: guard-AWS_DEFAULT_PROFILE guard-stack_name @@ -42,6 +43,7 @@ sam-deploy: guard-AWS_DEFAULT_PROFILE guard-stack_name --parameter-overrides \ EnableSplunk=false \ DeployCheckPrescriptionStatusUpdate=true \ + DeployNotificationsReporting=true \ EnableAlerts=false sam-delete: guard-AWS_DEFAULT_PROFILE guard-stack_name @@ -120,6 +122,7 @@ lint-node: compile-node npm run lint --workspace packages/cpsuLambda npm run lint --workspace packages/checkPrescriptionStatusUpdates npm run lint --workspace packages/nhsNotifyLambda + npm run lint --workspace packages/notificationsReportingLambda npm run lint --workspace packages/postDatedLambda npm run lint --workspace packages/nhsNotifyUpdateCallback npm run lint --workspace packages/common/testing @@ -153,6 +156,7 @@ test: compile npm run test --workspace packages/cpsuLambda npm run test --workspace packages/checkPrescriptionStatusUpdates npm run test --workspace packages/nhsNotifyLambda + npm run test --workspace packages/notificationsReportingLambda npm run test --workspace packages/postDatedLambda npm run test --workspace packages/nhsNotifyUpdateCallback npm run test --workspace packages/common/middyErrorHandler @@ -174,6 +178,8 @@ clean: rm -rf packages/cpsuLambda/lib rm -rf packages/nhsNotifyLambda/coverage rm -rf packages/nhsNotifyLambda/lib + rm -rf packages/notificationsReportingLambda/coverage + rm -rf packages/notificationsReportingLambda/lib rm -rf packages/postDatedLambda/coverage rm -rf packages/postDatedLambda/lib rm -rf packages/nhsNotifyUpdateCallback/coverage diff --git a/SAMtemplates/apis/main.yaml b/SAMtemplates/apis/main.yaml index 6a3e97ece1..4b06ea284d 100644 --- a/SAMtemplates/apis/main.yaml +++ b/SAMtemplates/apis/main.yaml @@ -62,6 +62,14 @@ Parameters: Type: String Default: none + NotificationsReportingFunctionName: + Type: String + Default: none + + NotificationsReportingFunctionArn: + Type: String + Default: none + LogRetentionInDays: Type: Number @@ -71,6 +79,10 @@ Parameters: DeployCheckPrescriptionStatusUpdate: Type: String + DeployNotificationsReporting: + Type: String + Default: false + ForwardCsocLogs: Type: String Default: false @@ -81,6 +93,9 @@ Conditions: ShouldDeployCheckPrescriptionStatusUpdate: !Equals - true - !Ref DeployCheckPrescriptionStatusUpdate + ShouldDeployNotificationsReporting: !Equals + - true + - !Ref DeployNotificationsReporting Resources: GenerateCertificate: @@ -549,11 +564,53 @@ Resources: Timeout: "1" Count: 0 + NotificationsReportingResource: + Condition: ShouldDeployNotificationsReporting + Type: AWS::ApiGateway::Resource + Properties: + RestApiId: !Ref RestApiGateway + ParentId: !GetAtt RestApiGateway.RootResourceId + PathPart: notifications-reporting + + NotificationsReportingMethod: + Condition: ShouldDeployNotificationsReporting + Type: AWS::ApiGateway::Method + Properties: + RestApiId: !Ref RestApiGateway + ResourceId: !Ref NotificationsReportingResource + HttpMethod: GET + AuthorizationType: NONE + Integration: + Type: AWS_PROXY + Credentials: !GetAtt RestApiGatewayResources.Outputs.ApiGwRoleArn + IntegrationHttpMethod: POST + Uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${NotificationsReportingFunctionArn}/invocations + + NotificationsReportingHandle: + Condition: ShouldDeployNotificationsReporting + DependsOn: NotificationsReportingMethod + Type: "AWS::CloudFormation::WaitConditionHandle" + + NotificationsReportingWaitHandle: + Type: "AWS::CloudFormation::WaitConditionHandle" + + NotificationsReportingWaitCondition: + Type: "AWS::CloudFormation::WaitCondition" + Properties: + Handle: + !If [ + ShouldDeployNotificationsReporting, + !Ref NotificationsReportingHandle, + !Ref NotificationsReportingWaitHandle, + ] + Timeout: "1" + Count: 0 + # ********************************************************************* # if you add a new endpoint, then change the name of this resource # also need to change it in RestApiGatewayStage.Properties.DeploymentId # ********************************************************************* - RestApiGatewayDeploymentV2f: + RestApiGatewayDeploymentV2g: Type: AWS::ApiGateway::Deployment DependsOn: # see note above if you add something in here when you add a new endpoint @@ -563,6 +620,7 @@ Resources: - Format1UpdatePrescriptionStatusMethod - CheckPrescriptionStatusUpdatesWaitCondition - NotificationDeliveryStatusCallbackMethod + - NotificationsReportingMethod # see note above if you add something in here when you add a new endpoint Properties: RestApiId: !Ref RestApiGateway @@ -571,7 +629,7 @@ Resources: Type: AWS::ApiGateway::Stage Properties: RestApiId: !Ref RestApiGateway - DeploymentId: !Ref RestApiGatewayDeploymentV2f + DeploymentId: !Ref RestApiGatewayDeploymentV2g StageName: prod TracingEnabled: true AccessLogSetting: @@ -600,6 +658,10 @@ Resources: - ShouldDeployCheckPrescriptionStatusUpdate - Fn::ImportValue: !Sub ${StackName}:functions:${CheckPrescriptionStatusUpdatesFunctionName}:ExecuteLambdaPolicyArn - !Ref AWS::NoValue + - !If + - ShouldDeployNotificationsReporting + - Fn::ImportValue: !Sub ${StackName}:functions:${NotificationsReportingFunctionName}:ExecuteLambdaPolicyArn + - !Ref AWS::NoValue ApiName: !Sub ${StackName}-apigw LogRetentionInDays: !Ref LogRetentionInDays diff --git a/SAMtemplates/functions/main.yaml b/SAMtemplates/functions/main.yaml index e25cc55107..fbd262a50f 100644 --- a/SAMtemplates/functions/main.yaml +++ b/SAMtemplates/functions/main.yaml @@ -90,6 +90,10 @@ Parameters: DeployCheckPrescriptionStatusUpdate: Type: String + DeployNotificationsReporting: + Type: String + Default: false + Environment: Type: String @@ -113,6 +117,10 @@ Conditions: - true - !Ref DeployCheckPrescriptionStatusUpdate + ShouldDeployNotificationsReporting: !Equals + - true + - !Ref DeployNotificationsReporting + EnableBackupCondition: !Equals - "True" - !Ref EnableBackup @@ -402,6 +410,53 @@ Resources: SplunkSubscriptionFilterRole: !ImportValue lambda-resources:SplunkSubscriptionFilterRole SplunkDeliveryStreamArn: !ImportValue lambda-resources:SplunkDeliveryStream + NotificationsReporting: + Condition: ShouldDeployNotificationsReporting + Type: AWS::Serverless::Function + Properties: + FunctionName: !Sub ${StackName}-NotificationsReporting + CodeUri: ../../packages + Handler: notificationsReportingLambda.handler + Role: !GetAtt NotificationsReportingResources.Outputs.LambdaRoleArn + Environment: + Variables: + TABLE_NAME: !Ref PrescriptionNotificationStatesTableName + Metadata: + BuildMethod: esbuild + guard: + SuppressedRules: + - LAMBDA_DLQ_CHECK + - LAMBDA_INSIDE_VPC + - LAMBDA_CONCURRENCY_CHECK + BuildProperties: + Minify: true + Target: es2020 + Sourcemap: true + tsconfig: notificationsReportingLambda/tsconfig.json + packages: bundle + EntryPoints: + - notificationsReportingLambda/src/notificationsReportingLambda.ts + + NotificationsReportingResources: + Condition: ShouldDeployNotificationsReporting + Type: AWS::Serverless::Application + Properties: + Location: lambda_resources.yaml + Parameters: + StackName: !Ref StackName + LambdaName: !Sub ${StackName}-NotificationsReporting + LambdaArn: !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${StackName}-NotificationsReporting + IncludeAdditionalPolicies: true + AdditionalPolicies: !Join + - "," + - - Fn::ImportValue: !Sub ${StackName}:tables:${PrescriptionNotificationStatesTableName}:TableReadPolicyArn + - Fn::ImportValue: !Sub ${StackName}:tables:UsePrescriptionNotificationStatesKMSKeyPolicyArn + LogRetentionInDays: !Ref LogRetentionInDays + CloudWatchKMSKeyId: !ImportValue account-resources:CloudwatchLogsKmsKeyArn + EnableSplunk: !Ref EnableSplunk + SplunkSubscriptionFilterRole: !ImportValue lambda-resources:SplunkSubscriptionFilterRole + SplunkDeliveryStreamArn: !ImportValue lambda-resources:SplunkDeliveryStream + NotifyProcessorScheduleEventRole: Type: AWS::IAM::Role Properties: @@ -825,6 +880,20 @@ Outputs: Description: The function ARN of the NHSNotifyUpdateCallback lambda Value: !GetAtt NHSNotifyUpdateCallback.Arn + NotificationsReportingFunctionName: + Description: The function name of the NotificationsReporting lambda + Value: !If + - ShouldDeployNotificationsReporting + - !Ref NotificationsReporting + - "" + + NotificationsReportingFunctionArn: + Description: The function ARN of the NotificationsReporting lambda + Value: !If + - ShouldDeployNotificationsReporting + - !GetAtt NotificationsReporting.Arn + - "" + # Post-dated lambda outputs PostDatedNotifyLambdaFunctionName: Description: The function name of the postDatedNotifyLambda diff --git a/SAMtemplates/main_template.yaml b/SAMtemplates/main_template.yaml index 48a2f6b8d3..2a7a88df01 100644 --- a/SAMtemplates/main_template.yaml +++ b/SAMtemplates/main_template.yaml @@ -71,6 +71,10 @@ Parameters: DeployCheckPrescriptionStatusUpdate: Type: String + DeployNotificationsReporting: + Type: String + Default: false + Environment: Type: String @@ -221,9 +225,12 @@ Resources: CheckPrescriptionStatusUpdatesFunctionArn: !GetAtt Functions.Outputs.CheckPrescriptionStatusUpdatesFunctionArn NHSNotifyUpdateCallbackFunctionName: !GetAtt Functions.Outputs.NHSNotifyUpdateCallbackFunctionName NHSNotifyUpdateCallbackFunctionArn: !GetAtt Functions.Outputs.NHSNotifyUpdateCallbackFunctionArn + NotificationsReportingFunctionName: !GetAtt Functions.Outputs.NotificationsReportingFunctionName + NotificationsReportingFunctionArn: !GetAtt Functions.Outputs.NotificationsReportingFunctionArn LogRetentionInDays: !Ref LogRetentionInDays EnableSplunk: !Ref EnableSplunk DeployCheckPrescriptionStatusUpdate: !Ref DeployCheckPrescriptionStatusUpdate + DeployNotificationsReporting: !Ref DeployNotificationsReporting ForwardCsocLogs: !Ref ForwardCsocLogs Functions: @@ -251,6 +258,7 @@ Resources: VersionNumber: !Ref VersionNumber CommitId: !Ref CommitId DeployCheckPrescriptionStatusUpdate: !Ref DeployCheckPrescriptionStatusUpdate + DeployNotificationsReporting: !Ref DeployNotificationsReporting Environment: !Ref Environment EnableBackup: !Ref EnableBackup RequireApplicationName: !Ref RequireApplicationName diff --git a/package-lock.json b/package-lock.json index f6486b7410..4b30846b34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "packages/cpsuLambda", "packages/checkPrescriptionStatusUpdates", "packages/nhsNotifyLambda", + "packages/notificationsReportingLambda", "packages/postDatedLambda", "packages/psuRestoreValidationLambda", "packages/nhsNotifyUpdateCallback", @@ -8568,6 +8569,10 @@ "node": ">=0.10.0" } }, + "node_modules/notificationsReportingLambda": { + "resolved": "packages/notificationsReportingLambda", + "link": true + }, "node_modules/npm-normalize-package-bin": { "version": "1.0.1", "dev": true, @@ -11257,6 +11262,23 @@ "axios-mock-adapter": "^2.1.0" } }, + "packages/notificationsReportingLambda": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@aws-lambda-powertools/commons": "^2.30.2", + "@aws-lambda-powertools/logger": "^2.30.2", + "@aws-sdk/client-dynamodb": "^3.975.0", + "@aws-sdk/lib-dynamodb": "^3.975.0", + "@middy/core": "^7.0.2", + "@middy/http-header-normalizer": "^7.0.2", + "@middy/input-output-logger": "^7.0.2", + "@psu-common/commonTypes": "^1.0.0" + }, + "devDependencies": { + "@psu-common/testing": "^1.0.0" + } + }, "packages/postDatedLambda": { "version": "1.0.0", "license": "MIT", diff --git a/package.json b/package.json index 90b83e8c48..c0003eae67 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "packages/cpsuLambda", "packages/checkPrescriptionStatusUpdates", "packages/nhsNotifyLambda", + "packages/notificationsReportingLambda", "packages/postDatedLambda", "packages/psuRestoreValidationLambda", "packages/nhsNotifyUpdateCallback", diff --git a/packages/common/commonTypes/src/index.ts b/packages/common/commonTypes/src/index.ts index 8f22b3e6e2..159c8b838e 100644 --- a/packages/common/commonTypes/src/index.ts +++ b/packages/common/commonTypes/src/index.ts @@ -66,6 +66,7 @@ export interface LastNotificationStateType extends NotificationUpdate { NHSNumber: string ODSCode: string RequestId: string // x-request-id header + PrescriptionID?: string SQSMessageID?: string // The SQS message ID NotifyMessageID?: string // The UUID we got back from Notify for the submitted message NotifyMessageReference: string // The references we generated for the message diff --git a/packages/nhsNotifyLambda/src/utils/dynamo.ts b/packages/nhsNotifyLambda/src/utils/dynamo.ts index aed6a2a352..ae3ed71e42 100644 --- a/packages/nhsNotifyLambda/src/utils/dynamo.ts +++ b/packages/nhsNotifyLambda/src/utils/dynamo.ts @@ -35,6 +35,7 @@ export async function addPrescriptionMessagesToNotificationStateStore( NHSNumber: data.PSUDataItem.PatientNHSNumber, ODSCode: data.PSUDataItem.PharmacyODSCode, RequestId: data.PSUDataItem.RequestID, + PrescriptionID: data.PSUDataItem.PrescriptionID, // only used for reporting and tracing SQSMessageID: data.MessageId, LastNotifiedPrescriptionStatus: data.PSUDataItem.Status, MessageStatus: data.messageStatus ?? "unknown", // Fall back to unknown if not set diff --git a/packages/notificationsReportingLambda/jest.config.ts b/packages/notificationsReportingLambda/jest.config.ts new file mode 100644 index 0000000000..acbc1493de --- /dev/null +++ b/packages/notificationsReportingLambda/jest.config.ts @@ -0,0 +1,9 @@ +import defaultConfig from "../../jest.default.config.ts" +import type {JestConfigWithTsJest} from "ts-jest" + +const jestConfig: JestConfigWithTsJest = { + ...defaultConfig, + rootDir: "./" +} + +export default jestConfig diff --git a/packages/notificationsReportingLambda/package.json b/packages/notificationsReportingLambda/package.json new file mode 100644 index 0000000000..dea3cb4930 --- /dev/null +++ b/packages/notificationsReportingLambda/package.json @@ -0,0 +1,29 @@ +{ + "name": "notificationsReportingLambda", + "version": "1.0.0", + "description": "Lambda that exposes internal notification reporting APIs", + "main": "notificationsReportingLambda.js", + "author": "NHS Digital", + "license": "MIT", + "type": "module", + "scripts": { + "unit": "POWERTOOLS_DEV=true NODE_OPTIONS=--experimental-vm-modules jest --no-cache --coverage", + "lint": "eslint --max-warnings 0 --fix --config ../../eslint.config.mjs .", + "compile": "tsc", + "test": "npm run compile && npm run unit", + "check-licenses": "license-checker --failOn GPL --failOn LGPL --start ../.." + }, + "dependencies": { + "@aws-lambda-powertools/commons": "^2.30.2", + "@aws-lambda-powertools/logger": "^2.30.2", + "@aws-sdk/client-dynamodb": "^3.975.0", + "@aws-sdk/lib-dynamodb": "^3.975.0", + "@middy/core": "^7.0.2", + "@middy/http-header-normalizer": "^7.0.2", + "@middy/input-output-logger": "^7.0.2", + "@psu-common/commonTypes": "^1.0.0" + }, + "devDependencies": { + "@psu-common/testing": "^1.0.0" + } +} diff --git a/packages/notificationsReportingLambda/src/notificationsReportingLambda.ts b/packages/notificationsReportingLambda/src/notificationsReportingLambda.ts new file mode 100644 index 0000000000..6f699c5f32 --- /dev/null +++ b/packages/notificationsReportingLambda/src/notificationsReportingLambda.ts @@ -0,0 +1,98 @@ +import {APIGatewayProxyEvent, APIGatewayProxyResult} from "aws-lambda" +import {Logger} from "@aws-lambda-powertools/logger" +import {injectLambdaContext} from "@aws-lambda-powertools/logger/middleware" +import middy from "@middy/core" +import inputOutputLogger from "@middy/input-output-logger" +import httpHeaderNormalizer from "@middy/http-header-normalizer" +import {DynamoDBClient} from "@aws-sdk/client-dynamodb" +import {DynamoDBDocumentClient} from "@aws-sdk/lib-dynamodb" + +import {NotificationQueryFilters, NotificationsRepository} from "./notificationsRepository" + +const logger = new Logger({serviceName: "notificationsReporting"}) + +const documentClient = DynamoDBDocumentClient.from(new DynamoDBClient({region: process.env.AWS_REGION})) +let cachedRepository: NotificationsRepository | undefined + +const getDefaultRepository = (): NotificationsRepository => { + cachedRepository ??= new NotificationsRepository(documentClient, process.env.TABLE_NAME ?? "") + return cachedRepository +} + +export const buildHandler = (repoProvider: () => NotificationsRepository) => { + return async (event: APIGatewayProxyEvent): Promise => { + logger.appendKeys({ + "nhsd-correlation-id": event.headers["nhsd-correlation-id"], + "x-request-id": event.headers["x-request-id"], + "apigw-request-id": event.requestContext.requestId + }) + + const filters = normalizeFilters(event.queryStringParameters ?? {}) + + if (!hasAnyFilter(filters)) { + return buildResponse(400, {message: "Provide at least one of prescriptionId, nhsNumber or odsCode"}) + } + + try { + const notifications = await repoProvider().fetch(filters) + logger.info("Returning notification results", { + hasNhsNumber: Boolean(filters.nhsNumber), + odsCode: filters.odsCode, + prescriptionIdProvided: Boolean(filters.prescriptionId), + resultCount: notifications.length + }) + return buildResponse(200, { + count: notifications.length, + filters: { + prescriptionId: filters.prescriptionId ?? null, + odsCode: filters.odsCode ?? null, + nhsNumberProvided: Boolean(filters.nhsNumber) + }, + notifications + }) + } catch (error) { + logger.error("Failed to retrieve notification records", {error}) + return buildResponse(500, {message: "Failed to fetch notification statuses"}) + } + } +} + +const normalizeFilters = (params: Record): NotificationQueryFilters => { + const trimmed = (value?: string | null) => { + if (!value) return undefined + const normalized = value.trim() + return normalized.length ? normalized : undefined + } + + return { + prescriptionId: trimmed(params.prescriptionId), + nhsNumber: trimmed(params.nhsNumber), + odsCode: trimmed(params.odsCode)?.toUpperCase() + } +} + +const hasAnyFilter = (filters: NotificationQueryFilters): boolean => ( + Boolean(filters.prescriptionId) || Boolean(filters.nhsNumber) || Boolean(filters.odsCode) +) + +const buildResponse = (statusCode: number, body: unknown): APIGatewayProxyResult => ({ + statusCode, + headers: { + "Content-Type": "application/json", + "Cache-Control": "no-cache" + }, + body: JSON.stringify(body) +}) + +export const lambdaHandler = buildHandler(getDefaultRepository) + +export const handler = middy(lambdaHandler) + .use(injectLambdaContext(logger, {clearState: true})) + .use(httpHeaderNormalizer()) + .use(inputOutputLogger({ + logger: request => { + logger.info("notificationsReporting invocation", {hasBody: Boolean(request.event.body)}) + } + })) + +export {normalizeFilters, buildResponse} diff --git a/packages/notificationsReportingLambda/src/notificationsRepository.ts b/packages/notificationsReportingLambda/src/notificationsRepository.ts new file mode 100644 index 0000000000..e4420b1bf3 --- /dev/null +++ b/packages/notificationsReportingLambda/src/notificationsRepository.ts @@ -0,0 +1,156 @@ +import { + DynamoDBDocumentClient, + QueryCommand, + ScanCommand, + QueryCommandInput, + ScanCommandInput +} from "@aws-sdk/lib-dynamodb" +import {LastNotificationStateType} from "@psu-common/commonTypes" + +export interface NotificationQueryFilters { + prescriptionId?: string + nhsNumber?: string + odsCode?: string +} + +interface FilterState { + clauses: Array + names: Record + values: Record + index: number +} + +export class NotificationsRepository { + private readonly client: DynamoDBDocumentClient + private readonly tableName: string + + constructor(client: DynamoDBDocumentClient, tableName: string) { + if (!tableName) { + throw new Error("Notifications table name is not configured") + } + this.client = client + this.tableName = tableName + } + + async fetch(filters: NotificationQueryFilters): Promise> { + if (!filters.nhsNumber && !filters.prescriptionId && !filters.odsCode) { + throw new Error("At least one filter must be provided") + } + + if (filters.nhsNumber) { + return await this.queryByNhsNumber(filters) + } + + return await this.scanByFilters(filters) + } + + // We can query by NHS number, since it's a partition key. Other fields will require a scan + private async queryByNhsNumber(filters: NotificationQueryFilters): Promise> { + const names: Record = {"#pk": "NHSNumber"} + const values: Record = {":pk": filters.nhsNumber} + const filterState: FilterState = { + clauses: [], + names, + values, + index: 0 + } + + if (filters.odsCode) { + this.appendFilterClause(filterState, "ODSCode", filters.odsCode) + } + if (filters.prescriptionId) { + this.appendFilterClause(filterState, "PrescriptionID", filters.prescriptionId) + } + + const input: QueryCommandInput = { + TableName: this.tableName, + KeyConditionExpression: "#pk = :pk", + ExpressionAttributeNames: names, + ExpressionAttributeValues: values + } + + if (filterState.clauses.length) { + input.FilterExpression = filterState.clauses.join(" AND ") + } + + return await this.paginatedQuery(input) + } + + private async scanByFilters(filters: NotificationQueryFilters): Promise> { + const names: Record = {} + const values: Record = {} + const filterState: FilterState = { + clauses: [], + names, + values, + index: 0 + } + + if (filters.prescriptionId) { + this.appendFilterClause(filterState, "PrescriptionID", filters.prescriptionId) + } + if (filters.odsCode) { + this.appendFilterClause(filterState, "ODSCode", filters.odsCode) + } + + if (filterState.clauses.length === 0) { + throw new Error("Scan requires at least one non-key filter") + } + + const input: ScanCommandInput = { + TableName: this.tableName, + FilterExpression: filterState.clauses.join(" AND "), + ExpressionAttributeNames: names, + ExpressionAttributeValues: values + } + + return await this.paginatedScan(input) + } + + private appendFilterClause(state: FilterState, attributeName: string, value: string): void { + const nameKey = `#f${state.index}` + const valueKey = `:f${state.index}` + state.index += 1 + state.names[nameKey] = attributeName + state.values[valueKey] = value + state.clauses.push(`${nameKey} = ${valueKey}`) + } + + private async paginatedQuery(input: QueryCommandInput): Promise> { + const items: Array = [] + let exclusiveStartKey: Record | undefined + + do { + const command = new QueryCommand({ + ...input, + ExclusiveStartKey: exclusiveStartKey + }) + const result = await this.client.send(command) + if (result.Items) { + items.push(...result.Items as Array) + } + exclusiveStartKey = result.LastEvaluatedKey as Record | undefined + } while (exclusiveStartKey) + + return items + } + + private async paginatedScan(input: ScanCommandInput): Promise> { + const items: Array = [] + let exclusiveStartKey: Record | undefined + + do { + const command = new ScanCommand({ + ...input, + ExclusiveStartKey: exclusiveStartKey + }) + const result = await this.client.send(command) + if (result.Items) { + items.push(...result.Items as Array) + } + exclusiveStartKey = result.LastEvaluatedKey as Record | undefined + } while (exclusiveStartKey) + + return items + } +} diff --git a/packages/notificationsReportingLambda/tests/notificationsReportingLambda.test.ts b/packages/notificationsReportingLambda/tests/notificationsReportingLambda.test.ts new file mode 100644 index 0000000000..597b34a897 --- /dev/null +++ b/packages/notificationsReportingLambda/tests/notificationsReportingLambda.test.ts @@ -0,0 +1,78 @@ +import { + jest, + describe, + it, + expect, + beforeEach +} from "@jest/globals" +import {APIGatewayProxyResult} from "aws-lambda" + +import {buildHandler} from "../src/notificationsReportingLambda" +import type {NotificationsRepository} from "../src/notificationsRepository" +import {mockAPIGatewayProxyEvent} from "@psu-common/testing" + +const buildEvent = (query: Record) => ({ + ...mockAPIGatewayProxyEvent, + httpMethod: "GET", + queryStringParameters: query +}) + +describe("notificationsReportingLambda", () => { + let fetchMock: jest.MockedFunction + let repo: NotificationsRepository + + beforeEach(() => { + fetchMock = jest.fn() as jest.MockedFunction + repo = {fetch: fetchMock} as unknown as NotificationsRepository + }) + + it("returns 400 when no filters are provided", async () => { + const handler = buildHandler(() => repo) + const result: APIGatewayProxyResult = await handler(buildEvent({})) + + expect(result.statusCode).toBe(400) + expect(JSON.parse(result.body)).toEqual({ + message: "Provide at least one of prescriptionId, nhsNumber or odsCode" + }) + expect(fetchMock).not.toHaveBeenCalled() + }) + + it("delegates to the repository and returns matches", async () => { + fetchMock.mockResolvedValue([ + { + RequestId: "req-1", + NHSNumber: "123", + ODSCode: "A1B", + LastNotifiedPrescriptionStatus: "DELIVERED", + LastNotificationRequestTimestamp: new Date().toISOString(), + ExpiryTime: 123, + NotifyMessageReference: "ref-1" + } + ]) + + const handler = buildHandler(() => repo) + const result: APIGatewayProxyResult = await handler( + buildEvent({nhsNumber: " 123 ", odsCode: "ab1"}) + ) + + expect(fetchMock).toHaveBeenCalledWith({nhsNumber: "123", odsCode: "AB1"}) + expect(result.statusCode).toBe(200) + expect(JSON.parse(result.body)).toMatchObject({ + count: 1, + filters: { + nhsNumberProvided: true, + odsCode: "AB1" + } + }) + }) + + it("handles repository errors", async () => { + fetchMock.mockRejectedValue(new Error("boom")) + const handler = buildHandler(() => repo) + + const result = await handler(buildEvent({prescriptionId: "rx-1"})) + + expect(result.statusCode).toBe(500) + expect(JSON.parse(result.body)).toEqual({message: "Failed to fetch notification statuses"}) + }) +}) diff --git a/packages/notificationsReportingLambda/tests/notificationsRepository.test.ts b/packages/notificationsReportingLambda/tests/notificationsRepository.test.ts new file mode 100644 index 0000000000..5ad03669e2 --- /dev/null +++ b/packages/notificationsReportingLambda/tests/notificationsRepository.test.ts @@ -0,0 +1,104 @@ +import { + jest, + describe, + it, + expect, + beforeEach +} from "@jest/globals" +import {QueryCommand, ScanCommand} from "@aws-sdk/lib-dynamodb" +import type {DynamoDBDocumentClient} from "@aws-sdk/lib-dynamodb" + +import {NotificationsRepository} from "../src/notificationsRepository" + +const mockSend: jest.MockedFunction<(command: QueryCommand | ScanCommand) => Promise> = jest.fn() +const mockClient = {send: mockSend} as unknown as DynamoDBDocumentClient + +describe("NotificationsRepository", () => { + beforeEach(() => { + mockSend.mockReset() + }) + + it("queries by NHS number when provided", async () => { + mockSend.mockResolvedValue({ + Items: [{ + NHSNumber: "123", + RequestId: "req", + ODSCode: "F1A", + LastNotificationRequestTimestamp: new Date().toISOString(), + ExpiryTime: 123, + LastNotifiedPrescriptionStatus: "DELIVERED", + NotifyMessageReference: "ref" + }] + }) + + const repo = new NotificationsRepository(mockClient, "table") + const results = await repo.fetch({nhsNumber: "123", odsCode: "F1A"}) + + expect(results).toHaveLength(1) + expect(mockSend).toHaveBeenCalledTimes(1) + const command = mockSend.mock.calls[0][0] as QueryCommand + expect(command).toBeInstanceOf(QueryCommand) + expect(command.input.ExpressionAttributeNames?.["#pk"]).toBe("NHSNumber") + expect(command.input.FilterExpression).toBe("#f0 = :f0") + expect(command.input.ExpressionAttributeNames?.["#f0"]).toBe("ODSCode") + }) + + it("scans when only non-key filters are supplied", async () => { + mockSend + .mockResolvedValueOnce({ + Items: [{ + PrescriptionID: "RX1", + RequestId: "req-1", + NHSNumber: "999", + ODSCode: "T1O", + LastNotificationRequestTimestamp: new Date().toISOString(), + ExpiryTime: 123, + LastNotifiedPrescriptionStatus: "REQUESTED", + NotifyMessageReference: "ref-1" + }], + LastEvaluatedKey: {RequestId: "req-1"} + }) + .mockResolvedValueOnce({ + Items: [{ + PrescriptionID: "RX1", + RequestId: "req-2", + NHSNumber: "999", + ODSCode: "T1O", + LastNotificationRequestTimestamp: new Date().toISOString(), + ExpiryTime: 456, + LastNotifiedPrescriptionStatus: "REQUESTED", + NotifyMessageReference: "ref-2" + }] + }) + + const repo = new NotificationsRepository(mockClient, "table") + const results = await repo.fetch({prescriptionId: "rx1", odsCode: "t1o"}) + + expect(results).toHaveLength(2) + expect(mockSend).toHaveBeenCalledTimes(2) + const firstCall = mockSend.mock.calls[0][0] as ScanCommand + expect(firstCall).toBeInstanceOf(ScanCommand) + expect(firstCall.input.FilterExpression).toBe("#f0 = :f0 AND #f1 = :f1") + expect(firstCall.input.ExpressionAttributeNames?.["#f0"]).toBe("PrescriptionID") + expect(firstCall.input.ExpressionAttributeNames?.["#f1"]).toBe("ODSCode") + }) + + it("allows scanning by ODS code only", async () => { + mockSend.mockResolvedValue({Items: []}) + + const repo = new NotificationsRepository(mockClient, "table") + await repo.fetch({odsCode: "A1B"}) + + const call = mockSend.mock.calls[0][0] as ScanCommand + expect(call).toBeInstanceOf(ScanCommand) + expect(call.input.FilterExpression).toBe("#f0 = :f0") + expect(call.input.ExpressionAttributeNames?.["#f0"]).toBe("ODSCode") + }) + + it("throws when no filters are supplied", async () => { + const repo = new NotificationsRepository(mockClient, "table") + + await expect(repo.fetch({})).rejects.toThrow("At least one filter must be provided") + }) + +}) diff --git a/packages/notificationsReportingLambda/tsconfig.json b/packages/notificationsReportingLambda/tsconfig.json new file mode 100644 index 0000000000..eac36c1c82 --- /dev/null +++ b/packages/notificationsReportingLambda/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../tsconfig.defaults.json", + "compilerOptions": { + "rootDir": ".", + "outDir": "lib" + }, + "references": [ + { + "path": "../common/testing" + } + ], + "include": [ + "src/**/*", + "tests/**/*", + "cli/**/*" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/sonar-project.properties b/sonar-project.properties index 815621e84a..afda66e577 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -26,6 +26,7 @@ sonar.modules=\ cpsuLambda, \ gsul, \ nhsNotifyLambda, \ + notificationsReportingLambda, \ nhsNotifyUpdateCallback, \ psuRestoreValidationLambda, \ sandbox, \ @@ -53,6 +54,10 @@ nhsNotifyLambda.sonar.projectBaseDir=packages/nhsNotifyLambda nhsNotifyLambda.sonar.sources=. nhsNotifyLambda.sonar.javascript.lcov.reportPaths=coverage/lcov.info +notificationsReportingLambda.sonar.projectBaseDir=packages/notificationsReportingLambda +notificationsReportingLambda.sonar.sources=. +notificationsReportingLambda.sonar.javascript.lcov.reportPaths=coverage/lcov.info + postDatedLambda.sonar.projectBaseDir=packages/postDatedLambda postDatedLambda.sonar.sources=. postDatedLambda.sonar.javascript.lcov.reportPaths=coverage/lcov.info diff --git a/tsconfig.build.json b/tsconfig.build.json index 479e9c0313..47cb9c8044 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -45,6 +45,9 @@ }, { "path": "packages/nhsNotifyUpdateCallback" + }, + { + "path": "packages/notificationsReportingLambda" } ] } From 89994c8ba33bffeb0464053513f6d73727831e2a Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Wed, 4 Feb 2026 09:48:00 +0000 Subject: [PATCH 64/75] Remove custom data type for sqs batch message entries, and use the given one --- packages/common/commonTypes/src/index.ts | 15 --------------- packages/postDatedLambda/src/sqs.ts | 7 ++++--- .../src/utils/sqsClient.ts | 13 ++++--------- 3 files changed, 8 insertions(+), 27 deletions(-) diff --git a/packages/common/commonTypes/src/index.ts b/packages/common/commonTypes/src/index.ts index 8f22b3e6e2..b522e7e0b7 100644 --- a/packages/common/commonTypes/src/index.ts +++ b/packages/common/commonTypes/src/index.ts @@ -28,21 +28,6 @@ export interface NotifyDataItem { PrescriptionID: string // Needed to query NPPTS } -/** - * The structure of a single SQS message in a batch send. - * I couldn't find this type exported from the SDK, and the Message type that IS exported is for receiving data, - * not sending it. - * - * So, I've just done it myself. These are the core attrtibutes we use. - */ -export interface SQSBatchMessage { - Id: string - MessageBody: string - MessageDeduplicationId: string - MessageGroupId: string - MessageAttributes: {[key: string]: {DataType: string; StringValue: string}} -} - /** * The fields stored in the Notifications table potentially updated by the Notify callback. */ diff --git a/packages/postDatedLambda/src/sqs.ts b/packages/postDatedLambda/src/sqs.ts index 103189dca6..acb0524396 100644 --- a/packages/postDatedLambda/src/sqs.ts +++ b/packages/postDatedLambda/src/sqs.ts @@ -5,11 +5,12 @@ import { ChangeMessageVisibilityBatchCommand, GetQueueAttributesCommand, SendMessageBatchCommand, + SendMessageBatchRequestEntry, Message } from "@aws-sdk/client-sqs" import {Logger} from "@aws-lambda-powertools/logger" -import {NotifyDataItem, SQSBatchMessage} from "@psu-common/commonTypes" +import {NotifyDataItem} from "@psu-common/commonTypes" import {BatchProcessingResult, PostDatedSQSMessage} from "./types" @@ -35,7 +36,7 @@ function chunkArray(arr: Array, size: number): Array> { function buildNotificationBatchEntries( messages: Array, logger: Logger -): Array { +): Array { return messages.map((message, idx) => { const {prescriptionData} = message @@ -70,7 +71,7 @@ function buildNotificationBatchEntries( } async function sendEntriesToQueue( - entries: Array, + entries: Array, queueUrl: string, logger: Logger ): Promise> { diff --git a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts index 1ee67d57d8..aff5bcfbbe 100644 --- a/packages/updatePrescriptionStatus/src/utils/sqsClient.ts +++ b/packages/updatePrescriptionStatus/src/utils/sqsClient.ts @@ -1,15 +1,10 @@ import {Logger} from "@aws-lambda-powertools/logger" -import {SQSClient, SendMessageBatchCommand} from "@aws-sdk/client-sqs" +import {SQSClient, SendMessageBatchCommand, SendMessageBatchRequestEntry} from "@aws-sdk/client-sqs" import {getSecret} from "@aws-lambda-powertools/parameters/secrets" import {createHmac} from "node:crypto" -import { - NotifyDataItem, - PSUDataItem, - PSUDataItemWithPrevious, - SQSBatchMessage -} from "@psu-common/commonTypes" +import {NotifyDataItem, PSUDataItem, PSUDataItemWithPrevious} from "@psu-common/commonTypes" import {checkSiteOrSystemIsNotifyEnabled} from "../validation/notificationSiteAndSystemFilters" @@ -43,7 +38,7 @@ function buildSqsBatchEntries( items: Array, requestId: string, sqsSalt: string -): Array { +): Array { return items.map((item, idx) => ({ Id: idx.toString(), MessageBody: JSON.stringify(item as NotifyDataItem), @@ -67,7 +62,7 @@ function buildSqsBatchEntries( * @returns An array of the created MessageIds */ async function sendEntriesToQueue( - entries: Array, + entries: Array, queueUrl: string, requestId: string, logger: Logger From a3a196a0a9e8bb16c0d8186725753bbeddae6e86 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Wed, 4 Feb 2026 11:53:01 +0000 Subject: [PATCH 65/75] Address some of Tim's comments --- packages/postDatedLambda/src/businessLogic.ts | 28 ++----------------- .../postDatedLambda/src/databaseClient.ts | 21 ++++++++------ 2 files changed, 14 insertions(+), 35 deletions(-) diff --git a/packages/postDatedLambda/src/businessLogic.ts b/packages/postDatedLambda/src/businessLogic.ts index 9d00eef8b6..520d676456 100644 --- a/packages/postDatedLambda/src/businessLogic.ts +++ b/packages/postDatedLambda/src/businessLogic.ts @@ -4,24 +4,6 @@ import {PSUDataItem} from "@psu-common/commonTypes" import {PostDatedSQSMessageWithExistingRecords, PostDatedProcessingResult} from "./types" -// defaults to false -const POST_DATED_OVERRIDE = process.env.POST_DATED_OVERRIDE === "true" - -// set from environment variable POST_DATED_OVERRIDE_VALUE -const POST_DATED_OVERRIDE_VALUE_ENV = process.env.POST_DATED_OVERRIDE_VALUE ?? "ignore" -let POST_DATED_OVERRIDE_VALUE: PostDatedProcessingResult -switch (POST_DATED_OVERRIDE_VALUE_ENV.toLowerCase()) { - case "matured": - POST_DATED_OVERRIDE_VALUE = PostDatedProcessingResult.MATURED - break - case "immature": - POST_DATED_OVERRIDE_VALUE = PostDatedProcessingResult.IMMATURE - break - default: - POST_DATED_OVERRIDE_VALUE = PostDatedProcessingResult.IGNORE - break -} - export function getMostRecentRecord( existingRecords: Array ): PSUDataItem { @@ -53,12 +35,6 @@ export function processMessage( prescriptionData: message.prescriptionData, existingRecords: message.existingRecords }) - if (POST_DATED_OVERRIDE) { - logger.info("Post-dated override is enabled, returning override value", { - overrideValue: POST_DATED_OVERRIDE_VALUE - }) - return POST_DATED_OVERRIDE_VALUE - } // The existingRecords array contains all records from the DynamoDB table // that match this prescription's PrescriptionID @@ -141,7 +117,7 @@ export function computeTimeUntilMaturity( } const lastModified = new Date(prescriptionRecord.LastModified) - const currentTime = new Date() + const currentTime = Date.now() - return (lastModified.getTime() - currentTime.getTime()) / 1000 + return (lastModified.getTime() - currentTime) / 1000 } diff --git a/packages/postDatedLambda/src/databaseClient.ts b/packages/postDatedLambda/src/databaseClient.ts index 640dc80bdd..2e636337e9 100644 --- a/packages/postDatedLambda/src/databaseClient.ts +++ b/packages/postDatedLambda/src/databaseClient.ts @@ -46,27 +46,30 @@ export async function getExistingRecordsByPrescriptionID( let lastEvaluatedKey let items: Array = [] + logger.info("Querying DynamoDB for existing prescription records", { + prescriptionID: normalizedPrescriptionID, + tableName, + indexName: pharmacyPrescriptionIndexName + }) + try { - do { + while (true) { if (lastEvaluatedKey) { query.ExclusiveStartKey = lastEvaluatedKey } - logger.info("Querying DynamoDB for existing prescription records", { - prescriptionID: normalizedPrescriptionID, - tableName, - indexName: pharmacyPrescriptionIndexName - }) - const result = await client.send(new QueryCommand(query)) if (result.Items) { const parsedItems = result.Items.map((item) => unmarshall(item) as PSUDataItem) - items = items.concat(parsedItems) + items.push(parsedItems) } lastEvaluatedKey = result.LastEvaluatedKey - } while (lastEvaluatedKey) + if (!lastEvaluatedKey) { + break + } + } logger.info("Retrieved existing prescription records from DynamoDB", { prescriptionID: normalizedPrescriptionID, From 78322dc6ae24d9df686a1f7bac3cf9e871d794df Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 5 Feb 2026 17:12:13 +0000 Subject: [PATCH 66/75] Fix typo --- packages/postDatedLambda/src/databaseClient.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/postDatedLambda/src/databaseClient.ts b/packages/postDatedLambda/src/databaseClient.ts index 2e636337e9..a07c89defe 100644 --- a/packages/postDatedLambda/src/databaseClient.ts +++ b/packages/postDatedLambda/src/databaseClient.ts @@ -61,8 +61,8 @@ export async function getExistingRecordsByPrescriptionID( const result = await client.send(new QueryCommand(query)) if (result.Items) { - const parsedItems = result.Items.map((item) => unmarshall(item) as PSUDataItem) - items.push(parsedItems) + const parsedItems: Array = result.Items.map((item) => unmarshall(item) as PSUDataItem) + items.push(...parsedItems) } lastEvaluatedKey = result.LastEvaluatedKey From 45782e30476d8b0e9242086802bebcc8d2f7231b Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 6 Feb 2026 10:25:34 +0000 Subject: [PATCH 67/75] Remove references to overriding post-dated logic --- SAMtemplates/functions/main.yaml | 2 -- .../docs/mature_prescription_check.md | 4 +--- .../postDatedLambda/tests/testBusinessLogic.test.ts | 12 ------------ 3 files changed, 1 insertion(+), 17 deletions(-) diff --git a/SAMtemplates/functions/main.yaml b/SAMtemplates/functions/main.yaml index e25cc55107..601f9f2c43 100644 --- a/SAMtemplates/functions/main.yaml +++ b/SAMtemplates/functions/main.yaml @@ -541,8 +541,6 @@ Resources: LOG_LEVEL: !Ref LogLevel NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL: !Ref NHSNotifyPrescriptionsSQSQueueUrl POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL: !Ref PostDatedNotificationsSQSQueueUrl - POST_DATED_OVERRIDE: false - POST_DATED_OVERRIDE_VALUE: false # By default, override never matures post dated prescriptions TABLE_NAME: !Ref PrescriptionStatusUpdatesTableName Events: ScheduleEvent: diff --git a/packages/postDatedLambda/docs/mature_prescription_check.md b/packages/postDatedLambda/docs/mature_prescription_check.md index a2ef90e748..4db4a2adb6 100644 --- a/packages/postDatedLambda/docs/mature_prescription_check.md +++ b/packages/postDatedLambda/docs/mature_prescription_check.md @@ -4,9 +4,7 @@ The business logic's `processMessage()` function accepts the SQS message for thi ```mermaid flowchart TD - A["Start `processMessage()`"] --> C{"POST_DATED_OVERRIDE?"} - C -- Yes --> D["Log override + return override value"] - C -- No --> E{"existingRecords empty?"} + A["Start `processMessage()`"] --> E{"existingRecords empty?"} E -- Yes --> F["Log error + return IGNORE"] E -- No --> G["Fetch most recently submitted NPPTS record"] G --> H{"PostDatedLastModifiedSetAt present?"} diff --git a/packages/postDatedLambda/tests/testBusinessLogic.test.ts b/packages/postDatedLambda/tests/testBusinessLogic.test.ts index d3b86f98df..820102daa5 100644 --- a/packages/postDatedLambda/tests/testBusinessLogic.test.ts +++ b/packages/postDatedLambda/tests/testBusinessLogic.test.ts @@ -142,18 +142,6 @@ describe("businessLogic", () => { }) describe("processMessage", () => { - it("should return the override value when override mode is enabled", async () => { - const {processMessage} = await loadBusinessLogic({ - POST_DATED_OVERRIDE: "true", - POST_DATED_OVERRIDE_VALUE: "immature" - }) - const logger = new Logger({serviceName: "post-dated-tests"}) - - const result = processMessage(logger, createMessage()) - - expect(result).toBe(PostDatedProcessingResult.IMMATURE) - }) - it("should ignore messages that have no existing records", async () => { const {processMessage} = await loadBusinessLogic() const logger = new Logger({serviceName: "post-dated-tests"}) From 64b2466029a28c0bd220c7c0585761c8d02f5d3d Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 6 Feb 2026 11:06:34 +0000 Subject: [PATCH 68/75] Update trivy ignore with braces expansion --- .trivyignore.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.trivyignore.yaml b/.trivyignore.yaml index 95b257a2f9..9b69d3157d 100644 --- a/.trivyignore.yaml +++ b/.trivyignore.yaml @@ -2,4 +2,8 @@ vulnerabilities: - id: CVE-2026-25128 statement: fast-xml-parser vulnerability accepted as risk - dependency of aws-sdk/client-dynamodb expired_at: 2026-03-01 - + - id: CVE-2026-25547 + paths: + - "package-lock.json" + statement: downstream dependency of @isaacs/brace-expansion + expired_at: 2026-06-01 From f03ed6170f48d132cdfd42830eba405ac3db25cf Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 6 Feb 2026 12:21:53 +0000 Subject: [PATCH 69/75] Fix indentation --- SAMtemplates/apis/main.yaml | 82 ++++++++--------- .../notificationsReportingLambda/README.md | 92 +++++++++++++++++++ 2 files changed, 133 insertions(+), 41 deletions(-) create mode 100644 packages/notificationsReportingLambda/README.md diff --git a/SAMtemplates/apis/main.yaml b/SAMtemplates/apis/main.yaml index 4b06ea284d..57e6c0a4e3 100644 --- a/SAMtemplates/apis/main.yaml +++ b/SAMtemplates/apis/main.yaml @@ -564,47 +564,47 @@ Resources: Timeout: "1" Count: 0 - NotificationsReportingResource: - Condition: ShouldDeployNotificationsReporting - Type: AWS::ApiGateway::Resource - Properties: - RestApiId: !Ref RestApiGateway - ParentId: !GetAtt RestApiGateway.RootResourceId - PathPart: notifications-reporting - - NotificationsReportingMethod: - Condition: ShouldDeployNotificationsReporting - Type: AWS::ApiGateway::Method - Properties: - RestApiId: !Ref RestApiGateway - ResourceId: !Ref NotificationsReportingResource - HttpMethod: GET - AuthorizationType: NONE - Integration: - Type: AWS_PROXY - Credentials: !GetAtt RestApiGatewayResources.Outputs.ApiGwRoleArn - IntegrationHttpMethod: POST - Uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${NotificationsReportingFunctionArn}/invocations - - NotificationsReportingHandle: - Condition: ShouldDeployNotificationsReporting - DependsOn: NotificationsReportingMethod - Type: "AWS::CloudFormation::WaitConditionHandle" - - NotificationsReportingWaitHandle: - Type: "AWS::CloudFormation::WaitConditionHandle" - - NotificationsReportingWaitCondition: - Type: "AWS::CloudFormation::WaitCondition" - Properties: - Handle: - !If [ - ShouldDeployNotificationsReporting, - !Ref NotificationsReportingHandle, - !Ref NotificationsReportingWaitHandle, - ] - Timeout: "1" - Count: 0 + NotificationsReportingResource: + Condition: ShouldDeployNotificationsReporting + Type: AWS::ApiGateway::Resource + Properties: + RestApiId: !Ref RestApiGateway + ParentId: !GetAtt RestApiGateway.RootResourceId + PathPart: notifications-reporting + + NotificationsReportingMethod: + Condition: ShouldDeployNotificationsReporting + Type: AWS::ApiGateway::Method + Properties: + RestApiId: !Ref RestApiGateway + ResourceId: !Ref NotificationsReportingResource + HttpMethod: GET + AuthorizationType: NONE + Integration: + Type: AWS_PROXY + Credentials: !GetAtt RestApiGatewayResources.Outputs.ApiGwRoleArn + IntegrationHttpMethod: POST + Uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${NotificationsReportingFunctionArn}/invocations + + NotificationsReportingHandle: + Condition: ShouldDeployNotificationsReporting + DependsOn: NotificationsReportingMethod + Type: "AWS::CloudFormation::WaitConditionHandle" + + NotificationsReportingWaitHandle: + Type: "AWS::CloudFormation::WaitConditionHandle" + + NotificationsReportingWaitCondition: + Type: "AWS::CloudFormation::WaitCondition" + Properties: + Handle: + !If [ + ShouldDeployNotificationsReporting, + !Ref NotificationsReportingHandle, + !Ref NotificationsReportingWaitHandle, + ] + Timeout: "1" + Count: 0 # ********************************************************************* # if you add a new endpoint, then change the name of this resource diff --git a/packages/notificationsReportingLambda/README.md b/packages/notificationsReportingLambda/README.md new file mode 100644 index 0000000000..be9d3480ff --- /dev/null +++ b/packages/notificationsReportingLambda/README.md @@ -0,0 +1,92 @@ +# Notifications reporting lambda + +This lambda exposes a lightweight, internal reporting endpoint for notification status records stored in the notifications DynamoDB table. + +## Overview + +- **API path**: `GET /notifications-reporting` +- **Purpose**: return notification status records for troubleshooting and test reporting. +- **Data source**: the notifications table (`PrescriptionNotificationStatesTableName`). +- **Deployment**: controlled by the `DeployNotificationsReporting` parameter. + +## Request + +Query parameters (at least one is required): + +- `prescriptionId` (string) +- `nhsNumber` (string) +- `odsCode` (string) + +Notes: + +- `odsCode` is upper‑cased before querying. +- If `nhsNumber` is provided, the lambda performs a **query** by partition key and applies additional filters. +- If only `prescriptionId` and/or `odsCode` are provided, the lambda performs a **scan** using those filters. + +### Example + +``` +GET /notifications-reporting?nhsNumber=9999999999&odsCode=A12345 +``` + +## Response + +Success (200): + +```json +{ + "count": 2, + "filters": { + "prescriptionId": null, + "odsCode": "A12345", + "nhsNumberProvided": true + }, + "notifications": [ + { + "NHSNumber": "9999999999", + "ODSCode": "A12345", + "RequestId": "req-1", + "PrescriptionID": "RX123", + "LastNotifiedPrescriptionStatus": "DELIVERED", + "LastNotificationRequestTimestamp": "2025-01-01T10:00:00.000Z", + "ExpiryTime": 1735735200, + "NotifyMessageReference": "ref-1", + "MessageStatus": "delivered", + "SupplierStatus": "delivered" + } + ] +} +``` + +Error responses: + +- **400** when no filters are provided. +- **500** for unexpected failures reading DynamoDB. + +## Data flow + +```mermaid +sequenceDiagram + autonumber + actor Client + participant APIGW as API Gateway + participant Lambda as NotificationsReporting Lambda + participant DynamoDB as Notifications Table + + Client->>APIGW: GET /notifications-reporting?filters + APIGW->>Lambda: Invoke (event) + Lambda->>Lambda: Normalize filters + alt No filters + Lambda-->>APIGW: 400 {message} + APIGW-->>Client: 400 + else Filters present + alt nhsNumber provided + Lambda->>DynamoDB: Query by NHSNumber + filters + else no nhsNumber + Lambda->>DynamoDB: Scan by filters + end + DynamoDB-->>Lambda: Items + Lambda-->>APIGW: 200 {count, filters, notifications} + APIGW-->>Client: 200 + end +``` From 01cd0b6527cf5f197e9592fa2260b14521e61d1f Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 6 Feb 2026 14:32:30 +0000 Subject: [PATCH 70/75] Use a switch instead of sequential if else --- packages/postDatedLambda/src/orchestration.ts | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/postDatedLambda/src/orchestration.ts b/packages/postDatedLambda/src/orchestration.ts index a1001864ae..f89c719675 100644 --- a/packages/postDatedLambda/src/orchestration.ts +++ b/packages/postDatedLambda/src/orchestration.ts @@ -31,14 +31,24 @@ export async function processMessages( for (const message of enrichedMessages) { try { const action = processMessage(logger, message) - if (action === PostDatedProcessingResult.MATURED) { - maturedPrescriptionUpdates.push(message) - } else if (action === PostDatedProcessingResult.IMMATURE) { - // Set visibility timeout to time until maturity, or default if calculation fails - message.visibilityTimeoutSeconds = computeTimeUntilMaturity(message) - immaturePrescriptionUpdates.push(message) - } else { - ignoredPrescriptionUpdates.push(message) + switch (action) { + case PostDatedProcessingResult.MATURED: + maturedPrescriptionUpdates.push(message) + break + case PostDatedProcessingResult.IMMATURE: + // Set visibility timeout to time until maturity, or default if calculation fails + message.visibilityTimeoutSeconds = computeTimeUntilMaturity(message) + immaturePrescriptionUpdates.push(message) + break + case PostDatedProcessingResult.IGNORE: + ignoredPrescriptionUpdates.push(message) + break + default: + logger.error("Unexpected processing result", { + messageId: message.MessageId, + action + }) + ignoredPrescriptionUpdates.push(message) } } catch (error) { logger.error("Error processing message", { From ebe85a187d39a17a5b427733ac78cfb28b789a7a Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 6 Feb 2026 16:53:49 +0000 Subject: [PATCH 71/75] Refactor after some discussion --- packages/postDatedLambda/src/businessLogic.ts | 49 ++-- .../postDatedLambda/src/databaseClient.ts | 94 +++---- packages/postDatedLambda/src/orchestration.ts | 99 +++---- packages/postDatedLambda/src/sqs.ts | 251 ++++++------------ packages/postDatedLambda/src/types.ts | 28 +- .../tests/testBusinessLogic.test.ts | 126 ++++----- .../tests/testDatabaseClient.test.ts | 139 ++++------ .../tests/testOrchestration.test.ts | 159 +++++------ .../postDatedLambda/tests/testSqs.test.ts | 240 ++++------------- 9 files changed, 419 insertions(+), 766 deletions(-) diff --git a/packages/postDatedLambda/src/businessLogic.ts b/packages/postDatedLambda/src/businessLogic.ts index 520d676456..2367d87e69 100644 --- a/packages/postDatedLambda/src/businessLogic.ts +++ b/packages/postDatedLambda/src/businessLogic.ts @@ -2,8 +2,10 @@ import {Logger} from "@aws-lambda-powertools/logger" import {PSUDataItem} from "@psu-common/commonTypes" -import {PostDatedSQSMessageWithExistingRecords, PostDatedProcessingResult} from "./types" +import {PostDatedSQSMessageWithRecentDataItem, PostDatedProcessingResult} from "./types" +// This is only used in the dynamo handler, but since it's part of the core logic of determining maturity, +// it felt wrong to put it in the database client file export function getMostRecentRecord( existingRecords: Array ): PSUDataItem { @@ -26,63 +28,54 @@ export function getMostRecentRecord( * @param message - The SQS message containing post-dated prescription data and existing records * @returns Promise - true if the post-dated prescription has matured, and false otherwise */ -export function processMessage( +export function determineAction( logger: Logger, - message: PostDatedSQSMessageWithExistingRecords -): string { + message: PostDatedSQSMessageWithRecentDataItem +): PostDatedProcessingResult { logger.info("Processing post-dated prescription message", { messageId: message.MessageId, prescriptionData: message.prescriptionData, - existingRecords: message.existingRecords + mostRecentRecord: message.mostRecentRecord }) // The existingRecords array contains all records from the DynamoDB table // that match this prescription's PrescriptionID - // NOTE: It is technically possible for the array to be empty if no existing records are found + // NOTE: It is technically possible for this to be undefined if no existing records are found // This SHOULD never happen in practice, but catch it anyway - if (message.existingRecords.length === 0) { + if (!message.mostRecentRecord) { logger.error("No existing records found for post-dated prescription, cannot process. Ignoring this message", { badMessage: message }) // throw new Error("No existing records found for post-dated prescription") // maybe? - return PostDatedProcessingResult.IGNORE + return PostDatedProcessingResult.REMOVE_FROM_PD_QUEUE } - // We only care about the most recent submission for this prescription - // If PostDatedLastModifiedSetAt IS set, it is the timestamp we received the submission - // If it is NOT set, then LastModified is the timestamp we received the submission - const mostRecentRecord = getMostRecentRecord(message.existingRecords) - - logger.info("Most recent NPPTS record for post-dated processing", { - mostRecentRecord - }) - // Is it post-dated? - if (!mostRecentRecord.PostDatedLastModifiedSetAt) { + if (!message.mostRecentRecord.PostDatedLastModifiedSetAt) { logger.info( "Most recent record is not marked as post-dated, and will have been processed " + "by the standard logic already. Marking as to be ignored by the post-dated notifications lambda." ) - return PostDatedProcessingResult.IGNORE + return PostDatedProcessingResult.REMOVE_FROM_PD_QUEUE } // Is it still RTC? - const mostRecentStatus = mostRecentRecord.Status.toLowerCase() + const mostRecentStatus = message.mostRecentRecord.Status.toLowerCase() const notifiableStatuses: Array = ["ready to collect", "ready to collect - partial"] if (!notifiableStatuses.includes(mostRecentStatus)) { logger.info("Most recent status in the NPPTS data store is not a notifiable status, so will be ignored", { mostRecentStatus: mostRecentStatus }) - return PostDatedProcessingResult.IGNORE + return PostDatedProcessingResult.REMOVE_FROM_PD_QUEUE } // We know that we have a recent, post-dated prescription status update. // Check if its LastModified time is in the future. // Stored as YYYY-MM-DDTHH:mm:ss.sssZ - const mostRecentLastModified = new Date(mostRecentRecord.LastModified) + const mostRecentLastModified = new Date(message.mostRecentRecord.LastModified) const currentTime = new Date() logger.info("Most recent NPPTS record is Post-dated. Checking if the post-dated prescription has matured", { LastModified: mostRecentLastModified.toISOString(), @@ -94,7 +87,7 @@ export function processMessage( lastModified: mostRecentLastModified.toISOString(), currentTime: currentTime.toISOString() }) - return PostDatedProcessingResult.IMMATURE + return PostDatedProcessingResult.REPROCESS } logger.info("Post-dated prescription has matured (LastModified is in the past)", { @@ -102,17 +95,19 @@ export function processMessage( currentTime: currentTime.toISOString() }) - return PostDatedProcessingResult.MATURED + return PostDatedProcessingResult.FORWARD_TO_NOTIFICATIONS } /** * returns time in seconds until maturity, or undefined if cannot be determined */ export function computeTimeUntilMaturity( - data: PostDatedSQSMessageWithExistingRecords + data: PostDatedSQSMessageWithRecentDataItem ): number | undefined { - const prescriptionRecord = getMostRecentRecord(data.existingRecords) - if (!prescriptionRecord.PostDatedLastModifiedSetAt) { + const prescriptionRecord = data.mostRecentRecord + + // catches both no existing record, and one that's not post-dated. + if (!prescriptionRecord?.PostDatedLastModifiedSetAt) { return undefined } diff --git a/packages/postDatedLambda/src/databaseClient.ts b/packages/postDatedLambda/src/databaseClient.ts index a07c89defe..030976ffe9 100644 --- a/packages/postDatedLambda/src/databaseClient.ts +++ b/packages/postDatedLambda/src/databaseClient.ts @@ -3,11 +3,8 @@ import {DynamoDBClient, QueryCommand, QueryCommandInput} from "@aws-sdk/client-d import {unmarshall} from "@aws-sdk/util-dynamodb" import {PSUDataItem, NotifyDataItem} from "@psu-common/commonTypes" -import { - PostDatedPrescriptionWithExistingRecords, - PostDatedSQSMessage, - PostDatedSQSMessageWithExistingRecords -} from "./types" +import {PostDatedSQSMessage, PostDatedSQSMessageWithRecentDataItem} from "./types" +import {getMostRecentRecord} from "./businessLogic" const client = new DynamoDBClient() const tableName = process.env.TABLE_NAME ?? "PrescriptionStatusUpdates" @@ -27,7 +24,7 @@ type PrescriptionLookupRequest = { * @param logger - The AWS Lambda Powertools logger instance * @returns Array of PSUDataItem records matching the prescription ID. Sorted by LastModified descending. */ -export async function getExistingRecordsByPrescriptionID( +export async function getRecentDataItemByPrescriptionID( prescriptionID: string, logger: Logger ): Promise> { @@ -86,41 +83,6 @@ export async function getExistingRecordsByPrescriptionID( } } -/** - * For each post-dated prescription, fetch any existing records from DynamoDB - * that have a matching prescription ID. - * - * @param postDatedItems - Array of post-dated prescription data items - * @param logger - The AWS Lambda Powertools logger instance - * @returns Array of objects containing both the post-dated data and existing records. - * Existing records are sorted by LastModified descending. - */ -export async function fetchExistingRecordsForPrescriptions( - postDatedItems: Array, - logger: Logger -): Promise> { - logger.info("Fetching existing records for post-dated prescriptions", { - prescriptionCount: postDatedItems.length, - prescriptionIDs: postDatedItems.map((p) => p.PrescriptionID) - }) - const lookupRequests = buildLookupRequests(postDatedItems) - const existingRecordsMap = await buildExistingRecordsMap(lookupRequests, logger) - - // Map each post-dated item to its corresponding existing records - const results: Array = postDatedItems.map( - (postDatedData) => { - const lookupKey = postDatedData.PrescriptionID.toUpperCase() // Case insensitive - const existingRecords = existingRecordsMap.get(lookupKey) ?? [] - - return { - postDatedData, - existingRecords - } - }) - - return results -} - function buildLookupRequests(postDatedItems: Array): Array { // Run though a map to deduplicate lookups const lookups = new Map() @@ -138,29 +100,29 @@ function buildLookupRequests(postDatedItems: Array): Array, logger: Logger ): Promise>> { - const existingRecordsMap = new Map>() + const RecentDataItemMap = new Map>() // await all lookups in parallel await Promise.all( lookupRequests.map(async ({lookupKey, prescriptionID}) => { try { - const records = await getExistingRecordsByPrescriptionID(prescriptionID, logger) - existingRecordsMap.set(lookupKey, records) + const records = await getRecentDataItemByPrescriptionID(prescriptionID, logger) + RecentDataItemMap.set(lookupKey, records) } catch (error) { logger.error("Failed to fetch existing records for prescription", { prescriptionID, error }) - existingRecordsMap.set(lookupKey, []) // Continue processing other prescriptions even when one fails + RecentDataItemMap.set(lookupKey, []) // Continue processing other prescriptions even when one fails } }) ) - return existingRecordsMap + return RecentDataItemMap } /** @@ -169,21 +131,35 @@ async function buildExistingRecordsMap( * * @param messages - Array of SQS messages to enrich * @param logger - Logger instance - * @returns Array of enriched messages with existing records + * @returns Array of messages enriched with a mostRecentRecord field containing the most + * recent matching record from DynamoDB, if any. If no matching records are found, + * mostRecentRecord will be undefined. */ -export async function enrichMessagesWithExistingRecords( +export async function enrichMessagesWithMostRecentDataItem( messages: Array, logger: Logger -): Promise> { - const postDatedItems = messages.map((m) => m.prescriptionData) - - const prescriptionsWithRecords = await fetchExistingRecordsForPrescriptions(postDatedItems, logger) - // prescription IDs are unique, even across pharmacies. so we can build a map keyed by prescription ID just fine. - const recordsMap = new Map(prescriptionsWithRecords.map((p) => [p.postDatedData.PrescriptionID, p.existingRecords])) - const enrichedMessages: Array = messages.map((message) => ({ - ...message, - existingRecords: recordsMap.get(message.prescriptionData.PrescriptionID) ?? [] - })) +): Promise> { + if (messages.length === 0) { + return [] + } + + const postDatedItems = messages.map((message) => message.prescriptionData) + + // There may be repeated prescription IDs in the messages. + // Use a map to dedupe the lookups to dynamo, submit them all in async, then map the results back to the messages. + const lookupRequests = buildLookupRequests(postDatedItems) + const recentDataItemMap = await buildRecentDataItemMap(lookupRequests, logger) + + const enrichedMessages: Array = messages.map((message) => { + const lookupKey = message.prescriptionData.PrescriptionID.toUpperCase() + const existingRecords = recentDataItemMap.get(lookupKey) ?? [] + const mostRecentRecord = existingRecords.length > 0 ? getMostRecentRecord(existingRecords) : undefined + + return { + ...message, + mostRecentRecord + } + }) return enrichedMessages } diff --git a/packages/postDatedLambda/src/orchestration.ts b/packages/postDatedLambda/src/orchestration.ts index f89c719675..d7c205f099 100644 --- a/packages/postDatedLambda/src/orchestration.ts +++ b/packages/postDatedLambda/src/orchestration.ts @@ -1,13 +1,36 @@ import {Logger} from "@aws-lambda-powertools/logger" -import {computeTimeUntilMaturity, processMessage} from "./businessLogic" -import {enrichMessagesWithExistingRecords} from "./databaseClient" -import {receivePostDatedSQSMessages, reportQueueStatus, handleProcessedMessages} from "./sqs" -import {BatchProcessingResult, PostDatedProcessingResult, PostDatedSQSMessage} from "./types" +import {computeTimeUntilMaturity, determineAction} from "./businessLogic" +import {enrichMessagesWithMostRecentDataItem} from "./databaseClient" +import { + receivePostDatedSQSMessages, + reportQueueStatus, + forwardSQSMessageToNotificationQueue, + removeSQSMessage, + returnMessageToQueue +} from "./sqs" +import {PostDatedProcessingResult, PostDatedSQSMessage, PostDatedSQSMessageWithRecentDataItem} from "./types" export const MAX_QUEUE_RUNTIME = 14 * 60 * 1000 // 14 minutes, to avoid Lambda timeout issues (timeout is 15 minutes) const MIN_RECEIVED_THRESHOLD = 3 // If fewer than this number of messages are received, consider the queue empty +async function handleMaturedPrescription( + logger: Logger, + message: PostDatedSQSMessageWithRecentDataItem +): Promise { + await forwardSQSMessageToNotificationQueue(logger, message) + await removeSQSMessage(logger, message) +} + +async function handleImmaturePrescription( + logger: Logger, + message: PostDatedSQSMessageWithRecentDataItem +): Promise { + // Set visibility timeout to time until maturity, or default if calculation fails + message.visibilityTimeoutSeconds = computeTimeUntilMaturity(message) + await returnMessageToQueue(logger, message) +} + /** * Process a batch of SQS messages. Returns arrays of matured and immature prescription updates. * Messages are enriched with existing records from DynamoDB and processed individually. @@ -20,53 +43,34 @@ const MIN_RECEIVED_THRESHOLD = 3 // If fewer than this number of messages are re export async function processMessages( messages: Array, logger: Logger -): Promise { - // Enrich messages with existing records from DynamoDB - const enrichedMessages = await enrichMessagesWithExistingRecords(messages, logger) - - const maturedPrescriptionUpdates: Array = [] - const immaturePrescriptionUpdates: Array = [] - const ignoredPrescriptionUpdates: Array = [] +): Promise { + const enrichedMessages = await enrichMessagesWithMostRecentDataItem(messages, logger) + // Build an array of promises to await in parallel + const promises = [] for (const message of enrichedMessages) { - try { - const action = processMessage(logger, message) - switch (action) { - case PostDatedProcessingResult.MATURED: - maturedPrescriptionUpdates.push(message) - break - case PostDatedProcessingResult.IMMATURE: - // Set visibility timeout to time until maturity, or default if calculation fails - message.visibilityTimeoutSeconds = computeTimeUntilMaturity(message) - immaturePrescriptionUpdates.push(message) - break - case PostDatedProcessingResult.IGNORE: - ignoredPrescriptionUpdates.push(message) - break - default: - logger.error("Unexpected processing result", { - messageId: message.MessageId, - action - }) - ignoredPrescriptionUpdates.push(message) - } - } catch (error) { - logger.error("Error processing message", { - messageId: message.MessageId, - error - }) - immaturePrescriptionUpdates.push(message) + const action = determineAction(logger, message) + + switch (action) { + case PostDatedProcessingResult.REPROCESS: + promises.push(handleImmaturePrescription(logger, message)) + break + case PostDatedProcessingResult.FORWARD_TO_NOTIFICATIONS: + promises.push(handleMaturedPrescription(logger, message)) + break + case PostDatedProcessingResult.REMOVE_FROM_PD_QUEUE: + promises.push(removeSQSMessage(logger, message)) + break + default: + logger.error("Unexpected processing result", { + messageId: message.MessageId, + action + }) + throw new Error(`Unexpected processing result: ${action}`) } } - logger.info("Batch processing complete", { - totalMessages: messages.length, - maturedPrescriptionUpdatesCount: maturedPrescriptionUpdates.length, - immaturePrescriptionUpdatesCount: immaturePrescriptionUpdates.length, - ignoredPrescriptionUpdatesCount: ignoredPrescriptionUpdates.length - }) - - return {maturedPrescriptionUpdates, immaturePrescriptionUpdates, ignoredPrescriptionUpdates} + await Promise.all(promises) } /** @@ -101,7 +105,6 @@ export async function processPostDatedQueue(logger: Logger): Promise { } // Process messages for this batch - const result = await processMessages(messages, logger) - await handleProcessedMessages(result, logger) + await processMessages(messages, logger) } } diff --git a/packages/postDatedLambda/src/sqs.ts b/packages/postDatedLambda/src/sqs.ts index acb0524396..92c03631e2 100644 --- a/packages/postDatedLambda/src/sqs.ts +++ b/packages/postDatedLambda/src/sqs.ts @@ -12,7 +12,7 @@ import {Logger} from "@aws-lambda-powertools/logger" import {NotifyDataItem} from "@psu-common/commonTypes" -import {BatchProcessingResult, PostDatedSQSMessage} from "./types" +import {PostDatedSQSMessage} from "./types" const sqs = new SQSClient({region: process.env.AWS_REGION}) @@ -25,99 +25,77 @@ const MAXIMUM_VISIBILITY_TIMEOUT_SECONDS = 10 * 60 * 60 // 10 hours // for the sake of temporarily supporting post-dated messages. // - Jim Wild, Jan. 2026 -function chunkArray(arr: Array, size: number): Array> { - const chunks: Array> = [] - for (let i = 0; i < arr.length; i += size) { - chunks.push(arr.slice(i, i + size)) - } - return chunks -} - -function buildNotificationBatchEntries( - messages: Array, +function buildNotificationBatchEntry( + message: PostDatedSQSMessage, logger: Logger -): Array { - return messages.map((message, idx) => { - const {prescriptionData} = message - - // If we get something with no deduplication ID, then something upstream is wrong and we should fail out - if (!message.Attributes?.MessageDeduplicationId) { - logger.error("Post-dated SQS message is missing MessageDeduplicationId attribute", { - messageId: message.MessageId, messageContents: message - }) - throw new Error("Missing MessageDeduplicationId in SQS message attributes") - } - // Same for group ID - if (!message.Attributes?.MessageGroupId) { - logger.error("Post-dated SQS message is missing MessageGroupId attribute", { - messageId: message.MessageId, messageContents: message - }) - throw new Error("Missing MessageGroupId in SQS message attributes") - } +): SendMessageBatchRequestEntry { + const {prescriptionData} = message - return { - Id: idx.toString(), - MessageBody: JSON.stringify(prescriptionData), - MessageDeduplicationId: message.Attributes?.MessageDeduplicationId, - MessageGroupId: message.Attributes?.MessageGroupId, - MessageAttributes: { - RequestId: { - DataType: "String", - StringValue: message.Attributes?.MessageGroupId - } + // If we get something with no deduplication ID, then something upstream is wrong and we should fail out + if (!message.Attributes?.MessageDeduplicationId) { + logger.error("Post-dated SQS message is missing MessageDeduplicationId attribute", { + messageId: message.MessageId, messageContents: message + }) + throw new Error("Missing MessageDeduplicationId in SQS message attributes") + } + // Same for group ID + if (!message.Attributes?.MessageGroupId) { + logger.error("Post-dated SQS message is missing MessageGroupId attribute", { + messageId: message.MessageId, messageContents: message + }) + throw new Error("Missing MessageGroupId in SQS message attributes") + } + + return { + Id: message.MessageId!, + MessageBody: JSON.stringify(prescriptionData), + MessageDeduplicationId: message.Attributes?.MessageDeduplicationId, + MessageGroupId: message.Attributes?.MessageGroupId, + MessageAttributes: { + RequestId: { + DataType: "String", + StringValue: message.Attributes?.MessageGroupId } } - }) + } } -async function sendEntriesToQueue( - entries: Array, +/** Send an entry to an SQS queue. Returns the message ID if successful, throw otherwise */ +async function sendEntryToQueue( + entry: SendMessageBatchRequestEntry, queueUrl: string, logger: Logger -): Promise> { - if (entries.length === 0) { - return [] - } - - const batches = chunkArray(entries, 10) +): Promise { - const batchPromises = batches.map(async (batch) => { - try { - logger.info( - "Pushing a batch of notification requests to SQS", - { - batchLength: batch.length, - deduplicationIds: batch.map((entry) => entry.MessageDeduplicationId), - queueUrl - } - ) + logger.info( + "Pushing a notification request to SQS", + { + deduplicationId: entry.MessageDeduplicationId, + queueUrl + } + ) - const command = new SendMessageBatchCommand({ - QueueUrl: queueUrl, - Entries: batch - }) - const result = await sqs.send(command) - - let successfulIds: Array = [] - if (result.Successful?.length) { - logger.info("Successfully sent a batch of prescriptions to the SQS", {result, queueUrl}) - successfulIds = result.Successful - .map((entry) => entry.MessageId) - .filter((msgId): msgId is string => msgId !== undefined) - } + const command = new SendMessageBatchCommand({ + QueueUrl: queueUrl, + Entries: [entry] + }) + const result = await sqs.send(command) - if (result.Failed?.length) { - throw new Error(`Failed to send a batch of prescriptions to the SQS ${queueUrl}`) - } + if (result.Failed && result.Failed.length > 0) { + logger.error("Failed to send message to notification queue", {failed: result.Failed}) + throw new Error(`Failed to send message to notification queue: ${JSON.stringify(result.Failed)}`) + } - return successfulIds - } catch (error) { - logger.error("Failed to send a batch of prescriptions to the SQS", {error, queueUrl}) - throw error - } - }) + // It may be that the send was successful but we didn't get a message ID back, + // which shouldn't happen but if it does we should catch it and log an error rather than returning undefined + const sentMessageId = result.Successful?.[0].MessageId + if (!sentMessageId) { + logger.error("No message ID returned from SQS for successful send", {result}) + throw new Error("No message ID returned from SQS for successful send") + } - return Promise.all(batchPromises).then((results) => results.flat()) + logger.info("Successfully sent message to notification queue", {sentMessageId}) + return sentMessageId } /** @@ -242,57 +220,32 @@ export async function receivePostDatedSQSMessages(logger: Logger): Promise -): Promise> { - if (messages.length === 0) { - // exit early so we don't send a SendMessageBatch with no entries - logger.info("No messages to forward to notifications queue") - return [] - } - + message: PostDatedSQSMessage +): Promise { const queueUrl = getNotificationQueueUrl(logger) - const entries = buildNotificationBatchEntries(messages, logger) - - const sentMessageIds = await sendEntriesToQueue(entries, queueUrl, logger) + const entry = buildNotificationBatchEntry(message, logger) - logger.info("Forwarded matured post-dated messages to notifications queue", { - queueUrl, - forwardedCount: sentMessageIds.length, - sqsMessageIds: sentMessageIds - }) - - return sentMessageIds + return sendEntryToQueue(entry, queueUrl, logger) } /** - * Delete successfully processed messages from the SQS queue. + * Delete successfully processed message from the SQS queue. * * @param logger - The logging object - * @param messages - The messages that were successfully processed and should be deleted + * @param message - The message that should be deleted */ -export async function removeSQSMessages( +export async function removeSQSMessage( logger: Logger, - messages: Array + message: Message ): Promise { - if (messages.length === 0) { - // exit early so we don't send a DeleteMessageBatch with no entries - logger.info("No messages to delete") - return - } - const sqsUrl = getPostDatedQueueUrl(logger) - const entries = messages.map((m) => ({ - Id: m.MessageId!, - ReceiptHandle: m.ReceiptHandle! - })) - - logger.info("Deleting messages from SQS", { - numberOfMessages: entries.length, - messageIds: entries.map((e) => e.Id) - }) + const entries = [{ + Id: message.MessageId!, + ReceiptHandle: message.ReceiptHandle! + }] const deleteCmd = new DeleteMessageBatchCommand({ QueueUrl: sqsUrl, @@ -320,32 +273,26 @@ export async function removeSQSMessages( * @param logger - The logging object * @param messages - The messages that failed processing and should be returned to the queue */ -export async function returnMessagesToQueue( +export async function returnMessageToQueue( logger: Logger, - messages: Array + message: PostDatedSQSMessage ): Promise { - if (messages.length === 0) { - // exit early so we don't send a ChangeMessageVisibilityBatch with no entries - logger.info("No messages to return to queue") - return - } - const sqsUrl = getPostDatedQueueUrl(logger) - const entries = messages.map((m) => ({ - Id: m.MessageId!, - ReceiptHandle: m.ReceiptHandle!, - VisibilityTimeout: Math.min( - m.visibilityTimeoutSeconds || DEFAULT_VISIBILITY_TIMEOUT_SECONDS, - MAXIMUM_VISIBILITY_TIMEOUT_SECONDS) // Cap at the max - })) + const timeout = Math.max(0, Math.min( // greater than 0 + message.visibilityTimeoutSeconds || DEFAULT_VISIBILITY_TIMEOUT_SECONDS, // fallback + MAXIMUM_VISIBILITY_TIMEOUT_SECONDS // limit + )) + + const entries = [{ + Id: message.MessageId!, + ReceiptHandle: message.ReceiptHandle!, + VisibilityTimeout: timeout + }] logger.info( - `Returning messages to queue with timeouts`, - { - numberOfMessages: entries.length, - idAndTimeouts: entries.map((e) => ({id: e.Id, visibilityTimeout: e.VisibilityTimeout})) - } + `Returning message to queue with timeouts`, + {sqsMessage: message, visibilityTimeout: timeout} ) const changeVisibilityCmd = new ChangeMessageVisibilityBatchCommand({ @@ -369,29 +316,3 @@ export async function returnMessagesToQueue( logger.error(message, {error}) } } - -/** - * Handle the results of message processing: - * - Delete matured messages from the queue - * - Return immature messages to the queue with a visibility timeout - * Does not alter the input result object, only performs side effects. - * - * @param result - The batch processing result - * @param logger - The logging object - */ -export async function handleProcessedMessages( - result: BatchProcessingResult, - logger: Logger -): Promise { - const {maturedPrescriptionUpdates, immaturePrescriptionUpdates, ignoredPrescriptionUpdates} = result - - // Move matured messages to the notification queue and remove them from the post-dated queue - await forwardSQSMessagesToNotificationQueue(logger, maturedPrescriptionUpdates) - await removeSQSMessages(logger, maturedPrescriptionUpdates) - - // Return failed messages to the queue - await returnMessagesToQueue(logger, immaturePrescriptionUpdates) - - // Remove ignored messages from the queue, so they are not reprocessed - await removeSQSMessages(logger, ignoredPrescriptionUpdates) -} diff --git a/packages/postDatedLambda/src/types.ts b/packages/postDatedLambda/src/types.ts index 63fca0ae55..46d5889bad 100644 --- a/packages/postDatedLambda/src/types.ts +++ b/packages/postDatedLambda/src/types.ts @@ -14,34 +14,24 @@ export interface PostDatedSQSMessage extends Message { * Combines post-dated prescription data from SQS with any existing * records from the PrescriptionStatusUpdates DynamoDB table. */ -export interface PostDatedPrescriptionWithExistingRecords { +export interface PostDatedPrescriptionWithRecentDataItem { /** The post-dated prescription data from the SQS message */ postDatedData: NotifyDataItem - /** Existing records from DynamoDB that match the prescription ID */ - existingRecords: Array + /** The most recently submitted PSU data item for this prescription ID */ + mostRecentRecord?: PSUDataItem } /** * Extended SQS message interface that includes existing records from DynamoDB. * Used during processing to have access to both the SQS message and related database records. */ -export interface PostDatedSQSMessageWithExistingRecords extends PostDatedSQSMessage { - /** Existing records from DynamoDB that match the prescription ID */ - existingRecords: Array +export interface PostDatedSQSMessageWithRecentDataItem extends PostDatedSQSMessage { + /** The most recently submitted PSU data item for this prescription ID */ + mostRecentRecord?: PSUDataItem } -// Enum of strings, "matured", "immature", "ignore" export enum PostDatedProcessingResult { - MATURED = "matured", - IMMATURE = "immature", - IGNORE = "ignore" -} - -/** - * Result of processing a batch of messages. - */ -export interface BatchProcessingResult { - maturedPrescriptionUpdates: Array - immaturePrescriptionUpdates: Array - ignoredPrescriptionUpdates: Array + FORWARD_TO_NOTIFICATIONS = "forward_to_notifications", + REPROCESS = "reprocess", + REMOVE_FROM_PD_QUEUE = "remove_from_pd_queue" } diff --git a/packages/postDatedLambda/tests/testBusinessLogic.test.ts b/packages/postDatedLambda/tests/testBusinessLogic.test.ts index 820102daa5..095a1593ef 100644 --- a/packages/postDatedLambda/tests/testBusinessLogic.test.ts +++ b/packages/postDatedLambda/tests/testBusinessLogic.test.ts @@ -10,7 +10,7 @@ import {Logger} from "@aws-lambda-powertools/logger" import {PSUDataItem} from "@psu-common/commonTypes" -import {PostDatedProcessingResult, PostDatedSQSMessageWithExistingRecords} from "../src/types" +import {PostDatedProcessingResult, PostDatedSQSMessageWithRecentDataItem} from "../src/types" import {createMockPostModifiedDataItem} from "./testUtils" type BusinessLogicModule = typeof import("../src/businessLogic") @@ -50,10 +50,10 @@ function createPSURecord(overrides: Partial = {}): PSUDataItem { } function createMessage( - overrides: Partial = {} -): PostDatedSQSMessageWithExistingRecords { + overrides: Partial = {} +): PostDatedSQSMessageWithRecentDataItem { const prescData = createMockPostModifiedDataItem({}) - const baseMessage: PostDatedSQSMessageWithExistingRecords = { + const baseMessage: PostDatedSQSMessageWithRecentDataItem = { MessageId: "msg-123", ReceiptHandle: "receipt-123", Body: JSON.stringify(prescData), @@ -62,14 +62,16 @@ function createMessage( }, prescriptionData: prescData, // In theory, this should contain the record corresponding to prescData, but for testing purposes it's fine - existingRecords: [createPSURecord()] + mostRecentRecord: createPSURecord() } return { ...baseMessage, ...overrides, prescriptionData: overrides.prescriptionData ?? baseMessage.prescriptionData, - existingRecords: overrides.existingRecords ?? baseMessage.existingRecords + mostRecentRecord: Object.hasOwn(overrides, "mostRecentRecord") + ? overrides.mostRecentRecord + : baseMessage.mostRecentRecord } } @@ -141,116 +143,100 @@ describe("businessLogic", () => { }) }) - describe("processMessage", () => { - it("should ignore messages that have no existing records", async () => { - const {processMessage} = await loadBusinessLogic() + describe("determineAction", () => { + it("should remove messages that have no existing records", async () => { + const {determineAction} = await loadBusinessLogic() const logger = new Logger({serviceName: "post-dated-tests"}) - const message = createMessage({existingRecords: []}) + const message = createMessage({mostRecentRecord: undefined}) - const result = processMessage(logger, message) + const result = determineAction(logger, message) - expect(result).toBe(PostDatedProcessingResult.IGNORE) + expect(result).toBe(PostDatedProcessingResult.REMOVE_FROM_PD_QUEUE) }) - it("should ignore messages when the most recent record is not post-dated", async () => { - const {processMessage} = await loadBusinessLogic() + it("should remove messages when the most recent record is not post-dated", async () => { + const {determineAction} = await loadBusinessLogic() const logger = new Logger({serviceName: "post-dated-tests"}) const message = createMessage({ - existingRecords: [ - createPSURecord({ - LineItemID: "line-no-post-date", - PostDatedLastModifiedSetAt: undefined - }) - ] + mostRecentRecord: createPSURecord({ + LineItemID: "line-no-post-date", + PostDatedLastModifiedSetAt: undefined + }) }) - const result = processMessage(logger, message) + const result = determineAction(logger, message) - expect(result).toBe(PostDatedProcessingResult.IGNORE) + expect(result).toBe(PostDatedProcessingResult.REMOVE_FROM_PD_QUEUE) }) - it("should ignore messages when the status is not notifiable", async () => { - const {processMessage} = await loadBusinessLogic() + it("should remove messages when the status is not notifiable", async () => { + const {determineAction} = await loadBusinessLogic() const logger = new Logger({serviceName: "post-dated-tests"}) const message = createMessage({ - existingRecords: [ - createPSURecord({ - Status: "dispensed", - LineItemID: "line-not-notifiable" - }) - ] + mostRecentRecord: createPSURecord({ + Status: "dispensed", + LineItemID: "line-not-notifiable" + }) }) - const result = processMessage(logger, message) + const result = determineAction(logger, message) - expect(result).toBe(PostDatedProcessingResult.IGNORE) + expect(result).toBe(PostDatedProcessingResult.REMOVE_FROM_PD_QUEUE) }) it("should classify a message as immature when LastModified is in the future", async () => { jest.useFakeTimers() jest.setSystemTime(new Date("2026-01-01T12:00:00.000Z")) - const {processMessage} = await loadBusinessLogic() + const {determineAction} = await loadBusinessLogic() const logger = new Logger({serviceName: "post-dated-tests"}) const message = createMessage({ - existingRecords: [ - createPSURecord({ - LastModified: "2026-01-02T12:00:00.000Z", - PostDatedLastModifiedSetAt: "2026-01-02T12:00:00.000Z", - LineItemID: "line-future" - }) - ] + mostRecentRecord: createPSURecord({ + LastModified: "2026-01-02T12:00:00.000Z", + PostDatedLastModifiedSetAt: "2026-01-02T12:00:00.000Z", + LineItemID: "line-future" + }) }) - const result = processMessage(logger, message) + const result = determineAction(logger, message) - expect(result).toBe(PostDatedProcessingResult.IMMATURE) + expect(result).toBe(PostDatedProcessingResult.REPROCESS) }) it("should classify a message as matured when LastModified is in the past", async () => { jest.useFakeTimers() jest.setSystemTime(new Date("2026-01-03T12:00:00.000Z")) - const {processMessage} = await loadBusinessLogic() + const {determineAction} = await loadBusinessLogic() const logger = new Logger({serviceName: "post-dated-tests"}) const message = createMessage({ - existingRecords: [ - createPSURecord({ - LastModified: "2026-01-02T12:00:00.000Z", - PostDatedLastModifiedSetAt: "2026-01-02T12:00:00.000Z", - LineItemID: "line-past" - }) - ] + mostRecentRecord: createPSURecord({ + LastModified: "2026-01-02T12:00:00.000Z", + PostDatedLastModifiedSetAt: "2026-01-02T12:00:00.000Z", + LineItemID: "line-past" + }) }) - const result = processMessage(logger, message) + const result = determineAction(logger, message) - expect(result).toBe(PostDatedProcessingResult.MATURED) + expect(result).toBe(PostDatedProcessingResult.FORWARD_TO_NOTIFICATIONS) }) - it("should use the most recent record when determining maturity", async () => { + it("should use the provided most recent record when determining maturity", async () => { jest.useFakeTimers() jest.setSystemTime(new Date("2026-01-05T12:00:00.000Z")) - const {processMessage} = await loadBusinessLogic() + const {determineAction} = await loadBusinessLogic() const logger = new Logger({serviceName: "post-dated-tests"}) const message = createMessage({ - existingRecords: [ - createPSURecord({ - LineItemID: "line-old", - LastModified: "2026-01-01T12:00:00.000Z", - PostDatedLastModifiedSetAt: "2026-01-01T12:00:00.000Z", - Status: "ready to collect - partial" - }), - createPSURecord({ - LineItemID: "line-new", - LastModified: "2026-01-06T12:00:00.000Z", - PostDatedLastModifiedSetAt: "2026-01-06T12:00:00.000Z", - Status: "ready to collect" - }) - ] + mostRecentRecord: createPSURecord({ + LineItemID: "line-new", + LastModified: "2026-01-06T12:00:00.000Z", + PostDatedLastModifiedSetAt: "2026-01-06T12:00:00.000Z", + Status: "ready to collect" + }) }) - const result = processMessage(logger, message) + const result = determineAction(logger, message) - expect(result).toBe(PostDatedProcessingResult.IMMATURE) + expect(result).toBe(PostDatedProcessingResult.REPROCESS) }) }) }) diff --git a/packages/postDatedLambda/tests/testDatabaseClient.test.ts b/packages/postDatedLambda/tests/testDatabaseClient.test.ts index 23df0473fb..5d2e938ea7 100644 --- a/packages/postDatedLambda/tests/testDatabaseClient.test.ts +++ b/packages/postDatedLambda/tests/testDatabaseClient.test.ts @@ -27,9 +27,8 @@ export function mockDynamoDBClient() { const {mockSend} = mockDynamoDBClient() const { - getExistingRecordsByPrescriptionID, - fetchExistingRecordsForPrescriptions, - enrichMessagesWithExistingRecords + getRecentDataItemByPrescriptionID, + enrichMessagesWithMostRecentDataItem } = await import("../src/databaseClient") const logger = new Logger({serviceName: "postDatedLambdaTEST"}) @@ -39,7 +38,7 @@ describe("databaseClient", () => { mockSend.mockReset() }) - describe("getExistingRecordsByPrescriptionID", () => { + describe("getRecentDataItemByPrescriptionID", () => { it("should return existing records from DynamoDB", async () => { const prescriptionID = "testPrescID" @@ -62,7 +61,7 @@ describe("databaseClient", () => { LastEvaluatedKey: undefined }) - const records = await getExistingRecordsByPrescriptionID( + const records = await getRecentDataItemByPrescriptionID( prescriptionID, logger ) @@ -80,7 +79,7 @@ describe("databaseClient", () => { LastEvaluatedKey: undefined }) - const records = await getExistingRecordsByPrescriptionID(prescriptionID, logger) + const records = await getRecentDataItemByPrescriptionID(prescriptionID, logger) expect(records).toEqual([]) }) @@ -90,12 +89,10 @@ describe("databaseClient", () => { // Mock DynamoDB to throw an error const mockError = new Error("DynamoDB query failed") - mockSend.mockImplementationOnce(() => { - throw mockError - }) + mockSend.mockReturnValueOnce(Promise.reject(mockError)) await expect( - getExistingRecordsByPrescriptionID( + getRecentDataItemByPrescriptionID( prescriptionID, logger ) @@ -133,7 +130,7 @@ describe("databaseClient", () => { LastEvaluatedKey: undefined }) - const records = await getExistingRecordsByPrescriptionID(prescriptionID, logger) + const records = await getRecentDataItemByPrescriptionID(prescriptionID, logger) expect(mockSend).toHaveBeenCalledTimes(2) expect(records).toHaveLength(2) @@ -142,8 +139,8 @@ describe("databaseClient", () => { }) }) - describe("fetchExistingRecordsForPrescriptions", () => { - it("should fetch existing records for multiple prescriptions", async () => { + describe("enrichMessagesWithMostRecentDataItem", () => { + it("should enrich messages with the most recent record", async () => { const prescriptions = [ createMockPostModifiedDataItem({PrescriptionID: "presc1", PharmacyODSCode: "pharmA"}), createMockPostModifiedDataItem({PrescriptionID: "presc2", PharmacyODSCode: "pharmB"}) @@ -156,6 +153,12 @@ describe("databaseClient", () => { PharmacyODSCode: {S: "pharmA"}, Status: {S: "With pharmacy"}, LastModified: {S: "2024-01-01T12:00:00Z"} + }, + { + PrescriptionID: {S: "presc1"}, + PharmacyODSCode: {S: "pharmA"}, + Status: {S: "Ready to collect"}, + LastModified: {S: "2024-01-03T12:00:00Z"} } ] @@ -178,57 +181,27 @@ describe("databaseClient", () => { LastEvaluatedKey: undefined }) - const result = await fetchExistingRecordsForPrescriptions( - prescriptions, + const messages = prescriptions.map((presc) => ({ + prescriptionData: presc + })) + + const enrichedMessages = await enrichMessagesWithMostRecentDataItem( + messages, logger ) - expect(result.length).toBe(2) - expect(result[0].existingRecords.length).toBe(1) - expect(result[0].existingRecords[0].Status).toBe("With pharmacy") - expect(result[1].existingRecords.length).toBe(1) - expect(result[1].existingRecords[0].Status).toBe("Ready to collect") + expect(enrichedMessages.length).toBe(2) + expect(enrichedMessages[0].mostRecentRecord?.Status).toBe("Ready to collect") + expect(enrichedMessages[1].mostRecentRecord?.Status).toBe("Ready to collect") }) - it( - "Should log an error if the fetch fails for one prescription, and set the existingRecords to empty array", - async () => { - const prescriptions = [ - createMockPostModifiedDataItem({PrescriptionID: "presc1", PharmacyODSCode: "pharmA"}), - createMockPostModifiedDataItem({PrescriptionID: "errorPresc", PharmacyODSCode: "errorPharm"}) - ] - - // Mock DynamoDB responses - const mockItemsPresc1 = [ - { - PrescriptionID: {S: "presc1"}, - PharmacyODSCode: {S: "pharmA"}, - Status: {S: "With pharmacy"}, - LastModified: {S: "2024-01-01T12:00:00Z"} - } - ] - - mockSend - .mockReturnValueOnce({ - Items: mockItemsPresc1, - LastEvaluatedKey: undefined - }) - .mockImplementationOnce(() => { - throw new Error("DynamoDB query failed") - }) - - const result = await fetchExistingRecordsForPrescriptions( - prescriptions, - logger - ) + it("should return an empty array when no messages are provided", async () => { + const enrichedMessages = await enrichMessagesWithMostRecentDataItem([], logger) - expect(result.length).toBe(2) - expect(result[0].existingRecords.length).toBe(1) - expect(result[0].existingRecords[0].Status).toBe("With pharmacy") - expect(result[1].existingRecords.length).toBe(0) - }) + expect(enrichedMessages).toEqual([]) + }) - it("should return prescriptions with empty existingRecords when DynamoDB has no matches", async () => { + it("should set mostRecentRecord to undefined when DynamoDB has no matches", async () => { const prescriptions = [ createMockPostModifiedDataItem({PrescriptionID: "noPresc1", PharmacyODSCode: "pharmA"}), createMockPostModifiedDataItem({PrescriptionID: "noPresc2", PharmacyODSCode: "pharmB"}) @@ -244,22 +217,23 @@ describe("databaseClient", () => { LastEvaluatedKey: undefined }) - const result = await fetchExistingRecordsForPrescriptions(prescriptions, logger) + const messages = prescriptions.map((presc) => ({ + prescriptionData: presc + })) + + const enrichedMessages = await enrichMessagesWithMostRecentDataItem(messages, logger) - expect(result).toHaveLength(2) - expect(result[0].existingRecords).toEqual([]) - expect(result[1].existingRecords).toEqual([]) + expect(enrichedMessages).toHaveLength(2) + expect(enrichedMessages[0].mostRecentRecord).toBeUndefined() + expect(enrichedMessages[1].mostRecentRecord).toBeUndefined() }) - }) - describe("enrichMessagesWithExistingRecords", () => { - it("should enrich messages with existing records", async () => { + it("should keep processing when one prescription lookup fails", async () => { const prescriptions = [ createMockPostModifiedDataItem({PrescriptionID: "presc1", PharmacyODSCode: "pharmA"}), - createMockPostModifiedDataItem({PrescriptionID: "presc2", PharmacyODSCode: "pharmB"}) + createMockPostModifiedDataItem({PrescriptionID: "errorPresc", PharmacyODSCode: "errorPharm"}) ] - // Mock DynamoDB responses const mockItemsPresc1 = [ { PrescriptionID: {S: "presc1"}, @@ -269,45 +243,22 @@ describe("databaseClient", () => { } ] - const mockItemsPresc2 = [ - { - PrescriptionID: {S: "presc2"}, - PharmacyODSCode: {S: "pharmB"}, - Status: {S: "Ready to collect"}, - LastModified: {S: "2024-01-02T12:00:00Z"} - } - ] - mockSend .mockReturnValueOnce({ Items: mockItemsPresc1, LastEvaluatedKey: undefined }) - .mockReturnValueOnce({ - Items: mockItemsPresc2, - LastEvaluatedKey: undefined - }) + .mockReturnValueOnce(Promise.reject(new Error("DynamoDB query failed"))) const messages = prescriptions.map((presc) => ({ prescriptionData: presc })) - const enrichedMessages = await enrichMessagesWithExistingRecords( - messages, - logger - ) - - expect(enrichedMessages.length).toBe(2) - expect(enrichedMessages[0].existingRecords.length).toBe(1) - expect(enrichedMessages[0].existingRecords[0].Status).toBe("With pharmacy") - expect(enrichedMessages[1].existingRecords.length).toBe(1) - expect(enrichedMessages[1].existingRecords[0].Status).toBe("Ready to collect") - }) - - it("should return an empty array when no messages are provided", async () => { - const enrichedMessages = await enrichMessagesWithExistingRecords([], logger) + const enrichedMessages = await enrichMessagesWithMostRecentDataItem(messages, logger) - expect(enrichedMessages).toEqual([]) + expect(enrichedMessages).toHaveLength(2) + expect(enrichedMessages[0].mostRecentRecord?.Status).toBe("With pharmacy") + expect(enrichedMessages[1].mostRecentRecord).toBeUndefined() }) }) }) diff --git a/packages/postDatedLambda/tests/testOrchestration.test.ts b/packages/postDatedLambda/tests/testOrchestration.test.ts index 728ab5f923..0dbed78a38 100644 --- a/packages/postDatedLambda/tests/testOrchestration.test.ts +++ b/packages/postDatedLambda/tests/testOrchestration.test.ts @@ -6,37 +6,41 @@ import { } from "@jest/globals" // Mock the imports from local modules -const mockProcessMessage = jest.fn() +const mockDetermineAction = jest.fn() const mockComputeTimeUntilMaturity = jest.fn().mockReturnValue(300) jest.unstable_mockModule("../src/businessLogic", () => { return { - processMessage: mockProcessMessage, + determineAction: mockDetermineAction, computeTimeUntilMaturity: mockComputeTimeUntilMaturity } }) -const mockEnrichMessagesWithExistingRecords = jest.fn() +const mockEnrichMessagesWithMostRecentDataItem = jest.fn() jest.unstable_mockModule("../src/databaseClient", () => { return { - enrichMessagesWithExistingRecords: mockEnrichMessagesWithExistingRecords + enrichMessagesWithMostRecentDataItem: mockEnrichMessagesWithMostRecentDataItem } }) const mockReceivePostDatedSQSMessages = jest.fn() const mockReportQueueStatus = jest.fn() -const mockHandleProcessedMessages = jest.fn() +const mockForwardSQSMessageToNotificationQueue = jest.fn() +const mockRemoveSQSMessage = jest.fn() +const mockReturnMessageToQueue = jest.fn() jest.unstable_mockModule("../src/sqs", () => { return { receivePostDatedSQSMessages: mockReceivePostDatedSQSMessages, reportQueueStatus: mockReportQueueStatus, - handleProcessedMessages: mockHandleProcessedMessages + forwardSQSMessageToNotificationQueue: mockForwardSQSMessageToNotificationQueue, + removeSQSMessage: mockRemoveSQSMessage, + returnMessageToQueue: mockReturnMessageToQueue } }) import {Logger} from "@aws-lambda-powertools/logger" import {createMockPostModifiedDataItem} from "./testUtils" -import {BatchProcessingResult, PostDatedProcessingResult, PostDatedSQSMessage} from "../src/types" +import {PostDatedProcessingResult, PostDatedSQSMessage, PostDatedSQSMessageWithRecentDataItem} from "../src/types" // Import the orchestration module after mocking dependencies const {processMessages, processPostDatedQueue} = await import("../src/orchestration") @@ -52,10 +56,10 @@ function createBatch(ids: Array): Array { })) } -function enrich(messages: Array) { +function enrich(messages: Array): Array { return messages.map((message) => ({ ...message, - existingRecords: [] + mostRecentRecord: undefined })) } @@ -63,6 +67,9 @@ describe("orchestration", () => { describe("processMessages", () => { beforeEach(() => { jest.clearAllMocks() + mockForwardSQSMessageToNotificationQueue.mockReturnValue(Promise.resolve("forwarded-id")) + mockRemoveSQSMessage.mockReturnValue(Promise.resolve()) + mockReturnMessageToQueue.mockReturnValue(Promise.resolve()) }) it("should process messages and categorize them correctly", async () => { @@ -73,58 +80,28 @@ describe("orchestration", () => { ] // Mock the enrichment function to return the same messages - mockEnrichMessagesWithExistingRecords.mockReturnValueOnce(mockMessages) - - // Mock processMessage to return true for first message and false for second - mockProcessMessage - .mockReturnValueOnce(PostDatedProcessingResult.MATURED) - .mockReturnValueOnce(PostDatedProcessingResult.IMMATURE) - .mockReturnValueOnce(PostDatedProcessingResult.IGNORE) - - const result = await processMessages(mockMessages, logger) - - expect(result.maturedPrescriptionUpdates).toHaveLength(1) - expect(result.maturedPrescriptionUpdates[0].MessageId).toBe("1") - expect(result.immaturePrescriptionUpdates).toHaveLength(1) - expect(result.immaturePrescriptionUpdates[0].MessageId).toBe("2") - expect(result.ignoredPrescriptionUpdates).toHaveLength(1) - expect(result.ignoredPrescriptionUpdates[0].MessageId).toBe("3") - }) + mockEnrichMessagesWithMostRecentDataItem.mockReturnValueOnce(enrich(mockMessages)) - it("should handle empty message array", async () => { - mockEnrichMessagesWithExistingRecords.mockReturnValueOnce([]) - const result = await processMessages([], logger) + // Mock determineAction to return action for each message + mockDetermineAction + .mockReturnValueOnce(PostDatedProcessingResult.FORWARD_TO_NOTIFICATIONS) + .mockReturnValueOnce(PostDatedProcessingResult.REPROCESS) + .mockReturnValueOnce(PostDatedProcessingResult.REMOVE_FROM_PD_QUEUE) - expect(result.maturedPrescriptionUpdates).toHaveLength(0) - expect(result.immaturePrescriptionUpdates).toHaveLength(0) - expect(result.ignoredPrescriptionUpdates).toHaveLength(0) + await processMessages(mockMessages, logger) + expect(mockForwardSQSMessageToNotificationQueue).toHaveBeenCalledTimes(1) + expect(mockReturnMessageToQueue).toHaveBeenCalledTimes(1) + expect(mockRemoveSQSMessage).toHaveBeenCalledTimes(2) }) - it("should log errors and mark messages immature when processing throws", async () => { - const mockMessages: Array = [ - {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostModifiedDataItem({})}, - {MessageId: "2", Body: "Message 2", prescriptionData: createMockPostModifiedDataItem({})} - ] + it("should handle empty message array", async () => { + mockEnrichMessagesWithMostRecentDataItem.mockReturnValueOnce([]) - mockEnrichMessagesWithExistingRecords.mockReturnValueOnce(mockMessages) - mockProcessMessage - .mockReturnValueOnce(PostDatedProcessingResult.MATURED) - .mockImplementationOnce(() => { - throw new Error("processing failed") - }) - - const errorSpy = jest.spyOn(logger, "error") - const result = await processMessages(mockMessages, logger) - - expect(result.maturedPrescriptionUpdates).toHaveLength(1) - expect(result.immaturePrescriptionUpdates).toHaveLength(1) - expect(result.ignoredPrescriptionUpdates).toHaveLength(0) - expect(result.immaturePrescriptionUpdates[0].MessageId).toBe("2") - expect(errorSpy).toHaveBeenCalledWith( - "Error processing message", - expect.objectContaining({messageId: "2"}) - ) - errorSpy.mockRestore() + await processMessages([], logger) + + expect(mockForwardSQSMessageToNotificationQueue).not.toHaveBeenCalled() + expect(mockReturnMessageToQueue).not.toHaveBeenCalled() + expect(mockRemoveSQSMessage).not.toHaveBeenCalled() }) it("should pass enriched records into processMessage", async () => { @@ -132,23 +109,26 @@ describe("orchestration", () => { {MessageId: "1", Body: "Message 1", prescriptionData: createMockPostModifiedDataItem({})} ] - const enrichedMessage = { + const enrichedMessage: PostDatedSQSMessageWithRecentDataItem = { ...mockMessages[0], - existingRecords: [{prescriptionId: "abc"}] + mostRecentRecord: undefined } - mockEnrichMessagesWithExistingRecords.mockReturnValueOnce([enrichedMessage]) - mockProcessMessage.mockReturnValue(PostDatedProcessingResult.MATURED) + mockEnrichMessagesWithMostRecentDataItem.mockReturnValueOnce([enrichedMessage]) + mockDetermineAction.mockReturnValue(PostDatedProcessingResult.FORWARD_TO_NOTIFICATIONS) await processMessages(mockMessages, logger) - expect(mockProcessMessage).toHaveBeenCalledWith(logger, enrichedMessage) + expect(mockDetermineAction).toHaveBeenCalledWith(logger, enrichedMessage) }) }) describe("processPostDatedQueue", () => { beforeEach(() => { jest.clearAllMocks() + mockForwardSQSMessageToNotificationQueue.mockReturnValue(Promise.resolve("forwarded-id")) + mockRemoveSQSMessage.mockReturnValue(Promise.resolve()) + mockReturnMessageToQueue.mockReturnValue(Promise.resolve()) }) it("should process the SQS queue correctly", async () => { @@ -159,27 +139,21 @@ describe("orchestration", () => { const mockEnrichedMessages = mockMessages.map((message) => ({ ...message, - existingRecords: [] + mostRecentRecord: undefined })) mockReceivePostDatedSQSMessages.mockReturnValueOnce(mockMessages) - mockEnrichMessagesWithExistingRecords.mockReturnValueOnce(mockEnrichedMessages) - mockProcessMessage.mockReturnValue(PostDatedProcessingResult.MATURED) + mockEnrichMessagesWithMostRecentDataItem.mockReturnValueOnce(mockEnrichedMessages) + mockDetermineAction.mockReturnValue(PostDatedProcessingResult.FORWARD_TO_NOTIFICATIONS) await processPostDatedQueue(logger) expect(mockReceivePostDatedSQSMessages).toHaveBeenCalledWith(logger) expect(mockReportQueueStatus).not.toHaveBeenCalled() - expect(mockHandleProcessedMessages).toHaveBeenCalled() - const [res, lg] = - mockHandleProcessedMessages.mock.calls[0] as [BatchProcessingResult, Logger] - expect(lg).toBe(logger) - expect(res.maturedPrescriptionUpdates).toHaveLength(mockMessages.length) - expect(res.immaturePrescriptionUpdates).toHaveLength(0) - expect(res.maturedPrescriptionUpdates.map((message) => message.MessageId)).toEqual( - mockMessages.map((message) => message.MessageId) - ) - expect(mockProcessMessage).toHaveBeenCalledTimes(mockMessages.length) + expect(mockForwardSQSMessageToNotificationQueue).toHaveBeenCalledTimes(mockMessages.length) + expect(mockRemoveSQSMessage).toHaveBeenCalledTimes(mockMessages.length) + expect(mockReturnMessageToQueue).not.toHaveBeenCalled() + expect(mockDetermineAction).toHaveBeenCalledTimes(mockMessages.length) }) it("Should stop processing if the max runtime is exceeded", async () => { @@ -194,20 +168,14 @@ describe("orchestration", () => { ] mockReceivePostDatedSQSMessages.mockReturnValue(mockMessages) - mockEnrichMessagesWithExistingRecords.mockReturnValue( - mockMessages.map((message) => ({ - ...message, - existingRecords: [] - })) - ) + mockEnrichMessagesWithMostRecentDataItem.mockReturnValue(enrich(mockMessages)) const {MAX_QUEUE_RUNTIME} = await import("../src/orchestration") - mockProcessMessage.mockImplementation(async () => { - // Overrun by a second - jest.advanceTimersByTime(MAX_QUEUE_RUNTIME + 1000) - return PostDatedProcessingResult.MATURED - }) + mockDetermineAction.mockReturnValue(PostDatedProcessingResult.FORWARD_TO_NOTIFICATIONS) - await processPostDatedQueue(logger) + const promise = processPostDatedQueue(logger) + // Overrun by a second + jest.advanceTimersByTime(MAX_QUEUE_RUNTIME + 1000) + await promise expect(mockReportQueueStatus).toHaveBeenCalled() jest.useRealTimers() @@ -222,31 +190,32 @@ describe("orchestration", () => { .mockReturnValueOnce(batch1) .mockReturnValueOnce(batch2) .mockReturnValueOnce(batch3) - mockEnrichMessagesWithExistingRecords + mockEnrichMessagesWithMostRecentDataItem .mockReturnValueOnce(enrich(batch1)) .mockReturnValueOnce(enrich(batch2)) .mockReturnValueOnce(enrich(batch3)) - mockProcessMessage.mockReturnValue(PostDatedProcessingResult.MATURED) + mockDetermineAction.mockReturnValue(PostDatedProcessingResult.FORWARD_TO_NOTIFICATIONS) await processPostDatedQueue(logger) expect(mockReceivePostDatedSQSMessages).toHaveBeenCalledTimes(3) - expect(mockHandleProcessedMessages).toHaveBeenCalledTimes(3) + expect(mockForwardSQSMessageToNotificationQueue) + .toHaveBeenCalledTimes(batch1.length + batch2.length + batch3.length) + expect(mockRemoveSQSMessage).toHaveBeenCalledTimes(batch1.length + batch2.length + batch3.length) expect(mockReportQueueStatus).not.toHaveBeenCalled() const totalMessages = batch1.length + batch2.length + batch3.length - expect(mockProcessMessage).toHaveBeenCalledTimes(totalMessages) + expect(mockDetermineAction).toHaveBeenCalledTimes(totalMessages) }) it("should treat empty receives as drained batches", async () => { mockReceivePostDatedSQSMessages.mockReturnValueOnce([]) - mockEnrichMessagesWithExistingRecords.mockReturnValueOnce([]) + mockEnrichMessagesWithMostRecentDataItem.mockReturnValueOnce([]) await processPostDatedQueue(logger) - expect(mockHandleProcessedMessages).toHaveBeenCalledTimes(1) - const [result] = mockHandleProcessedMessages.mock.calls[0] as [BatchProcessingResult] - expect(result.maturedPrescriptionUpdates).toHaveLength(0) - expect(result.immaturePrescriptionUpdates).toHaveLength(0) + expect(mockForwardSQSMessageToNotificationQueue).not.toHaveBeenCalled() + expect(mockRemoveSQSMessage).not.toHaveBeenCalled() + expect(mockReturnMessageToQueue).not.toHaveBeenCalled() }) }) }) diff --git a/packages/postDatedLambda/tests/testSqs.test.ts b/packages/postDatedLambda/tests/testSqs.test.ts index 80adb58055..94cdd74a6d 100644 --- a/packages/postDatedLambda/tests/testSqs.test.ts +++ b/packages/postDatedLambda/tests/testSqs.test.ts @@ -9,7 +9,7 @@ import {SpiedFunction} from "jest-mock" import {Logger} from "@aws-lambda-powertools/logger" import {LogItemMessage, LogItemExtraInput} from "@aws-lambda-powertools/logger/lib/cjs/types/Logger" import * as sqs from "@aws-sdk/client-sqs" -import {BatchProcessingResult, PostDatedSQSMessage} from "../src/types" +import {PostDatedSQSMessage} from "../src/types" import {createMockPostModifiedDataItem} from "./testUtils" export function mockSQSClient() { @@ -31,10 +31,9 @@ const { getPostDatedQueueUrl, reportQueueStatus, receivePostDatedSQSMessages, - removeSQSMessages, - returnMessagesToQueue, - handleProcessedMessages, - forwardSQSMessagesToNotificationQueue: sendSQSMessagesToNotificationQueue + removeSQSMessage, + returnMessageToQueue, + forwardSQSMessageToNotificationQueue } = await import("../src/sqs") const ORIGINAL_ENV = {...process.env} @@ -180,90 +179,67 @@ describe("sqs", () => { }) }) - describe("sendSQSMessagesToNotificationQueue", () => { - it("should send matured post-dated messages to the notifications queue", async () => { + describe("forwardSQSMessageToNotificationQueue", () => { + it("should send a matured post-dated message to the notifications queue", async () => { const notifyUrl = "https://sqs.eu-west-2.amazonaws.com/123456789012/notify" process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL = notifyUrl - const messages: Array = [ - { - MessageId: "1", - ReceiptHandle: "handle-1", - prescriptionData: createMockPostModifiedDataItem({RequestID: "req-1"}), - Attributes: {MessageDeduplicationId: "dedup1", MessageGroupId: "group1"} - } - ] + const message: PostDatedSQSMessage = { + MessageId: "1", + ReceiptHandle: "handle-1", + prescriptionData: createMockPostModifiedDataItem({RequestID: "req-1"}), + Attributes: {MessageDeduplicationId: "dedup1", MessageGroupId: "group1"} + } mockSend.mockReturnValueOnce({ Successful: [{Id: "0", MessageId: "notify-msg-1"}], Failed: [] }) - const result = await sendSQSMessagesToNotificationQueue(logger, messages) + const result = await forwardSQSMessageToNotificationQueue(logger, message) expect(mockSend).toHaveBeenCalledTimes(1) const command = mockSend.mock.calls[0][0] as {input: {QueueUrl: string; Entries: Array<{MessageBody: string}>}} expect(command.input.QueueUrl).toBe(notifyUrl) - expect(command.input.Entries[0].MessageBody).toBe(JSON.stringify(messages[0].prescriptionData)) - expect(result).toEqual(["notify-msg-1"]) - }) - - it("should short circuit when there are no matured messages", async () => { - process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL = "https://sqs.eu-west-2.amazonaws.com/123456789012/notify" - - const result = await sendSQSMessagesToNotificationQueue(logger, []) - - expect(result).toEqual([]) - expect(mockSend).not.toHaveBeenCalled() + expect(command.input.Entries[0].MessageBody).toBe(JSON.stringify(message.prescriptionData)) + expect(result).toBe("notify-msg-1") }) it("should throw an error if the deduplication ID is missing", async () => { process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL = "https://sqs.eu-west-2.amazonaws.com/123456789012/notify" - const messages: Array = [ - { - MessageId: "1", - ReceiptHandle: "handle-1", - prescriptionData: createMockPostModifiedDataItem({RequestID: "req-1"}), - Attributes: {} // Missing MessageDeduplicationId - } - ] + const message: PostDatedSQSMessage = { + MessageId: "1", + ReceiptHandle: "handle-1", + prescriptionData: createMockPostModifiedDataItem({RequestID: "req-1"}), + Attributes: {} // Missing MessageDeduplicationId + } await expect( - sendSQSMessagesToNotificationQueue(logger, messages) + forwardSQSMessageToNotificationQueue(logger, message) ).rejects.toThrow("Missing MessageDeduplicationId in SQS message attributes") expect(mockSend).not.toHaveBeenCalled() }) }) - describe("removeSQSMessages", () => { - it("Should remove messages from the SQS queue", async () => { + describe("removeSQSMessage", () => { + it("Should remove a message from the SQS queue", async () => { const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue" process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl - const messagesToRemove = [ - {MessageId: "1", ReceiptHandle: "handle1"}, - {MessageId: "2", ReceiptHandle: "handle2"} - ] + const messageToRemove = {MessageId: "1", ReceiptHandle: "handle1"} // Mock SQS delete response mockSend.mockReturnValueOnce({ - Successful: messagesToRemove.map((msg) => ({Id: msg.MessageId})), + Successful: [{Id: messageToRemove.MessageId}], Failed: [] }) - await removeSQSMessages(logger, messagesToRemove) + await removeSQSMessage(logger, messageToRemove) expect(mockSend).toHaveBeenCalledTimes(1) - expect(infoSpy).toHaveBeenCalledWith("Successfully removed 2 messages from SQS") - }) - - it("Should handle empty message array gracefully", async () => { - await removeSQSMessages(logger, []) - - expect(mockSend).toHaveBeenCalledTimes(0) - expect(infoSpy).toHaveBeenCalledWith("No messages to delete") + expect(infoSpy).toHaveBeenCalledWith("Successfully removed 1 messages from SQS") }) it("Should log errors but not throw if deletion fails", async () => { @@ -273,181 +249,67 @@ describe("sqs", () => { const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue" process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl - const messagesToRemove = [ - {MessageId: "1", ReceiptHandle: "handle1"}, - {MessageId: "2", ReceiptHandle: "handle2"} - ] + const messageToRemove = {MessageId: "1", ReceiptHandle: "handle1"} // Mock SQS delete response with failures mockSend.mockReturnValueOnce({ - Successful: [{Id: "1"}], - Failed: [{Id: "2", Message: "Some error"}] + Successful: [], + Failed: [{Id: "1", Message: "Some error"}] }) - await removeSQSMessages(logger, messagesToRemove) + await removeSQSMessage(logger, messageToRemove) expect(mockSend).toHaveBeenCalledTimes(1) expect(errorSpy).toHaveBeenCalledWith("Some messages failed to delete", { - failed: [{Id: "2", Message: "Some error"}] + failed: [{Id: "1", Message: "Some error"}] }) - expect(infoSpy).toHaveBeenCalledWith("Successfully removed 1 messages from SQS") }) }) - describe("returnMessagesToQueue", () => { - it("Should return messages to the SQS queue by updating their visibility timeout", async () => { + describe("returnMessageToQueue", () => { + it("Should return a message to the SQS queue by updating its visibility timeout", async () => { const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue" process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl - const messagesToReturn: Array = [ - {MessageId: "1", ReceiptHandle: "handle1", prescriptionData: createMockPostModifiedDataItem({})}, - {MessageId: "2", ReceiptHandle: "handle2", prescriptionData: createMockPostModifiedDataItem({})} - ] + const messageToReturn: PostDatedSQSMessage = { + MessageId: "1", + ReceiptHandle: "handle1", + prescriptionData: createMockPostModifiedDataItem({}) + } // Mock SQS change visibility response mockSend.mockReturnValueOnce({ // No specific return value needed for ChangeMessageVisibilityBatch }) - await returnMessagesToQueue(logger, messagesToReturn) + await returnMessageToQueue(logger, messageToReturn) expect(mockSend).toHaveBeenCalledTimes(1) - expect(infoSpy).toHaveBeenCalledWith("Returning messages to queue with timeouts", { - numberOfMessages: 2, - idAndTimeouts: [ - {id: "1", visibilityTimeout: 300}, - {id: "2", visibilityTimeout: 300} - ] + expect(infoSpy).toHaveBeenCalledWith("Returning message to queue with timeouts", { + sqsMessage: messageToReturn, + visibilityTimeout: 300 }) expect(errorSpy).not.toHaveBeenCalled() }) - it("Should handle empty message array gracefully", async () => { - await returnMessagesToQueue(logger, []) - - expect(mockSend).toHaveBeenCalledTimes(0) - expect(infoSpy).toHaveBeenCalledWith("No messages to return to queue") - }) - it("should log an error if SQS change visibility fails", async () => { const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue" process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl - const messagesToReturn: Array = [ - {MessageId: "1", ReceiptHandle: "handle1", prescriptionData: createMockPostModifiedDataItem({})} - ] + const messageToReturn: PostDatedSQSMessage = { + MessageId: "1", + ReceiptHandle: "handle1", + prescriptionData: createMockPostModifiedDataItem({}) + } // Mock SQS change visibility to throw an error const expectedError = new Error("SQS change visibility failed") - mockSend.mockImplementationOnce(() => { - throw expectedError - }) + mockSend.mockReturnValueOnce(Promise.reject(expectedError)) - await returnMessagesToQueue(logger, messagesToReturn) + await returnMessageToQueue(logger, messageToReturn) expect(mockSend).toHaveBeenCalledTimes(1) expect(errorSpy).toHaveBeenCalledWith("SQS change visibility failed", {error: expectedError}) }) }) - - describe("handleProcessedMessages", () => { - it("should remove matured messages and return immature messages to the queue", async () => { - const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue" - process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl - process.env.NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL = "https://sqs.us-east-1.amazonaws.com/123456789012/notify" - - const maturedMessages: Array = [ - { - MessageId: "1", ReceiptHandle: "handle1", - prescriptionData: createMockPostModifiedDataItem({}), - Attributes: {MessageDeduplicationId: "dedup1", MessageGroupId: "group1"} - } - ] - const immatureMessages: Array = [ - { - MessageId: "2", ReceiptHandle: "handle2", - prescriptionData: createMockPostModifiedDataItem({}), - Attributes: {MessageDeduplicationId: "dedup2", MessageGroupId: "group2"} - } - ] - - const batchResult: BatchProcessingResult = { - maturedPrescriptionUpdates: maturedMessages, - immaturePrescriptionUpdates: immatureMessages, - ignoredPrescriptionUpdates: [] - } - - // Mock SQS responses - mockSend - .mockReturnValueOnce({ - Successful: [{Id: "0", MessageId: "notify-msg"}], - Failed: [] - }) // sendSQSMessagesToNotificationQueue - .mockReturnValueOnce({ - Successful: [{Id: "1"}], - Failed: [] - }) // For removeSQSMessages - .mockReturnValueOnce({}) // For returnMessagesToQueue - - await handleProcessedMessages(batchResult, logger) - - expect(mockSend).toHaveBeenCalledTimes(3) - expect(infoSpy).toHaveBeenCalledWith("Successfully removed 1 messages from SQS") - expect(infoSpy).toHaveBeenCalledWith("Returning messages to queue with timeouts", { - numberOfMessages: 1, - idAndTimeouts: [ - {id: "2", visibilityTimeout: 300} - ] - }) - }) - - it("should handle empty matured and immature message arrays gracefully", async () => { - const batchResult: BatchProcessingResult = { - maturedPrescriptionUpdates: [], - immaturePrescriptionUpdates: [], - ignoredPrescriptionUpdates: [] - } - - await handleProcessedMessages(batchResult, logger) - - expect(mockSend).toHaveBeenCalledTimes(0) - expect(infoSpy).not.toHaveBeenCalledWith("Successfully removed") - expect(infoSpy).not.toHaveBeenCalledWith("Returning messages to queue with timeouts", expect.anything()) - }) - - it("should remove ignored messages from the queue so they are not reprocessed", async () => { - const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue" - process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl - - const ignoredMessages: Array = [ - { - MessageId: "ignored-1", - ReceiptHandle: "ignored-handle-1", - prescriptionData: createMockPostModifiedDataItem({}), - Attributes: {MessageDeduplicationId: "dedup-ignored", MessageGroupId: "group-ignored"} - } - ] - - const batchResult: BatchProcessingResult = { - maturedPrescriptionUpdates: [], - immaturePrescriptionUpdates: [], - ignoredPrescriptionUpdates: ignoredMessages - } - - mockSend.mockReturnValueOnce({ - Successful: ignoredMessages.map((msg) => ({Id: msg.MessageId})), - Failed: [] - }) - - await handleProcessedMessages(batchResult, logger) - - expect(mockSend).toHaveBeenCalledTimes(1) - - const deleteCommand = mockSend.mock.calls[0][0] as {input: {Entries: Array<{Id: string; ReceiptHandle: string}>}} - expect(deleteCommand.input.Entries).toEqual([ - {Id: "ignored-1", ReceiptHandle: "ignored-handle-1"} - ]) - expect(infoSpy).toHaveBeenCalledWith("Successfully removed 1 messages from SQS") - }) - }) }) From aa91e50e554e53c8ce5113ac82d2f14dd4401517 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Wed, 11 Feb 2026 09:21:57 +0000 Subject: [PATCH 72/75] Update docs --- .../docs/mature_prescription_check.md | 55 ++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/packages/postDatedLambda/docs/mature_prescription_check.md b/packages/postDatedLambda/docs/mature_prescription_check.md index 4db4a2adb6..650d99b0f4 100644 --- a/packages/postDatedLambda/docs/mature_prescription_check.md +++ b/packages/postDatedLambda/docs/mature_prescription_check.md @@ -1,33 +1,36 @@ This is the business logic used for checking if a prescription ID has matured or not. It needs to tolerate a mixture of post-dated, and contemporary prescription updates; e.g. "ready to collect", post-dated, followed by "with pharmacy" non-post-dated. -The business logic's `processMessage()` function accepts the SQS message for this prescription ID, enriched with the results of querying the NPPTS data store for this prescription ID (this is the `existingRecords` attribute. Then, it inspects only the most recently submitted update for this prescription ID, and checks if it is a) actually post dated (a contemporary update may have come in since the post-dated one that triggered this SQS message), and b) has matured from "will be ready to collect" to "is now ready to collect". The decision of if a notification needs to be sent to this patient is handled by the notifications lambda, still. - -```mermaid -flowchart TD - A["Start `processMessage()`"] --> E{"existingRecords empty?"} - E -- Yes --> F["Log error + return IGNORE"] - E -- No --> G["Fetch most recently submitted NPPTS record"] - G --> H{"PostDatedLastModifiedSetAt present?"} - H -- No --> I["Log non post-dated + return IGNORE"] - H -- Yes --> K{"NPPTS Status in [RTC, RTC-partial]?"} - K -- No --> L["Log non-notifiable + return IGNORE"] - K -- Yes --> M["mostRecentLastModified = Date(LastModified)"] - M --> N["currentTime = new Date()"] - N --> O{"mostRecentLastModified AFTER currentTime?"} - O -- Yes --> P["return IMMATURE"] - O -- No --> Q["return MATURED"] -``` - -Immature prescriptions will be re-processed after some delay, by being updated on SQS. Some prescriptions may no longer need to be considered (e.g. if there has been a subsequent PSU request marking it as "with pharmacy" again), so are deleted from the post-dated SQS without being forwarded to the notifications SQS. Mature prescriptions are forwarded to the notifications SQS. +The lambda drains the post-dated SQS queue in batches and handles each message based on the result of `determineAction()`. Immature prescriptions will be re-processed after some delay by changing their visibility timeout. Some prescriptions may no longer need to be considered (e.g. if there has been a subsequent PSU request marking it as "with pharmacy" again), so are deleted from the post-dated SQS without being forwarded to the notifications SQS. Mature prescriptions are forwarded to the notifications SQS. ```mermaid flowchart TB - PSU[PSU] -- "Post dated" --> Qp["Post-dated SQS queue"] - PSU -- "Contemporary" --> Qn[Notifications SQS queue] + START["Scheduled EventBridge trigger"] --> REPORT["Report post-dated queue status"] + REPORT --> LOOP{"Within max runtime?"} + LOOP -- No --> EXIT["Exit and report queue status"] + LOOP -- Yes --> RECV["Receive up to 10 SQS messages (long poll)"] + RECV --> ENRICH["Enrich messages with most recent NPPTS record"] + ENRICH --> DETERMINE["Run `determineAction()` per message"] + DETERMINE --> REPROCESS["Change visibility timeout (reprocess later)"] + DETERMINE --> FORWARD["Forward to Notifications queue"] + DETERMINE --> REMOVE["Remove from post-dated queue"] + FORWARD --> DELETE["Delete from post-dated queue"] + REPROCESS --> LOOP + REMOVE --> LOOP + DELETE --> LOOP +``` - Qp --> lp["Post-dated lambda"] - Qn --> ln["Notifications lambda"] +The `determineAction()` function accepts the SQS message for this prescription ID, enriched with the most recent NPPTS record for this prescription ID. It checks whether that record is post-dated and still in a notifiable status, then compares `LastModified` (i.e. the time that a post-dated update will transition) to the current time to determine whether the update is still immature or has matured. The decision of if a notification needs to be sent to this patient is handled by the notifications lambda, still. - lp -- MATURE --> Qn - lp -- IMMATURE --> Qp - lp -- IGNORE --> X((Delete)) +```mermaid +flowchart TD + A["Start `determineAction()`"] --> E{"mostRecentRecord present?"} + E -- No --> F["Log error + return REMOVE_FROM_PD_QUEUE"] + E -- Yes --> H{"PostDatedLastModifiedSetAt present?"} + H -- No --> I["Log non post-dated + return REMOVE_FROM_PD_QUEUE"] + H -- Yes --> K{"Status in [ready to collect, ready to collect - partial]?"} + K -- No --> L["Log non-notifiable + return REMOVE_FROM_PD_QUEUE"] + K -- Yes --> M["mostRecentLastModified = Date(LastModified)"] + M --> N["currentTime = new Date()"] + N --> O{"mostRecentLastModified AFTER currentTime?"} + O -- Yes --> P["return REPROCESS"] + O -- No --> Q["return FORWARD_TO_NOTIFICATIONS"] ``` From d4518c25ac717ee10f9c5a5cde953b44d7891c7f Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Wed, 11 Feb 2026 12:25:07 +0000 Subject: [PATCH 73/75] Update mermaid again --- .../docs/mature_prescription_check.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/postDatedLambda/docs/mature_prescription_check.md b/packages/postDatedLambda/docs/mature_prescription_check.md index 650d99b0f4..3d572a60de 100644 --- a/packages/postDatedLambda/docs/mature_prescription_check.md +++ b/packages/postDatedLambda/docs/mature_prescription_check.md @@ -4,18 +4,17 @@ The lambda drains the post-dated SQS queue in batches and handles each message b ```mermaid flowchart TB START["Scheduled EventBridge trigger"] --> REPORT["Report post-dated queue status"] - REPORT --> LOOP{"Within max runtime?"} + REPORT --> LOOP{"Within max runtime?
Still messages to process?"} LOOP -- No --> EXIT["Exit and report queue status"] - LOOP -- Yes --> RECV["Receive up to 10 SQS messages (long poll)"] + LOOP -- Yes --> RECV["Receive up to 10 SQS messages"] RECV --> ENRICH["Enrich messages with most recent NPPTS record"] ENRICH --> DETERMINE["Run `determineAction()` per message"] - DETERMINE --> REPROCESS["Change visibility timeout (reprocess later)"] - DETERMINE --> FORWARD["Forward to Notifications queue"] - DETERMINE --> REMOVE["Remove from post-dated queue"] - FORWARD --> DELETE["Delete from post-dated queue"] + DETERMINE -- FORWARD_TO_NOTIFICATIONS --> FORWARD["Forward to Notifications queue"] + DETERMINE -- REPROCESS --> REPROCESS["Change visibility timeout (reprocess later)"] + DETERMINE -- REMOVE_FROM_PD_QUEUE --> REMOVE["Remove from post-dated queue"] + FORWARD --> REMOVE REPROCESS --> LOOP REMOVE --> LOOP - DELETE --> LOOP ``` The `determineAction()` function accepts the SQS message for this prescription ID, enriched with the most recent NPPTS record for this prescription ID. It checks whether that record is post-dated and still in a notifiable status, then compares `LastModified` (i.e. the time that a post-dated update will transition) to the current time to determine whether the update is still immature or has matured. The decision of if a notification needs to be sent to this patient is handled by the notifications lambda, still. From 57d1e02b5c33739b2c89f63afcac27a6f08e4007 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 12 Feb 2026 09:53:54 +0000 Subject: [PATCH 74/75] remove completed todo --- SAMtemplates/functions/main.yaml | 2 +- packages/common/commonTypes/src/index.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/SAMtemplates/functions/main.yaml b/SAMtemplates/functions/main.yaml index 601f9f2c43..2c8868377a 100644 --- a/SAMtemplates/functions/main.yaml +++ b/SAMtemplates/functions/main.yaml @@ -547,7 +547,7 @@ Resources: Type: ScheduleV2 Properties: Name: !Sub ${StackName}-PostDatedNotifySchedule - ScheduleExpression: "rate(2 minutes)" # TODO: Increase to 15 minutes + ScheduleExpression: "rate(15 minutes)" RoleArn: !GetAtt PostDatedNotifyLambdaScheduleEventRole.Arn Metadata: BuildMethod: esbuild diff --git a/packages/common/commonTypes/src/index.ts b/packages/common/commonTypes/src/index.ts index b522e7e0b7..808e28df60 100644 --- a/packages/common/commonTypes/src/index.ts +++ b/packages/common/commonTypes/src/index.ts @@ -13,8 +13,6 @@ export interface PSUDataItem { ExpiryTime: number // (Optional, legacy batch-processors only) Indicates that {@link LastModified} is postdated; // contains the ISO 8601 timestamp when the postdated update was set. - // todo: This field needs to be passed to the sqs message for post-dated updates - // FIXME: This should be called PostDatedLastUpdatedSetAt PostDatedLastModifiedSetAt?: string } From 263c7712db66d4aefe0650f2a118e64963de5e73 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 13 Feb 2026 16:52:52 +0000 Subject: [PATCH 75/75] Fix middy --- .../src/notificationsReportingLambda.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/notificationsReportingLambda/src/notificationsReportingLambda.ts b/packages/notificationsReportingLambda/src/notificationsReportingLambda.ts index 6f699c5f32..8de50d0185 100644 --- a/packages/notificationsReportingLambda/src/notificationsReportingLambda.ts +++ b/packages/notificationsReportingLambda/src/notificationsReportingLambda.ts @@ -89,10 +89,12 @@ export const lambdaHandler = buildHandler(getDefaultRepository) export const handler = middy(lambdaHandler) .use(injectLambdaContext(logger, {clearState: true})) .use(httpHeaderNormalizer()) - .use(inputOutputLogger({ - logger: request => { - logger.info("notificationsReporting invocation", {hasBody: Boolean(request.event.body)}) - } - })) + .use( + inputOutputLogger({ + logger: (request) => { + logger.info(request) + } + }) + ) export {normalizeFilters, buildResponse}