@emarketeer/ts-microservice-commons 8.0.0 → 8.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -830,19 +830,30 @@ class EmLambdaFunction extends constructs.Construct {
830
830
  class EmDynamoDBTable extends constructs.Construct {
831
831
  constructor(scope, id, config) {
832
832
  super(scope, id);
833
- const tableName = generateTableName(config.stage, config.serviceName, config.tableName);
833
+ const tableName = config.rawTableName ?? generateTableName(config.stage, config.serviceName, config.tableName);
834
834
  // Create DynamoDB table
835
835
  this.table = new awsDynamodb.Table(this, 'Table', {
836
836
  tableName,
837
837
  partitionKey: config.partitionKey,
838
838
  sortKey: config.sortKey,
839
839
  billingMode: config.billingMode || awsDynamodb.BillingMode.PAY_PER_REQUEST,
840
- pointInTimeRecovery: config.pointInTimeRecovery ?? config.stage === 'prod',
840
+ pointInTimeRecoverySpecification: {
841
+ pointInTimeRecoveryEnabled: config.pointInTimeRecovery ?? config.stage === 'prod'
842
+ },
843
+ deletionProtection: config.deletionProtection ?? config.stage === 'prod',
841
844
  stream: config.stream ? awsDynamodb.StreamViewType.NEW_AND_OLD_IMAGES : undefined,
842
845
  timeToLiveAttribute: config.timeToLiveAttribute,
843
846
  encryption: awsDynamodb.TableEncryption.AWS_MANAGED,
844
847
  removalPolicy: getRemovalPolicy(config.stage)
845
848
  });
849
+ if (config.overrideLogicalId) {
850
+ const cfnTable = this.table.node.defaultChild;
851
+ if (!(cfnTable instanceof awsDynamodb.CfnTable)) {
852
+ throw new Error(`Cannot override table logical ID to "${config.overrideLogicalId}": ` +
853
+ 'table does not have a CfnTable default child.');
854
+ }
855
+ cfnTable.overrideLogicalId(config.overrideLogicalId);
856
+ }
846
857
  // Add Global Secondary Indexes
847
858
  if (config.globalSecondaryIndexes) {
848
859
  config.globalSecondaryIndexes.forEach(gsi => {
@@ -2221,6 +2232,41 @@ class EmStack extends cdk.Stack {
2221
2232
  })
2222
2233
  });
2223
2234
  }
2235
+ /**
2236
+ * Create a Lambda function consuming messages from an SNS topic via SQS.
2237
+ * Combines `createQueueConsumer()` + `subscribeToTopic()` in one call and
2238
+ * inherits the stack's default function config (env vars, VPC, timeout).
2239
+ *
2240
+ * Prefer this over constructing `TopicQueueConsumer` directly — the raw
2241
+ * constructor bypasses default-config merging.
2242
+ *
2243
+ * @example
2244
+ * ```typescript
2245
+ * this.createTopicQueueConsumer('ProcessInvoices', {
2246
+ * topic: invoiceTopic,
2247
+ * subscriptionOptions: { rawMessageDelivery: true },
2248
+ * handlerPath: 'src/handlers/process-invoices',
2249
+ * queueName: 'dev-my-service-invoice-queue',
2250
+ * alarmTopic,
2251
+ * })
2252
+ * ```
2253
+ */
2254
+ createTopicQueueConsumer(id, config) {
2255
+ const { topic, subscriptionOptions, ...queueConfig } = config;
2256
+ const merged = this.mergeConfig(queueConfig);
2257
+ const { functionName } = resolveHandlerPath(merged);
2258
+ return new TopicQueueConsumer(this, id, {
2259
+ ...merged,
2260
+ stage: merged.stage ?? this.stage,
2261
+ serviceName: merged.serviceName ?? this.serviceName,
2262
+ role: merged.role ?? this.sharedRole,
2263
+ topic,
2264
+ subscriptionOptions,
2265
+ ...(this.sharedRole && {
2266
+ serverlessFunctionName: merged.serverlessFunctionName ?? functionName
2267
+ })
2268
+ });
2269
+ }
2224
2270
  /**
2225
2271
  * Create a scheduled Lambda function with an EventBridge rule.
2226
2272
  * Combines `createFunction()` + `EmEventBridgeRule` + `addLambdaTarget()` in one call.
@@ -2360,10 +2406,17 @@ function createRdsVpcConfig(scope, stage, config) {
2360
2406
  const vpc = ec2.Vpc.fromLookup(scope, `RdsVpc-${stage}`, {
2361
2407
  vpcId: config.vpcId
2362
2408
  });
2363
- const privateSubnets = config.privateSubnetIds.map((subnetId, index) => ec2.Subnet.fromSubnetId(scope, `RdsPrivateSubnet${index}-${stage}`, subnetId));
2409
+ // The imported subnets are only handed to Lambda's VpcConfig. We never read
2410
+ // `subnet.routeTable.routeTableId`, so acknowledge the CDK warning that
2411
+ // `fromSubnetId` emits for imports without route-table metadata.
2412
+ const privateSubnets = config.privateSubnetIds.map((subnetId, index) => {
2413
+ const subnet = ec2.Subnet.fromSubnetId(scope, `RdsPrivateSubnet${index}-${stage}`, subnetId);
2414
+ cdk.Annotations.of(subnet).acknowledgeWarning('@aws-cdk/aws-ec2:noSubnetRouteTableId', 'This construct only passes imported subnets to Lambda VpcConfig and does not require routeTableId metadata.');
2415
+ return subnet;
2416
+ });
2364
2417
  const lambdaSecurityGroup = new ec2.SecurityGroup(scope, `RdsLambdaSecurityGroup-${stage}`, {
2365
2418
  vpc,
2366
- description: 'Lambda security group for RDS access',
2419
+ description: config.securityGroupDescription ?? 'Lambda security group for RDS access',
2367
2420
  allowAllOutbound: true
2368
2421
  });
2369
2422
  if (config.overrideLogicalIds?.securityGroup) {
@@ -2423,6 +2476,42 @@ const createEmApp = (options) => {
2423
2476
  return { app, stage: rawStage };
2424
2477
  };
2425
2478
 
2479
+ /**
2480
+ * Account-level RDS networking constants — the VPC, private subnets, and the
2481
+ * RDS security group that Lambda functions need to reach the shared database
2482
+ * cluster.
2483
+ *
2484
+ * These values are account-level constants: every service deploying into the
2485
+ * eMarketeer AWS account uses the same VPC/subnets/SG per stage. The VPC and
2486
+ * private subnets exist solely to give Lambdas a route to RDS — Lambdas that
2487
+ * don't talk to RDS do not need them.
2488
+ *
2489
+ * Designed to compose with `createRdsVpcConfig()` in `./rds-vpc`:
2490
+ *
2491
+ * ```typescript
2492
+ * const account = getAccountRdsVpcConfig(this.stage)
2493
+ * const vpcConfig = createRdsVpcConfig(this, this.stage, { ...account })
2494
+ * ```
2495
+ */
2496
+ function getAccountRdsVpcConfig(stage) {
2497
+ switch (stage) {
2498
+ case 'dev':
2499
+ return {
2500
+ vpcId: 'vpc-d2fd89b5',
2501
+ privateSubnetIds: ['subnet-7c74d81a', 'subnet-de04eb84', 'subnet-e29b26aa'],
2502
+ dbSecurityGroupId: 'sg-711c3f09'
2503
+ };
2504
+ case 'prod':
2505
+ return {
2506
+ vpcId: 'vpc-aeaf41cb',
2507
+ privateSubnetIds: ['subnet-dab14f80', 'subnet-e06ad686', 'subnet-14ea665c'],
2508
+ dbSecurityGroupId: 'sg-427bda39'
2509
+ };
2510
+ default:
2511
+ throw new Error(`Unsupported RDS VPC stage: ${stage}. Only 'dev' and 'prod' are supported.`);
2512
+ }
2513
+ }
2514
+
2426
2515
  exports.DEFAULT_LAMBDA_RUNTIME = DEFAULT_LAMBDA_RUNTIME;
2427
2516
  exports.DlqAlarm = DlqAlarm;
2428
2517
  exports.EVENT_PATTERNS = EVENT_PATTERNS;
@@ -2490,6 +2579,7 @@ exports.generateStackName = generateStackName;
2490
2579
  exports.generateStandardTags = generateStandardTags;
2491
2580
  exports.generateTableName = generateTableName;
2492
2581
  exports.generateTopicName = generateTopicName;
2582
+ exports.getAccountRdsVpcConfig = getAccountRdsVpcConfig;
2493
2583
  exports.getAlarmThresholds = getAlarmThresholds;
2494
2584
  exports.getComplianceTags = getComplianceTags;
2495
2585
  exports.getCostAllocationTags = getCostAllocationTags;