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