@aws-blocks/core 0.3.0 → 0.5.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 (147) hide show
  1. package/README.md +25 -0
  2. package/dist/api.d.ts.map +1 -1
  3. package/dist/api.js +32 -0
  4. package/dist/api.test.js +22 -0
  5. package/dist/bb-utils.d.ts +1 -0
  6. package/dist/bb-utils.d.ts.map +1 -1
  7. package/dist/bb-utils.js +3 -0
  8. package/dist/cdk/apigateway-account.d.ts +33 -0
  9. package/dist/cdk/apigateway-account.d.ts.map +1 -0
  10. package/dist/cdk/apigateway-account.js +60 -0
  11. package/dist/cdk/blocks-backend.d.ts +11 -0
  12. package/dist/cdk/blocks-backend.d.ts.map +1 -1
  13. package/dist/cdk/blocks-backend.js +72 -13
  14. package/dist/cdk/blocks-backend.test.js +88 -1
  15. package/dist/cdk/blocks-defaults.d.ts +76 -1
  16. package/dist/cdk/blocks-defaults.d.ts.map +1 -1
  17. package/dist/cdk/blocks-defaults.js +17 -1
  18. package/dist/cdk/blocks-defaults.test.js +17 -0
  19. package/dist/cdk/blocks-stack.test.js +44 -3
  20. package/dist/cdk/compute/compute-registry.d.ts +19 -0
  21. package/dist/cdk/compute/compute-registry.d.ts.map +1 -0
  22. package/dist/cdk/compute/compute-registry.js +38 -0
  23. package/dist/cdk/compute/compute.d.ts +82 -2
  24. package/dist/cdk/compute/compute.d.ts.map +1 -1
  25. package/dist/cdk/compute/compute.js +64 -2
  26. package/dist/cdk/compute/default-compute-factory.d.ts +1 -0
  27. package/dist/cdk/compute/default-compute-factory.d.ts.map +1 -1
  28. package/dist/cdk/config-registry.d.ts +34 -4
  29. package/dist/cdk/config-registry.d.ts.map +1 -1
  30. package/dist/cdk/config-registry.js +83 -25
  31. package/dist/cdk/config-registry.test.d.ts +2 -0
  32. package/dist/cdk/config-registry.test.d.ts.map +1 -0
  33. package/dist/cdk/config-registry.test.js +127 -0
  34. package/dist/cdk/dashboard-registry.d.ts +41 -0
  35. package/dist/cdk/dashboard-registry.d.ts.map +1 -0
  36. package/dist/cdk/dashboard-registry.js +61 -0
  37. package/dist/cdk/index.d.ts +90 -6
  38. package/dist/cdk/index.d.ts.map +1 -1
  39. package/dist/cdk/index.js +135 -13
  40. package/dist/cdk/internal.d.ts +3 -1
  41. package/dist/cdk/internal.d.ts.map +1 -1
  42. package/dist/cdk/internal.js +4 -1
  43. package/dist/cdk/tracer-registry.d.ts +31 -0
  44. package/dist/cdk/tracer-registry.d.ts.map +1 -0
  45. package/dist/cdk/tracer-registry.js +49 -0
  46. package/dist/cdk/vpc-requirements-registry.d.ts +33 -0
  47. package/dist/cdk/vpc-requirements-registry.d.ts.map +1 -0
  48. package/dist/cdk/vpc-requirements-registry.js +46 -0
  49. package/dist/cdk/vpc-types.d.ts +151 -0
  50. package/dist/cdk/vpc-types.d.ts.map +1 -0
  51. package/dist/cdk/vpc-types.js +3 -0
  52. package/dist/cdk/vpc.d.ts +59 -0
  53. package/dist/cdk/vpc.d.ts.map +1 -0
  54. package/dist/cdk/vpc.js +298 -0
  55. package/dist/cdk/vpc.test.d.ts +2 -0
  56. package/dist/cdk/vpc.test.d.ts.map +1 -0
  57. package/dist/cdk/vpc.test.js +285 -0
  58. package/dist/client/index.d.ts +1 -1
  59. package/dist/client/index.d.ts.map +1 -1
  60. package/dist/client/index.js +1 -1
  61. package/dist/common/config.d.ts +34 -0
  62. package/dist/common/config.d.ts.map +1 -1
  63. package/dist/common/config.js +45 -3
  64. package/dist/common/config.test.js +19 -0
  65. package/dist/common/index.d.ts +8 -0
  66. package/dist/common/index.d.ts.map +1 -1
  67. package/dist/errors.d.ts +21 -0
  68. package/dist/errors.d.ts.map +1 -1
  69. package/dist/errors.js +25 -0
  70. package/dist/hosting.d.ts +9 -0
  71. package/dist/hosting.d.ts.map +1 -1
  72. package/dist/hosting.js +12 -1
  73. package/dist/hosting.test.js +63 -1
  74. package/dist/index.cdk.d.ts +3 -2
  75. package/dist/index.cdk.d.ts.map +1 -1
  76. package/dist/index.cdk.js +2 -2
  77. package/dist/index.d.ts +1 -1
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +1 -1
  80. package/dist/lambda-handler.d.ts +17 -0
  81. package/dist/lambda-handler.d.ts.map +1 -1
  82. package/dist/lambda-handler.js +61 -5
  83. package/dist/lambda-handler.test.js +191 -2
  84. package/dist/raw-route.d.ts +15 -1
  85. package/dist/raw-route.d.ts.map +1 -1
  86. package/dist/raw-route.js +96 -12
  87. package/dist/raw-route.test.js +332 -1
  88. package/dist/scripts/dev-server.d.ts.map +1 -1
  89. package/dist/scripts/dev-server.js +11 -0
  90. package/dist/scripts/extract-ts-types.d.ts.map +1 -1
  91. package/dist/scripts/extract-ts-types.js +107 -23
  92. package/dist/scripts/extract-ts-types.test.js +225 -26
  93. package/dist/scripts/generate-spec.d.ts.map +1 -1
  94. package/dist/scripts/generate-spec.js +14 -5
  95. package/dist/scripts/generate-spec.test.js +93 -0
  96. package/dist/scripts/sandbox-empty-buckets.test.d.ts +2 -0
  97. package/dist/scripts/sandbox-empty-buckets.test.d.ts.map +1 -0
  98. package/dist/scripts/sandbox-empty-buckets.test.js +171 -0
  99. package/dist/scripts/sandbox.d.ts +54 -0
  100. package/dist/scripts/sandbox.d.ts.map +1 -1
  101. package/dist/scripts/sandbox.js +163 -25
  102. package/dist/version.d.ts +1 -1
  103. package/dist/version.js +1 -1
  104. package/package.json +10 -1
  105. package/src/api.test.ts +25 -0
  106. package/src/api.ts +39 -0
  107. package/src/bb-utils.ts +3 -0
  108. package/src/cdk/apigateway-account.ts +66 -0
  109. package/src/cdk/blocks-backend.test.ts +168 -60
  110. package/src/cdk/blocks-backend.ts +299 -227
  111. package/src/cdk/blocks-defaults.test.ts +21 -0
  112. package/src/cdk/blocks-defaults.ts +79 -1
  113. package/src/cdk/blocks-stack.test.ts +57 -13
  114. package/src/cdk/compute/compute-registry.ts +45 -0
  115. package/src/cdk/compute/compute.ts +114 -2
  116. package/src/cdk/compute/default-compute-factory.ts +1 -0
  117. package/src/cdk/config-registry.test.ts +149 -0
  118. package/src/cdk/config-registry.ts +92 -34
  119. package/src/cdk/dashboard-registry.ts +68 -0
  120. package/src/cdk/index.ts +427 -256
  121. package/src/cdk/internal.ts +6 -2
  122. package/src/cdk/tracer-registry.ts +54 -0
  123. package/src/cdk/vpc-requirements-registry.ts +63 -0
  124. package/src/cdk/vpc-types.ts +158 -0
  125. package/src/cdk/vpc.test.ts +348 -0
  126. package/src/cdk/vpc.ts +336 -0
  127. package/src/client/index.ts +1 -1
  128. package/src/common/config.test.ts +21 -0
  129. package/src/common/config.ts +47 -3
  130. package/src/common/index.ts +8 -0
  131. package/src/errors.ts +26 -0
  132. package/src/hosting.test.ts +87 -1
  133. package/src/hosting.ts +24 -1
  134. package/src/index.cdk.ts +11 -1
  135. package/src/index.ts +1 -1
  136. package/src/lambda-handler.test.ts +220 -2
  137. package/src/lambda-handler.ts +65 -4
  138. package/src/raw-route.test.ts +427 -1
  139. package/src/raw-route.ts +125 -12
  140. package/src/scripts/dev-server.ts +12 -1
  141. package/src/scripts/extract-ts-types.test.ts +228 -26
  142. package/src/scripts/extract-ts-types.ts +104 -20
  143. package/src/scripts/generate-spec.test.ts +101 -0
  144. package/src/scripts/generate-spec.ts +15 -5
  145. package/src/scripts/sandbox-empty-buckets.test.ts +191 -0
  146. package/src/scripts/sandbox.ts +185 -24
  147. package/src/version.ts +1 -1
@@ -1,18 +1,23 @@
1
1
  // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
+ import { pathToFileURL } from 'node:url';
4
5
  import * as cdk from 'aws-cdk-lib';
5
6
  import type * as apigateway from 'aws-cdk-lib/aws-apigateway';
6
7
  import * as iam from 'aws-cdk-lib/aws-iam';
7
8
  import { CfnGroup } from 'aws-cdk-lib/aws-resourcegroups';
8
9
  import { Construct } from 'constructs';
9
- import { pathToFileURL } from 'node:url';
10
- import { addBlocksStackMetadata } from './stack-metadata.js';
11
- import { finalizeConfigRegistry, registerConfig } from './config-registry.js';
12
- import type { BlocksDefaults } from './blocks-defaults.js';
13
10
  import { registerBuiltinRoutes } from '../builtin-routes.js';
11
+ import type { BlocksDefaults } from './blocks-defaults.js';
14
12
  import type { Compute } from './compute/compute.js';
13
+ import { getComputes } from './compute/compute-registry.js';
15
14
  import type { DefaultComputeFactory, LambdaShapedCompute } from './compute/default-compute-factory.js';
15
+ import { finalizeConfigRegistry, registerConfig } from './config-registry.js';
16
+ import { finalizeDashboards } from './dashboard-registry.js';
17
+ import { finalizeTracing } from './tracer-registry.js';
18
+ import { addBlocksStackMetadata } from './stack-metadata.js';
19
+ import { anyRequirementNeedsVpc, finalizeVpc, getOrCreateVpc, initializeVpc } from './vpc.js';
20
+ import type { BlocksVpcOptions } from './vpc-types.js';
16
21
 
17
22
  /**
18
23
  * Validate that the Node.js process was started with `--conditions=cdk`.
@@ -23,23 +28,23 @@ import type { DefaultComputeFactory, LambdaShapedCompute } from './compute/defau
23
28
  * no real infrastructure (no tables, no IAM, no Lambda configs).
24
29
  */
25
30
  export function assertCdkConditionActive(): void {
26
- const nodeOptions = process.env.NODE_OPTIONS ?? '';
27
- const execArgv = process.execArgv ?? [];
28
-
29
- const hasCdkCondition =
30
- execArgv.some(arg => arg === '--conditions=cdk') ||
31
- execArgv.some((arg, i) => (arg === '--conditions' || arg === '-C') && execArgv[i + 1] === 'cdk') ||
32
- nodeOptions.includes('--conditions=cdk') ||
33
- /(?:--conditions|-C)\s+cdk/.test(nodeOptions);
34
-
35
- if (!hasCdkCondition) {
36
- throw new Error(
37
- 'Missing --conditions=cdk: Building Blocks will silently load mock implementations instead of CDK constructs.\n\n' +
38
- 'Fix: Set NODE_OPTIONS="--conditions=cdk" before running CDK synth:\n' +
39
- ' NODE_OPTIONS="--conditions=cdk" npx cdk synth\n\n' +
40
- 'Or use the Blocks CLI commands (npm run deploy / npm run sandbox) which set this automatically.',
41
- );
42
- }
31
+ const nodeOptions = process.env.NODE_OPTIONS ?? '';
32
+ const execArgv = process.execArgv ?? [];
33
+
34
+ const hasCdkCondition =
35
+ execArgv.some((arg) => arg === '--conditions=cdk') ||
36
+ execArgv.some((arg, i) => (arg === '--conditions' || arg === '-C') && execArgv[i + 1] === 'cdk') ||
37
+ nodeOptions.includes('--conditions=cdk') ||
38
+ /(?:--conditions|-C)\s+cdk/.test(nodeOptions);
39
+
40
+ if (!hasCdkCondition) {
41
+ throw new Error(
42
+ 'Missing --conditions=cdk: Building Blocks will silently load mock implementations instead of CDK constructs.\n\n' +
43
+ 'Fix: Set NODE_OPTIONS="--conditions=cdk" before running CDK synth:\n' +
44
+ ' NODE_OPTIONS="--conditions=cdk" npx cdk synth\n\n' +
45
+ 'Or use the Blocks CLI commands (npm run deploy / npm run sandbox) which set this automatically.',
46
+ );
47
+ }
43
48
  }
44
49
 
45
50
  /**
@@ -50,16 +55,16 @@ export function assertCdkConditionActive(): void {
50
55
  export const SHARED_HANDLER_TIMEOUT_SECONDS = 60 * 15;
51
56
 
52
57
  export interface BlocksBackendProps {
53
- backendHandlerPath: string;
54
- backendCDKPath: string;
55
- /**
56
- * Stack-wide infrastructure defaults applied to every Building Block (removal
57
- * policy, deletion protection, …). See {@link BlocksDefaults}. Start from
58
- * `BlocksPresets.sandbox` or `BlocksPresets.production` and override
59
- * individual fields as needed. A per-block option always wins over the
60
- * corresponding stack default.
61
- */
62
- defaults: BlocksDefaults;
58
+ backendHandlerPath: string;
59
+ backendCDKPath: string;
60
+ /**
61
+ * Stack-wide infrastructure defaults applied to every Building Block (removal
62
+ * policy, deletion protection, …). See {@link BlocksDefaults}. Start from
63
+ * `BlocksPresets.sandbox` or `BlocksPresets.production` and override
64
+ * individual fields as needed. A per-block option always wins over the
65
+ * corresponding stack default.
66
+ */
67
+ defaults: BlocksDefaults;
63
68
  }
64
69
 
65
70
  /**
@@ -70,8 +75,8 @@ export interface BlocksBackendProps {
70
75
  * @internal
71
76
  */
72
77
  export interface CoreBlocksBackendProps extends BlocksBackendProps {
73
- /** Builds the backend's default compute. Injected by `@aws-blocks/blocks`. */
74
- defaultComputeFactory: DefaultComputeFactory;
78
+ /** Builds the backend's default compute. Injected by `@aws-blocks/blocks`. */
79
+ defaultComputeFactory: DefaultComputeFactory;
75
80
  }
76
81
 
77
82
  /**
@@ -80,93 +85,106 @@ export interface CoreBlocksBackendProps extends BlocksBackendProps {
80
85
  * routes.
81
86
  */
82
87
  export function setupBlocksInfra(scope: Construct, props: BlocksBackendProps, id?: string) {
83
- // Fail fast with an actionable message at the create() call site if `defaults`
84
- // is missing (e.g. a plain-JS caller, `as any`, or a dynamically-built props
85
- // object) — otherwise the first Building Block to read `scope.defaults` throws
86
- // a cryptic `Cannot read properties of undefined (reading 'removalPolicy')`.
87
- if (!props.defaults) {
88
- throw new Error(
89
- 'BlocksStack/BlocksBackend requires a `defaults` field. Pass a posture from ' +
90
- '`@aws-blocks/core/cdk` — typically `defaults: sandboxMode ? BlocksPresets.sandbox : BlocksPresets.production`.',
91
- );
92
- }
93
-
94
- // ── Shared execution role ───────────────────────────────────────────────
95
- // A single IAM role that every Building Block grants to. Provisioned here so
96
- // it exists before the backend module is imported (Building Blocks reach it
97
- // via `scope.executionRole`). Block grants sit on the role's default (inline)
98
- // policy. AWSLambdaBasicExecutionRole is attached so compute functions retain
99
- // CloudWatch Logs permissions.
100
- //
101
- // INVARIANT: this must be a mutable, framework-owned `iam.Role` — never an
102
- // imported role (`Role.fromRoleArn`/`fromRoleName`), which is immutable by
103
- // default. On an immutable role, every Building Block's `grant*()` /
104
- // `addToPrincipalPolicy()` silently becomes a no-op (returns false, no error),
105
- // so permissions would quietly vanish. If a bring-your-own-role option is ever
106
- // added, it must resolve to a mutable role (`{ mutable: true }`).
107
- const executionRole = new iam.Role(scope, 'BlocksRole', {
108
- // CompositePrincipal (rather than a bare ServicePrincipal) so additional
109
- // compute types can assume this same shared role as they are introduced
110
- // (e.g. ECS tasks via ecs-tasks.amazonaws.com), by adding principals here.
111
- assumedBy: new iam.CompositePrincipal(new iam.ServicePrincipal('lambda.amazonaws.com')),
112
- managedPolicies: [
113
- iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'),
114
- ],
115
- });
116
-
117
- // ── Resource Groups ───────────────────────────────────────────────────
118
- let rootStack = cdk.Stack.of(scope);
119
- while (rootStack.nestedStackParent) rootStack = rootStack.nestedStackParent;
120
- const groupPrefix = (id && id !== rootStack.stackName) ? `${rootStack.stackName}-${id}` : rootStack.stackName;
121
-
122
- new CfnGroup(scope, 'StackResources', {
123
- name: `${groupPrefix}-resources`,
124
- resourceQuery: {
125
- type: 'CLOUDFORMATION_STACK_1_0',
126
- query: {
127
- resourceTypeFilters: [
128
- 'AWS::CloudWatch::Dashboard',
129
- 'AWS::Cognito::UserPool',
130
- 'AWS::DynamoDB::Table',
131
- 'AWS::Logs::LogGroup',
132
- 'AWS::RDS::DBCluster',
133
- 'AWS::RDS::DBInstance',
134
- 'AWS::S3::Bucket',
135
- 'AWS::SQS::Queue',
136
- ],
137
- stackIdentifier: cdk.Stack.of(scope).stackId,
138
- },
139
- },
140
- });
141
-
142
- new CfnGroup(scope, 'StackSettings', {
143
- name: `${groupPrefix}-settings`,
144
- resourceQuery: {
145
- type: 'TAG_FILTERS_1_0',
146
- query: {
147
- resourceTypeFilters: ['AWS::SSM::Parameter'],
148
- tagFilters: [{ key: 'aws-blocks-stack', values: [rootStack.stackName] }],
149
- },
150
- },
151
- });
152
-
153
- // ── Console redirect routes ───────────────────────────────────────────
154
- const region = cdk.Fn.ref('AWS::Region');
155
- const resourcesUrl = cdk.Fn.join('', [
156
- 'https://', region, '.console.aws.amazon.com/resource-groups/group/',
157
- `${groupPrefix}-resources`, '?region=', region,
158
- ]);
159
- const settingsUrl = cdk.Fn.join('', [
160
- 'https://', region, '.console.aws.amazon.com/resource-groups/group/',
161
- `${groupPrefix}-settings`, '?region=', region,
162
- ]);
163
-
164
- registerConfig(scope, 'BB_RESOURCES_GROUP_URL', resourcesUrl);
165
- registerConfig(scope, 'BB_SETTINGS_GROUP_URL', settingsUrl);
166
-
167
- registerBuiltinRoutes();
168
-
169
- return { executionRole };
88
+ // Fail fast with an actionable message at the create() call site if `defaults`
89
+ // is missing (e.g. a plain-JS caller, `as any`, or a dynamically-built props
90
+ // object) — otherwise the first Building Block to read `scope.defaults` throws
91
+ // a cryptic `Cannot read properties of undefined (reading 'removalPolicy')`.
92
+ if (!props.defaults) {
93
+ throw new Error(
94
+ 'BlocksStack/BlocksBackend requires a `defaults` field. Pass a posture from ' +
95
+ '`@aws-blocks/core/cdk` — typically `defaults: sandboxMode ? BlocksPresets.sandbox : BlocksPresets.production`.',
96
+ );
97
+ }
98
+
99
+ // ── Shared execution role ───────────────────────────────────────────────
100
+ // A single IAM role that every Building Block grants to. Provisioned here so
101
+ // it exists before the backend module is imported (Building Blocks reach it
102
+ // via `scope.executionRole`). Block grants sit on the role's default (inline)
103
+ // policy. AWSLambdaBasicExecutionRole is attached so compute functions retain
104
+ // CloudWatch Logs permissions.
105
+ //
106
+ // INVARIANT: this must be a mutable, framework-owned `iam.Role` — never an
107
+ // imported role (`Role.fromRoleArn`/`fromRoleName`), which is immutable by
108
+ // default. On an immutable role, every Building Block's `grant*()` /
109
+ // `addToPrincipalPolicy()` silently becomes a no-op (returns false, no error),
110
+ // so permissions would quietly vanish. If a bring-your-own-role option is ever
111
+ // added, it must resolve to a mutable role (`{ mutable: true }`).
112
+ const executionRole = new iam.Role(scope, 'BlocksRole', {
113
+ // CompositePrincipal (rather than a bare ServicePrincipal) so a Building Block
114
+ // whose compute runs AS this shared role can add its own trust principal here
115
+ // (e.g. the Agent BB adds bedrock-agentcore in its CDK construct) core stays
116
+ // agnostic and only Lambda is trusted by default.
117
+ assumedBy: new iam.CompositePrincipal(new iam.ServicePrincipal('lambda.amazonaws.com')),
118
+ managedPolicies: [
119
+ iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'),
120
+ // When Lambda is placed in a VPC it needs ENI management permissions
121
+ ...(props.defaults.vpc
122
+ ? [iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaVPCAccessExecutionRole')]
123
+ : []),
124
+ ],
125
+ });
126
+
127
+ // ── Resource Groups ───────────────────────────────────────────────────
128
+ let rootStack = cdk.Stack.of(scope);
129
+ while (rootStack.nestedStackParent) rootStack = rootStack.nestedStackParent;
130
+ const groupPrefix = id && id !== rootStack.stackName ? `${rootStack.stackName}-${id}` : rootStack.stackName;
131
+
132
+ new CfnGroup(scope, 'StackResources', {
133
+ name: `${groupPrefix}-resources`,
134
+ resourceQuery: {
135
+ type: 'CLOUDFORMATION_STACK_1_0',
136
+ query: {
137
+ resourceTypeFilters: [
138
+ 'AWS::CloudWatch::Dashboard',
139
+ 'AWS::Cognito::UserPool',
140
+ 'AWS::DynamoDB::Table',
141
+ 'AWS::Logs::LogGroup',
142
+ 'AWS::RDS::DBCluster',
143
+ 'AWS::RDS::DBInstance',
144
+ 'AWS::S3::Bucket',
145
+ 'AWS::SQS::Queue',
146
+ ],
147
+ stackIdentifier: cdk.Stack.of(scope).stackId,
148
+ },
149
+ },
150
+ });
151
+
152
+ new CfnGroup(scope, 'StackSettings', {
153
+ name: `${groupPrefix}-settings`,
154
+ resourceQuery: {
155
+ type: 'TAG_FILTERS_1_0',
156
+ query: {
157
+ resourceTypeFilters: ['AWS::SSM::Parameter'],
158
+ tagFilters: [{ key: 'aws-blocks-stack', values: [rootStack.stackName] }],
159
+ },
160
+ },
161
+ });
162
+
163
+ // ── Console redirect routes ───────────────────────────────────────────
164
+ const region = cdk.Fn.ref('AWS::Region');
165
+ const resourcesUrl = cdk.Fn.join('', [
166
+ 'https://',
167
+ region,
168
+ '.console.aws.amazon.com/resource-groups/group/',
169
+ `${groupPrefix}-resources`,
170
+ '?region=',
171
+ region,
172
+ ]);
173
+ const settingsUrl = cdk.Fn.join('', [
174
+ 'https://',
175
+ region,
176
+ '.console.aws.amazon.com/resource-groups/group/',
177
+ `${groupPrefix}-settings`,
178
+ '?region=',
179
+ region,
180
+ ]);
181
+
182
+ registerConfig(scope, 'BB_RESOURCES_GROUP_URL', resourcesUrl);
183
+ registerConfig(scope, 'BB_SETTINGS_GROUP_URL', settingsUrl);
184
+
185
+ registerBuiltinRoutes();
186
+
187
+ return { executionRole };
170
188
  }
171
189
 
172
190
  /**
@@ -187,111 +205,165 @@ export function setupBlocksInfra(scope: Construct, props: BlocksBackendProps, id
187
205
  * ```
188
206
  */
189
207
  export class BlocksBackend extends Construct {
190
- public readonly backendHandlerPath: string;
191
- /** Shared IAM role assumed by all Blocks compute. Building Blocks grant to this role. */
192
- public readonly executionRole: iam.IRole;
193
- /** Infrastructure defaults for Building Blocks created under this backend. */
194
- public readonly defaults: BlocksDefaults;
195
- /** The default compute (owns the Lambda function + API Gateway); set in `create()`. @internal */
196
- _defaultCompute?: Compute;
197
-
198
- /** The default compute's Lambda function. To be removed once consumers move to the multi-compute model. */
199
- get handler(): cdk.aws_lambda_nodejs.NodejsFunction {
200
- return this.requireDefaultCompute().fn;
201
- }
202
- /** The default compute's API Gateway REST API. To be removed once consumers move to the multi-compute model. */
203
- get gateway(): apigateway.RestApi {
204
- return this.requireDefaultCompute().apiGateway;
205
- }
206
- /** The default compute's RPC endpoint URL. To be removed once consumers move to the multi-compute model. */
207
- get apiUrl(): string {
208
- return this.requireDefaultCompute().apiUrl;
209
- }
210
-
211
- private requireDefaultCompute(): LambdaShapedCompute {
212
- if (!this._defaultCompute) {
213
- throw new Error('Blocks backend not fully initialized — access .handler/.gateway/.apiUrl after BlocksBackend.create() resolves.');
214
- }
215
- return this._defaultCompute as LambdaShapedCompute;
216
- }
217
-
218
- /**
219
- * The fullId used by child Scopes to compute their env var names,
220
- * construct IDs, and physical resource names (e.g., DynamoDB table names).
221
- *
222
- * Includes the CDK stack name to ensure physical resources are unique
223
- * per deployment. This matches what the runtime sees via BLOCKS_STACK_NAME.
224
- *
225
- * IMPORTANT: this value MUST be token-free. Child Scopes embed `fullId` in
226
- * CDK construct IDs (e.g. `${fullId}DsqlMigrationFn`), and CDK forbids
227
- * unresolved tokens in construct IDs ("ID components may not include
228
- * unresolved tokens"). It is also used to build env-var keys that must match
229
- * byte-for-byte between synth time and runtime.
230
- *
231
- * A nested stack (e.g. Amplify Gen2 `backend.createStack('blocks')`) has a
232
- * tokenized `stackName` that only resolves at deploy time. We therefore walk
233
- * up to the top-level stack, whose name is concrete at synth time and still
234
- * unique per deployment. The `Token.isUnresolved` guard is a defensive
235
- * fallback to the (token-free) construct id should no resolvable name exist.
236
- */
237
- get fullId(): string {
238
- let stack = cdk.Stack.of(this);
239
- while (stack.nestedStackParent) {
240
- stack = stack.nestedStackParent;
241
- }
242
- const stackName = stack.stackName;
243
- if (cdk.Token.isUnresolved(stackName)) {
244
- return this.node.id;
245
- }
246
- return `${stackName}-${this.node.id}`;
247
- }
248
-
249
- private constructor(scope: Construct, id: string, props: BlocksBackendProps) {
250
- super(scope, id);
251
-
252
- this.backendHandlerPath = props.backendHandlerPath;
253
-
254
- // Expose self to Building Blocks at CDK time
255
- (globalThis as any).CURRENT_BLOCKS_STACK = this;
256
-
257
- // Store defaults on the backend (not the stack) so several BlocksBackends
258
- // in one stack each keep their own posture; Building Blocks resolve them by
259
- // walking up to their owning backend (see Scope.defaults).
260
- this.defaults = props.defaults;
261
-
262
- const infra = setupBlocksInfra(this, props, id);
263
- this.executionRole = infra.executionRole;
264
- // The default compute (and thus handler/gateway) is created in create(),
265
- // after construction it derives BLOCKS_STACK_NAME from this.fullId.
266
- }
267
-
268
- static async create(scope: Construct, id: string, props: CoreBlocksBackendProps) {
269
- assertCdkConditionActive();
270
- const backend = new BlocksBackend(scope, id, props);
271
- // Create the default compute before importing the backend: it OWNS the
272
- // Lambda function + API Gateway (which back .handler/.gateway/.apiUrl), and
273
- // a block reading `this.compute` in its constructor (during that import)
274
- // must resolve to it. The factory is supplied by the umbrella
275
- // @aws-blocks/blocks (which injects LambdaCompute) via props, so core never
276
- // imports the concrete compute class.
277
- backend._defaultCompute = props.defaultComputeFactory(backend);
278
- // file:// URL (not a raw path) so the cache-busting query works on Windows,
279
- // where an absolute path like `D:\...` is rejected as URL scheme `d:`.
280
- const backendUrl = pathToFileURL(props.backendCDKPath);
281
- backendUrl.searchParams.set('stack', id);
282
- const mod = await import(backendUrl.href);
283
- if (typeof mod.default === 'function') {
284
- try {
285
- await mod.default(backend);
286
- } catch (error) {
287
- throw new Error(`Error executing default export function for backend "${id}": ${error instanceof Error ? error.message : error}`, { cause: error });
288
- }
289
- }
290
- addBlocksStackMetadata(cdk.Stack.of(backend));
291
-
292
- // Finalize BB config → S3 (after all BBs have registered their config)
293
- finalizeConfigRegistry(backend, backend.handler);
294
-
295
- return backend;
296
- }
208
+ public readonly backendHandlerPath: string;
209
+ /**
210
+ * Path to the app's backend module (`props.backendCDKPath`). Exposed so Building Blocks that
211
+ * co-bundle the backend at synth (e.g. the Agent BB's AgentCore Runtime) can discover it via
212
+ * `globalThis.CURRENT_BLOCKS_STACK.backendModulePath`.
213
+ */
214
+ public readonly backendModulePath: string;
215
+ /** Shared IAM role assumed by all Blocks compute. Building Blocks grant to this role. */
216
+ public readonly executionRole: iam.IRole;
217
+ /** Infrastructure defaults for Building Blocks created under this backend. */
218
+ public readonly defaults: BlocksDefaults;
219
+ /** The default compute (owns the Lambda function + API Gateway); set in `create()`. @internal */
220
+ _defaultCompute?: Compute;
221
+
222
+ /** The default compute's Lambda function. To be removed once consumers move to the multi-compute model. */
223
+ get handler(): cdk.aws_lambda_nodejs.NodejsFunction {
224
+ return this.requireDefaultCompute().fn;
225
+ }
226
+ /** The default compute's API Gateway REST API. To be removed once consumers move to the multi-compute model. */
227
+ get gateway(): apigateway.RestApi {
228
+ return this.requireDefaultCompute().apiGateway;
229
+ }
230
+ /** The default compute's RPC endpoint URL. To be removed once consumers move to the multi-compute model. */
231
+ get apiUrl(): string {
232
+ return this.requireDefaultCompute().apiUrl;
233
+ }
234
+ /** The default compute's handler CloudWatch log group. Its retention comes from
235
+ * the compute's `logRetention` (falling back to `defaults.logRetention`); the
236
+ * `bb-logger` CDK construct is a no-op and no longer touches it. */
237
+ get handlerLogGroup(): cdk.aws_logs.ILogGroup {
238
+ return this.requireDefaultCompute().logGroup;
239
+ }
240
+
241
+ private requireDefaultCompute(): LambdaShapedCompute {
242
+ if (!this._defaultCompute) {
243
+ throw new Error(
244
+ 'Blocks backend not fully initialized — access .handler/.gateway/.apiUrl after BlocksBackend.create() resolves.',
245
+ );
246
+ }
247
+ return this._defaultCompute as LambdaShapedCompute;
248
+ }
249
+
250
+ /**
251
+ * The fullId used by child Scopes to compute their env var names,
252
+ * construct IDs, and physical resource names (e.g., DynamoDB table names).
253
+ *
254
+ * Includes the CDK stack name to ensure physical resources are unique
255
+ * per deployment. This matches what the runtime sees via BLOCKS_STACK_NAME.
256
+ *
257
+ * IMPORTANT: this value MUST be token-free. Child Scopes embed `fullId` in
258
+ * CDK construct IDs (e.g. `${fullId}DsqlMigrationFn`), and CDK forbids
259
+ * unresolved tokens in construct IDs ("ID components may not include
260
+ * unresolved tokens"). It is also used to build env-var keys that must match
261
+ * byte-for-byte between synth time and runtime.
262
+ *
263
+ * A nested stack (e.g. Amplify Gen2 `backend.createStack('blocks')`) has a
264
+ * tokenized `stackName` that only resolves at deploy time. We therefore walk
265
+ * up to the top-level stack, whose name is concrete at synth time and still
266
+ * unique per deployment. The `Token.isUnresolved` guard is a defensive
267
+ * fallback to the (token-free) construct id should no resolvable name exist.
268
+ */
269
+ get fullId(): string {
270
+ let stack = cdk.Stack.of(this);
271
+ while (stack.nestedStackParent) {
272
+ stack = stack.nestedStackParent;
273
+ }
274
+ const stackName = stack.stackName;
275
+ if (cdk.Token.isUnresolved(stackName)) {
276
+ return this.node.id;
277
+ }
278
+ return `${stackName}-${this.node.id}`;
279
+ }
280
+
281
+ private _vpcOptions?: BlocksVpcOptions;
282
+
283
+ private constructor(scope: Construct, id: string, props: BlocksBackendProps) {
284
+ super(scope, id);
285
+
286
+ this.backendHandlerPath = props.backendHandlerPath;
287
+ this.backendModulePath = props.backendCDKPath;
288
+ this._vpcOptions = props.defaults.vpc;
289
+
290
+ // Expose self to Building Blocks at CDK time
291
+ (globalThis as any).CURRENT_BLOCKS_STACK = this;
292
+
293
+ // Store defaults on the backend (not the stack) so several BlocksBackends
294
+ // in one stack each keep their own posture; Building Blocks resolve them by
295
+ // walking up to their owning backend (see Scope.defaults).
296
+ this.defaults = props.defaults;
297
+
298
+ // Initialize VPC context before the default compute is created and before
299
+ // BBs are constructed, so both can discover it: the default compute
300
+ // (LambdaCompute) reads it via getVpcContext(this) to place its function in
301
+ // the VPC, and BBs (e.g. bb-data) read it to co-locate their resources.
302
+ if (this._vpcOptions) {
303
+ initializeVpc(this, this._vpcOptions);
304
+ }
305
+
306
+ const infra = setupBlocksInfra(this, props, id);
307
+ this.executionRole = infra.executionRole;
308
+ // The default compute (and thus handler/gateway) is created in create(),
309
+ // after construction — it derives BLOCKS_STACK_NAME from this.fullId.
310
+ }
311
+
312
+ static async create(scope: Construct, id: string, props: CoreBlocksBackendProps) {
313
+ assertCdkConditionActive();
314
+ const backend = new BlocksBackend(scope, id, props);
315
+ // Create the default compute before importing the backend: it OWNS the
316
+ // Lambda function + API Gateway (which back .handler/.gateway/.apiUrl), and
317
+ // a block reading `this.compute` in its constructor (during that import)
318
+ // must resolve to it. The factory is supplied by the umbrella
319
+ // @aws-blocks/blocks (which injects LambdaCompute) via props, so core never
320
+ // imports the concrete compute class.
321
+ backend._defaultCompute = props.defaultComputeFactory(backend);
322
+ // file:// URL (not a raw path) so the cache-busting query works on Windows,
323
+ // where an absolute path like `D:\...` is rejected as URL scheme `d:`.
324
+ const backendUrl = pathToFileURL(props.backendCDKPath);
325
+ backendUrl.searchParams.set('stack', id);
326
+ const mod = await import(backendUrl.href);
327
+ if (typeof mod.default === 'function') {
328
+ try {
329
+ await mod.default(backend);
330
+ } catch (error) {
331
+ throw new Error(
332
+ `Error executing default export function for backend "${id}": ${error instanceof Error ? error.message : error}`,
333
+ { cause: error },
334
+ );
335
+ }
336
+ }
337
+ addBlocksStackMetadata(cdk.Stack.of(backend));
338
+
339
+ // Finalize BB config → S3 (after all BBs have registered their config)
340
+ finalizeConfigRegistry(backend, backend.executionRole, getComputes(backend));
341
+
342
+ // Tracing is presence-gated: if the app contains a Tracer, enable X-Ray on
343
+ // every compute. Runs before the dashboard finalize.
344
+ finalizeTracing(backend, backend.executionRole);
345
+
346
+ // Build any deferred Dashboards now that every compute's observability
347
+ // state is settled — so the dashboard is order-independent.
348
+ finalizeDashboards(backend);
349
+
350
+ // Finalize VPC. Derived resource: use the customer's if provided, else
351
+ // lazily create one only if a Building Block requires it.
352
+ if (backend._vpcOptions) {
353
+ finalizeVpc(backend, backend._vpcOptions);
354
+ } else if (anyRequirementNeedsVpc(backend)) {
355
+ const derived = getOrCreateVpc(backend);
356
+ const options = { network: derived };
357
+ initializeVpc(backend, options);
358
+ finalizeVpc(backend, options);
359
+ cdk.Annotations.of(backend).addInfoV2(
360
+ 'blocks:vpc:derived',
361
+ 'A Building Block required a VPC and none was provided, so Blocks created one ' +
362
+ '(with a NAT gateway, which has an ongoing cost). Pass `defaults.vpc: { network }` to ' +
363
+ 'bring your own. See packages/blocks/VPC.md.',
364
+ );
365
+ }
366
+
367
+ return backend;
368
+ }
297
369
  }
@@ -10,6 +10,7 @@
10
10
  import { test, describe } from 'node:test';
11
11
  import assert from 'node:assert';
12
12
  import { RemovalPolicy } from 'aws-cdk-lib';
13
+ import { RetentionDays } from 'aws-cdk-lib/aws-logs';
13
14
  import { BlocksPresets } from './blocks-defaults.js';
14
15
 
15
16
  describe('BlocksPresets', () => {
@@ -24,4 +25,24 @@ describe('BlocksPresets', () => {
24
25
  assert.strictEqual(BlocksPresets.production.deletionProtection, true);
25
26
  assert.strictEqual(BlocksPresets.production.pointInTimeRecovery, true);
26
27
  });
28
+
29
+ test('sandbox keeps logs briefly', () => {
30
+ assert.strictEqual(BlocksPresets.sandbox.logRetention, RetentionDays.ONE_WEEK);
31
+ });
32
+
33
+ test('production keeps logs a year', () => {
34
+ assert.strictEqual(BlocksPresets.production.logRetention, RetentionDays.ONE_YEAR);
35
+ });
36
+
37
+ test('access logging is off by default in BOTH presets (opt-in; mutates an account singleton)', () => {
38
+ assert.strictEqual(BlocksPresets.sandbox.accessLogging, false);
39
+ assert.strictEqual(BlocksPresets.production.accessLogging, false);
40
+ });
41
+
42
+ test('sandbox caps tighter (200/400) than production (1000/2000)', () => {
43
+ assert.strictEqual(BlocksPresets.sandbox.throttling.rateLimit, 200);
44
+ assert.strictEqual(BlocksPresets.sandbox.throttling.burstLimit, 400);
45
+ assert.strictEqual(BlocksPresets.production.throttling.rateLimit, 1000);
46
+ assert.strictEqual(BlocksPresets.production.throttling.burstLimit, 2000);
47
+ });
27
48
  });