diff --git a/user-management/Stickerlandia.UserManagement.sln b/user-management/Stickerlandia.UserManagement.sln index b267d331..8a47247f 100644 --- a/user-management/Stickerlandia.UserManagement.sln +++ b/user-management/Stickerlandia.UserManagement.sln @@ -27,7 +27,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{FC9DDF76-8 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stickerlandia.UserManagement.Agnostic", "src\Stickerlandia.UserManagement.Agnostic\Stickerlandia.UserManagement.Agnostic.csproj", "{295E8315-2CD2-4CD8-B7F4-21EF587B74A9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stickerlandia.UserManagement.Lambda", "src\Stickerlandia.UserManagement.Lambda\src\Stickerlandia.UserManagement.Lambda\Stickerlandia.UserManagement.Lambda.csproj", "{5C770F92-B6E7-4270-A041-A32F385DA6C4}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stickerlandia.UserManagement.Lambda", "src\Stickerlandia.UserManagement.Lambda\Stickerlandia.UserManagement.Lambda.csproj", "{5C770F92-B6E7-4270-A041-A32F385DA6C4}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Driving", "Driving", "{924749FC-98D5-40A6-8F4B-BA9BFDEDDEC3}" EndProject diff --git a/user-management/infra/aws/.gitignore b/user-management/infra/aws/.gitignore new file mode 100644 index 00000000..f60797b6 --- /dev/null +++ b/user-management/infra/aws/.gitignore @@ -0,0 +1,8 @@ +*.js +!jest.config.js +*.d.ts +node_modules + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/user-management/infra/aws/.npmignore b/user-management/infra/aws/.npmignore new file mode 100644 index 00000000..c1d6d45d --- /dev/null +++ b/user-management/infra/aws/.npmignore @@ -0,0 +1,6 @@ +*.ts +!*.d.ts + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/user-management/infra/aws/README.md b/user-management/infra/aws/README.md new file mode 100644 index 00000000..9315fe5b --- /dev/null +++ b/user-management/infra/aws/README.md @@ -0,0 +1,14 @@ +# Welcome to your CDK TypeScript project + +This is a blank project for CDK development with TypeScript. + +The `cdk.json` file tells the CDK Toolkit how to execute your app. + +## Useful commands + +* `npm run build` compile typescript to js +* `npm run watch` watch for changes and compile +* `npm run test` perform the jest unit tests +* `npx cdk deploy` deploy this stack to your default AWS account/region +* `npx cdk diff` compare deployed stack with current state +* `npx cdk synth` emits the synthesized CloudFormation template diff --git a/user-management/infra/aws/cdk.json b/user-management/infra/aws/cdk.json new file mode 100644 index 00000000..cfac2b69 --- /dev/null +++ b/user-management/infra/aws/cdk.json @@ -0,0 +1,96 @@ +{ + "app": "npx ts-node --prefer-ts-exts bin/user-service.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, + "@aws-cdk/aws-route53-patters:useCertificate": true, + "@aws-cdk/customresources:installLatestAwsSdkDefault": false, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, + "@aws-cdk/aws-redshift:columnId": true, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, + "@aws-cdk/aws-kms:aliasNameRef": true, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, + "@aws-cdk/aws-efs:denyAnonymousAccess": true, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, + "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, + "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, + "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true, + "@aws-cdk/aws-eks:nodegroupNameAttribute": true, + "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true, + "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true, + "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false, + "@aws-cdk/aws-s3:keepNotificationInImportedBucket": false, + "@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": false, + "@aws-cdk/aws-ecs:disableEcsImdsBlocking": true, + "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true, + "@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true, + "@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true, + "@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true, + "@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true, + "@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true, + "@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true, + "@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true, + "@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": true, + "@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": true, + "@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": true, + "@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": true, + "@aws-cdk/core:enableAdditionalMetadataCollection": true, + "@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": false, + "@aws-cdk/aws-s3:setUniqueReplicationRoleName": true, + "@aws-cdk/aws-events:requireEventBusPolicySid": true, + "@aws-cdk/core:aspectPrioritiesMutating": true, + "@aws-cdk/aws-dynamodb:retainTableReplica": true, + "@aws-cdk/aws-stepfunctions:useDistributedMapResultWriterV2": true, + "@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions": true, + "@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway": true, + "@aws-cdk/aws-s3:publicAccessBlockedByDefault": true + } +} diff --git a/user-management/infra/aws/jest.config.js b/user-management/infra/aws/jest.config.js new file mode 100644 index 00000000..08263b89 --- /dev/null +++ b/user-management/infra/aws/jest.config.js @@ -0,0 +1,8 @@ +module.exports = { + testEnvironment: 'node', + roots: ['/test'], + testMatch: ['**/*.test.ts'], + transform: { + '^.+\\.tsx?$': 'ts-jest' + } +}; diff --git a/user-management/infra/aws/lib/api.ts b/user-management/infra/aws/lib/api.ts new file mode 100644 index 00000000..05d20fd6 --- /dev/null +++ b/user-management/infra/aws/lib/api.ts @@ -0,0 +1,200 @@ +import { Vpc, SecurityGroup, Port } from "aws-cdk-lib/aws-ec2"; +import { Construct } from "constructs"; +import { SharedProps } from "./constructs/shared-props"; +import { + Cluster, + ContainerImage, + CpuArchitecture, + FargateService, + FargateTaskDefinition, + FirelensLogRouterType, + LogDriver, + LogDrivers, + OperatingSystemFamily, + Protocol, +} from "aws-cdk-lib/aws-ecs"; +import { Repository } from "aws-cdk-lib/aws-ecr"; +import { Topic } from "aws-cdk-lib/aws-sns"; +import { Queue } from "aws-cdk-lib/aws-sqs"; +import { + ApplicationLoadBalancer, + ApplicationTargetGroup, + TargetType, + ApplicationProtocol, + HealthCheck, + Protocol as HealthCheckProtocol, +} from "aws-cdk-lib/aws-elasticloadbalancingv2"; +import { Duration, CfnOutput } from "aws-cdk-lib"; +import { ApplicationLoadBalancedFargateService } from "aws-cdk-lib/aws-ecs-patterns"; + +export class ApiProps { + sharedProps: SharedProps; + vpc: Vpc; + cluster: Cluster; +} + +export class Api extends Construct { + stickerClaimedQueue: Queue; + stickerClaimedDLQ: Queue; + userRegisteredTopic: Topic; + constructor(scope: Construct, id: string, props: ApiProps) { + super(scope, id); + + const ecrRepository = Repository.fromRepositoryName( + this, + "UserServiceRepo", + "stickerlandia-user-management" + ); + + this.userRegisteredTopic = new Topic(this, "UserRegisteredTopic", { + topicName: `${props.sharedProps.serviceName}-${props.sharedProps.environment}-user-registered`, + }); + this.stickerClaimedDLQ = new Queue(this, "StickerClaimedDLQ", { + queueName: `${props.sharedProps.serviceName}-${props.sharedProps.environment}-sticker-claimed-dlq`, + }); + this.stickerClaimedQueue = new Queue(this, "StickerClaimedQueue", { + queueName: `${props.sharedProps.serviceName}-${props.sharedProps.environment}-sticker-claimed`, + deadLetterQueue: { + queue: this.stickerClaimedDLQ, + maxReceiveCount: 5, // Messages will be sent to DLQ after 5 failed attempts + }, + }); + + const applicationTaskDef = new FargateTaskDefinition( + this, + "UserServiceTaskDef", + { + runtimePlatform: { + operatingSystemFamily: OperatingSystemFamily.LINUX, + cpuArchitecture: CpuArchitecture.ARM64, + }, + cpu: 256, + memoryLimitMiB: 512, + } + ); + + const containerPort = 8080; + + const container = applicationTaskDef.addContainer("UserServiceContainer", { + image: ContainerImage.fromEcrRepository( + ecrRepository, + props.sharedProps.version + ), + portMappings: [ + { + containerPort: containerPort, + protocol: Protocol.TCP, + }, + ], + environment: { + ConnectionStrings__messaging: "", + ConnectionStrings__database: props.sharedProps.connectionString, + Aws__UserRegisteredTopicArn: this.userRegisteredTopic.topicArn, + Aws__StickerClaimedQueueUrl: this.stickerClaimedQueue.queueUrl, + Aws__StickerClaimedDLQUrl: this.stickerClaimedDLQ.queueUrl, + DRIVING: "ASPNET", + DRIVEN: "AWS", + DISABLE_SSL: "true", + }, + logging: LogDrivers.firelens({ + options: { + Name: "datadog", + Host: "http-intake.logs.datadoghq.eu", + TLS: "on", + dd_service: props.sharedProps.serviceName, + dd_source: "aspnet", + dd_message_key: "log", + dd_tags: `project:${props.sharedProps.serviceName}`, + provider: "ecs", + apikey: props.sharedProps.datadog.apiKey, + }, + }), + }); + container.addDockerLabel( + "com.datadoghq.tags.env", + props.sharedProps.environment + ); + container.addDockerLabel( + "com.datadoghq.tags.service", + props.sharedProps.serviceName + ); + container.addDockerLabel( + "com.datadoghq.tags.version", + props.sharedProps.version + ); + + container.addPortMappings({ + containerPort: containerPort, + protocol: Protocol.TCP, + }); + + applicationTaskDef.addContainer("datadog-agent", { + image: ContainerImage.fromRegistry("public.ecr.aws/datadog/agent:latest"), + portMappings: [ + { + containerPort: 8125, + protocol: Protocol.UDP, // Dogstatsd port + }, + { + containerPort: 8126, + protocol: Protocol.TCP, // APM port + }, + ], + containerName: "datadog-agent", + environment: { + DD_API_KEY: props.sharedProps.datadog.apiKey, + DD_SITE: props.sharedProps.datadog.site, + DD_APM_ENABLED: "true", + DD_LOGS_ENABLED: "true", + ECS_FARGATE: "true", + DD_APM_NON_LOCAL_TRAFFIC: "true", + DD_DOGSTATSD_NON_LOCAL_TRAFFIC: "true", + DD_APM_IGNORE_RESOURCES: `(GET) /api/users/v1/health`, + }, + }); + applicationTaskDef.addFirelensLogRouter("firelens-router", { + essential: true, + image: ContainerImage.fromRegistry("amazon/aws-for-fluent-bit:stable"), + firelensConfig: { + type: FirelensLogRouterType.FLUENTBIT, + options: { + enableECSLogMetadata: true, + }, + }, + }); + + this.userRegisteredTopic.grantPublish(applicationTaskDef.taskRole); + this.stickerClaimedQueue.grantSendMessages(applicationTaskDef.taskRole); + this.stickerClaimedDLQ.grantSendMessages(applicationTaskDef.taskRole); + this.stickerClaimedQueue.grantConsumeMessages(applicationTaskDef.taskRole); + this.stickerClaimedDLQ.grantConsumeMessages(applicationTaskDef.taskRole); + + const service = new ApplicationLoadBalancedFargateService( + this, + "UserServiceFargateService", + { + cluster: props.cluster, + taskDefinition: applicationTaskDef, + assignPublicIp: false, + publicLoadBalancer: true, + desiredCount: 1, + loadBalancerName: `${props.sharedProps.serviceName}-${props.sharedProps.environment}-user-service-lb`, + serviceName: `${props.sharedProps.serviceName}-${props.sharedProps.environment}-user-service`, + listenerPort: 80, + protocol: ApplicationProtocol.HTTP, + healthCheckGracePeriod: Duration.minutes(2), + circuitBreaker: { + rollback: true, + enable: true, + }, + } + ); + service.targetGroup.configureHealthCheck({ + path: "/api/users/v1/health", + interval: Duration.seconds(60), + timeout: Duration.seconds(5), + enabled: true, + healthyHttpCodes: "200-499", + }); + } +} diff --git a/user-management/infra/aws/lib/background-workers.ts b/user-management/infra/aws/lib/background-workers.ts new file mode 100644 index 00000000..565758cd --- /dev/null +++ b/user-management/infra/aws/lib/background-workers.ts @@ -0,0 +1,117 @@ +import { SubnetType, Vpc } from "aws-cdk-lib/aws-ec2"; +import { Construct } from "constructs"; +import { SharedProps } from "./constructs/shared-props"; +import { InstrumentedLambdaFunction } from "./constructs/instrumented-function"; +import { Duration } from "aws-cdk-lib"; +import { IQueue } from "aws-cdk-lib/aws-sqs"; +import { SqsEventSource } from "aws-cdk-lib/aws-lambda-event-sources"; +import { SqsDestination } from "aws-cdk-lib/aws-lambda-destinations"; +import { + EventBus, + Rule, + RuleTargetInput, + Schedule, +} from "aws-cdk-lib/aws-events"; +import { LambdaFunction, SqsQueue } from "aws-cdk-lib/aws-events-targets"; +import { ITopic } from "aws-cdk-lib/aws-sns"; + +export interface BackgroundWorkersProps { + sharedProps: SharedProps; + stickerClaimedQueue: IQueue; + stickerClaimedDLQ: IQueue; + userRegisteredTopic: ITopic; +} + +export class BackgroundWorkers extends Construct { + constructor(scope: Construct, id: string, props: BackgroundWorkersProps) { + super(scope, id); + + const eventBus = new EventBus(this, "UserManagementEventBus", { + eventBusName: `${props.sharedProps.serviceName}-${props.sharedProps.environment}-bus`, + }); + + const environmentVariables = { + POWERTOOLS_SERVICE_NAME: props.sharedProps.serviceName, + POWERTOOLS_LOG_LEVEL: + props.sharedProps.environment === "prod" ? "WARN" : "INFO", + ENV: props.sharedProps.environment, + ConnectionStrings__messaging: "", + ConnectionStrings__database: props.sharedProps.connectionString, + Aws__UserRegisteredTopicArn: props.userRegisteredTopic.topicArn, + Aws__StickerClaimedQueueUrl: props.stickerClaimedQueue.queueUrl, + Aws__StickerClaimedDLQUrl: props.stickerClaimedDLQ.queueUrl, + DRIVING: "ASPNET", + DRIVEN: "AWS", + DISABLE_SSL: "true", + }; + + const stickerClaimedWorker = new InstrumentedLambdaFunction( + this, + "StickerClaimedWorkerFunction", + { + sharedProps: props.sharedProps, + handler: + "Stickerlandia.UserManagement.Lambda::Stickerlandia.UserManagement.Lambda.Sqs_StickerClaimed_Generated::StickerClaimed", + buildDef: + "../../src/Stickerlandia.UserManagement.Lambda/", + functionName: "sticker-claimed-worker", + environment: environmentVariables, + memorySize: 1024, + timeout: Duration.seconds(25), + logLevel: props.sharedProps.environment === "prod" ? "WARN" : "INFO", + onFailure: new SqsDestination(props.stickerClaimedDLQ), + } + ); + + stickerClaimedWorker.function.addEventSource( + new SqsEventSource(props.stickerClaimedQueue, { + batchSize: 10, + reportBatchItemFailures: true, + }) + ); + + const rule = new Rule(this, "StickerClaimedEventRule", { + eventBus: eventBus, + ruleName: `${props.sharedProps.serviceName}-${props.sharedProps.environment}-sticker-claimed-rule`, + eventPattern: { + source: [`${props.sharedProps.environment}.stickers`], + detailType: ["users.stickerClaimed.v1"], + }, + }); + rule.addTarget(new SqsQueue(props.stickerClaimedQueue)); + + const outboxWorker = new InstrumentedLambdaFunction( + this, + "OutboxWorkerFunction", + { + sharedProps: props.sharedProps, + handler: + "Stickerlandia.UserManagement.Lambda::Stickerlandia.UserManagement.Lambda.OutboxFunctions_Worker_Generated::Worker", + buildDef: + "../../src/Stickerlandia.UserManagement.Lambda/", + functionName: "outbox-worker", + environment: environmentVariables, + memorySize: 1024, + timeout: Duration.seconds(50), + logLevel: props.sharedProps.environment === "prod" ? "WARN" : "INFO", + onFailure: undefined, + } + ); + props.userRegisteredTopic.grantPublish(outboxWorker.function); + + const outboxWorkerSchedule = new Rule(this, "OutboxWorkerSchedule", { + description: "Trigger outbox worker every 1 minute", + schedule: Schedule.rate(Duration.minutes(1)), + }); + + // Add the Lambda function as a target + outboxWorkerSchedule.addTarget( + new LambdaFunction(outboxWorker.function, { + retryAttempts: 2, + event: RuleTargetInput.fromObject({ + run: true, + }), + }) + ); + } +} diff --git a/user-management/infra/aws/lib/constructs/instrumented-function.ts b/user-management/infra/aws/lib/constructs/instrumented-function.ts new file mode 100644 index 00000000..1de9d3da --- /dev/null +++ b/user-management/infra/aws/lib/constructs/instrumented-function.ts @@ -0,0 +1,115 @@ +// +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. +// + +import { Construct } from "constructs"; +import { + Architecture, + IDestination, + LayerVersion, + Runtime, +} from "aws-cdk-lib/aws-lambda"; +import { Duration, RemovalPolicy, Stack, Tags } from "aws-cdk-lib"; +import { Alias } from "aws-cdk-lib/aws-kms"; +import { SharedProps } from "./shared-props"; +import { Effect, PolicyStatement } from "aws-cdk-lib/aws-iam"; +import { DotNetFunction } from "@aws-cdk/aws-lambda-dotnet"; + +export class InstrumentedLambdaFunctionProps { + sharedProps: SharedProps; + handler: string; + buildDef: string; + functionName: string; + environment: { [key: string]: string }; + timeout?: Duration; + memorySize?: number; + logLevel?: string; + onFailure: IDestination | undefined; +} + +export class InstrumentedLambdaFunction extends Construct { + function: DotNetFunction; + + constructor( + scope: Construct, + id: string, + props: InstrumentedLambdaFunctionProps + ) { + super(scope, id); + + const region = Stack.of(this).region; + + this.function = new DotNetFunction(this, props.functionName, { + runtime: Runtime.DOTNET_8, + functionName: `${props.sharedProps.serviceName}-${props.functionName}-${props.sharedProps.environment}`, + projectDir: props.buildDef, + handler: props.handler, + architecture: Architecture.ARM_64, + memorySize: props.memorySize ?? 512, + timeout: props.timeout ?? Duration.seconds(29), + onFailure: props.onFailure, + environment: { + POWERTOOLS_SERVICE_NAME: props.sharedProps.serviceName, + POWERTOOLS_LOG_LEVEL: + props.logLevel ?? props.sharedProps.environment === "prod" + ? "WARN" + : "INFO", + ENV: props.sharedProps.environment, + DEPLOYED_AT: new Date().toISOString(), + BUILD_ID: props.sharedProps.version, + TEAM: props.sharedProps.team, + DOMAIN: props.sharedProps.domain, + DD_API_KEY: props.sharedProps.datadog.apiKey, + DD_SITE: props.sharedProps.datadog.site, + DD_DATA_STREAMS_ENABLED: "true", + DD_APM_REPLACE_TAGS: `[ + { + "name": "function.request.headers.Authorization", + "pattern": "(?s).*", + "repl": "****" + }, + { + "name": "function.request.multiValueHeaders.Authorization", + "pattern": "(?s).*", + "repl": "****" + } +]`, + ...props.environment, + }, + layers: [ + LayerVersion.fromLayerVersionArn( + this, + "DatadogLayer", + `arn:aws:lambda:${region}:464622532012:layer:Datadog-Extension-ARM:80` + ), + LayerVersion.fromLayerVersionArn( + this, + "DDTraceLayer", + `arn:aws:lambda:${region}:464622532012:layer:dd-trace-dotnet-ARM:20` + ), + ], + }); + + this.function.addToRolePolicy( + new PolicyStatement({ + actions: [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + resources: ["arn:aws:logs:*:*:*"], + effect: Effect.DENY, + }) + ); + + const kmsAlias = Alias.fromAliasName(this, "SSMAlias", "aws/ssm"); + kmsAlias.grantDecrypt(this.function); + + Tags.of(this.function).add("service", props.sharedProps.serviceName); + Tags.of(this.function).add("env", props.sharedProps.environment); + Tags.of(this.function).add("version", props.sharedProps.version); + } +} diff --git a/user-management/infra/aws/lib/constructs/shared-props.ts b/user-management/infra/aws/lib/constructs/shared-props.ts new file mode 100644 index 00000000..b7bfcf77 --- /dev/null +++ b/user-management/infra/aws/lib/constructs/shared-props.ts @@ -0,0 +1,23 @@ +// +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. +// + +import { DatadogECSFargate, DatadogLambda } from "datadog-cdk-constructs-v2"; + +export interface SharedProps { + team: string; + domain: string; + serviceName: string; + environment: string; + version: string; + connectionString: string; + datadog: { + lambda: DatadogLambda; + ecsFargate: DatadogECSFargate; + apiKey: string; + site: string; + }; +} diff --git a/user-management/infra/aws/lib/network.ts b/user-management/infra/aws/lib/network.ts new file mode 100644 index 00000000..e1ec0d77 --- /dev/null +++ b/user-management/infra/aws/lib/network.ts @@ -0,0 +1,37 @@ +import { SubnetType, Vpc } from "aws-cdk-lib/aws-ec2"; +import { Construct } from "constructs"; +import { SharedProps } from "./constructs/shared-props"; + +export interface NetworkProps { + networkName: string; +} + +export class Network extends Construct { + vpc: Vpc; + constructor(scope: Construct, id: string, props: NetworkProps) { + super(scope, id); + + this.vpc = new Vpc(this, "Vpc", { + vpcName: props.networkName, + maxAzs: 2, + natGateways: 1, // Use a single NAT Gateway for cost efficiency + subnetConfiguration: [ + { + cidrMask: 24, + name: "Public", + subnetType: SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: "Private", + subnetType: SubnetType.PRIVATE_WITH_EGRESS, + }, + { + cidrMask: 24, + name: "Isolated", + subnetType: SubnetType.PRIVATE_ISOLATED, + }, + ], + }); + } +} diff --git a/user-management/infra/aws/lib/user-service-stack.ts b/user-management/infra/aws/lib/user-service-stack.ts new file mode 100644 index 00000000..74d84a07 --- /dev/null +++ b/user-management/infra/aws/lib/user-service-stack.ts @@ -0,0 +1,96 @@ +import * as cdk from "aws-cdk-lib"; +import { Construct } from "constructs"; +import { SharedProps } from "./constructs/shared-props"; +import { DatadogECSFargate, DatadogLambda } from "datadog-cdk-constructs-v2"; +import { Network } from "./network"; +import { Api } from "./api"; +import { Cluster } from "aws-cdk-lib/aws-ecs"; +import { BackgroundWorkers } from "./background-workers"; + +// import * as sqs from 'aws-cdk-lib/aws-sqs'; + +export class UserServiceStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const serviceName = "user-service"; + const environment = process.env.ENV || "dev"; + const version = process.env.VERSION || "latest"; + + const network = new Network(this, "Network", { + networkName: `${serviceName}-${environment}-vpc`, + }); + + const ddSite = process.env.DD_SITE || "datadoghq.com"; + const ddApiKey = process.env.DD_API_KEY || ""; + + const cluster = new Cluster(this, "ApiCluster", { + vpc: network.vpc, + clusterName: `${serviceName}-${environment}`, + }); + + const sharedProps: SharedProps = { + connectionString: + "Server=ep-divine-snow-abgipduo-pooler.eu-west-2.aws.neon.tech;Port=5432;Database=stickerlandia-users;User Id=stickerlandia-users_owner;Password=npg_buwe2PoK1NgV;sslmode=require;", + serviceName: "user-service", + environment: process.env.ENV || "dev", + version: process.env.VERSION || "latest", + team: "users", + domain: "users", + datadog: { + apiKey: ddApiKey, + site: ddSite, + lambda: new DatadogLambda(this, "DatadogLambda", { + apiKey: ddApiKey, + site: ddSite, + }), + ecsFargate: new DatadogECSFargate({ + // One of the following 3 apiKey params are required + apiKey: ddApiKey, + cpu: 256, + memoryLimitMiB: 512, + isDatadogEssential: true, + isDatadogDependencyEnabled: true, + site: ddSite, + clusterName: cluster.clusterName, + environmentVariables: {}, + dogstatsd: { + isEnabled: true, + }, + apm: { + isEnabled: true, + traceInferredProxyServices: true, + }, + logCollection: { + isEnabled: true, + fluentbitConfig: { + firelensOptions: { + enableECSLogMetadata: true, + }, + logDriverConfig: { + hostEndpoint: `http-intake.logs.${ddSite}`, + serviceName: serviceName, + }, + }, + }, + env: environment, + service: serviceName, + version: version, + }), + }, + }; + + const api = new Api(this, "Api", { + sharedProps: sharedProps, + vpc: network.vpc, + cluster: cluster, + }); + + const backgroundWorkers = new BackgroundWorkers(this, "BackgroundWorkers", { + sharedProps: sharedProps, + stickerClaimedQueue: api.stickerClaimedQueue, + stickerClaimedDLQ: api.stickerClaimedDLQ, + userRegisteredTopic: api.userRegisteredTopic, + }); + } +} diff --git a/user-management/infra/aws/package-lock.json b/user-management/infra/aws/package-lock.json new file mode 100644 index 00000000..d043964d --- /dev/null +++ b/user-management/infra/aws/package-lock.json @@ -0,0 +1,4200 @@ +{ + "name": "aws-ts", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "aws-ts", + "version": "0.1.0", + "dependencies": { + "@aws-cdk/aws-lambda-dotnet": "^0.0.6", + "aws-cdk-lib": "2.199.0", + "constructs": "^10.0.0", + "datadog-cdk-constructs-v2": "^2.6.0" + }, + "bin": { + "aws-ts": "bin/aws-ts.js" + }, + "devDependencies": { + "@types/jest": "^29.5.14", + "@types/node": "22.7.9", + "aws-cdk": "2.1018.0", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "ts-node": "^10.9.2", + "typescript": "~5.6.3" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@aws-cdk/asset-awscli-v1": { + "version": "2.2.237", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.237.tgz", + "integrity": "sha512-OlXylbXI52lboFVJBFLae+WB99qWmI121x/wXQHEMj2RaVNVbWE+OAHcDk2Um1BitUQCaTf9ki57B0Fuqx0Rvw==" + }, + "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz", + "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==" + }, + "node_modules/@aws-cdk/aws-lambda-dotnet": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@aws-cdk/aws-lambda-dotnet/-/aws-lambda-dotnet-0.0.6.tgz", + "integrity": "sha512-25/fAeKC0Lt2Vd0SrJ02enJJRNlGaSAuXF1xfXCn3ADc6jvCYydM1XNqVy2kbrwmvVWGdHqGCG0unZrp1v2JxA==", + "engines": { + "node": ">= 18.12.0" + }, + "peerDependencies": { + "aws-cdk-lib": "^2.129.0", + "constructs": "^10.0.5" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema": { + "version": "41.2.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-41.2.0.tgz", + "integrity": "sha512-JaulVS6z9y5+u4jNmoWbHZRs9uGOnmn/ktXygNWKNu1k6lF3ad4so3s18eRu15XCbUIomxN9WPYT6Ehh7hzONw==", + "bundleDependencies": [ + "jsonschema", + "semver" + ], + "dependencies": { + "jsonschema": "~1.4.1", + "semver": "^7.7.1" + }, + "engines": { + "node": ">= 14.15.0" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { + "version": "1.4.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { + "version": "7.7.1", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "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.27.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", + "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", + "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.4", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.27.4", + "@babel/types": "^7.27.3", + "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/generator": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", + "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.27.5", + "@babel/types": "^7.27.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "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-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "dev": true, + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", + "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.27.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "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.27.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", + "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", + "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "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/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/node": { + "version": "22.7.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.9.tgz", + "integrity": "sha512-jrTfRC7FM6nChvU7X2KqcrgquofrWLFDeYC1hKfwNWomVvrn7JIksqf344WN2X/y8xrgqBd2dJATZV4GbatBfg==", + "dev": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true + }, + "node_modules/aws-cdk": { + "version": "2.1018.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1018.0.tgz", + "integrity": "sha512-sppVsNtFJTW4wawS/PBudHCSNHb8xwaZ2WX1mpsfwaPNyTWm0eSUVJsRbRiRBu9O/Us8pgrd4woUjfM1lgD7Kw==", + "dev": true, + "bin": { + "cdk": "bin/cdk" + }, + "engines": { + "node": ">= 18.0.0" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/aws-cdk-lib": { + "version": "2.199.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.199.0.tgz", + "integrity": "sha512-hAZHdb7bPHepIGpuyg0jS/F3toY7VRvJDqxo4+C2cYY5zvktGP3lgcC9ukE2ehxYU1Pa9YOAehEDIxrita0Hvw==", + "bundleDependencies": [ + "@balena/dockerignore", + "case", + "fs-extra", + "ignore", + "jsonschema", + "minimatch", + "punycode", + "semver", + "table", + "yaml", + "mime-types" + ], + "dependencies": { + "@aws-cdk/asset-awscli-v1": "2.2.237", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", + "@aws-cdk/cloud-assembly-schema": "^41.2.0", + "@balena/dockerignore": "^1.0.2", + "case": "1.6.3", + "fs-extra": "^11.3.0", + "ignore": "^5.3.2", + "jsonschema": "^1.5.0", + "mime-types": "^2.1.35", + "minimatch": "^3.1.2", + "punycode": "^2.3.1", + "semver": "^7.7.2", + "table": "^6.9.0", + "yaml": "1.10.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "constructs": "^10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { + "version": "1.0.2", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/aws-cdk-lib/node_modules/ajv": { + "version": "8.17.1", + "inBundle": 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/aws-cdk-lib/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/astral-regex": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/balanced-match": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/brace-expansion": { + "version": "1.1.11", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/aws-cdk-lib/node_modules/case": { + "version": "1.6.3", + "inBundle": true, + "license": "(MIT OR GPL-3.0-or-later)", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/concat-map": { + "version": "0.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { + "version": "3.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-uri": { + "version": "3.0.6", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "inBundle": true, + "license": "BSD-3-Clause" + }, + "node_modules/aws-cdk-lib/node_modules/fs-extra": { + "version": "11.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/aws-cdk-lib/node_modules/graceful-fs": { + "version": "4.2.11", + "inBundle": true, + "license": "ISC" + }, + "node_modules/aws-cdk-lib/node_modules/ignore": { + "version": "5.3.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/jsonfile": { + "version": "6.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/jsonschema": { + "version": "1.5.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { + "version": "4.4.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/mime-db": { + "version": "1.52.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/mime-types": { + "version": "2.1.35", + "inBundle": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/minimatch": { + "version": "3.1.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/punycode": { + "version": "2.3.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/aws-cdk-lib/node_modules/require-from-string": { + "version": "2.0.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/semver": { + "version": "7.7.2", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aws-cdk-lib/node_modules/slice-ansi": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/string-width": { + "version": "4.2.3", + "inBundle": 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/aws-cdk-lib/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/table": { + "version": "6.9.0", + "inBundle": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/universalify": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/yaml": { + "version": "1.10.2", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", + "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", + "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" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001718", + "electron-to-chromium": "^1.5.160", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001723", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001723.tgz", + "integrity": "sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==", + "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" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/constructs": { + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.4.2.tgz", + "integrity": "sha512-wsNxBlAott2qg8Zv87q3eYZYgheb9lchtBfjHzzLHtXbttwSrHPs1NNQbBrmbb1YZvYg2+Vh0Dor76w4mFxJkA==" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/datadog-cdk-constructs-v2": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/datadog-cdk-constructs-v2/-/datadog-cdk-constructs-v2-2.6.0.tgz", + "integrity": "sha512-dyRopMdZF6KMojnAxtP3wFHR3zToCwfXtD7gUu7SwvJuYVj6Ua/iW4W2Vhrx3ybONB6uspixR/bBpGdy3/TGrA==", + "bundleDependencies": [ + "loglevel" + ], + "dependencies": { + "loglevel": "^1.9.2" + }, + "engines": { + "node": ">= 18.18.0" + }, + "peerDependencies": { + "aws-cdk-lib": "^2.177.0", + "constructs": "^10.0.5" + } + }, + "node_modules/datadog-cdk-constructs-v2/node_modules/loglevel": { + "version": "1.9.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", + "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.168", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.168.tgz", + "integrity": "sha512-RUNQmFLNIWVW6+z32EJQ5+qx8ci6RGvdtDC0Ls+F89wz6I2AthpXF0w0DIrn2jpLX0/PU9ZCo+Qp7bg/EckJmA==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "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" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "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": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "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" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "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/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "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" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-jest": { + "version": "29.4.0", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.0.tgz", + "integrity": "sha512-d423TJMnJGu80/eSgfQ5w/R+0zFJvdtTxwtF9KzFFunOpSeD+79lHJQIiAhluJoyGRbvj9NZJsl9WjCUo0ND7Q==", + "dev": true, + "dependencies": { + "bs-logger": "^0.2.6", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "^2.1.0", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.2", + "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" + }, + "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_modules/ts-jest/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": 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" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "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" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "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" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/user-management/infra/aws/package.json b/user-management/infra/aws/package.json new file mode 100644 index 00000000..d25f0531 --- /dev/null +++ b/user-management/infra/aws/package.json @@ -0,0 +1,28 @@ +{ + "name": "aws-ts", + "version": "0.1.0", + "bin": { + "aws-ts": "bin/aws-ts.js" + }, + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "test": "jest", + "cdk": "cdk" + }, + "devDependencies": { + "@types/jest": "^29.5.14", + "@types/node": "22.7.9", + "aws-cdk": "2.1018.0", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "ts-node": "^10.9.2", + "typescript": "~5.6.3" + }, + "dependencies": { + "@aws-cdk/aws-lambda-dotnet": "^0.0.6", + "aws-cdk-lib": "2.199.0", + "constructs": "^10.0.0", + "datadog-cdk-constructs-v2": "^2.6.0" + } +} diff --git a/user-management/infra/aws/test/aws-ts.test.ts b/user-management/infra/aws/test/aws-ts.test.ts new file mode 100644 index 00000000..6975e2ff --- /dev/null +++ b/user-management/infra/aws/test/aws-ts.test.ts @@ -0,0 +1,17 @@ +// import * as cdk from 'aws-cdk-lib'; +// import { Template } from 'aws-cdk-lib/assertions'; +// import * as AwsTs from '../lib/aws-ts-stack'; + +// example test. To run these tests, uncomment this file along with the +// example resource in lib/aws-ts-stack.ts +test('SQS Queue Created', () => { +// const app = new cdk.App(); +// // WHEN +// const stack = new AwsTs.AwsTsStack(app, 'MyTestStack'); +// // THEN +// const template = Template.fromStack(stack); + +// template.hasResourceProperties('AWS::SQS::Queue', { +// VisibilityTimeout: 300 +// }); +}); diff --git a/user-management/infra/aws/tsconfig.json b/user-management/infra/aws/tsconfig.json new file mode 100644 index 00000000..28bb557f --- /dev/null +++ b/user-management/infra/aws/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": [ + "es2022" + ], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "typeRoots": [ + "./node_modules/@types" + ] + }, + "exclude": [ + "node_modules", + "cdk.out" + ] +} diff --git a/user-management/infra/.terraform.lock.hcl b/user-management/infra/azure/.terraform.lock.hcl similarity index 100% rename from user-management/infra/.terraform.lock.hcl rename to user-management/infra/azure/.terraform.lock.hcl diff --git a/user-management/infra/azure/application.tf b/user-management/infra/azure/application.tf new file mode 100644 index 00000000..e06d10e0 --- /dev/null +++ b/user-management/infra/azure/application.tf @@ -0,0 +1,128 @@ +resource "azurerm_container_app_environment" "user_service_environment" { + name = var.env + location = azurerm_resource_group.rg.location + resource_group_name = azurerm_resource_group.rg.name + + tags = { + source = "terraform" + env = var.env + } +} + +resource "azurerm_user_assigned_identity" "app_identity" { + location = azurerm_resource_group.rg.location + name = "userServiceAppIdentity-${var.env}" + resource_group_name = azurerm_resource_group.rg.name +} + +resource "azurerm_container_app" "user-management-api" { + name = "user-management" + container_app_environment_id = azurerm_container_app_environment.user_service_environment.id + resource_group_name = azurerm_resource_group.rg.name + revision_mode = "Single" + identity { + identity_ids = [azurerm_user_assigned_identity.app_identity.id] + type = "UserAssigned" + } + ingress { + external_enabled = true + target_port = 8080 + traffic_weight { + percentage = 100 + latest_revision = true + } + } + template { + min_replicas = 1 + max_replicas = 1 + container { + name = "application" + image = "public.ecr.aws/h1j5s6w8/stickerlandia-user-management:${var.app_version}" + cpu = 0.25 + memory = "0.5Gi" + env { + name = "ConnectionStrings__database" + value = var.database_connection_string + } + env { + name = "ConnectionStrings__messaging" + value = azurerm_servicebus_namespace.stickerlandia_users_service_bus.default_primary_connection_string + } + env { + name = "DRIVING" + value = "ASPNET" + } + env { + name = "DRIVEN" + value = "AZURE" + } + env { + name = "DISABLE_SSL" + value = "true" + } + } + container { + name = "datadog" + image = "index.docker.io/datadog/serverless-init:latest" + cpu = 0.25 + memory = "0.5Gi" + + env { + name = "DD_SITE" + value = var.dd_site + } + env { + name = "DD_API_KEY" + value = var.dd_api_key + } + env { + name = "DD_ENV" + value = var.env + } + env { + name = "DD_VERSION" + value = var.app_version + } + env { + name = "DD_SERVICE" + value = "user-management" + } + env { + name = "DD_LOGS_ENABLED" + value = "true" + } + env { + name = "DD_LOGS_INJECTION" + value = "true" + } + env { + name = "DD_APM_COMPUTE_STATS_BY_SPAN_KIND" + value = "true" + } + env { + name = "DD_APM_PEER_TAGS_AGGREGATION" + value = "true" + } + env { + name = "DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED" + value = "true" + } + env { + name = "DD_APM_IGNORE_RESOURCES" + value = "/opentelemetry.proto.collector.trace.v1.TraceService/Export$" + } + env { + name = "DD_OTLP_CONFIG_RECEIVER_PROTOCOLS_GRPC_ENDPOINT" + value = "0.0.0.0:4317" + } + env { + name = "DD_AZURE_SUBSCRIPTION_ID" + value = data.azurerm_subscription.primary.subscription_id + } + env { + name = "DD_AZURE_RESOURCE_GROUP" + value = azurerm_resource_group.rg.name + } + } + } +} \ No newline at end of file diff --git a/user-management/infra/azure/data.tf b/user-management/infra/azure/data.tf new file mode 100644 index 00000000..4c51099f --- /dev/null +++ b/user-management/infra/azure/data.tf @@ -0,0 +1,2 @@ +data "azurerm_subscription" "primary" { +} diff --git a/user-management/infra/main.tf b/user-management/infra/azure/main.tf similarity index 52% rename from user-management/infra/main.tf rename to user-management/infra/azure/main.tf index 104d4898..d02fd254 100644 --- a/user-management/infra/main.tf +++ b/user-management/infra/azure/main.tf @@ -1,7 +1,7 @@ resource "azurerm_resource_group" "rg" { location = var.location name = "${var.resourceGroupName}-${var.env}" - tags = local.tags + tags = local.tags } resource "azurerm_storage_account" "functions_storage_account" { @@ -10,7 +10,7 @@ resource "azurerm_storage_account" "functions_storage_account" { location = azurerm_resource_group.rg.location account_tier = "Standard" account_replication_type = "LRS" - tags = local.tags + tags = local.tags } resource "azurerm_storage_container" "functions_storage_container" { @@ -31,24 +31,24 @@ resource "azurerm_service_plan" "functions_app_service_plan" { location = azurerm_resource_group.rg.location os_type = "Linux" sku_name = "Y1" - tags = local.tags + tags = local.tags } locals { environment_variables_base = { - DD_API_KEY = var.dd_api_key - DD_SITE = var.dd_site - DD_ENV = var.env - DD_SERVICE = "stickerlandia-users" - DD_VERSION = "latest" - WEBSITE_RUN_FROM_PACKAGE = 1 + DD_API_KEY = var.dd_api_key + DD_SITE = var.dd_site + DD_ENV = var.env + DD_SERVICE = "stickerlandia-users" + DD_VERSION = "latest" + WEBSITE_RUN_FROM_PACKAGE = 1 } environment_variables_flex_base = { - DD_API_KEY = var.dd_api_key - DD_SITE = var.dd_site - DD_ENV = var.env - DD_SERVICE = "stickerlandia-users" - DD_VERSION = "latest" + DD_API_KEY = var.dd_api_key + DD_SITE = var.dd_site + DD_ENV = var.env + DD_SERVICE = "stickerlandia-users" + DD_VERSION = "latest" } dotnet_tracer_home = "/home/site/wwwroot/datadog" environment_variables_dotnet = { @@ -63,11 +63,13 @@ locals { app_settings = { "ConnectionStrings__messaging" = azurerm_servicebus_namespace.stickerlandia_users_service_bus.default_primary_connection_string - "ConnectionStrings__database" = "AccountEndpoint=${azurerm_cosmosdb_account.user_management.endpoint};AccountKey=${azurerm_cosmosdb_account.user_management.primary_key};" + "ConnectionStrings__database" = var.database_connection_string, + "DRIVING" : "AZURE", + "DRIVEN" : "AZURE", } tags = { - env = var.env + env = var.env service = "stickerlandia-users" version = "latest" } @@ -83,15 +85,6 @@ locals { ) } -resource "azurerm_application_insights" "ai" { - name = "ai-stickerlandia-users-${var.env}" - location = azurerm_resource_group.rg.location - resource_group_name = azurerm_resource_group.rg.name - application_type = "web" - workspace_id = "/subscriptions/4ee0be82-09a4-484d-a7e7-789e02eb88fe/resourceGroups/ai_ai-stickerlandia-users-local_84db782e-6908-4bbe-8f2a-3811f89893b7_managed/providers/Microsoft.OperationalInsights/workspaces/managed-ai-stickerlandia-users-local-ws" - tags = local.tags -} - resource "azurerm_linux_function_app" "function_app" { name = "stickerlandia-users-functions-${var.env}" resource_group_name = azurerm_resource_group.rg.name @@ -106,11 +99,9 @@ resource "azurerm_linux_function_app" "function_app" { use_dotnet_isolated_runtime = true dotnet_version = "8.0" } - application_insights_connection_string = azurerm_application_insights.ai.connection_string - application_insights_key = azurerm_application_insights.ai.instrumentation_key } app_settings = local.environment_variables - tags = local.tags + tags = local.tags lifecycle { ignore_changes = [ @@ -120,42 +111,3 @@ resource "azurerm_linux_function_app" "function_app" { ] } } - -resource "azurerm_service_plan" "flex_service_plan" { - name = "stickerlandia-users-flex-${var.env}" - resource_group_name = azurerm_resource_group.rg.name - location = azurerm_resource_group.rg.location - sku_name = "FC1" - os_type = "Linux" -} - -resource "azurerm_function_app_flex_consumption" "flex_function_app" { - name = "stickerlandia-users-flex-${var.env}" - resource_group_name = azurerm_resource_group.rg.name - location = azurerm_resource_group.rg.location - service_plan_id = azurerm_service_plan.flex_service_plan.id - - storage_container_type = "blobContainer" - storage_container_endpoint = "${azurerm_storage_account.functions_storage_account.primary_blob_endpoint}${azurerm_storage_container.functions_flex_storage_container.name}" - storage_authentication_type = "StorageAccountConnectionString" - storage_access_key = azurerm_storage_account.functions_storage_account.primary_access_key - runtime_name = "dotnet-isolated" - runtime_version = "8.0" - maximum_instance_count = 40 - instance_memory_in_mb = 2048 - - app_settings = local.flex_environment_variables - tags = local.tags - - site_config { - application_insights_connection_string = azurerm_application_insights.ai.connection_string - application_insights_key = azurerm_application_insights.ai.instrumentation_key - } - lifecycle { - ignore_changes = [ - tags["hidden-link: /app-insights-conn-string"], - tags["hidden-link: /app-insights-instrumentation-key"], - tags["hidden-link: /app-insights-resource-id"] - ] - } -} \ No newline at end of file diff --git a/user-management/infra/messaging.tf b/user-management/infra/azure/messaging.tf similarity index 100% rename from user-management/infra/messaging.tf rename to user-management/infra/azure/messaging.tf diff --git a/user-management/infra/providers.tf b/user-management/infra/azure/providers.tf similarity index 100% rename from user-management/infra/providers.tf rename to user-management/infra/azure/providers.tf diff --git a/user-management/infra/azure/variables.tf b/user-management/infra/azure/variables.tf new file mode 100644 index 00000000..48eedb99 --- /dev/null +++ b/user-management/infra/azure/variables.tf @@ -0,0 +1,33 @@ +variable "subscription_id" { + description = "The Azure Subscription ID in which all resources in this example should be created." +} + +variable "resourceGroupName" { + description = "The Azure Resource Group name in which all resources in this example should be created." +} + +variable "database_connection_string" { + description = "The connection string to the database used by the application." +} + +variable "location" { + description = "The Azure Region in which all resources in this example should be created." +} + +variable "env" { + description = "The environment you are deploying to" +} + +variable "app_version" { + description = "The version of the application to deploy" + default = "latest" +} + +variable "dd_api_key" { + description = "The Datadog API key" +} + +variable "dd_site" { + default = "datadoghq.com" + description = "The Datadog site" +} \ No newline at end of file diff --git a/user-management/infra/database.tf b/user-management/infra/database.tf deleted file mode 100644 index 2b0efb99..00000000 --- a/user-management/infra/database.tf +++ /dev/null @@ -1,32 +0,0 @@ -resource "azurerm_cosmosdb_account" "user_management" { - name = "stickerlandia-users" - location = azurerm_resource_group.rg.location - resource_group_name = azurerm_resource_group.rg.name - offer_type = "Standard" - kind = "GlobalDocumentDB" # SQL API - - free_tier_enabled = true - - consistency_policy { - consistency_level = "Session" - max_interval_in_seconds = 5 - max_staleness_prefix = 100 - } - - geo_location { - location = azurerm_resource_group.rg.location - failover_priority = 0 - } - - tags = { - environment = var.env - application = "stickerlandia-user-management" - } -} - -resource "azurerm_cosmosdb_sql_database" "user_database" { - name = "users" - resource_group_name = azurerm_resource_group.rg.name - account_name = azurerm_cosmosdb_account.user_management.name - # Free tier automatically adjusts throughput settings -} diff --git a/user-management/infra/variables.tf b/user-management/infra/variables.tf deleted file mode 100644 index 756c0190..00000000 --- a/user-management/infra/variables.tf +++ /dev/null @@ -1,56 +0,0 @@ -variable "subscription_id" { - description = "The Azure Subscription ID in which all resources in this example should be created." -} - -variable "resourceGroupName" { - description = "The Azure Resource Group name in which all resources in this example should be created." -} - -variable "location" { - description = "The Azure Region in which all resources in this example should be created." -} - -variable "functionAppName" { - description = "Your Flex Consumption app name." -} - -variable "functionPlanName" { - description = "Your Flex Consumption plan name." -} - -variable "storageAccountName" { - description = "Your storage account name." -} - -variable "env" { - description = "The environment you are deploying to" -} - -variable "maximumInstanceCount" { - default = 100 - description = "The maximum instance count for the app" -} - -variable "instanceMemoryMB" { - default = 2048 - description = "The instance memory for the instances of the app: 2048 or 4096" -} - -variable "functionAppRuntime" { - default = "dotnet-isolated" - description = "The runtime for your app. One of the following: 'dotnet-isolated', 'python', 'java', 'node', 'powershell'" -} - -variable "functionAppRuntimeVersion" { - default = "9.0" - description = "The runtime and version for your app. One of the following: '3.10', '3.11', '7.4', '8.0', '10', '11', '17', '20'" -} - -variable "dd_api_key" { - description = "The Datadog API key" -} - -variable "dd_site" { - default = "datadoghq.com" - description = "The Datadog site" -} \ No newline at end of file diff --git a/user-management/src/Stickerlandia.UserManagement.AWS/AwsConfiguration.cs b/user-management/src/Stickerlandia.UserManagement.AWS/AwsConfiguration.cs new file mode 100644 index 00000000..b10c2e6c --- /dev/null +++ b/user-management/src/Stickerlandia.UserManagement.AWS/AwsConfiguration.cs @@ -0,0 +1,14 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2025 Datadog, Inc. + +namespace Stickerlandia.UserManagement.AWS; + +public class AwsConfiguration +{ + public string UserRegisteredTopicArn { get; set; } = string.Empty; + + public string StickerClaimedQueueUrl { get; set; } = string.Empty; + + public string StickerClaimedDLQUrl { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/user-management/src/Stickerlandia.UserManagement.AWS/Class1.cs b/user-management/src/Stickerlandia.UserManagement.AWS/Class1.cs deleted file mode 100644 index e375d8be..00000000 --- a/user-management/src/Stickerlandia.UserManagement.AWS/Class1.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Stickerlandia.UserManagement.AWS; - -public class Class1 -{ -} \ No newline at end of file diff --git a/user-management/src/Stickerlandia.UserManagement.AWS/ServiceExtensions.cs b/user-management/src/Stickerlandia.UserManagement.AWS/ServiceExtensions.cs new file mode 100644 index 00000000..17001b54 --- /dev/null +++ b/user-management/src/Stickerlandia.UserManagement.AWS/ServiceExtensions.cs @@ -0,0 +1,32 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2025 Datadog, Inc. + +using Amazon.SimpleNotificationService; +using Amazon.SQS; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Stickerlandia.UserManagement.Agnostic; +using Stickerlandia.UserManagement.Core; + +namespace Stickerlandia.UserManagement.AWS; + +public static class ServiceExtensions +{ + public static IServiceCollection AddAwsAdapters(this IServiceCollection services, IConfiguration configuration) + { + services.AddAWSLambdaHosting(LambdaEventSource.RestApi); + services.AddPostgresAuthServices(configuration); + + services.Configure( + configuration.GetSection("Aws")); + + services.AddSingleton(); + services.AddSingleton(new AmazonSQSClient()); + services.AddSingleton(new AmazonSimpleNotificationServiceClient()); + + services.AddSingleton(); + + return services; + } +} \ No newline at end of file diff --git a/user-management/src/Stickerlandia.UserManagement.AWS/SnsEventPublisher.cs b/user-management/src/Stickerlandia.UserManagement.AWS/SnsEventPublisher.cs new file mode 100644 index 00000000..6b444756 --- /dev/null +++ b/user-management/src/Stickerlandia.UserManagement.AWS/SnsEventPublisher.cs @@ -0,0 +1,73 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2025 Datadog, Inc. + +using Amazon.SimpleNotificationService; +using Amazon.SimpleNotificationService.Model; +using CloudNative.CloudEvents; +using CloudNative.CloudEvents.SystemTextJson; +using Datadog.Trace; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Saunter.Attributes; +using Stickerlandia.UserManagement.Core; +using Stickerlandia.UserManagement.Core.RegisterUser; + +namespace Stickerlandia.UserManagement.AWS; + +[AsyncApi] +public class SnsEventPublisher( + ILogger logger, + AmazonSimpleNotificationServiceClient client, + IOptions awsConfiguration) : IUserEventPublisher +{ + [Channel("users.userRegistered.v1")] + [PublishOperation(typeof(UserRegisteredEvent))] + public async Task PublishUserRegisteredEventV1(UserRegisteredEvent userRegisteredEvent) + { + var cloudEvent = new CloudEvent(CloudEventsSpecVersion.V1_0) + { + Id = Guid.NewGuid().ToString(), + Source = new Uri("https://stickerlandia.com"), + Type = userRegisteredEvent.EventName, + Time = DateTime.UtcNow, + Data = userRegisteredEvent + }; + + await Publish(cloudEvent); + } + + private async Task Publish(CloudEvent cloudEvent) + { + var activeSpan = Tracer.Instance.ActiveScope?.Span; + IScope? processScope = null; + + try + { + if (activeSpan != null) + { + processScope = Tracer.Instance.StartActive($"publish {cloudEvent.Type}", new SpanCreationSettings + { + Parent = activeSpan.Context + }); + + cloudEvent.SetAttributeFromString("traceparent", $"00-{activeSpan.TraceId}-{activeSpan.SpanId}-01"); + } + + var formatter = new JsonEventFormatter(); + var data = formatter.EncodeStructuredModeMessage(cloudEvent, out _); + var jsonString = System.Text.Encoding.UTF8.GetString(data.Span); + + await client.PublishAsync(new PublishRequest(awsConfiguration.Value.UserRegisteredTopicArn, jsonString)); + } + catch (Exception ex) + { + logger.LogError(ex, "Failure publishing message"); + processScope?.Span.SetException(ex); + } + finally + { + processScope?.Close(); + } + } +} \ No newline at end of file diff --git a/user-management/src/Stickerlandia.UserManagement.AWS/SqsStickerClaimedWorker.cs b/user-management/src/Stickerlandia.UserManagement.AWS/SqsStickerClaimedWorker.cs new file mode 100644 index 00000000..533dd513 --- /dev/null +++ b/user-management/src/Stickerlandia.UserManagement.AWS/SqsStickerClaimedWorker.cs @@ -0,0 +1,94 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2025 Datadog, Inc. + +using System.Text.Json; +using Amazon.SQS; +using Amazon.SQS.Model; +using Datadog.Trace; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Saunter.Attributes; +using Stickerlandia.UserManagement.Core; +using Stickerlandia.UserManagement.Core.StickerClaimedEvent; + +namespace Stickerlandia.UserManagement.AWS; + +[AsyncApi] +public class SqsStickerClaimedWorker : IMessagingWorker +{ + private readonly IServiceScopeFactory _serviceScopeFactory; + private readonly ILogger _logger; + private readonly AmazonSQSClient _sqsClient; + private readonly IOptions _awsConfiguration; + + public SqsStickerClaimedWorker(ILogger logger, + AmazonSQSClient sqsClient, IServiceScopeFactory serviceScopeFactory, + IOptions awsConfiguration) + { + _logger = logger; + _sqsClient = sqsClient; + _serviceScopeFactory = serviceScopeFactory; + _awsConfiguration = awsConfiguration; + } + + [Channel("users.stickerClaimed.v1")] + [SubscribeOperation(typeof(StickerClaimedEventV1))] + private async Task ProcessMessageAsync(Message message) + { + using var processSpan = Tracer.Instance.StartActive($"process users.stickerClaimed.v1"); + + using var scope = _serviceScopeFactory.CreateScope(); + var handler = scope.ServiceProvider.GetRequiredService(); + + var evtData = JsonSerializer.Deserialize(message.Body); + + if (evtData == null) + { + await _sqsClient.SendMessageAsync(_awsConfiguration.Value.StickerClaimedDLQUrl, message.Body); + await _sqsClient.DeleteMessageAsync(_awsConfiguration.Value.StickerClaimedQueueUrl, message.ReceiptHandle); + return; + } + + // Process your message here + try + { + await handler.Handle(evtData!); + } + catch (InvalidUserException ex) + { + _logger.LogWarning(ex, "User with account in this event not found"); + await _sqsClient.SendMessageAsync(_awsConfiguration.Value.StickerClaimedDLQUrl, message.Body); + } + + await _sqsClient.DeleteMessageAsync(_awsConfiguration.Value.StickerClaimedQueueUrl, message.ReceiptHandle); + } + + public Task StartAsync() + { + _logger.LogInformation("Starting SQS processor"); + + return Task.CompletedTask; + } + + public async Task PollAsync(CancellationToken cancellationToken) + { + var request = new ReceiveMessageRequest + { + QueueUrl = _awsConfiguration.Value.StickerClaimedQueueUrl, + WaitTimeSeconds = 20, // Enable long polling with a 20-second wait time + MaxNumberOfMessages = 10 // Fetch up to 10 messages per call + }; + + var messages = await _sqsClient.ReceiveMessageAsync(request, cancellationToken); + foreach (var message in messages.Messages) await ProcessMessageAsync(message); + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Stopping SQS processor"); + // No specific stop logic for SQS, as it is a pull-based system. + await Task.CompletedTask; + } +} \ No newline at end of file diff --git a/user-management/src/Stickerlandia.UserManagement.AWS/Stickerlandia.UserManagement.AWS.csproj b/user-management/src/Stickerlandia.UserManagement.AWS/Stickerlandia.UserManagement.AWS.csproj index a688b61a..9598379f 100644 --- a/user-management/src/Stickerlandia.UserManagement.AWS/Stickerlandia.UserManagement.AWS.csproj +++ b/user-management/src/Stickerlandia.UserManagement.AWS/Stickerlandia.UserManagement.AWS.csproj @@ -7,15 +7,23 @@ + - + - - - + + + + + + + + + + diff --git a/user-management/src/Stickerlandia.UserManagement.Agnostic/ServiceExtensions.cs b/user-management/src/Stickerlandia.UserManagement.Agnostic/ServiceExtensions.cs index 590f128a..40cbe565 100644 --- a/user-management/src/Stickerlandia.UserManagement.Agnostic/ServiceExtensions.cs +++ b/user-management/src/Stickerlandia.UserManagement.Agnostic/ServiceExtensions.cs @@ -73,13 +73,13 @@ public static IServiceCollection AddPostgresAuthServices(this IServiceCollection .AddEntityFrameworkStores() .AddDefaultTokenProviders(); - var enableSsl = true; + var disableSsl = false; - if (configuration.GetValue("DISABLE_SSL")) enableSsl = false; + if (configuration.GetValue("DISABLE_SSL")) disableSsl = true; services.AddCoreAuthentication(options => options.UseEntityFrameworkCore() - .UseDbContext(), enableSsl); + .UseDbContext(), disableSsl); services.AddScoped(); diff --git a/user-management/src/Stickerlandia.UserManagement.Api/appsettings.Development.json b/user-management/src/Stickerlandia.UserManagement.Api/appsettings.Development.json index 151c795a..e6861800 100644 --- a/user-management/src/Stickerlandia.UserManagement.Api/appsettings.Development.json +++ b/user-management/src/Stickerlandia.UserManagement.Api/appsettings.Development.json @@ -6,15 +6,17 @@ } }, "Environment": "local", - "Auth": { - "Issuer": "https://plantbasedpizza.com", - "Audience": "https://plantbasedpizza.com", - "Key": "This is a sample secret key - please don't use in production environment.'" + "DRIVING": "ASPNET", + "DRIVEN": "AWS", + "ENABLE_BACKGROUND_WORKERS": "true", + "ConnectionStrings": { + "database": "", + "messaging": "" }, - "ApplicationConfig": { - "TeamName": "accounts", - "ApplicationName": "account-api", - "Environment": "local", - "Version": "latest" + "DISABLE_SSL": "true", + "Aws": { + "StickerClaimedQueueUrl": "", + "UserRegisteredTopicArn": "", + "StickerClaimedDLQUrl": "" } } diff --git a/user-management/src/Stickerlandia.UserManagement.Aspire/AppBuilderExtensions.cs b/user-management/src/Stickerlandia.UserManagement.Aspire/AppBuilderExtensions.cs index 1cd69d93..8d235691 100644 --- a/user-management/src/Stickerlandia.UserManagement.Aspire/AppBuilderExtensions.cs +++ b/user-management/src/Stickerlandia.UserManagement.Aspire/AppBuilderExtensions.cs @@ -3,6 +3,7 @@ // Copyright 2025 Datadog, Inc. using System.Text.Json; +using Aspire.Hosting.AWS.Lambda; using Aspire.Hosting.Azure; using Azure.Messaging.ServiceBus; using Confluent.Kafka; @@ -238,12 +239,23 @@ public static IDistributedApplicationBuilder WithAzureFunctions( return builder; } +#pragma warning disable CA2252 + public static IDistributedApplicationBuilder WithAwsLambda( this IDistributedApplicationBuilder builder, - IResourceBuilder databaseResource, - IResourceBuilder messagingResource) + InfrastructureResources resources) { - //TODO: Implement AWS Lambda support + var apiLambdaFunction = builder.AddAWSLambdaFunction("UsersApi", + "Stickerlandia.UserManagement.Api") + .WithEnvironment("ConnectionStrings__messaging", resources.MessagingResource) + .WithEnvironment("ConnectionStrings__database", resources.DatabaseResource) + .WithEnvironment("DRIVING", builder.Configuration["DRIVING"]) + .WithEnvironment("DRIVEN", builder.Configuration["DRIVEN"]); + + builder.AddAWSAPIGatewayEmulator("api", APIGatewayType.Rest) + .WithReference(apiLambdaFunction, Method.Any, "{proxy+}") + .WithHttpsEndpoint(51660); + ; return builder; } diff --git a/user-management/src/Stickerlandia.UserManagement.Aspire/Program.cs b/user-management/src/Stickerlandia.UserManagement.Aspire/Program.cs index 18863d52..74a41ef6 100644 --- a/user-management/src/Stickerlandia.UserManagement.Aspire/Program.cs +++ b/user-management/src/Stickerlandia.UserManagement.Aspire/Program.cs @@ -23,6 +23,7 @@ resources = builder.WithAgnosticServices(); break; case DrivenAdapters.AWS: + resources = builder.WithAgnosticServices(); break; } @@ -37,7 +38,7 @@ .WithBackgroundWorker(resources); break; case DrivingAdapter.AWS: - builder.WithContainerizedApi(resources); + builder.WithAwsLambda(resources); break; } diff --git a/user-management/src/Stickerlandia.UserManagement.Aspire/Stickerlandia.UserManagement.Aspire.csproj b/user-management/src/Stickerlandia.UserManagement.Aspire/Stickerlandia.UserManagement.Aspire.csproj index 6d677e25..d2409b34 100644 --- a/user-management/src/Stickerlandia.UserManagement.Aspire/Stickerlandia.UserManagement.Aspire.csproj +++ b/user-management/src/Stickerlandia.UserManagement.Aspire/Stickerlandia.UserManagement.Aspire.csproj @@ -23,10 +23,16 @@ - - - - + + + + + - + \ No newline at end of file diff --git a/user-management/src/Stickerlandia.UserManagement.Auth/AuthServiceExtensions.cs b/user-management/src/Stickerlandia.UserManagement.Auth/AuthServiceExtensions.cs index 54819b85..bcb5e86c 100644 --- a/user-management/src/Stickerlandia.UserManagement.Auth/AuthServiceExtensions.cs +++ b/user-management/src/Stickerlandia.UserManagement.Auth/AuthServiceExtensions.cs @@ -15,7 +15,7 @@ namespace Stickerlandia.UserManagement.Auth; public static class AuthServiceExtensions { public static IServiceCollection AddCoreAuthentication(this IServiceCollection services, - Action configuration, bool enableSsl) + Action configuration, bool disableSsl) { services.AddOpenIddict() .AddCore(configuration) @@ -47,12 +47,12 @@ public static IServiceCollection AddCoreAuthentication(this IServiceCollection s .AddEphemeralSigningKey(); // Register the ASP.NET Core host and configure the ASP.NET Core options. - if (enableSsl) + if (disableSsl) options.UseAspNetCore() + .DisableTransportSecurityRequirement() .EnableTokenEndpointPassthrough(); else options.UseAspNetCore() - .DisableTransportSecurityRequirement() .EnableTokenEndpointPassthrough(); diff --git a/user-management/src/Stickerlandia.UserManagement.FunctionApp/Stickerlandia.UserManagement.FunctionApp.csproj b/user-management/src/Stickerlandia.UserManagement.FunctionApp/Stickerlandia.UserManagement.FunctionApp.csproj index 9ad1c3af..d8865f57 100644 --- a/user-management/src/Stickerlandia.UserManagement.FunctionApp/Stickerlandia.UserManagement.FunctionApp.csproj +++ b/user-management/src/Stickerlandia.UserManagement.FunctionApp/Stickerlandia.UserManagement.FunctionApp.csproj @@ -5,6 +5,8 @@ Exe enable enable + false + false diff --git a/user-management/src/Stickerlandia.UserManagement.Lambda/Kafka.cs b/user-management/src/Stickerlandia.UserManagement.Lambda/Kafka.cs new file mode 100644 index 00000000..11982820 --- /dev/null +++ b/user-management/src/Stickerlandia.UserManagement.Lambda/Kafka.cs @@ -0,0 +1,40 @@ +using System.Text.Json; +using Amazon.Lambda.Annotations; +using Amazon.Lambda.KafkaEvents; +using Datadog.Trace; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Stickerlandia.UserManagement.Core; +using Stickerlandia.UserManagement.Core.Outbox; +using Stickerlandia.UserManagement.Core.StickerClaimedEvent; + +namespace Stickerlandia.UserManagement.Lambda; + +public class Kafka(ILogger logger, IServiceScopeFactory serviceScopeFactory, OutboxProcessor outboxProcessor) +{ + [LambdaFunction] + public async Task StickerClaimed(KafkaEvent kafkaEvent) + { + using var processSpan = Tracer.Instance.StartActive($"process users.stickerClaimed.v1"); + + using var scope = serviceScopeFactory.CreateScope(); + var handler = scope.ServiceProvider.GetRequiredService(); + + foreach (var message in kafkaEvent.Records) + foreach (var record in message.Value) + { + var evtData = JsonSerializer.Deserialize(record.Value); + + if (evtData == null) continue; + + try + { + await handler.Handle(evtData!); + } + catch (InvalidUserException ex) + { + logger.LogWarning(ex, "User with account in this event not found"); + } + } + } +} \ No newline at end of file diff --git a/user-management/src/Stickerlandia.UserManagement.Lambda/MigrationFunction.cs b/user-management/src/Stickerlandia.UserManagement.Lambda/MigrationFunction.cs new file mode 100644 index 00000000..c673c8b7 --- /dev/null +++ b/user-management/src/Stickerlandia.UserManagement.Lambda/MigrationFunction.cs @@ -0,0 +1,67 @@ +using Amazon.Lambda.Annotations; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using OpenIddict.Abstractions; +using Stickerlandia.UserManagement.Agnostic; +using Stickerlandia.UserManagement.Core.Outbox; + +namespace Stickerlandia.UserManagement.Lambda; + +public class MigrationFunction( + ILogger logger, + IServiceScopeFactory serviceScopeFactory, + OutboxProcessor outboxProcessor) +{ + [LambdaFunction] + public async Task Migrate(object evtData) + { + using var scope = serviceScopeFactory.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + var manager = scope.ServiceProvider.GetRequiredService(); + + await RunMigrationAsync(dbContext, default); + await SeedDataAsync(dbContext, manager, default); + } + + private static async Task RunMigrationAsync(UserManagementDbContext dbContext, CancellationToken cancellationToken) + { + var strategy = dbContext.Database.CreateExecutionStrategy(); + await strategy.ExecuteAsync(async () => + { + // Run migration in a transaction to avoid partial migration if it fails. + await dbContext.Database.MigrateAsync(cancellationToken); + }); + } + + private static async Task SeedDataAsync(UserManagementDbContext dbContext, IOpenIddictApplicationManager manager, + CancellationToken cancellationToken) + { + // Add seeding logic here if needed. + if (await manager.FindByClientIdAsync("user-authentication") is null) + await manager.CreateAsync(new OpenIddictApplicationDescriptor + { + ClientId = "user-authentication", + ClientSecret = "388D45FA-B36B-4988-BA59-B187D329C207", + Permissions = + { + OpenIddictConstants.Permissions.Endpoints.Token, + OpenIddictConstants.Permissions.GrantTypes.Password, + OpenIddictConstants.Permissions.GrantTypes.RefreshToken + } + }); + + if (await manager.FindByClientIdAsync("internal-service") is null) + await manager.CreateAsync(new OpenIddictApplicationDescriptor + { + ClientId = "internal-service", + ClientSecret = "8E1167EF-5C44-4209-A803-3A109155FDD3", + Permissions = + { + OpenIddictConstants.Permissions.Endpoints.Token, + OpenIddictConstants.Permissions.GrantTypes.Password, + OpenIddictConstants.Permissions.GrantTypes.ClientCredentials + } + }); + } +} \ No newline at end of file diff --git a/user-management/src/Stickerlandia.UserManagement.Lambda/OutboxFunctions.cs b/user-management/src/Stickerlandia.UserManagement.Lambda/OutboxFunctions.cs new file mode 100644 index 00000000..0a8d147e --- /dev/null +++ b/user-management/src/Stickerlandia.UserManagement.Lambda/OutboxFunctions.cs @@ -0,0 +1,17 @@ +using Amazon.Lambda.Annotations; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Stickerlandia.UserManagement.Core.Outbox; + +namespace Stickerlandia.UserManagement.Lambda; + +public class OutboxFunctions(ILogger logger, OutboxProcessor outboxProcessor) +{ + [LambdaFunction] + public async Task Worker(object evtData) + { + logger.LogInformation("Running outbox timer"); + + await outboxProcessor.ProcessAsync(); + } +} \ No newline at end of file diff --git a/user-management/src/Stickerlandia.UserManagement.Lambda/Properties/launchSettings.json b/user-management/src/Stickerlandia.UserManagement.Lambda/Properties/launchSettings.json new file mode 100644 index 00000000..457ad9f8 --- /dev/null +++ b/user-management/src/Stickerlandia.UserManagement.Lambda/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "Aspire_StickerClaimedProcessorFunction": { + "commandName": "Executable", + "executablePath": "dotnet", + "commandLineArgs": "exec --depsfile ./Stickerlandia.UserManagement.Lambda.deps.json --runtimeconfig ./Stickerlandia.UserManagement.Lambda.runtimeconfig.json $(HOME)/.dotnet/tools/.store/amazon.lambda.testtool/0.10.3/amazon.lambda.testtool/0.10.3/content/Amazon.Lambda.RuntimeSupport/net8.0/Amazon.Lambda.RuntimeSupport.dll Stickerlandia.UserManagement.Lambda::Stickerlandia.UserManagement.Lambda.Sqs_StickerClaimed_Generated::StickerClaimed", + "workingDirectory": "./bin/$(Configuration)/net8.0" + } + } +} \ No newline at end of file diff --git a/user-management/src/Stickerlandia.UserManagement.Lambda/Sqs.cs b/user-management/src/Stickerlandia.UserManagement.Lambda/Sqs.cs new file mode 100644 index 00000000..2773828c --- /dev/null +++ b/user-management/src/Stickerlandia.UserManagement.Lambda/Sqs.cs @@ -0,0 +1,55 @@ +using System.Text.Json; +using Amazon.Lambda.Annotations; +using Amazon.Lambda.CloudWatchEvents; +using Amazon.Lambda.SQSEvents; +using Datadog.Trace; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Stickerlandia.UserManagement.Core; +using Stickerlandia.UserManagement.Core.Outbox; +using Stickerlandia.UserManagement.Core.StickerClaimedEvent; + +namespace Stickerlandia.UserManagement.Lambda; + +public class Sqs(ILogger logger, IServiceScopeFactory serviceScopeFactory, OutboxProcessor outboxProcessor) +{ + private readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions + { PropertyNameCaseInsensitive = true }; + [LambdaFunction] + public async Task StickerClaimed(SQSEvent sqsEvent) + { + using var processSpan = Tracer.Instance.StartActive($"process users.stickerClaimed.v1"); + + using var scope = serviceScopeFactory.CreateScope(); + var handler = scope.ServiceProvider.GetRequiredService(); + + var failedMessages = new List(); + + foreach (var message in sqsEvent.Records) await ProcessMessage(message, failedMessages, handler); + + return new SQSBatchResponse(failedMessages); + } + + private async Task ProcessMessage(SQSEvent.SQSMessage message, + List failedMessages, + StickerClaimedEventHandler handler) + { + var evtData = JsonSerializer.Deserialize>(message.Body, _jsonSerializerOptions); + + if (evtData == null) + { + failedMessages.Add(new SQSBatchResponse.BatchItemFailure { ItemIdentifier = message.MessageId }); + return; + } + + try + { + await handler.Handle(evtData.Detail!); + } + catch (InvalidUserException ex) + { + logger.LogWarning(ex, "User with account in this event not found"); + failedMessages.Add(new SQSBatchResponse.BatchItemFailure { ItemIdentifier = message.MessageId }); + } + } +} \ No newline at end of file diff --git a/user-management/src/Stickerlandia.UserManagement.Lambda/Startup.cs b/user-management/src/Stickerlandia.UserManagement.Lambda/Startup.cs new file mode 100644 index 00000000..0289e2de --- /dev/null +++ b/user-management/src/Stickerlandia.UserManagement.Lambda/Startup.cs @@ -0,0 +1,38 @@ +using Amazon.Lambda.Core; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Serilog; +using Serilog.Events; +using Serilog.Extensions.Logging; +using Serilog.Formatting.Json; +using Stickerlandia.UserManagement.ServiceDefaults; + +[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] + +namespace Stickerlandia.UserManagement.Lambda; + +[Amazon.Lambda.Annotations.LambdaStartup] +public class Startup +{ + public void ConfigureServices(IServiceCollection services) + { + var configuration = new ConfigurationBuilder() + .AddEnvironmentVariables() + .Build(); + + var logger = Log.Logger = new LoggerConfiguration() + .MinimumLevel.Information() + .MinimumLevel.Override("Microsoft", LogEventLevel.Error) + .Enrich.FromLogContext() + .WriteTo.Console(new JsonFormatter()) + .CreateLogger(); + + services.ConfigureDefaultUserManagementServices(configuration); + + var appLogger = new SerilogLoggerFactory(logger) + .CreateLogger(); + + appLogger.LogInformation("Application configured"); + } +} \ No newline at end of file diff --git a/user-management/src/Stickerlandia.UserManagement.Lambda/src/Stickerlandia.UserManagement.Lambda/Stickerlandia.UserManagement.Lambda.csproj b/user-management/src/Stickerlandia.UserManagement.Lambda/Stickerlandia.UserManagement.Lambda.csproj similarity index 59% rename from user-management/src/Stickerlandia.UserManagement.Lambda/src/Stickerlandia.UserManagement.Lambda/Stickerlandia.UserManagement.Lambda.csproj rename to user-management/src/Stickerlandia.UserManagement.Lambda/Stickerlandia.UserManagement.Lambda.csproj index cfeccdc7..35e5fa29 100644 --- a/user-management/src/Stickerlandia.UserManagement.Lambda/src/Stickerlandia.UserManagement.Lambda/Stickerlandia.UserManagement.Lambda.csproj +++ b/user-management/src/Stickerlandia.UserManagement.Lambda/Stickerlandia.UserManagement.Lambda.csproj @@ -10,12 +10,19 @@ true true + false + false - - - - + + + + + + + + + - + + \ No newline at end of file diff --git a/user-management/src/Stickerlandia.UserManagement.Lambda/src/Stickerlandia.UserManagement.Lambda/aws-lambda-tools-defaults.json b/user-management/src/Stickerlandia.UserManagement.Lambda/aws-lambda-tools-defaults.json similarity index 100% rename from user-management/src/Stickerlandia.UserManagement.Lambda/src/Stickerlandia.UserManagement.Lambda/aws-lambda-tools-defaults.json rename to user-management/src/Stickerlandia.UserManagement.Lambda/aws-lambda-tools-defaults.json diff --git a/user-management/src/Stickerlandia.UserManagement.Lambda/serverless.template b/user-management/src/Stickerlandia.UserManagement.Lambda/serverless.template new file mode 100644 index 00000000..c6439ea4 --- /dev/null +++ b/user-management/src/Stickerlandia.UserManagement.Lambda/serverless.template @@ -0,0 +1,83 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Transform": "AWS::Serverless-2016-10-31", + "Description": "An AWS Serverless Application. This template is partially managed by Amazon.Lambda.Annotations (v1.7.0.0).", + "Resources": { + "StickerlandiaUserManagementLambdaOutboxFunctionsWorkerGenerated": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations" + }, + "Properties": { + "Runtime": "dotnet8", + "CodeUri": ".", + "MemorySize": 512, + "Timeout": 30, + "Policies": [ + "AWSLambdaBasicExecutionRole" + ], + "PackageType": "Zip", + "Handler": "Stickerlandia.UserManagement.Lambda::Stickerlandia.UserManagement.Lambda.OutboxFunctions_Worker_Generated::Worker" + } + }, + "StickerlandiaUserManagementLambdaKafkaStickerClaimedGenerated": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations" + }, + "Properties": { + "Runtime": "dotnet8", + "CodeUri": ".", + "MemorySize": 512, + "Timeout": 30, + "Policies": [ + "AWSLambdaBasicExecutionRole" + ], + "PackageType": "Zip", + "Handler": "Stickerlandia.UserManagement.Lambda::Stickerlandia.UserManagement.Lambda.Kafka_StickerClaimed_Generated::StickerClaimed" + } + }, + "StickerlandiaUserManagementLambdaSqsStickerClaimedGenerated": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations" + }, + "Properties": { + "Runtime": "dotnet8", + "CodeUri": ".", + "MemorySize": 512, + "Timeout": 30, + "Policies": [ + "AWSLambdaBasicExecutionRole" + ], + "PackageType": "Zip", + "Handler": "Stickerlandia.UserManagement.Lambda::Stickerlandia.UserManagement.Lambda.Sqs_StickerClaimed_Generated::StickerClaimed" + } + }, + "StickerlandiaUserManagementLambdaMigrationFunctionMigrateGenerated": { + "Type": "AWS::Serverless::Function", + "Metadata": { + "Tool": "Amazon.Lambda.Annotations" + }, + "Properties": { + "Runtime": "dotnet8", + "CodeUri": ".", + "MemorySize": 512, + "Timeout": 30, + "Policies": [ + "AWSLambdaBasicExecutionRole" + ], + "PackageType": "Zip", + "Handler": "Stickerlandia.UserManagement.Lambda::Stickerlandia.UserManagement.Lambda.MigrationFunction_Migrate_Generated::Migrate" + } + } + }, + "Outputs": { + "ApiURL": { + "Description": "API endpoint URL for Prod environment", + "Value": { + "Fn::Sub": "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/" + } + } + } +} \ No newline at end of file diff --git a/user-management/src/Stickerlandia.UserManagement.Lambda/src/Stickerlandia.UserManagement.Lambda/Functions.cs b/user-management/src/Stickerlandia.UserManagement.Lambda/src/Stickerlandia.UserManagement.Lambda/Functions.cs deleted file mode 100644 index 46500cda..00000000 --- a/user-management/src/Stickerlandia.UserManagement.Lambda/src/Stickerlandia.UserManagement.Lambda/Functions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Amazon.Lambda.Core; -using Amazon.Lambda.Annotations; -using Amazon.Lambda.Annotations.APIGateway; - -[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] - -namespace Stickerlandia.UserManagement.Lambda; - -/// -/// A collection of sample Lambda functions that provide a REST api for doing simple math calculations. -/// -public class Functions -{ - [LambdaFunction()] - [HttpApi(LambdaHttpMethod.Get, "/")] - public string Default() - { - var docs = @"Lambda Calculator Home: -You can make the following requests to invoke other Lambda functions perform calculator operations: -/add/{x}/{y} -/subtract/{x}/{y} -/multiply/{x}/{y} -/divide/{x}/{y} -"; - return docs; - } -} \ No newline at end of file diff --git a/user-management/src/Stickerlandia.UserManagement.Lambda/src/Stickerlandia.UserManagement.Lambda/Readme.md b/user-management/src/Stickerlandia.UserManagement.Lambda/src/Stickerlandia.UserManagement.Lambda/Readme.md deleted file mode 100644 index 99c2a4c6..00000000 --- a/user-management/src/Stickerlandia.UserManagement.Lambda/src/Stickerlandia.UserManagement.Lambda/Readme.md +++ /dev/null @@ -1,785 +0,0 @@ -# Amazon.Lambda.Annotations - -Lambda Annotations is a programming model for writing .NET Lambda functions. At a high level the programming model allows -idiomatic .NET coding patterns. [C# Source Generators](https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview) are used to bridge the -gap between the Lambda programming model to the Lambda Annotations programming model without adding any performance penalty. - -Topics: -* [Getting Started](#getting-started) -* [How does Lambda Annotations work?](#how-does-lambda-annotations-work) -* [Dependency Injection integration](#dependency-injection-integration) -* [Synchronizing CloudFormation template](#synchronizing-cloudFormation-template) -* [Getting build information](#getting-build-information) -* [Amazon API Gateway example](#amazon-api-gateway-example) -* [Amazon S3 example](#amazon-s3-example) -* [Lambda .NET Attributes Reference](#lambda-net-attributes-reference) - -## How does Lambda Annotations work? - -The default experience for writing .NET Lambda functions is to write a .NET method that takes in an event object. From there boiler plate code is written to -parse the data out of the event object and synchronize the CloudFormation template to define the Lambda function and the .NET method to call -for each event. Here is a simplistic example of a .NET Lambda function that acts like a calculator plus method using the default Lambda programming model. It responds to -an API Gateway REST API, pulls the operands from the resource paths, does the -addition and returns back an API Gateway response. - -```csharp -public class Functions -{ - public APIGatewayProxyResponse LambdaMathPlus(APIGatewayProxyRequest request, ILambdaContext context) - { - if (!request.PathParameters.TryGetValue("x", out var xs)) - { - return new APIGatewayProxyResponse - { - StatusCode = (int)HttpStatusCode.BadRequest - }; - } - if (!request.PathParameters.TryGetValue("y", out var ys)) - { - return new APIGatewayProxyResponse - { - StatusCode = (int)HttpStatusCode.BadRequest - }; - } - - var x = int.Parse(xs); - var y = int.Parse(ys); - - return new APIGatewayProxyResponse - { - StatusCode = (int)HttpStatusCode.OK, - Body = (x + y).ToString(), - Headers = new Dictionary { { "Content-Type", "text/plain" } } - }; - } -} -``` - -Using Lambda Annotations the same Lambda function can remove a lot of that boiler plate code and write the method like this. - -```csharp -public class Functions -{ - [LambdaFunction] - [RestApi("/plus/{x}/{y}")] - public int Plus(int x, int y) - { - return x + y; - } -} -``` - -Lambda Annotations uses C# source generators to generate that boiler plate code to bridge the gap between the default Lambda programming model to Lambda Annotations programming model at compile time. -In addition the source generator also synchronizes the CloudFormation template to declare all of the .NET methods with the `LambdaFunction` attribute as -Lambda functions in the CloudFormation template. - -## Getting started - -To get started with Lambda annotations a Lambda blueprint is available. For Visual Studio users the blueprint can be -accessed using the [AWS Toolkit for Visual Studio](https://marketplace.visualstudio.com/items?itemName=AmazonWebServices.AWSToolkitforVisualStudio2022). -For non-Visual Studio users the [Amazon.Lambda.Templates](https://www.nuget.org/packages/Amazon.Lambda.Templates) -NuGet package is available for creating .NET Lambda projects from the .NET CLI. - - -### Visual Studio 2022 - -To get started with Visual Studio install [AWS Toolkit for Visual Studio](https://marketplace.visualstudio.com/items?itemName=AmazonWebServices.AWSToolkitforVisualStudio2022) -extension. Once installed, a pre-configured Lambda Annotations project can be created using the following steps: - -* Select **Create a new project** -* In the template search box enter **AWS Serverless** -* Select the **AWS Serverless Application (.NET Core C#)** from the search result and click **Next** -* Name the project and click **Create** -* The **Select Blueprint** wizard will be displayed for choosing the initial content of the project. -* Select the **Annotations Framework** blueprint and click **Finish** - -### .NET CLI - -.NET Lambda projects can be be created using the .NET CLI's `dotnet new` command. To create a project using the -Lambda Annotations library run the following steps from a terminal. - -* Run `dotnet new install Amazon.Lambda.Templates` to install the AWS Lambda templates into the .NET CLI -* Run `dotnet new serverless.Annotations --output FirstAnnotationsProject` to create a project using Lambda Annotations - -This will create a project in a sub directory of the current director called `FirstAnnotationsProject`. The directory -will contain both a Lambda project using Annotations as well as a unit test project. - -### The sample project - -The sample project contains the following files: - -* **Functions.cs** - Defines a collection of REST API Lambda functions using Lambda Annotation. -* **Startup.cs** - Where services can be registered for dependency injection into the Lambda functions. -* **serverless.template** - CloudFormation template used to deploy the Lambda functions. The Lambda Annotations library -will automatically sync the functions defined in the project in the CloudFormation template. -* **aws-lambda-tools-defaults.json** - Config file for default settings used for deployment. - -To reset to an empty project delete the code in the `Functions` class and recompile the project. The Lambda -Annotations library will remove all of the Lambda function declarations from the CloudFormation template. -If the project will not include any Lambda functions that use API Gateway's HTTP API event sources then the `ApiURL` -output parameter should be manually removed from the CloudFormation template. - -### Deployment - -The Lambda Annotations library requires no special tooling for deployment. Any tool that supports CloudFormation-based -.NET Lambda function deployment is compatible with Lambda Annotations. This includes -[AWS Toolkit for Visual Studio](https://marketplace.visualstudio.com/items?itemName=AmazonWebServices.AWSToolkitforVisualStudio2022), -[Amazon.Lambda.Tools](https://github.com/aws/aws-extensions-for-dotnet-cli/#aws-lambda-amazonlambdatools) for the .NET CLI and [AWS SAM CLI](https://aws.amazon.com/serverless/sam/). - -For the AWS Toolkit for Visual Studio deployment can be initiated by right clicking on the Lambda project in the -Solution Explorer and selecting **Publish to AWS Lambda...**. This will launch a wizard to configure the name -of the CloudFormation stack and a S3 bucket used for storage of the compiled Lambda function deployment bundles. - -Amazon.Lambda.Tools is a .NET CLI global tool that can be install using the command -`dotnet tool install --global Amazon.Lambda.Tools`. Once installed deployment can be initiated by running the command -`dotnet lambda deploy-serverless` in the directory of the Lambda project. - - -### Adding Lambda Annotations to an existing project - -Lambda Annotations can be added to existing projects. Lambda Annotations does require that deployment of existing -projects is done using a CloudFormation template. In the future Lambda Annotations may support -other deployment technologies. - -To get started with Lambda Annotations in existing projects add a reference to the [Amazon.Lambda.Annotations](https://www.nuget.org/packages/Amazon.Lambda.Annotations/) -NuGet package. Then decorate C# methods that should be exposed as Lambda Functions with the `LambdaFunction` attribute. -If the `LambdaFunction` attribute is added to a method that was already declared in the CloudFormation template the -original declaration should be manually removed. - - -## Dependency Injection integration - -Lambda Annotations supports dependency injection. A class can be marked with a `LambdaStartup` attribute. The class will -have a `ConfigureServices` method for configuring services. - -The services can be injected by either constructor injection or using the `FromServices` attribute on a method parameter of -the function decorated with the `LambdaFunction` attribute. - -Services injected via the constructor have a lifecycle for the length of the Lambda compute container. For each Lambda -invocation a scope is created and the services injected using the `FromServices` attribute are created within the scope. - -Example startup class: -```csharp -[LambdaStartup] -public class Startup -{ - public void ConfigureServices(IServiceCollection services) - { - services.AddAWSService(); - services.AddScoped(); - } -} -``` - -Example function using DI: -```csharp -public class Functions -{ - IAmazonS3 _s3Client; - - public Functions(IAmazonS3 s3Client) - { - _s3Client = s3Client; - } - - - [LambdaFunction] - [HttpApi(LambdaHttpMethod.Put, "/process/{name}")] - public async Task Process([FromServices] ITracker tracker, string name, [FromBody] string data) - { - tracker.Record(); - - await _s3Client.PutObjectAsync(new PutObjectRequest - { - BucketName = "storage-bucket", - Key = name, - ContentBody = data - }); - } -} -``` - -## Synchronizing CloudFormation template - -When the .NET project is compiled the Lambda Annotation source generator will synchronize all of the C# methods with the `LambdaFunction` attribute in the -project's CloudFormation template. Support is available for both JSON and YAML based CloudFormation templates. -The source generator identifies the CloudFormation template for the project by looking at the `template` property in the `aws-lambda-tools-defaults.json` -file. If the `template` property is absent, the source generator will default to `serverless.template` and create the file if it does not exist. - -The source generator synchronizes Lambda resources in the CloudFormation template. The template can still be edited to add additional AWS resources or to further customize the Lambda functions, such as adding other event sources that are not currently supported by Lambda Annotations attributes. - -When a .NET Method is synchronized to the CloudFormation template the source generator adds the `Tool` metadata property shown below. This metadata -links the CloudFormation resource to the source generator. If the `LambdaFunction` attribute is removed the C# method then the source generator -will remove the CloudFormation resource. To unlink the CloudFormation resource from the source generator -remove the `Tool` metadata property. - -``` - - ... - -"CloudCalculatorFunctionsAddGenerated": { - "Type": "AWS::Serverless::Function", - "Metadata": { - "Tool": "Amazon.Lambda.Annotations", - "SyncedEvents": [ - "RootGet" - ] - }, - "Properties": { - "Runtime": "dotnet8", - "CodeUri": ".", - - ... -} -``` - -The `LambdaFunction` attribute contains properties that map to properties of the CloudFormation resource. For example in this snippet the Lambda function's `MemorySize` and `Timeout` -properties are set in the C# code. The source generator will synchronize these properties into the CloudFormation template. -```csharp -[LambdaFunction(MemorySize = 512, Timeout = 55)] -[HttpApi(LambdaHttpMethod.Get, "/add/{x}/{y}")] -public int Add(int x, int y, ILambdaContext context) -{ - context.Logger.LogInformation($"{x} plus {y} is {x + y}"); - return x + y; -} -``` - -Some CloudFormation properties are not set to a specific value but instead reference another resource or parameter defined in the CloudFormation template. To indicate the value for a -property of the .NET attribute is meant to reference another CloudFormation resource prefix the value with `@`. Here is an example of the `Role` for the Lambda function to reference -an IAM role defined in the CloudFormation template as `LambdaRoleParameter` - -```csharp -public class Functions -{ - [LambdaFunction( Role="@LambdaRoleParameter")] - [RestApi("/plus/{x}/{y}")] - public int Plus(int x, int y) - { - return x + y; - } -} -``` - -```json - "CloudCalculatorFunctionsAddGenerated": { - "Type": "AWS::Serverless::Function", - "Metadata": { - "Tool": "Amazon.Lambda.Annotations", - "SyncedEvents": [ - "RootGet" - ] - }, - "Properties": { - "Runtime": "dotnet8", - -... - "Role": { - "Fn::GetAtt": [ - "LambdaRoleParameter", - "Arn" - ] - } - } - }, -``` - -By default, Lambda Annotations will update the CloudFormation template's [description](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-description-structure.html) -field to include the version of Lambda Annotations that was used to modify the template. -``` -{ - "AWSTemplateFormatVersion": "2010-09-09", - "Transform": "AWS::Serverless-2016-10-31", - "Description": "An AWS Serverless Application. This template is partially managed by Amazon.Lambda.Annotations (v0.9.0.0).", - ... -``` - -This description allow AWS to record the version and the usage of the Lambda Annotations framework in order to improve its quality. We record details at the CloudFormation stack level, and do not identify the application, library, or tool that was deployed. Note that we do not record any personal information, such as usernames, email addresses or sensitive project-level information. - -If you do not want AWS to track the usage of the library, please set the following in your project (csproj) file: -``` - - true - -``` - -## Amazon API Gateway example - -This example creates a REST API through Amazon API Gateway that exposes the common arithmetic operations. - -To avoid putting business logic inside the REST API a separate calculator service is created to encapsulate the logic of the arithmetic operations. Here is both the -calculator service's interface and default implementation. - -```csharp -public interface ICalculatorService -{ - int Add(int x, int y); - - int Subtract(int x, int y); - - int Multiply(int x, int y); - - int Divide(int x, int y); -} - -public class DefaultCalculatorService : ICalculatorService -{ - public int Add(int x, int y) => x + y; - - public int Subtract(int x, int y) => x - y; - - public int Multiply(int x, int y) => x * y; - - public int Divide(int x, int y) => x / y; -} -``` - -The startup class contains the `LambdaStartup` attribute identifying it as the class to configure the services registered in the dependency injection framework. -Here the `ICalculatorService` is registered as a singleton service in the collection of services. - -```csharp -[LambdaStartup] -public class Startup -{ - - public void ConfigureServices(IServiceCollection services) - { - services.AddSingleton(); - } -} -``` - -Since the `ICalculatorService` is registered as a singleton the service is injected into the Lambda function via the constructor. -If the registered service is registered as scoped or transient and a new instance is needed for each Lambda invocation then the -`FromServices` attribute should be used on a method parameter of the Lambda function. - -```csharp -public class Functions -{ - ICalculatorService _calculatorService; - public Functions(ICalculatorService calculatorService) - { - _calculatorService = calculatorService; - } - - ... -``` - -For each arithmetic operation a separate C# method is added containing the `LambdaFunction` attribute. The `LambdaFunction` attribute -ensures the dependency injection framework is hooked up to the Lambda function and the Lambda function will be declared in the -CloudFormation template. - -Since these Lambda functions are responding to API Gateway events the `HttpApi` attribute is added -to register the event source in CloudFormation along with the HTTP verb and resource path. The `HttpApi` attribute also enables -mapping of the HTTP request components to method parameters. In this case the operands used for the arithmetic operations are -mapped from the resource path. Checkout the list of Lambda attributes in the reference section to see how to map other components -of the HTTP request to method parameters. - -```csharp -[LambdaFunction()] -[HttpApi(LambdaHttpMethod.Get, "/add/{x}/{y}")] -public int Add(int x, int y, ILambdaContext context) -{ - context.Logger.LogInformation($"{x} plus {y} is {x + y}"); - return _calculatorService.Add(x, y); -} - -[LambdaFunction()] -[HttpApi(LambdaHttpMethod.Get, "/subtract/{x}/{y}")] -public int Subtract(int x, int y, ILambdaContext context) -{ - context.Logger.LogInformation($"{x} subtract {y} is {x - y}"); - return _calculatorService.Subtract(x, y); -} - -[LambdaFunction()] -[HttpApi(LambdaHttpMethod.Get, "/multiply/{x}/{y}")] -public int Multiply(int x, int y, ILambdaContext context) -{ - context.Logger.LogInformation($"{x} multiply {y} is {x * y}"); - return _calculatorService.Multiply(x, y); -} - -[LambdaFunction()] -[HttpApi(LambdaHttpMethod.Get, "/divide/{x}/{y}")] -public int Divide(int x, int y, ILambdaContext context) -{ - context.Logger.LogInformation($"{x} divide {y} is {x / y}"); - return _calculatorService.Divide(x, y); -} -``` - -For each `LambdaFunction` declared the source generator will update the CloudFormation template with the corresponding resource. -The Lambda CloudFormation resource has the `Handler` property set to the generated method by Lambda Annotations. This generated -method is where Lambda Annotations bridges the gap between the Lambda Annotation programming model and the Lambda programming model. -The `HttpApi` attribute also adds the API Gateway event source. - -```json - "CloudCalculatorFunctionsAddGenerated": { - "Type": "AWS::Serverless::Function", - "Metadata": { - "Tool": "Amazon.Lambda.Annotations", - "SyncedEvents": [ - "RootGet" - ] - }, - "Properties": { - "Runtime": "dotnet8", - "CodeUri": ".", - "MemorySize": 512, - "Timeout": 30, - "PackageType": "Zip", - "Handler": "CloudCalculator::CloudCalculator.Functions_Add_Generated::Add", - "Events": { - "RootGet": { - "Type": "HttpApi", - "Properties": { - "Path": "/add/{x}/{y}", - "Method": "GET", - "PayloadFormatVersion": "2.0" - } - } - } - } - }, -``` - -Here is an example of the generated code from the source generator for the `Add` Lambda function. The generated code wraps around the -C# method that has the `LambdaFunction` attribute. It takes care of -configuring the dependency injection, gets the parameters from the API Gateway event and invokes the wrapped `LambdaFunction`. This code snippet is here for -informational purposes, as a user of the Lambda Annotations framework this code should not be needed to be seen. - -```csharp -public class Functions_Add_Generated -{ - private readonly ServiceProvider serviceProvider; - - public Functions_Add_Generated() - { - var services = new ServiceCollection(); - - // By default, Lambda function class is added to the service container using the singleton lifetime - // To use a different lifetime, specify the lifetime in Startup.ConfigureServices(IServiceCollection) method. - services.AddSingleton(); - - var startup = new CloudCalculator.Startup(); - startup.ConfigureServices(services); - serviceProvider = services.BuildServiceProvider(); - } - - public Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse Add(Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest request, Amazon.Lambda.Core.ILambdaContext context) - { - // Create a scope for every request, - // this allows creating scoped dependencies without creating a scope manually. - using var scope = serviceProvider.CreateScope(); - var functions = scope.ServiceProvider.GetRequiredService(); - - var validationErrors = new List(); - - var x = default(int); - if (request.PathParameters?.ContainsKey("x") == true) - { - try - { - x = (int)Convert.ChangeType(request.PathParameters["x"], typeof(int)); - } - catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) - { - validationErrors.Add($"Value {request.PathParameters["x"]} at 'x' failed to satisfy constraint: {e.Message}"); - } - } - - var y = default(int); - if (request.PathParameters?.ContainsKey("y") == true) - { - try - { - y = (int)Convert.ChangeType(request.PathParameters["y"], typeof(int)); - } - catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException) - { - validationErrors.Add($"Value {request.PathParameters["y"]} at 'y' failed to satisfy constraint: {e.Message}"); - } - } - - // return 400 Bad Request if there exists a validation error - if (validationErrors.Any()) - { - return new Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse - { - Body = @$"{{""message"": ""{validationErrors.Count} validation error(s) detected: {string.Join(",", validationErrors)}""}}", - Headers = new Dictionary - { - {"Content-Type", "application/json"}, - {"x-amzn-ErrorType", "ValidationException"} - }, - StatusCode = 400 - }; - } - - var response = functions.Add(x, y, context); - - var body = response.ToString(); - - return new Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse - { - Body = body, - Headers = new Dictionary - { - {"Content-Type", "application/json"} - }, - StatusCode = 200 - }; - } -} -``` - -## Amazon S3 example - -Lambda functions that are not using API Gateway can take advantage of Lambda Annotation's dependency injection integration and CloudFormation -synchronization features. This example is a Lambda function that responds to S3 events and resizes images that are uploaded to S3. - -The `Startup` class is used to register the services needed for the function. Two services are registered in this example. First is the -AWS SDK's S3 client. The second is the `IImageServices` to handle image manipulation. In this example the `IImageService` -is registered as a transient service so we can have a new instance created for every invocation. This is commonly needed if a -service has state that should not be preserved per invocation. - -```csharp -[LambdaStartup] -public class Startup -{ - public void ConfigureServices(IServiceCollection services) - { - // Using the AWSSDK.Extensions.NETCore.Setup package add the AWS SDK's S3 client - services.AddAWSService(); - - // Add service for handling image manipulation. - // IImageServices is added as transient service so a new instance - // is created for each Lambda invocation. This can be important if services - // have state that should not be persisted per invocation. - services.AddTransient(); - } -} -``` - -In the Lambda function the AWS SDK's S3 client is injected by the dependency injection framework via the constructor. The constructor is only ever called -once per Lambda invocation so for the `IImageServices` which was registered as transient it would not make sense to inject that service via the constructor. -Instead the `IImageServices` is injected as a method parameter using the `FromServices` attribute. That ensures each time the method is called a new instance -of `IImageServices` is created. - -On the `Resize` method the `LambdaFunction` attribute sets the `MemorySize` and `Timeout` properties for the Lambda function. The source generator will sync these -values to the corresponding properties in the CloudFormation template. The `Role` property is also set but in this case the value is prefixed with a `@`. -The `@` tells the source generator to treat the value for a role as a reference to another element in the CloudFormation template. In this case the -CloudFormation template defines an IAM role called `LambdaResizeImageRole` and the Lambda function should use that IAM role. - -```csharp -public class Functions -{ - private IAmazonS3 _s3Client; - - public Functions(IAmazonS3 s3Client) - { - _s3Client = s3Client; - } - - [LambdaFunction(MemorySize = 1024, Timeout = 120, Role = "@LambdaResizeImageRole")] - public async Task Resize([FromServices] IImageServices imageServices, S3Event evnt, ILambdaContext context) - { - var transferUtility = new TransferUtility(this._s3Client); - - foreach(var record in evnt.Records) - { - var tempFile = Path.GetTempFileName(); - - // Download image from S3 - await transferUtility.DownloadAsync(tempFile, record.S3.Bucket.Name, record.S3.Object.Key); - - // Resize the image - var resizeImagePath = await imageServices.ResizeImageAsync(imagePath: tempFile, width: 50, height: 50); - - // Upload resized image to S3 with a "/thumbnails" prefix in the object key. - await transferUtility.UploadAsync(resizeImagePath, record.S3.Bucket.Name, "/thumbnails" + record.S3.Object.Key); - } - } -} -``` - -The source generator will create the Lambda function resource in the CloudFormation template. The source generator will sync the properties that were -defined in the `LambdaFunction` attribute. The Lambda function resources synchronized in the template can also be modified directly in the template as well. -In this example the function is modified to define the event source in this case to S3. - -```json - "ImageResizerFunctionFunctionsResizeGenerated": { - "Type": "AWS::Serverless::Function", - "Metadata": { - "Tool": "Amazon.Lambda.Annotations" - }, - "Properties": { - "Runtime": "dotnet8", - "CodeUri": ".", - "MemorySize": 1024, - "Timeout": 120, - "PackageType": "Zip", - "Handler": "ImageResizerFunction::ImageResizerFunction.Functions_Resize_Generated::Resize", - "Role": { - "Fn::GetAtt": [ - "LambdaResizeImageRole", - "Arn" - ] - }, - "Events": { - "S3Objects": { - "Type": "S3", - "Properties": { - "Bucket": { - "Ref": "ImageBucket" - }, - "Filter": { - "S3Key": { - "Rules": [ - { - "Name": "prefix", - "Value": "/images" - } - ] - } - }, - "Events": [ - "s3:ObjectCreated:*" - ] - } - } - } - } - }, -``` - -This is the code the source generator will produce for this function. The constructor is handling setting up the dependency injection. During the generated `Resize` -method a dependency injection scope is created and then the `IImageServices` is retrieved from the dependency injection and passed into the function written -by the developer. By creating the scope in the generated `Resize` method all services registered as scoped or transient will trigger a new instance to be created -when retrieved from the dependency injection framework. This code snippet is here for -informational purposes, as a user of the Lambda Annotations framework this code should not be needed to be seen. - -```csharp -public class Functions_Resize_Generated -{ - private readonly ServiceProvider serviceProvider; - - public Functions_Resize_Generated() - { - var services = new ServiceCollection(); - - // By default, Lambda function class is added to the service container using the singleton lifetime - // To use a different lifetime, specify the lifetime in Startup.ConfigureServices(IServiceCollection) method. - services.AddSingleton(); - - var startup = new ImageResizerFunction.Startup(); - startup.ConfigureServices(services); - serviceProvider = services.BuildServiceProvider(); - } - - public async System.Threading.Tasks.Task Resize(Amazon.Lambda.S3Events.S3Event evnt, Amazon.Lambda.Core.ILambdaContext __context__) - { - // Create a scope for every request, - // this allows creating scoped dependencies without creating a scope manually. - using var scope = serviceProvider.CreateScope(); - var functions = scope.ServiceProvider.GetRequiredService(); - - var imageServices = scope.ServiceProvider.GetRequiredService(); - await functions.Resize(imageServices, evnt, __context__); - } -} -``` - -## Getting build information - -The source generator integrates with MSBuild's compiler error and warning reporting when there are problems generating the boiler plate code. - -To see the code that is generated by the source generator turn the verbosity to detailed when executing a build. From the command this -is done by using the `--verbosity` switch. -``` -dotnet build --verbosity detailed -``` -To change the verbosity in Visual Studio go to Tools -> Options -> Projects and Solutions and adjust the MSBuild verbosity drop down boxes. - - - -## Lambda .NET Attributes Reference - -List of .NET attributes currently supported. - - -* LambdaFunction - * Placed on a method. Indicates this method should be exposed as a Lambda function. -* LambdaStartup - * Placed on a class. Indicates this type should be used as the startup class and is used to configure the dependency injection and middleware. There can only be one class in a Lambda project with this attribute. - -### Event Attributes - -Event attributes configuring the source generator for the type of event to expect and setup the event source in the CloudFormation template. If an event attribute is not set the -parameter to the `LambdaFunction` must be the event object and the event source must be configured outside of the code. - -* RestApi - * Configures the Lambda function to be called from an API Gateway REST API. The HTTP method and resource path are required to be set on the attribute. -* HttpApi - * Configures the Lambda function to be called from an API Gateway HTTP API. The HTTP method, HTTP API payload version and resource path are required to be set on the attribute. - -### Parameter Attributes - -* FromHeader - * Map method parameter to HTTP header value -* FromQuery - * Map method parameter to query string parameter -* FromRoute - * Map method parameter to resource path segment -* FromBody - * Map method parameter to HTTP request body. If parameter is a complex type then request body will be assumed to be JSON and deserialized into the type. -* FromServices - * Map method parameter to registered service in IServiceProvider - -### Customizing responses for API Gateway Lambda functions - -The attributes `RestApi` or `HttpApi` configure a `LambdaFunction` method to use API Gateway as the event source for the function. By default these methods return an -HTTP status code of 200. To customize the HTTP response, including adding HTTP headers, the method signature must return an `Amazon.Lambda.Annotations.APIGateway.IHttpResult` -or `Task`. -The `Amazon.Lambda.Annotations.APIGateway.HttpResults` class contains static methods for creating an instance of `IHttpResult` with the appropriate HTTP status code and headers. - -The example below shows how to return a HTTP status code 404 with a response body and custom header. - -``` -[LambdaFunction(PackageType = LambdaPackageType.Image)] -[HttpApi(LambdaHttpMethod.Get, "/resource/{id}")] -public IHttpResult NotFoundResponseWithHeaderV2(int id, ILambdaContext context) -{ - return HttpResults.NotFound($"Resource with id {id} could not be found") - .AddHeader("Custom-Header1", "Value1"); -} -``` - -Available static methods for creating an instance of `IHttpResult`. -* HttpResults.Accepted() -* HttpResults.BadGateway() -* HttpResults.BadRequest() -* HttpResults.Conflict() -* HttpResults.Created() -* HttpResults.Forbid() -* HttpResults.InternalServerError() -* HttpResults.NotFound() -* HttpResults.Ok() -* HttpResults.Redirect() -* HttpResults.ServiceUnavailable() -* HttpResults.Unauthorized() -* HttpResults.NewResult() - -#### Content-Type -`HttpResults` will automatically assign a content-type for the response if there is a response body and content type was not specified using the `AddHeader` method. -The content type is determined using the following rules. - -* Content type will be set to `text/plain` when the response body is a string. -* Content type will be set to `application/octet-stream` when the response body is a `Stream`, `byte[]` or `IList`. -* For any other response body the content type is set to `application/json`. - - -## Project References - -If API Gateway event attributes, such as `RestAPI` or `HttpAPI`, are being used then a package reference to `Amazon.Lambda.APIGatewayEvents` must be added to the project, otherwise the project will not compile. We do not include it by default in order to keep the `Amazon.Lambda.Annotations` library lightweight. diff --git a/user-management/src/Stickerlandia.UserManagement.Lambda/src/Stickerlandia.UserManagement.Lambda/Startup.cs b/user-management/src/Stickerlandia.UserManagement.Lambda/src/Stickerlandia.UserManagement.Lambda/Startup.cs deleted file mode 100644 index f8fe6d78..00000000 --- a/user-management/src/Stickerlandia.UserManagement.Lambda/src/Stickerlandia.UserManagement.Lambda/Startup.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace Stickerlandia.UserManagement.Lambda; - -[Amazon.Lambda.Annotations.LambdaStartup] -public class Startup -{ - public void ConfigureServices(IServiceCollection services) - { - } -} \ No newline at end of file diff --git a/user-management/src/Stickerlandia.UserManagement.Lambda/src/Stickerlandia.UserManagement.Lambda/serverless.template b/user-management/src/Stickerlandia.UserManagement.Lambda/src/Stickerlandia.UserManagement.Lambda/serverless.template deleted file mode 100644 index 0155391d..00000000 --- a/user-management/src/Stickerlandia.UserManagement.Lambda/src/Stickerlandia.UserManagement.Lambda/serverless.template +++ /dev/null @@ -1,44 +0,0 @@ -{ - "AWSTemplateFormatVersion": "2010-09-09", - "Transform": "AWS::Serverless-2016-10-31", - "Description": "An AWS Serverless Application. This template is partially managed by Amazon.Lambda.Annotations (v1.3.0.0).", - "Resources": { - "StickerlandiaUserManagementLambdaFunctionsDefaultGenerated": { - "Type": "AWS::Serverless::Function", - "Metadata": { - "Tool": "Amazon.Lambda.Annotations", - "SyncedEvents": [ - "RootGet" - ] - }, - "Properties": { - "Runtime": "dotnet8", - "CodeUri": ".", - "MemorySize": 512, - "Timeout": 30, - "Policies": [ - "AWSLambdaBasicExecutionRole" - ], - "PackageType": "Zip", - "Handler": "Stickerlandia.UserManagement.Lambda::Stickerlandia.UserManagement.Lambda.Functions_Default_Generated::Default", - "Events": { - "RootGet": { - "Type": "HttpApi", - "Properties": { - "Path": "/", - "Method": "GET" - } - } - } - } - } - }, - "Outputs": { - "ApiURL": { - "Description": "API endpoint URL for Prod environment", - "Value": { - "Fn::Sub": "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/" - } - } - } -} \ No newline at end of file diff --git a/user-management/src/Stickerlandia.UserManagement.Lambda/test/Stickerlandia.UserManagement.Lambda.Tests/FunctionsTest.cs b/user-management/src/Stickerlandia.UserManagement.Lambda/test/Stickerlandia.UserManagement.Lambda.Tests/FunctionsTest.cs deleted file mode 100644 index 3804ab6f..00000000 --- a/user-management/src/Stickerlandia.UserManagement.Lambda/test/Stickerlandia.UserManagement.Lambda.Tests/FunctionsTest.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Xunit; -using Amazon.Lambda.TestUtilities; -using Moq; - - -namespace Stickerlandia.UserManagement.Lambda.Tests; - -public class FunctionsTest -{ - ICalculatorService _mockCalculatorService; - - public FunctionsTest() - { - // This isn't an accurate calculator, but rather shows how the business logic - // for our Lambda functions can be moved to a class that can be replaced by a - // mocked implementation for testing. - var mock = new Mock(); - mock.Setup(m => m.Add(It.IsAny(), It.IsAny())).Returns(12); - - _mockCalculatorService = mock.Object; - } - - [Fact] - public void TestAdd() - { - TestLambdaContext context = new TestLambdaContext(); - - var functions = new Functions(_mockCalculatorService); - Assert.Equal(12, functions.Add(3, 9, context)); - } -} diff --git a/user-management/src/Stickerlandia.UserManagement.Lambda/test/Stickerlandia.UserManagement.Lambda.Tests/Stickerlandia.UserManagement.Lambda.Tests.csproj b/user-management/src/Stickerlandia.UserManagement.Lambda/test/Stickerlandia.UserManagement.Lambda.Tests/Stickerlandia.UserManagement.Lambda.Tests.csproj deleted file mode 100644 index 52dcac5b..00000000 --- a/user-management/src/Stickerlandia.UserManagement.Lambda/test/Stickerlandia.UserManagement.Lambda.Tests/Stickerlandia.UserManagement.Lambda.Tests.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - net8.0 - enable - enable - true - - - - - - - - - - - - - - \ No newline at end of file diff --git a/user-management/src/Stickerlandia.UserManagement.MigrationService/Program.cs b/user-management/src/Stickerlandia.UserManagement.MigrationService/Program.cs index 92fadeb9..629d98c9 100644 --- a/user-management/src/Stickerlandia.UserManagement.MigrationService/Program.cs +++ b/user-management/src/Stickerlandia.UserManagement.MigrationService/Program.cs @@ -1,8 +1,10 @@ +using Stickerlandia.UserManagement.Agnostic; using Stickerlandia.UserManagement.MigrationService; using Stickerlandia.UserManagement.ServiceDefaults; var builder = Host.CreateApplicationBuilder(args); -builder.AddServiceDefaults(); +//builder.AddServiceDefaults(); +builder.Services.AddPostgresAuthServices(builder.Configuration); builder.Services.AddHostedService(); var host = builder.Build(); diff --git a/user-management/src/Stickerlandia.UserManagement.MigrationService/appsettings.Development.json b/user-management/src/Stickerlandia.UserManagement.MigrationService/appsettings.Development.json index b2dcdb67..3c68f237 100644 --- a/user-management/src/Stickerlandia.UserManagement.MigrationService/appsettings.Development.json +++ b/user-management/src/Stickerlandia.UserManagement.MigrationService/appsettings.Development.json @@ -4,5 +4,9 @@ "Default": "Information", "Microsoft.Hosting.Lifetime": "Information" } + }, + "ConnectionStrings": { + "database": "", + "messaging": "" } } diff --git a/user-management/src/Stickerlandia.UserManagement.ServiceDefaults/ServiceDefaults.cs b/user-management/src/Stickerlandia.UserManagement.ServiceDefaults/ServiceDefaults.cs index 89839d54..161f86f4 100644 --- a/user-management/src/Stickerlandia.UserManagement.ServiceDefaults/ServiceDefaults.cs +++ b/user-management/src/Stickerlandia.UserManagement.ServiceDefaults/ServiceDefaults.cs @@ -6,6 +6,7 @@ using Serilog.Events; using Serilog.Formatting.Json; using Stickerlandia.UserManagement.Agnostic; +using Stickerlandia.UserManagement.AWS; using Stickerlandia.UserManagement.Azure; using Stickerlandia.UserManagement.Core; @@ -45,6 +46,7 @@ public static IServiceCollection ConfigureDefaultUserManagementServices(this ISe services.AddAgnosticAdapters(configuration); break; case "AWS": + services.AddAwsAdapters(configuration); break; default: throw new ArgumentException($"Unknown driven adapters {drivenAdapters}"); diff --git a/user-management/tests/Stickerlandia.UserManagement.IntegrationTest/Drivers/EventBridgeMessaging.cs b/user-management/tests/Stickerlandia.UserManagement.IntegrationTest/Drivers/EventBridgeMessaging.cs new file mode 100644 index 00000000..a0738ea6 --- /dev/null +++ b/user-management/tests/Stickerlandia.UserManagement.IntegrationTest/Drivers/EventBridgeMessaging.cs @@ -0,0 +1,44 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2025 Datadog, Inc. + +using System.Text.Json; +using Amazon.EventBridge; +using Amazon.EventBridge.Model; +using Confluent.Kafka; + +namespace Stickerlandia.UserManagement.IntegrationTest.Drivers; + +public class EventBridgeMessaging : IMessaging, IAsyncDisposable +{ + private readonly string _environment; + private readonly AmazonEventBridgeClient client; + + public EventBridgeMessaging(string environment) + { + _environment = environment; + client = new AmazonEventBridgeClient(); + } + + public async Task SendMessageAsync(string queueName, object message) + { + await client.PutEventsAsync(new PutEventsRequest + { + Entries = new List + { + new() + { + EventBusName = $"user-service-{_environment}-bus", + Source = $"{_environment}.stickers", + DetailType = queueName, + Detail = JsonSerializer.Serialize(message) + } + } + }); + } + + public async ValueTask DisposeAsync() + { + // Nothing to dispose + } +} \ No newline at end of file diff --git a/user-management/tests/Stickerlandia.UserManagement.IntegrationTest/Drivers/MessagingProviderFactory.cs b/user-management/tests/Stickerlandia.UserManagement.IntegrationTest/Drivers/MessagingProviderFactory.cs index 61ee34df..c2de7351 100644 --- a/user-management/tests/Stickerlandia.UserManagement.IntegrationTest/Drivers/MessagingProviderFactory.cs +++ b/user-management/tests/Stickerlandia.UserManagement.IntegrationTest/Drivers/MessagingProviderFactory.cs @@ -14,6 +14,7 @@ public static IMessaging From(string hostOn, string? connectionString) { "AZURE" => new AzureServiceBusMessaging(connectionString), "AGNOSTIC" => new KafkaMessaging(connectionString), + "AWS" => new EventBridgeMessaging(Environment.GetEnvironmentVariable("ENV") ?? "dev"), _ => throw new NotSupportedException($"Unsupported messaging provider: {connectionString}") }; } diff --git a/user-management/tests/Stickerlandia.UserManagement.IntegrationTest/Drivers/SqsMessaging.cs b/user-management/tests/Stickerlandia.UserManagement.IntegrationTest/Drivers/SqsMessaging.cs new file mode 100644 index 00000000..44de7e65 --- /dev/null +++ b/user-management/tests/Stickerlandia.UserManagement.IntegrationTest/Drivers/SqsMessaging.cs @@ -0,0 +1,34 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2025 Datadog, Inc. + +using System.Text.Json; +using Amazon.SQS; +using Confluent.Kafka; + +namespace Stickerlandia.UserManagement.IntegrationTest.Drivers; + +public class SqsMessaging : IMessaging, IAsyncDisposable +{ + private readonly AmazonSQSClient client; + private readonly Dictionary queueUrlNameMap; + public SqsMessaging(Dictionary queueUrlNameMap) + { + client = new AmazonSQSClient(); + this.queueUrlNameMap = queueUrlNameMap; + } + public async Task SendMessageAsync(string queueName, object message) + { + if (!queueUrlNameMap.TryGetValue(queueName, out var queueUrl)) + { + throw new ArgumentException($"Queue name '{queueName}' not found in the queue URL map."); + } + + await client.SendMessageAsync(queueUrl, JsonSerializer.Serialize(message)); + } + + public async ValueTask DisposeAsync() + { + // Nothing to dispose + } +} \ No newline at end of file diff --git a/user-management/tests/Stickerlandia.UserManagement.IntegrationTest/Drivers/TestConstants.cs b/user-management/tests/Stickerlandia.UserManagement.IntegrationTest/Drivers/TestConstants.cs index 3fd98cdf..148eea5c 100644 --- a/user-management/tests/Stickerlandia.UserManagement.IntegrationTest/Drivers/TestConstants.cs +++ b/user-management/tests/Stickerlandia.UserManagement.IntegrationTest/Drivers/TestConstants.cs @@ -18,6 +18,7 @@ public static string DefaultMessagingConnection(string hostOn, string? messaging "AZURE" => "Endpoint=sb://localhost:60001;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;", "AGNOSTIC" => "localhost:53477", + "AWS" => "", // SQS does not require a connection string in this context _ => throw new NotSupportedException($"Unsupported messaging provider: {hostOn}") }; } diff --git a/user-management/tests/Stickerlandia.UserManagement.IntegrationTest/Stickerlandia.UserManagement.IntegrationTest.csproj b/user-management/tests/Stickerlandia.UserManagement.IntegrationTest/Stickerlandia.UserManagement.IntegrationTest.csproj index b5f9f01c..8385fcad 100644 --- a/user-management/tests/Stickerlandia.UserManagement.IntegrationTest/Stickerlandia.UserManagement.IntegrationTest.csproj +++ b/user-management/tests/Stickerlandia.UserManagement.IntegrationTest/Stickerlandia.UserManagement.IntegrationTest.csproj @@ -11,6 +11,7 @@ + @@ -31,7 +32,7 @@ - + diff --git a/user-management/tests/Stickerlandia.UserManagement.IntegrationTest/Stickerlandia.UserManagement.IntegrationTest.sln b/user-management/tests/Stickerlandia.UserManagement.IntegrationTest/Stickerlandia.UserManagement.IntegrationTest.sln new file mode 100644 index 00000000..cc475ff6 --- /dev/null +++ b/user-management/tests/Stickerlandia.UserManagement.IntegrationTest/Stickerlandia.UserManagement.IntegrationTest.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stickerlandia.UserManagement.IntegrationTest", "Stickerlandia.UserManagement.IntegrationTest.csproj", "{98DE833D-826D-BA8C-F431-8916E85DC043}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {98DE833D-826D-BA8C-F431-8916E85DC043}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {98DE833D-826D-BA8C-F431-8916E85DC043}.Debug|Any CPU.Build.0 = Debug|Any CPU + {98DE833D-826D-BA8C-F431-8916E85DC043}.Release|Any CPU.ActiveCfg = Release|Any CPU + {98DE833D-826D-BA8C-F431-8916E85DC043}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9BAA921E-BAEA-459E-BD44-A0D47E7F3C82} + EndGlobalSection +EndGlobal