@emarketeer/ts-microservice-commons 7.1.0-beta.21 → 7.1.0-beta.23

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.
@@ -2075,6 +2075,7 @@ class EmStack extends cdk.Stack {
2075
2075
  costCenter: props.costCenter,
2076
2076
  customTags: props.tags
2077
2077
  });
2078
+ cdk.Tags.of(this).add('em-microservice', `${props.stage}-${props.serviceName}`);
2078
2079
  if (props.useSharedRole) {
2079
2080
  this.sharedRole = new awsIam.Role(this, 'LambdaExecutionRole', {
2080
2081
  roleName: `${props.serviceName}-${props.stage}-${this.region}-lambdaRole`,
@@ -2088,6 +2089,44 @@ class EmStack extends cdk.Stack {
2088
2089
  overrideRoleLogicalId(this.sharedRole);
2089
2090
  }
2090
2091
  }
2092
+ /**
2093
+ * Update default function config after construction.
2094
+ * Use this when defaults depend on resources created after `super()`.
2095
+ * Environment is deep-merged with any existing defaults.
2096
+ *
2097
+ * @example
2098
+ * ```typescript
2099
+ * // After creating resources:
2100
+ * this.setDefaultFunctionConfig({
2101
+ * environment: sharedEnvironment,
2102
+ * vpcConfig,
2103
+ * })
2104
+ * ```
2105
+ */
2106
+ setDefaultFunctionConfig(config) {
2107
+ this.defaultFunctionConfig = {
2108
+ ...this.defaultFunctionConfig,
2109
+ ...config,
2110
+ environment: {
2111
+ ...(this.defaultFunctionConfig.environment ?? {}),
2112
+ ...(config.environment ?? {})
2113
+ }
2114
+ };
2115
+ }
2116
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2117
+ mergeConfig(config) {
2118
+ const defaults = this.defaultFunctionConfig;
2119
+ const merged = { ...defaults, ...config };
2120
+ const defaultEnv = this.defaultFunctionConfig.environment;
2121
+ const configEnv = config.environment;
2122
+ if (defaultEnv || configEnv) {
2123
+ merged.environment = {
2124
+ ...(defaultEnv ?? {}),
2125
+ ...(configEnv ?? {})
2126
+ };
2127
+ }
2128
+ return merged;
2129
+ }
2091
2130
  /**
2092
2131
  * Create a Lambda function. Defaults `stage` and `serviceName` from the stack.
2093
2132
  *
@@ -2096,12 +2135,9 @@ class EmStack extends cdk.Stack {
2096
2135
  * - Overrides log group logical ID to `{prefix}LogGroup`
2097
2136
  * - Sets log group removal policy to RETAIN
2098
2137
  * - Uses the shared role unless `config.role` is provided
2099
- *
2100
- * This means existing Serverless stacks are migrated in-place — no manual
2101
- * logical ID overrides needed.
2102
2138
  */
2103
2139
  createFunction(id, config) {
2104
- const merged = { ...this.defaultFunctionConfig, ...config };
2140
+ const merged = this.mergeConfig(config);
2105
2141
  const resolved = resolveHandlerPath(merged);
2106
2142
  const functionName = resolved.functionName;
2107
2143
  const handler = resolved.handler ?? merged.handler;
@@ -2148,7 +2184,7 @@ class EmStack extends cdk.Stack {
2148
2184
  * ```
2149
2185
  */
2150
2186
  createQueueConsumer(id, config) {
2151
- const merged = { ...this.defaultFunctionConfig, ...config };
2187
+ const merged = this.mergeConfig(config);
2152
2188
  const { functionName } = resolveHandlerPath(merged);
2153
2189
  return new LambdaWithQueue(this, id, {
2154
2190
  ...merged,
@@ -2175,11 +2211,10 @@ class EmStack extends cdk.Stack {
2175
2211
  createScheduledFunction(id, config) {
2176
2212
  const { schedule, ruleName, ruleDescription, ...functionConfig } = config;
2177
2213
  const fn = this.createFunction(id, functionConfig);
2178
- const resolved = resolveHandlerPath({ ...this.defaultFunctionConfig, ...functionConfig });
2179
2214
  const rule = new EmEventBridgeRule(this, `${id}Rule`, {
2180
2215
  stage: config.stage ?? this.stage,
2181
2216
  serviceName: config.serviceName ?? this.serviceName,
2182
- ruleName: ruleName ?? resolved.functionName,
2217
+ ruleName: ruleName ?? resolveHandlerPath(functionConfig).functionName,
2183
2218
  description: ruleDescription,
2184
2219
  schedule
2185
2220
  });
@@ -2202,6 +2237,68 @@ class EmStack extends cdk.Stack {
2202
2237
  const arn = `arn:${cdk.Aws.PARTITION}:sns:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:${this.stage}-alarm-email`;
2203
2238
  return awsSns.Topic.fromTopicArn(this, 'AlarmTopic', arn);
2204
2239
  }
2240
+ /**
2241
+ * Add a Lambda invoke policy to the shared role.
2242
+ * @param functionPattern - Optional function name pattern. Defaults to `{stage}-{serviceName}-*`.
2243
+ * Pass `'*'` for account-wide access.
2244
+ */
2245
+ addLambdaInvokePolicy(functionPattern) {
2246
+ this.requireSharedRole('addLambdaInvokePolicy');
2247
+ const pattern = functionPattern ?? `${this.stage}-${this.serviceName}-*`;
2248
+ this.sharedRole.addToPolicy(new awsIam.PolicyStatement({
2249
+ effect: awsIam.Effect.ALLOW,
2250
+ actions: ['lambda:InvokeFunction'],
2251
+ resources: [
2252
+ `arn:${cdk.Aws.PARTITION}:lambda:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:function:${pattern}`
2253
+ ]
2254
+ }));
2255
+ }
2256
+ /**
2257
+ * Add a Kinesis PutRecord/PutRecords policy to the shared role.
2258
+ * @param streamName - Short stream name (prefixed with `{stage}-`).
2259
+ */
2260
+ addKinesisPolicy(streamName) {
2261
+ this.requireSharedRole('addKinesisPolicy');
2262
+ const arn = `arn:${cdk.Aws.PARTITION}:kinesis:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:stream/${this.stage}-${streamName}`;
2263
+ this.sharedRole.addToPolicy(new awsIam.PolicyStatement({
2264
+ effect: awsIam.Effect.ALLOW,
2265
+ actions: ['kinesis:PutRecord', 'kinesis:PutRecords'],
2266
+ resources: [arn]
2267
+ }));
2268
+ }
2269
+ /**
2270
+ * Add an SNS Publish policy to the shared role.
2271
+ * @param topicOrName - An ITopic reference, or a short topic name (prefixed with `{stage}-`).
2272
+ */
2273
+ addSnsPublishPolicy(topicOrName) {
2274
+ this.requireSharedRole('addSnsPublishPolicy');
2275
+ const arn = typeof topicOrName === 'string'
2276
+ ? `arn:${cdk.Aws.PARTITION}:sns:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:${this.stage}-${topicOrName}`
2277
+ : topicOrName.topicArn;
2278
+ this.sharedRole.addToPolicy(new awsIam.PolicyStatement({
2279
+ effect: awsIam.Effect.ALLOW,
2280
+ actions: ['sns:Publish'],
2281
+ resources: [arn]
2282
+ }));
2283
+ }
2284
+ /**
2285
+ * Add an SQS SendMessage policy to the shared role.
2286
+ * @param queueName - Short queue name (prefixed with `{stage}-`).
2287
+ */
2288
+ addSqsSendPolicy(queueName) {
2289
+ this.requireSharedRole('addSqsSendPolicy');
2290
+ const arn = `arn:${cdk.Aws.PARTITION}:sqs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:${this.stage}-${queueName}`;
2291
+ this.sharedRole.addToPolicy(new awsIam.PolicyStatement({
2292
+ effect: awsIam.Effect.ALLOW,
2293
+ actions: ['sqs:SendMessage', 'sqs:GetQueueAttributes', 'sqs:GetQueueUrl'],
2294
+ resources: [arn]
2295
+ }));
2296
+ }
2297
+ requireSharedRole(methodName) {
2298
+ if (!this.sharedRole) {
2299
+ throw new Error(`${methodName}() requires useSharedRole: true on the stack.`);
2300
+ }
2301
+ }
2205
2302
  /**
2206
2303
  * Create a CfnOutput with a stable export name.
2207
2304
  * Export pattern: `sls-{serviceName}-{stage}-{outputKey}`
@@ -2245,6 +2342,9 @@ function createRdsVpcConfig(scope, stage, config) {
2245
2342
  if (config.overrideLogicalIds?.ingress) {
2246
2343
  ingress.overrideLogicalId(config.overrideLogicalIds.ingress);
2247
2344
  }
2345
+ if (config.sharedRole) {
2346
+ config.sharedRole.addManagedPolicy(awsIam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaVPCAccessExecutionRole'));
2347
+ }
2248
2348
  return {
2249
2349
  vpc,
2250
2350
  vpcSubnets: { subnets: privateSubnets },