@mbc-cqrs-serverless/cli 1.2.7-beta.0 → 1.3.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mbc-cqrs-serverless/cli",
3
- "version": "1.2.7-beta.0",
3
+ "version": "1.3.0",
4
4
  "description": "a CLI to get started with MBC CQRS serverless framework",
5
5
  "keywords": [
6
6
  "mbc",
@@ -58,5 +58,5 @@
58
58
  "@faker-js/faker": "^8.3.1",
59
59
  "copyfiles": "^2.4.1"
60
60
  },
61
- "gitHead": "1975de0bba0555ec74f3f0607bd4f86b6c65c1f5"
61
+ "gitHead": "3e6f1b6d57bf38433d5f63dfb9f5e03a4c9bb25d"
62
62
  }
@@ -10,8 +10,33 @@ export type Config = {
10
10
  domain: {
11
11
  http: string
12
12
  appsync: string
13
+ appsyncEvents?: string // optional: custom domain for AppSync Events API
13
14
  }
14
15
 
16
+ appsyncEvents?: {
17
+ /** Set true to create the AppSync Events API and inject env vars into Lambda/ECS */
18
+ enabled: boolean
19
+ /**
20
+ * Channel namespace name — must be a pre-registered name in the AppSync Event API.
21
+ * Becomes segment 1 of every channel path: /{namespace}/{tenantCode}/{table}/{action}/{id}
22
+ * Defaults to 'default'.
23
+ */
24
+ namespace: string
25
+ /** API key TTL in days. Defaults to 365. */
26
+ apiKeyExpireDays?: number
27
+ }
28
+
29
+ /**
30
+ * Value injected as NOTIFICATION_TRANSPORTS env var into Lambda/ECS.
31
+ * Controls which transports NotificationEventHandler uses.
32
+ * Examples:
33
+ * 'appsync-event' — Events API only
34
+ * 'appsync-graphql' — GraphQL API only
35
+ * 'appsync-graphql,appsync-event' — dual-publish (migration mode)
36
+ * Defaults to 'appsync-event' when appsyncEvents is enabled.
37
+ */
38
+ notificationTransports?: string
39
+
15
40
  // existing resources
16
41
  userPoolId?: string
17
42
 
@@ -249,6 +249,76 @@ export class InfraStack extends cdk.Stack {
249
249
  this.graphqlApiKey = new cdk.CfnOutput(this, 'GraphQLAPIKey', {
250
250
  value: appSyncApi.apiKey || '',
251
251
  })
252
+
253
+ // AppSync Events API (HTTP pub/sub, schema-free)
254
+ // Uses L2 EventApi so that ChannelNamespace can set explicit publishAuthModeTypes
255
+ // and subscribeAuthModeTypes — without these the namespace has no effective
256
+ // authorization and Lambda publish calls receive 401.
257
+ let appSyncEventsApi: cdk.aws_appsync.EventApi | undefined
258
+ if (props.config.appsyncEvents?.enabled) {
259
+ const expireDays = props.config.appsyncEvents.apiKeyExpireDays ?? 365
260
+
261
+ appSyncEventsApi = new cdk.aws_appsync.EventApi(this, 'events-api', {
262
+ apiName: prefix + 'events',
263
+ authorizationConfig: {
264
+ authProviders: [
265
+ {
266
+ authorizationType: cdk.aws_appsync.AppSyncAuthorizationType.IAM,
267
+ },
268
+ {
269
+ authorizationType:
270
+ cdk.aws_appsync.AppSyncAuthorizationType.API_KEY,
271
+ apiKeyConfig: {
272
+ expires: cdk.Expiration.after(cdk.Duration.days(expireDays)),
273
+ },
274
+ },
275
+ ],
276
+ // API-level defaults — overridden per-namespace below
277
+ connectionAuthModeTypes: [
278
+ cdk.aws_appsync.AppSyncAuthorizationType.API_KEY,
279
+ ],
280
+ defaultPublishAuthModeTypes: [
281
+ cdk.aws_appsync.AppSyncAuthorizationType.IAM,
282
+ ],
283
+ defaultSubscribeAuthModeTypes: [
284
+ cdk.aws_appsync.AppSyncAuthorizationType.API_KEY,
285
+ ],
286
+ },
287
+ })
288
+
289
+ // Namespace — explicitly sets auth per operation so it is never left
290
+ // without authorization (which caused the 401).
291
+ // publish = AWS_IAM → Lambda signs with its execution role
292
+ // subscribe = API_KEY → browser clients pass x-api-key header
293
+ new cdk.aws_appsync.ChannelNamespace(this, 'events-namespace', {
294
+ api: appSyncEventsApi,
295
+ channelNamespaceName: props.config.appsyncEvents.namespace ?? 'default',
296
+ authorizationConfig: {
297
+ publishAuthModeTypes: [cdk.aws_appsync.AppSyncAuthorizationType.IAM],
298
+ subscribeAuthModeTypes: [
299
+ cdk.aws_appsync.AppSyncAuthorizationType.API_KEY,
300
+ ],
301
+ },
302
+ })
303
+
304
+ // Outputs
305
+ new cdk.CfnOutput(this, 'AppSyncEventsHttpEndpoint', {
306
+ value: `https://${appSyncEventsApi.httpDns}/event`,
307
+ description:
308
+ 'AppSync Events HTTP endpoint — APPSYNC_EVENTS_ENDPOINT env var',
309
+ })
310
+ new cdk.CfnOutput(this, 'AppSyncEventsRealtimeEndpoint', {
311
+ value: `wss://${appSyncEventsApi.realtimeDns}/event/realtime`,
312
+ description:
313
+ 'AppSync Events WebSocket endpoint for client subscriptions',
314
+ })
315
+ new cdk.CfnOutput(this, 'AppSyncEventsNamespace', {
316
+ value: props.config.appsyncEvents.namespace ?? 'default',
317
+ description:
318
+ 'AppSync Events namespace — APPSYNC_EVENTS_NAMESPACE env var',
319
+ })
320
+ }
321
+
252
322
  // S3
253
323
  const ddbBucket = new cdk.aws_s3.Bucket(this, 'ddb-attributes', {
254
324
  bucketName: prefix + 'ddb-attributes', // Globally unique bucket name
@@ -398,6 +468,16 @@ export class InfraStack extends cdk.Stack {
398
468
  SNS_ALARM_TOPIC_ARN: alarmSns.topicArn,
399
469
  COGNITO_USER_POOL_ID: userPool.userPoolId,
400
470
  APPSYNC_ENDPOINT: appSyncApi.graphqlUrl,
471
+ // AppSync Events — only injected when feature is enabled
472
+ ...(props.config.appsyncEvents?.enabled && appSyncEventsApi
473
+ ? {
474
+ NOTIFICATION_TRANSPORTS:
475
+ props.config.notificationTransports ?? 'appsync-event',
476
+ APPSYNC_EVENTS_ENDPOINT: `https://${appSyncEventsApi.httpDns}/event`,
477
+ APPSYNC_EVENTS_NAMESPACE:
478
+ props.config.appsyncEvents.namespace ?? 'default',
479
+ }
480
+ : {}),
401
481
  SES_FROM_EMAIL: props.config.fromEmailAddress,
402
482
  DATABASE_URL: `postgresql://${props.config.rds.accountSsmKey}@${props.config.rds.endpoint}/${props.config.rds.dbName}?schema=public`,
403
483
  S3_PUBLIC_BUCKET_NAME: publicBucket.bucketName,
@@ -645,6 +725,14 @@ export class InfraStack extends cdk.Stack {
645
725
  domainName: httpDistribution.distributionDomainName,
646
726
  })
647
727
 
728
+ if (props.config.domain.appsyncEvents && appSyncEventsApi) {
729
+ new cdk.aws_route53.CnameRecord(this, 'AppSyncEventsCnameRecord', {
730
+ zone: hostedZone,
731
+ recordName: props.config.domain.appsyncEvents,
732
+ domainName: appSyncEventsApi.httpDns,
733
+ })
734
+ }
735
+
648
736
  this.httpDistributionDomain = new cdk.CfnOutput(
649
737
  this,
650
738
  'http-distribution-domain',
@@ -1100,6 +1188,8 @@ export class InfraStack extends cdk.Stack {
1100
1188
  appSyncApi.grantMutation(lambdaApi)
1101
1189
  importActionSqs.grantSendMessages(lambdaApi)
1102
1190
 
1191
+ appSyncEventsApi?.grantPublish(lambdaApi)
1192
+
1103
1193
  // Define an IAM policy for full DynamoDB access
1104
1194
  const dynamoDbTablePrefixArn = cdk.Arn.format({
1105
1195
  partition: 'aws',
@@ -1231,6 +1321,7 @@ export class InfraStack extends cdk.Stack {
1231
1321
  statements: [ssmPolicy],
1232
1322
  }),
1233
1323
  )
1324
+ appSyncEventsApi?.grantPublish(taskRole)
1234
1325
  }
1235
1326
  }
1236
1327
  }