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

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`,
@@ -2100,8 +2101,24 @@ class EmStack extends cdk.Stack {
2100
2101
  * This means existing Serverless stacks are migrated in-place — no manual
2101
2102
  * logical ID overrides needed.
2102
2103
  */
2104
+ /**
2105
+ * Merge per-call config with defaultFunctionConfig.
2106
+ * Environment is deep-merged (per-call values override matching keys).
2107
+ * All other fields use per-call values when provided.
2108
+ */
2109
+ mergeConfig(config) {
2110
+ const defaults = this.defaultFunctionConfig;
2111
+ const merged = { ...defaults, ...config };
2112
+ if (defaults.environment || config.environment) {
2113
+ merged.environment = {
2114
+ ...(defaults.environment ?? {}),
2115
+ ...(config.environment ?? {})
2116
+ };
2117
+ }
2118
+ return merged;
2119
+ }
2103
2120
  createFunction(id, config) {
2104
- const merged = { ...this.defaultFunctionConfig, ...config };
2121
+ const merged = this.mergeConfig(config);
2105
2122
  const resolved = resolveHandlerPath(merged);
2106
2123
  const functionName = resolved.functionName;
2107
2124
  const handler = resolved.handler ?? merged.handler;
@@ -2148,7 +2165,7 @@ class EmStack extends cdk.Stack {
2148
2165
  * ```
2149
2166
  */
2150
2167
  createQueueConsumer(id, config) {
2151
- const merged = { ...this.defaultFunctionConfig, ...config };
2168
+ const merged = this.mergeConfig(config);
2152
2169
  const { functionName } = resolveHandlerPath(merged);
2153
2170
  return new LambdaWithQueue(this, id, {
2154
2171
  ...merged,
@@ -2175,7 +2192,7 @@ class EmStack extends cdk.Stack {
2175
2192
  createScheduledFunction(id, config) {
2176
2193
  const { schedule, ruleName, ruleDescription, ...functionConfig } = config;
2177
2194
  const fn = this.createFunction(id, functionConfig);
2178
- const resolved = resolveHandlerPath({ ...this.defaultFunctionConfig, ...functionConfig });
2195
+ const resolved = resolveHandlerPath(this.mergeConfig(functionConfig));
2179
2196
  const rule = new EmEventBridgeRule(this, `${id}Rule`, {
2180
2197
  stage: config.stage ?? this.stage,
2181
2198
  serviceName: config.serviceName ?? this.serviceName,
@@ -2202,6 +2219,61 @@ class EmStack extends cdk.Stack {
2202
2219
  const arn = `arn:${cdk.Aws.PARTITION}:sns:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:${this.stage}-alarm-email`;
2203
2220
  return awsSns.Topic.fromTopicArn(this, 'AlarmTopic', arn);
2204
2221
  }
2222
+ /**
2223
+ * Add a Lambda invoke policy to the shared role (account-scoped).
2224
+ * Requires `useSharedRole: true`.
2225
+ */
2226
+ addLambdaInvokePolicy() {
2227
+ this.requireSharedRole('addLambdaInvokePolicy');
2228
+ this.sharedRole.addToPolicy(new awsIam.PolicyStatement({
2229
+ effect: awsIam.Effect.ALLOW,
2230
+ actions: ['lambda:InvokeFunction'],
2231
+ resources: [`arn:${cdk.Aws.PARTITION}:lambda:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:function:*`]
2232
+ }));
2233
+ }
2234
+ /**
2235
+ * Add a Kinesis PutRecord/PutRecords policy to the shared role.
2236
+ * @param streamName - Short stream name (prefixed with `{stage}-`).
2237
+ */
2238
+ addKinesisPolicy(streamName) {
2239
+ this.requireSharedRole('addKinesisPolicy');
2240
+ const arn = `arn:${cdk.Aws.PARTITION}:kinesis:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:stream/${this.stage}-${streamName}`;
2241
+ this.sharedRole.addToPolicy(new awsIam.PolicyStatement({
2242
+ effect: awsIam.Effect.ALLOW,
2243
+ actions: ['kinesis:PutRecord', 'kinesis:PutRecords'],
2244
+ resources: [arn]
2245
+ }));
2246
+ }
2247
+ /**
2248
+ * Add an SNS Publish policy to the shared role.
2249
+ * @param topic - An ITopic reference.
2250
+ */
2251
+ addSnsPublishPolicy(topic) {
2252
+ this.requireSharedRole('addSnsPublishPolicy');
2253
+ this.sharedRole.addToPolicy(new awsIam.PolicyStatement({
2254
+ effect: awsIam.Effect.ALLOW,
2255
+ actions: ['sns:Publish'],
2256
+ resources: [topic.topicArn]
2257
+ }));
2258
+ }
2259
+ /**
2260
+ * Add an SQS SendMessage policy to the shared role.
2261
+ * @param queueName - Short queue name (prefixed with `{stage}-`).
2262
+ */
2263
+ addSqsSendPolicy(queueName) {
2264
+ this.requireSharedRole('addSqsSendPolicy');
2265
+ const arn = `arn:${cdk.Aws.PARTITION}:sqs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:${this.stage}-${queueName}`;
2266
+ this.sharedRole.addToPolicy(new awsIam.PolicyStatement({
2267
+ effect: awsIam.Effect.ALLOW,
2268
+ actions: ['sqs:SendMessage', 'sqs:GetQueueAttributes', 'sqs:GetQueueUrl'],
2269
+ resources: [arn]
2270
+ }));
2271
+ }
2272
+ requireSharedRole(methodName) {
2273
+ if (!this.sharedRole) {
2274
+ throw new Error(`${methodName}() requires useSharedRole: true on the stack.`);
2275
+ }
2276
+ }
2205
2277
  /**
2206
2278
  * Create a CfnOutput with a stable export name.
2207
2279
  * Export pattern: `sls-{serviceName}-{stage}-{outputKey}`
@@ -2245,6 +2317,9 @@ function createRdsVpcConfig(scope, stage, config) {
2245
2317
  if (config.overrideLogicalIds?.ingress) {
2246
2318
  ingress.overrideLogicalId(config.overrideLogicalIds.ingress);
2247
2319
  }
2320
+ if (config.sharedRole) {
2321
+ config.sharedRole.addManagedPolicy(awsIam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaVPCAccessExecutionRole'));
2322
+ }
2248
2323
  return {
2249
2324
  vpc,
2250
2325
  vpcSubnets: { subnets: privateSubnets },