@mettlecast/domain-cdk-packer 0.2.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.
Files changed (66) hide show
  1. package/README.md +83 -0
  2. package/dist/DomainStack.d.ts +68 -0
  3. package/dist/DomainStack.js +567 -0
  4. package/dist/__tests__/domain-stack.test.d.ts +1 -0
  5. package/dist/__tests__/domain-stack.test.js +223 -0
  6. package/dist/__tests__/lambda-factory.test.d.ts +1 -0
  7. package/dist/__tests__/lambda-factory.test.js +76 -0
  8. package/dist/__tests__/pack-flows.test.d.ts +1 -0
  9. package/dist/__tests__/pack-flows.test.js +283 -0
  10. package/dist/__tests__/registry.test.d.ts +1 -0
  11. package/dist/__tests__/registry.test.js +34 -0
  12. package/dist/__tests__/step-functions-codegen.test.d.ts +1 -0
  13. package/dist/__tests__/step-functions-codegen.test.js +159 -0
  14. package/dist/aspects/iam-boundaries-aspect.d.ts +11 -0
  15. package/dist/aspects/iam-boundaries-aspect.js +15 -0
  16. package/dist/aspects/index.d.ts +5 -0
  17. package/dist/aspects/index.js +3 -0
  18. package/dist/aspects/log-retention-aspect.d.ts +8 -0
  19. package/dist/aspects/log-retention-aspect.js +24 -0
  20. package/dist/aspects/tagging-aspect.d.ts +13 -0
  21. package/dist/aspects/tagging-aspect.js +17 -0
  22. package/dist/constructs/action-construct.d.ts +35 -0
  23. package/dist/constructs/action-construct.js +35 -0
  24. package/dist/constructs/alarm-construct.d.ts +19 -0
  25. package/dist/constructs/alarm-construct.js +58 -0
  26. package/dist/constructs/alarms-construct.d.ts +17 -0
  27. package/dist/constructs/alarms-construct.js +42 -0
  28. package/dist/constructs/api-construct.d.ts +30 -0
  29. package/dist/constructs/api-construct.js +64 -0
  30. package/dist/constructs/canary-construct.d.ts +13 -0
  31. package/dist/constructs/canary-construct.js +30 -0
  32. package/dist/constructs/cmk-construct.d.ts +9 -0
  33. package/dist/constructs/cmk-construct.js +20 -0
  34. package/dist/constructs/dashboard-construct.d.ts +15 -0
  35. package/dist/constructs/dashboard-construct.js +62 -0
  36. package/dist/constructs/health-construct.d.ts +11 -0
  37. package/dist/constructs/health-construct.js +29 -0
  38. package/dist/constructs/job-construct.d.ts +33 -0
  39. package/dist/constructs/job-construct.js +54 -0
  40. package/dist/constructs/schedule-construct.d.ts +27 -0
  41. package/dist/constructs/schedule-construct.js +54 -0
  42. package/dist/constructs/subscriber-construct.d.ts +30 -0
  43. package/dist/constructs/subscriber-construct.js +63 -0
  44. package/dist/constructs/waf-construct.d.ts +13 -0
  45. package/dist/constructs/waf-construct.js +75 -0
  46. package/dist/constructs/webhook-construct.d.ts +33 -0
  47. package/dist/constructs/webhook-construct.js +50 -0
  48. package/dist/flow-registry.d.ts +81 -0
  49. package/dist/flow-registry.js +2 -0
  50. package/dist/grouped-lambda-factory.d.ts +43 -0
  51. package/dist/grouped-lambda-factory.js +71 -0
  52. package/dist/iam/iam-policy-builder.d.ts +95 -0
  53. package/dist/iam/iam-policy-builder.js +232 -0
  54. package/dist/index.d.ts +36 -0
  55. package/dist/index.js +17 -0
  56. package/dist/lambda-factory.d.ts +46 -0
  57. package/dist/lambda-factory.js +80 -0
  58. package/dist/pack-domain.d.ts +35 -0
  59. package/dist/pack-domain.js +32 -0
  60. package/dist/pack-flows.d.ts +28 -0
  61. package/dist/pack-flows.js +154 -0
  62. package/dist/registry.d.ts +224 -0
  63. package/dist/registry.js +1 -0
  64. package/dist/step-functions-codegen.d.ts +65 -0
  65. package/dist/step-functions-codegen.js +55 -0
  66. package/package.json +36 -0
@@ -0,0 +1,15 @@
1
+ import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
2
+ import { Construct } from 'constructs';
3
+ export interface DashboardConstructProps {
4
+ domainId: string;
5
+ envCode: string;
6
+ /** List of endpointIds per primitiveClass for dashboard panels. */
7
+ endpoints: Array<{
8
+ id: string;
9
+ primitiveClass: string;
10
+ }>;
11
+ }
12
+ export declare class DashboardConstruct extends Construct {
13
+ readonly dashboard: cloudwatch.Dashboard;
14
+ constructor(scope: Construct, id: string, props: DashboardConstructProps);
15
+ }
@@ -0,0 +1,62 @@
1
+ import * as cdk from 'aws-cdk-lib';
2
+ import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
3
+ import { Construct } from 'constructs';
4
+ export class DashboardConstruct extends Construct {
5
+ dashboard;
6
+ constructor(scope, id, props) {
7
+ super(scope, id);
8
+ const { domainId, envCode, endpoints } = props;
9
+ const namespace = `TIB/Domain/${domainId}`;
10
+ const widgets = [];
11
+ // Group endpoints by primitiveClass for rows
12
+ const byClass = endpoints.reduce((acc, ep) => {
13
+ (acc[ep.primitiveClass] ??= []).push(ep.id);
14
+ return acc;
15
+ }, {});
16
+ for (const [primitiveClass, endpointIds] of Object.entries(byClass)) {
17
+ // Row title
18
+ widgets.push(new cloudwatch.TextWidget({
19
+ markdown: `## ${primitiveClass.toUpperCase()} — ${domainId}`,
20
+ width: 24,
21
+ height: 1,
22
+ }));
23
+ for (const endpointId of endpointIds) {
24
+ const dims = { domainId, endpointId, primitiveClass };
25
+ widgets.push(new cloudwatch.GraphWidget({
26
+ title: `${endpointId} — latency`,
27
+ width: 6,
28
+ height: 6,
29
+ left: [
30
+ new cloudwatch.Metric({ namespace, metricName: 'latency_ms', dimensionsMap: dims, statistic: 'p50', label: 'p50' }),
31
+ new cloudwatch.Metric({ namespace, metricName: 'latency_ms', dimensionsMap: dims, statistic: 'p95', label: 'p95' }),
32
+ new cloudwatch.Metric({ namespace, metricName: 'latency_ms', dimensionsMap: dims, statistic: 'p99', label: 'p99' }),
33
+ ],
34
+ }), new cloudwatch.GraphWidget({
35
+ title: `${endpointId} — errors & cold starts`,
36
+ width: 6,
37
+ height: 6,
38
+ left: [
39
+ new cloudwatch.Metric({ namespace, metricName: 'errors', dimensionsMap: dims, statistic: 'Sum', label: 'errors' }),
40
+ new cloudwatch.Metric({ namespace, metricName: 'cold_starts', dimensionsMap: dims, statistic: 'Sum', label: 'cold_starts' }),
41
+ ],
42
+ }), new cloudwatch.GraphWidget({
43
+ title: `${endpointId} — DB & events`,
44
+ width: 6,
45
+ height: 6,
46
+ left: [
47
+ new cloudwatch.Metric({ namespace, metricName: 'db_query_ms', dimensionsMap: dims, statistic: 'Average', label: 'db_ms_avg' }),
48
+ new cloudwatch.Metric({ namespace, metricName: 'event_publish_count', dimensionsMap: dims, statistic: 'Sum', label: 'events' }),
49
+ ],
50
+ }));
51
+ }
52
+ }
53
+ this.dashboard = new cloudwatch.Dashboard(this, 'Dashboard', {
54
+ dashboardName: `TIB-${envCode}-domain-${domainId}`,
55
+ widgets: [widgets],
56
+ });
57
+ new cdk.CfnOutput(scope, 'DashboardArn', {
58
+ value: this.dashboard.dashboardArn,
59
+ exportName: `TIB-${envCode}-domain-${domainId}-DashboardArn`,
60
+ });
61
+ }
62
+ }
@@ -0,0 +1,11 @@
1
+ import * as nodejs from 'aws-cdk-lib/aws-lambda-nodejs';
2
+ import { Construct } from 'constructs';
3
+ export interface HealthConstructProps {
4
+ domainId: string;
5
+ environment: Record<string, string>;
6
+ }
7
+ export declare class HealthConstruct extends Construct {
8
+ readonly healthFn: nodejs.NodejsFunction;
9
+ readonly readyFn: nodejs.NodejsFunction;
10
+ constructor(scope: Construct, id: string, props: HealthConstructProps);
11
+ }
@@ -0,0 +1,29 @@
1
+ import * as cdk from 'aws-cdk-lib';
2
+ import * as lambda from 'aws-cdk-lib/aws-lambda';
3
+ import * as nodejs from 'aws-cdk-lib/aws-lambda-nodejs';
4
+ import { Construct } from 'constructs';
5
+ export class HealthConstruct extends Construct {
6
+ healthFn;
7
+ readyFn;
8
+ constructor(scope, id, props) {
9
+ super(scope, id);
10
+ const commonProps = {
11
+ runtime: lambda.Runtime.NODEJS_20_X,
12
+ environment: props.environment,
13
+ timeout: cdk.Duration.seconds(10),
14
+ memorySize: 128,
15
+ };
16
+ this.healthFn = new nodejs.NodejsFunction(this, 'HealthFn', {
17
+ ...commonProps,
18
+ entry: require.resolve('@mettlecast/domain-runtime/runtime/health-handler'),
19
+ handler: 'healthHandler',
20
+ description: `${props.domainId} /_health — Lambda warm check`,
21
+ });
22
+ this.readyFn = new nodejs.NodejsFunction(this, 'ReadyFn', {
23
+ ...commonProps,
24
+ entry: require.resolve('@mettlecast/domain-runtime/runtime/health-handler'),
25
+ handler: 'readinessHandler',
26
+ description: `${props.domainId} /_ready — dependency reachability check`,
27
+ });
28
+ }
29
+ }
@@ -0,0 +1,33 @@
1
+ import * as sqs from 'aws-cdk-lib/aws-sqs';
2
+ import { Construct } from 'constructs';
3
+ import { LambdaFactory } from '../lambda-factory.js';
4
+ import { IamPolicyBuilder } from '../iam/iam-policy-builder.js';
5
+ import type { DomainRegistry } from '../registry.js';
6
+ /**
7
+ * Configuration properties for JobConstruct.
8
+ */
9
+ export interface JobConstructProps {
10
+ /** Domain registry containing job definitions. */
11
+ registry: DomainRegistry;
12
+ /** Factory for creating Lambda functions. */
13
+ lambdaFactory: LambdaFactory;
14
+ /** Builder for generating IAM policies. */
15
+ iamBuilder: IamPolicyBuilder;
16
+ }
17
+ /**
18
+ * CDK Construct that synthesizes Lambda functions with SQS queues, dead-letter queues,
19
+ * and event source mappings for background jobs defined in the domain registry.
20
+ */
21
+ export declare class JobConstruct extends Construct {
22
+ /**
23
+ * Map of job IDs to their corresponding SQS queues.
24
+ */
25
+ readonly queues: Map<string, sqs.Queue>;
26
+ /**
27
+ * Create a new JobConstruct.
28
+ * @param scope - Parent CDK scope
29
+ * @param id - Construct identifier
30
+ * @param props - Configuration properties
31
+ */
32
+ constructor(scope: Construct, id: string, props: JobConstructProps);
33
+ }
@@ -0,0 +1,54 @@
1
+ import * as cdk from 'aws-cdk-lib';
2
+ import * as sqs from 'aws-cdk-lib/aws-sqs';
3
+ import * as lambdaEventSources from 'aws-cdk-lib/aws-lambda-event-sources';
4
+ import { Construct } from 'constructs';
5
+ /**
6
+ * CDK Construct that synthesizes Lambda functions with SQS queues, dead-letter queues,
7
+ * and event source mappings for background jobs defined in the domain registry.
8
+ */
9
+ export class JobConstruct extends Construct {
10
+ /**
11
+ * Map of job IDs to their corresponding SQS queues.
12
+ */
13
+ queues;
14
+ /**
15
+ * Create a new JobConstruct.
16
+ * @param scope - Parent CDK scope
17
+ * @param id - Construct identifier
18
+ * @param props - Configuration properties
19
+ */
20
+ constructor(scope, id, props) {
21
+ super(scope, id);
22
+ this.queues = new Map();
23
+ for (const entry of props.registry.jobs) {
24
+ const pascalId = toPascalCase(entry.id);
25
+ const dlq = new sqs.Queue(this, `${pascalId}Dlq`, {
26
+ retentionPeriod: cdk.Duration.days(14),
27
+ });
28
+ const queue = new sqs.Queue(this, `${pascalId}Queue`, {
29
+ visibilityTimeout: cdk.Duration.seconds(entry.visibilityTimeoutSeconds),
30
+ deadLetterQueue: {
31
+ queue: dlq,
32
+ maxReceiveCount: entry.maxRetries,
33
+ },
34
+ });
35
+ this.queues.set(entry.id, queue);
36
+ const fn = props.lambdaFactory.createFunction(entry, entry.deployment);
37
+ props.iamBuilder
38
+ .forJob({ queueArn: queue.queueArn, dlqArn: dlq.queueArn })
39
+ .forEach((s) => fn.addToRolePolicy(s));
40
+ fn.addEventSource(new lambdaEventSources.SqsEventSource(queue, { batchSize: 1 }));
41
+ }
42
+ }
43
+ }
44
+ /**
45
+ * Convert a kebab-case or snake_case string to PascalCase.
46
+ * @param s - Input string to convert
47
+ * @returns PascalCase string
48
+ */
49
+ function toPascalCase(s) {
50
+ return s
51
+ .split(/[-_]/)
52
+ .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
53
+ .join('');
54
+ }
@@ -0,0 +1,27 @@
1
+ import { Construct } from 'constructs';
2
+ import { LambdaFactory } from '../lambda-factory.js';
3
+ import { IamPolicyBuilder } from '../iam/iam-policy-builder.js';
4
+ import type { DomainRegistry } from '../registry.js';
5
+ /**
6
+ * Configuration properties for ScheduleConstruct.
7
+ */
8
+ export interface ScheduleConstructProps {
9
+ /** Domain registry containing schedule definitions */
10
+ registry: DomainRegistry;
11
+ /** Lambda factory for creating schedule handler functions */
12
+ lambdaFactory: LambdaFactory;
13
+ /** IAM policy builder for schedule handler permissions */
14
+ iamBuilder: IamPolicyBuilder;
15
+ }
16
+ /**
17
+ * CDK Construct that synthesises Lambda functions and EventBridge Scheduler rules per schedule entry.
18
+ */
19
+ export declare class ScheduleConstruct extends Construct {
20
+ /**
21
+ * Create a new ScheduleConstruct.
22
+ * @param scope - Parent CDK Construct scope
23
+ * @param id - Logical ID for this construct
24
+ * @param props - Configuration properties
25
+ */
26
+ constructor(scope: Construct, id: string, props: ScheduleConstructProps);
27
+ }
@@ -0,0 +1,54 @@
1
+ import * as iam from 'aws-cdk-lib/aws-iam';
2
+ import * as scheduler from 'aws-cdk-lib/aws-scheduler';
3
+ import { Construct } from 'constructs';
4
+ /**
5
+ * CDK Construct that synthesises Lambda functions and EventBridge Scheduler rules per schedule entry.
6
+ */
7
+ export class ScheduleConstruct extends Construct {
8
+ /**
9
+ * Create a new ScheduleConstruct.
10
+ * @param scope - Parent CDK Construct scope
11
+ * @param id - Logical ID for this construct
12
+ * @param props - Configuration properties
13
+ */
14
+ constructor(scope, id, props) {
15
+ super(scope, id);
16
+ for (const entry of props.registry.schedules) {
17
+ const pascalId = toPascalCase(entry.id);
18
+ // Create the Lambda function for this schedule
19
+ const fn = props.lambdaFactory.createFunction(entry, entry.deployment);
20
+ // Attach schedule-specific IAM policies
21
+ props.iamBuilder.forSchedule().forEach((statement) => {
22
+ fn.addToRolePolicy(statement);
23
+ });
24
+ // Create a dedicated IAM role for the EventBridge Scheduler to assume
25
+ const schedulerRole = new iam.Role(this, `${pascalId}SchedulerRole`, {
26
+ assumedBy: new iam.ServicePrincipal('scheduler.amazonaws.com'),
27
+ });
28
+ // Grant the scheduler role permission to invoke the Lambda
29
+ fn.grantInvoke(schedulerRole);
30
+ // Create the EventBridge Scheduler rule
31
+ new scheduler.CfnSchedule(this, `${pascalId}Schedule`, {
32
+ scheduleExpression: entry.cron,
33
+ flexibleTimeWindow: { mode: 'OFF' },
34
+ state: entry.enabled ? 'ENABLED' : 'DISABLED',
35
+ target: {
36
+ arn: fn.functionArn,
37
+ roleArn: schedulerRole.roleArn,
38
+ input: JSON.stringify({}),
39
+ },
40
+ });
41
+ }
42
+ }
43
+ }
44
+ /**
45
+ * Convert a kebab-case or snake_case string to PascalCase.
46
+ * @param s - Input string
47
+ * @returns PascalCase string
48
+ */
49
+ function toPascalCase(s) {
50
+ return s
51
+ .split(/[-_]/)
52
+ .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
53
+ .join('');
54
+ }
@@ -0,0 +1,30 @@
1
+ import * as events from 'aws-cdk-lib/aws-events';
2
+ import { Construct } from 'constructs';
3
+ import { LambdaFactory } from '../lambda-factory.js';
4
+ import { IamPolicyBuilder } from '../iam/iam-policy-builder.js';
5
+ import type { DomainRegistry } from '../registry.js';
6
+ /**
7
+ * Configuration properties for SubscriberConstruct.
8
+ */
9
+ export interface SubscriberConstructProps {
10
+ /** Domain registry containing all subscriber entries. */
11
+ registry: DomainRegistry;
12
+ /** Factory for creating Lambda functions from registry entries. */
13
+ lambdaFactory: LambdaFactory;
14
+ /** IAM policy builder for granting permissions to Lambda roles. */
15
+ iamBuilder: IamPolicyBuilder;
16
+ /** EventBridge event bus for routing subscriber events. */
17
+ eventBus: events.IEventBus;
18
+ }
19
+ /**
20
+ * CDK Construct that synthesises Lambda + SQS + EventBridge rule per registered subscriber.
21
+ */
22
+ export declare class SubscriberConstruct extends Construct {
23
+ /**
24
+ * Create a new SubscriberConstruct instance.
25
+ * @param scope - Parent CDK scope.
26
+ * @param id - Construct identifier.
27
+ * @param props - Construct properties.
28
+ */
29
+ constructor(scope: Construct, id: string, props: SubscriberConstructProps);
30
+ }
@@ -0,0 +1,63 @@
1
+ import * as cdk from 'aws-cdk-lib';
2
+ import * as sqs from 'aws-cdk-lib/aws-sqs';
3
+ import * as events from 'aws-cdk-lib/aws-events';
4
+ import * as eventsTargets from 'aws-cdk-lib/aws-events-targets';
5
+ import * as lambdaEventSources from 'aws-cdk-lib/aws-lambda-event-sources';
6
+ import { Construct } from 'constructs';
7
+ /**
8
+ * CDK Construct that synthesises Lambda + SQS + EventBridge rule per registered subscriber.
9
+ */
10
+ export class SubscriberConstruct extends Construct {
11
+ /**
12
+ * Create a new SubscriberConstruct instance.
13
+ * @param scope - Parent CDK scope.
14
+ * @param id - Construct identifier.
15
+ * @param props - Construct properties.
16
+ */
17
+ constructor(scope, id, props) {
18
+ super(scope, id);
19
+ for (const entry of props.registry.subscribers) {
20
+ const pascalId = toPascalCase(entry.id);
21
+ // Create dead-letter queue
22
+ const dlq = new sqs.Queue(this, `${pascalId}Dlq`, {
23
+ retentionPeriod: cdk.Duration.days(14),
24
+ });
25
+ // Create main queue with dead-letter queue configuration
26
+ const queue = new sqs.Queue(this, `${pascalId}Queue`, {
27
+ visibilityTimeout: cdk.Duration.seconds(180),
28
+ deadLetterQueue: {
29
+ queue: dlq,
30
+ maxReceiveCount: 3,
31
+ },
32
+ });
33
+ // Create Lambda function from registry entry
34
+ const fn = props.lambdaFactory.createFunction(entry, entry.deployment);
35
+ // Add IAM policies for SQS access
36
+ props.iamBuilder.forSubscriber({ queueArn: queue.queueArn, dlqArn: dlq.queueArn }).forEach((statement) => {
37
+ fn.addToRolePolicy(statement);
38
+ });
39
+ // Add SQS as event source for Lambda
40
+ fn.addEventSource(new lambdaEventSources.SqsEventSource(queue, {
41
+ batchSize: 10,
42
+ maxConcurrency: entry.concurrency ?? 5,
43
+ }));
44
+ // Create EventBridge rule targeting SQS queue
45
+ new events.Rule(this, `${pascalId}Rule`, {
46
+ eventBus: props.eventBus,
47
+ eventPattern: { detailType: [entry.event] },
48
+ targets: [new eventsTargets.SqsQueue(queue)],
49
+ });
50
+ }
51
+ }
52
+ }
53
+ /**
54
+ * Convert kebab-case or snake_case string to PascalCase.
55
+ * @param s - Input string.
56
+ * @returns PascalCase string.
57
+ */
58
+ function toPascalCase(s) {
59
+ return s
60
+ .split(/[-_]/)
61
+ .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
62
+ .join('');
63
+ }
@@ -0,0 +1,13 @@
1
+ import * as wafv2 from 'aws-cdk-lib/aws-wafv2';
2
+ import * as apigwv2 from 'aws-cdk-lib/aws-apigatewayv2';
3
+ import { Construct } from 'constructs';
4
+ export interface WafConstructProps {
5
+ domainId: string;
6
+ httpApi: apigwv2.HttpApi;
7
+ /** Requests per 5-minute window per IP before blocking. Default: 2000. */
8
+ rateLimit?: number;
9
+ }
10
+ export declare class WafConstruct extends Construct {
11
+ readonly webAcl: wafv2.CfnWebACL;
12
+ constructor(scope: Construct, id: string, props: WafConstructProps);
13
+ }
@@ -0,0 +1,75 @@
1
+ import * as cdk from 'aws-cdk-lib';
2
+ import * as wafv2 from 'aws-cdk-lib/aws-wafv2';
3
+ import { Construct } from 'constructs';
4
+ export class WafConstruct extends Construct {
5
+ webAcl;
6
+ constructor(scope, id, props) {
7
+ super(scope, id);
8
+ const rateLimit = props.rateLimit ?? 2000;
9
+ this.webAcl = new wafv2.CfnWebACL(this, 'WebAcl', {
10
+ name: `tib-${props.domainId}-waf`,
11
+ scope: 'REGIONAL',
12
+ defaultAction: { allow: {} },
13
+ visibilityConfig: {
14
+ sampledRequestsEnabled: true,
15
+ cloudWatchMetricsEnabled: true,
16
+ metricName: `tib-${props.domainId}-waf`,
17
+ },
18
+ rules: [
19
+ {
20
+ name: 'AWSManagedRulesCommonRuleSet',
21
+ priority: 1,
22
+ overrideAction: { none: {} },
23
+ statement: {
24
+ managedRuleGroupStatement: {
25
+ vendorName: 'AWS',
26
+ name: 'AWSManagedRulesCommonRuleSet',
27
+ },
28
+ },
29
+ visibilityConfig: {
30
+ sampledRequestsEnabled: true,
31
+ cloudWatchMetricsEnabled: true,
32
+ metricName: `tib-${props.domainId}-common-rules`,
33
+ },
34
+ },
35
+ {
36
+ name: 'AWSManagedRulesKnownBadInputsRuleSet',
37
+ priority: 2,
38
+ overrideAction: { none: {} },
39
+ statement: {
40
+ managedRuleGroupStatement: {
41
+ vendorName: 'AWS',
42
+ name: 'AWSManagedRulesKnownBadInputsRuleSet',
43
+ },
44
+ },
45
+ visibilityConfig: {
46
+ sampledRequestsEnabled: true,
47
+ cloudWatchMetricsEnabled: true,
48
+ metricName: `tib-${props.domainId}-bad-inputs`,
49
+ },
50
+ },
51
+ {
52
+ name: 'IpRateLimit',
53
+ priority: 3,
54
+ action: { block: {} },
55
+ statement: {
56
+ rateBasedStatement: {
57
+ limit: rateLimit,
58
+ aggregateKeyType: 'IP',
59
+ },
60
+ },
61
+ visibilityConfig: {
62
+ sampledRequestsEnabled: true,
63
+ cloudWatchMetricsEnabled: true,
64
+ metricName: `tib-${props.domainId}-rate-limit`,
65
+ },
66
+ },
67
+ ],
68
+ });
69
+ // Associate WAF with the API Gateway stage
70
+ new wafv2.CfnWebACLAssociation(this, 'WebAclAssociation', {
71
+ resourceArn: `arn:aws:apigateway:${cdk.Stack.of(this).region}::/restapis/${props.httpApi.httpApiId}/stages/$default`,
72
+ webAclArn: this.webAcl.attrArn,
73
+ });
74
+ }
75
+ }
@@ -0,0 +1,33 @@
1
+ import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
2
+ import * as apigwv2 from 'aws-cdk-lib/aws-apigatewayv2';
3
+ import { Construct } from 'constructs';
4
+ import { LambdaFactory } from '../lambda-factory.js';
5
+ import { IamPolicyBuilder } from '../iam/iam-policy-builder.js';
6
+ import type { DomainRegistry } from '../registry.js';
7
+ /**
8
+ * Configuration properties for WebhookConstruct.
9
+ */
10
+ export interface WebhookConstructProps {
11
+ /** Domain registry containing all webhook entries. */
12
+ registry: DomainRegistry;
13
+ /** Shared HttpApi to route requests to Lambdas. */
14
+ httpApi: apigwv2.HttpApi;
15
+ /** Factory for creating Lambda functions from registry entries. */
16
+ lambdaFactory: LambdaFactory;
17
+ /** IAM policy builder for granting permissions to Lambda roles. */
18
+ iamBuilder: IamPolicyBuilder;
19
+ }
20
+ /**
21
+ * CDK Construct that synthesises one Lambda per registered webhook, one shared DynamoDB dedupe table, and adds webhook routes to the HttpApi.
22
+ */
23
+ export declare class WebhookConstruct extends Construct {
24
+ /** Shared webhook deduplication table. */
25
+ readonly dedupeTable: dynamodb.Table;
26
+ /**
27
+ * Create a new WebhookConstruct instance.
28
+ * @param scope - Parent CDK scope.
29
+ * @param id - Construct identifier.
30
+ * @param props - Construct properties.
31
+ */
32
+ constructor(scope: Construct, id: string, props: WebhookConstructProps);
33
+ }
@@ -0,0 +1,50 @@
1
+ import * as cdk from 'aws-cdk-lib';
2
+ import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
3
+ import * as apigwv2 from 'aws-cdk-lib/aws-apigatewayv2';
4
+ import * as apigwv2integrations from 'aws-cdk-lib/aws-apigatewayv2-integrations';
5
+ import { Construct } from 'constructs';
6
+ /**
7
+ * CDK Construct that synthesises one Lambda per registered webhook, one shared DynamoDB dedupe table, and adds webhook routes to the HttpApi.
8
+ */
9
+ export class WebhookConstruct extends Construct {
10
+ /** Shared webhook deduplication table. */
11
+ dedupeTable;
12
+ /**
13
+ * Create a new WebhookConstruct instance.
14
+ * @param scope - Parent CDK scope.
15
+ * @param id - Construct identifier.
16
+ * @param props - Construct properties.
17
+ */
18
+ constructor(scope, id, props) {
19
+ super(scope, id);
20
+ this.dedupeTable = new dynamodb.Table(this, 'DedupeTable', {
21
+ partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING },
22
+ billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
23
+ timeToLiveAttribute: 'expiresAt',
24
+ removalPolicy: cdk.RemovalPolicy.DESTROY,
25
+ });
26
+ for (const entry of props.registry.webhooks) {
27
+ const fn = props.lambdaFactory.createFunction(entry, entry.deployment);
28
+ props.iamBuilder.forWebhook({ dedupeTableArn: this.dedupeTable.tableArn }).forEach((statement) => {
29
+ fn.addToRolePolicy(statement);
30
+ });
31
+ this.dedupeTable.grantReadWriteData(fn);
32
+ props.httpApi.addRoutes({
33
+ path: entry.path,
34
+ methods: [apigwv2.HttpMethod.POST],
35
+ integration: new apigwv2integrations.HttpLambdaIntegration(`${id}${toPascalCase(entry.id)}Integration`, fn),
36
+ });
37
+ }
38
+ }
39
+ }
40
+ /**
41
+ * Convert kebab-case or snake_case string to PascalCase.
42
+ * @param s - Input string.
43
+ * @returns PascalCase string.
44
+ */
45
+ function toPascalCase(s) {
46
+ return s
47
+ .split(/[-_]/)
48
+ .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
49
+ .join('');
50
+ }
@@ -0,0 +1,81 @@
1
+ /** Plain JSON-serialisable mirror of FlowStep types from domain-runtime. */
2
+ export interface SerialDomainActionStep {
3
+ type: 'domain-action';
4
+ name: string;
5
+ domainId: string;
6
+ actionId: string;
7
+ parameters?: Record<string, unknown>;
8
+ waitForTaskToken?: boolean;
9
+ next?: string;
10
+ }
11
+ export interface SerialDomainApiStep {
12
+ type: 'domain-api';
13
+ name: string;
14
+ domainId: string;
15
+ apiId: string;
16
+ parameters?: Record<string, unknown>;
17
+ next?: string;
18
+ }
19
+ export interface SerialDomainEventStep {
20
+ type: 'domain-event';
21
+ name: string;
22
+ eventId: string;
23
+ payload: Record<string, unknown>;
24
+ version: number;
25
+ next?: string;
26
+ }
27
+ export type SerialFlowControlStep = {
28
+ type: 'flow-control';
29
+ control: 'choice';
30
+ name: string;
31
+ choices: Array<{
32
+ when: string;
33
+ next: string;
34
+ }>;
35
+ default?: string;
36
+ } | {
37
+ type: 'flow-control';
38
+ control: 'parallel';
39
+ name: string;
40
+ branches: SerialFlowStep[][];
41
+ next?: string;
42
+ } | {
43
+ type: 'flow-control';
44
+ control: 'wait';
45
+ name: string;
46
+ seconds: number;
47
+ next?: string;
48
+ } | {
49
+ type: 'flow-control';
50
+ control: 'succeed';
51
+ name: string;
52
+ } | {
53
+ type: 'flow-control';
54
+ control: 'fail';
55
+ name: string;
56
+ cause?: string;
57
+ error?: string;
58
+ } | {
59
+ type: 'flow-control';
60
+ control: 'pass';
61
+ name: string;
62
+ result?: unknown;
63
+ next?: string;
64
+ };
65
+ export type SerialFlowStep = SerialDomainActionStep | SerialDomainApiStep | SerialDomainEventStep | SerialFlowControlStep;
66
+ export interface FlowRegistryEntry {
67
+ id: string;
68
+ owningDomain?: string;
69
+ type?: 'express' | 'standard';
70
+ name: string;
71
+ steps: SerialFlowStep[];
72
+ trigger?: {
73
+ type: 'event';
74
+ eventId: string;
75
+ semverRange: string;
76
+ };
77
+ }
78
+ export interface FlowRegistry {
79
+ schemaVersion: '1';
80
+ flows: FlowRegistryEntry[];
81
+ }
@@ -0,0 +1,2 @@
1
+ /** Plain JSON-serialisable mirror of FlowStep types from domain-runtime. */
2
+ export {};