@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
package/src/cdk/index.ts CHANGED
@@ -1,41 +1,53 @@
1
1
  // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
- import * as cdk from 'aws-cdk-lib';
5
- import { Construct } from 'constructs';
6
4
  import { pathToFileURL } from 'node:url';
7
5
  import { __PIPELINE_STAGE_SCOPE__ } from '@aws-blocks/pipeline';
6
+ import * as cdk from 'aws-cdk-lib';
7
+ import { Construct } from 'constructs';
8
8
  import {
9
- type BlocksStackProps,
10
- type BlocksStack as BaseBlocksStack,
11
- type ScopeParent,
12
- type ScopeOptions,
13
- computeScopeFullId,
9
+ type BlocksStack as BaseBlocksStack,
10
+ type BlocksStackProps,
11
+ computeScopeFullId,
12
+ type ScopeOptions,
13
+ type ScopeParent,
14
14
  } from '../common/index.js';
15
- import { setupBlocksInfra, BlocksBackend, assertCdkConditionActive } from './blocks-backend.js';
16
- import { addBlocksStackMetadata } from './stack-metadata.js';
17
- import { finalizeConfigRegistry } from './config-registry.js';
15
+ import { assertCdkConditionActive, BlocksBackend, setupBlocksInfra } from './blocks-backend.js';
18
16
  import { type BlocksDefaults, BlocksPresets } from './blocks-defaults.js';
19
17
  import type { Compute } from './compute/compute.js';
18
+ import { getComputes } from './compute/compute-registry.js';
20
19
  import type { DefaultComputeFactory, LambdaShapedCompute } from './compute/default-compute-factory.js';
20
+ import { finalizeConfigRegistry } from './config-registry.js';
21
+ import { finalizeDashboards } from './dashboard-registry.js';
22
+ import { addBlocksStackMetadata } from './stack-metadata.js';
23
+ import { finalizeTracing } from './tracer-registry.js';
24
+ import { anyRequirementNeedsVpc, finalizeVpc, getOrCreateVpc, initializeVpc } from './vpc.js';
25
+ import { registerVpcRequirements } from './vpc-requirements-registry.js';
26
+ import type { BlocksVpcOptions, VpcRequirements } from './vpc-types.js';
21
27
 
28
+ export { ApiError, DEFAULT_API_ERROR_NAME, hasAuthError, isBlocksError } from '../errors.js';
29
+ export type { ScopeOptions } from '../index.js';
30
+ export { ensureApiGatewayAccount } from './apigateway-account.js';
22
31
  export {
23
32
  BlocksBackend,
24
33
  type BlocksBackendProps,
25
34
  type CoreBlocksBackendProps,
26
35
  SHARED_HANDLER_TIMEOUT_SECONDS,
27
36
  } from './blocks-backend.js';
28
- export { DEFAULT_NODE_RUNTIME } from './node-version.js';
29
- export { blocksNodejsBundling } from './bundling.js';
30
- export { SandboxDisableDeletionProtection } from './mixins.js';
31
- export { registerConfig, finalizeConfigRegistry } from './config-registry.js';
32
37
  export {
33
- type BlocksDefaults,
34
- BlocksPresets,
38
+ type BlocksDefaults,
39
+ BlocksPresets,
40
+ type BlocksThrottling,
35
41
  } from './blocks-defaults.js';
42
+ export { blocksNodejsBundling } from './bundling.js';
43
+ export { finalizeConfigRegistry, getConfigLocation, registerConfig } from './config-registry.js';
44
+ export { finalizeDashboards, registerDashboardFinalizer } from './dashboard-registry.js';
45
+ export { SandboxDisableDeletionProtection } from './mixins.js';
46
+ export { DEFAULT_NODE_RUNTIME } from './node-version.js';
36
47
  export { synthGuard } from './synth-guard.js';
37
- export type { ScopeOptions } from '../index.js';
38
- export { ApiError, isBlocksError, hasAuthError, DEFAULT_API_ERROR_NAME } from '../errors.js';
48
+ export { finalizeTracing, registerTracer } from './tracer-registry.js';
49
+ export { getVpcContext } from './vpc.js';
50
+ export type { BlocksVpcOptions, SubnetRole, VpcContext, VpcRequirements } from './vpc-types.js';
39
51
 
40
52
  /**
41
53
  * Core's `create()` props: the public {@link BlocksStackProps} plus the required
@@ -49,248 +61,407 @@ export { ApiError, isBlocksError, hasAuthError, DEFAULT_API_ERROR_NAME } from '.
49
61
  * @internal
50
62
  */
51
63
  export interface CoreBlocksStackProps extends BlocksStackProps {
52
- /** Builds the stack's default compute. Injected by `@aws-blocks/blocks`. */
53
- defaultComputeFactory: DefaultComputeFactory;
64
+ /** Builds the stack's default compute. Injected by `@aws-blocks/blocks`. */
65
+ defaultComputeFactory: DefaultComputeFactory;
54
66
  }
55
67
 
56
68
  export class BlocksStack extends cdk.Stack implements BaseBlocksStack {
57
- public readonly id: string;
58
- public readonly backendHandlerPath: string;
59
- /** Shared IAM role assumed by all Blocks compute. Building Blocks grant to this role. */
60
- public readonly executionRole: cdk.aws_iam.IRole;
61
- /** Infrastructure defaults for Building Blocks created under this stack. */
62
- public readonly defaults: BlocksDefaults;
63
- /** The default compute (owns the Lambda function + API Gateway); set in `create()`. @internal */
64
- _defaultCompute?: Compute;
65
-
66
- /** The default compute's Lambda function. To be removed once consumers move to the multi-compute model. */
67
- get handler(): cdk.aws_lambda_nodejs.NodejsFunction {
68
- return this.requireDefaultCompute().fn;
69
- }
70
- /** The default compute's API Gateway REST API. To be removed once consumers move to the multi-compute model. */
71
- get gateway(): cdk.aws_apigateway.RestApi {
72
- return this.requireDefaultCompute().apiGateway;
73
- }
74
- /** The default compute's RPC endpoint URL. To be removed once consumers move to the multi-compute model. */
75
- get apiUrl(): string {
76
- return this.requireDefaultCompute().apiUrl;
77
- }
78
-
79
- private requireDefaultCompute(): LambdaShapedCompute {
80
- if (!this._defaultCompute) {
81
- throw new Error('Blocks stack not fully initialized — access .handler/.gateway/.apiUrl after BlocksStack.create() resolves.');
82
- }
83
- return this._defaultCompute as LambdaShapedCompute;
84
- }
85
-
86
- private constructor(scope: Construct, id: string, props: BlocksStackProps) {
87
- super(scope, id, props);
88
- this.id = id;
89
- this.backendHandlerPath = props.backendHandlerPath;
90
- this.defaults = props.defaults;
91
-
92
- // Set globalThis so Building Blocks attach directly to this stack
93
- (globalThis as any).CURRENT_BLOCKS_STACK = this;
94
-
95
- const infra = setupBlocksInfra(this, props, id);
96
- this.executionRole = infra.executionRole;
97
- }
98
-
99
- static async create(scope: Construct, id: string, props: CoreBlocksStackProps) {
100
- assertCdkConditionActive();
101
-
102
- // Detect ambient pipeline stage scope set by Pipeline appFile imports
103
- const pipelineScope = (globalThis as any)[__PIPELINE_STAGE_SCOPE__];
104
- const actualScope = pipelineScope || scope;
105
-
106
- const stack = new BlocksStack(actualScope, id, props);
107
- // Create the default compute before importing the backend: it OWNS the
108
- // Lambda function + API Gateway (which back .handler/.gateway/.apiUrl), and
109
- // a block reading `this.compute` in its constructor (during that import)
110
- // must resolve to it. The factory is supplied by the umbrella
111
- // @aws-blocks/blocks (which injects LambdaCompute) via props, so core never
112
- // imports the concrete compute class.
113
- stack._defaultCompute = props.defaultComputeFactory(stack);
114
- // file:// URL (not a raw path) so the cache-busting query works on Windows,
115
- // where an absolute path like `D:\...` is rejected as URL scheme `d:`.
116
- const backendUrl = pathToFileURL(props.backendCDKPath);
117
- backendUrl.searchParams.set('stack', id);
118
- const mod = await import(backendUrl.href);
119
- if (typeof mod.default === 'function') {
120
- try {
121
- await mod.default(stack);
122
- } catch (error) {
123
- throw new Error(`Error executing default export function for stack "${id}": ${error instanceof Error ? error.message : error}`, { cause: error });
124
- }
125
- }
126
- // Finalize BB config → S3 (after all BBs have registered their config)
127
- finalizeConfigRegistry(stack, stack.handler);
128
-
129
- new cdk.CfnOutput(stack, 'ApiUrl', { value: stack.apiUrl });
130
-
131
- addBlocksStackMetadata(stack);
132
-
133
- return stack;
134
- }
69
+ public readonly id: string;
70
+ public readonly backendHandlerPath: string;
71
+ /**
72
+ * Path to the app's backend module (`props.backendCDKPath`). Exposed so Building Blocks that
73
+ * co-bundle the backend at synth (e.g. the Agent BB's AgentCore Runtime) can discover it via
74
+ * `globalThis.CURRENT_BLOCKS_STACK.backendModulePath`.
75
+ */
76
+ public readonly backendModulePath: string;
77
+ /** Shared IAM role assumed by all Blocks compute. Building Blocks grant to this role. */
78
+ public readonly executionRole: cdk.aws_iam.IRole;
79
+ /** Infrastructure defaults for Building Blocks created under this stack. */
80
+ public readonly defaults: BlocksDefaults;
81
+ /** The default compute (owns the Lambda function + API Gateway); set in `create()`. @internal */
82
+ _defaultCompute?: Compute;
83
+
84
+ /** The default compute's Lambda function. To be removed once consumers move to the multi-compute model. */
85
+ get handler(): cdk.aws_lambda_nodejs.NodejsFunction {
86
+ return this.requireDefaultCompute().fn;
87
+ }
88
+ /** The default compute's API Gateway REST API. To be removed once consumers move to the multi-compute model. */
89
+ get gateway(): cdk.aws_apigateway.RestApi {
90
+ return this.requireDefaultCompute().apiGateway;
91
+ }
92
+ /** The default compute's RPC endpoint URL. To be removed once consumers move to the multi-compute model. */
93
+ get apiUrl(): string {
94
+ return this.requireDefaultCompute().apiUrl;
95
+ }
96
+ /** The default compute's handler CloudWatch log group. Its retention comes from
97
+ * the compute's `logRetention` (falling back to `defaults.logRetention`); the
98
+ * `bb-logger` CDK construct is a no-op and no longer touches it. */
99
+ get handlerLogGroup(): cdk.aws_logs.ILogGroup {
100
+ return this.requireDefaultCompute().logGroup;
101
+ }
102
+
103
+ private requireDefaultCompute(): LambdaShapedCompute {
104
+ if (!this._defaultCompute) {
105
+ throw new Error(
106
+ 'Blocks stack not fully initialized — access .handler/.gateway/.apiUrl after BlocksStack.create() resolves.',
107
+ );
108
+ }
109
+ return this._defaultCompute as LambdaShapedCompute;
110
+ }
111
+
112
+ private _vpcOptions?: BlocksVpcOptions;
113
+
114
+ private constructor(scope: Construct, id: string, props: BlocksStackProps) {
115
+ super(scope, id, props);
116
+ this.id = id;
117
+ this.backendHandlerPath = props.backendHandlerPath;
118
+ this.backendModulePath = props.backendCDKPath;
119
+ this.defaults = props.defaults;
120
+ this._vpcOptions = props.defaults.vpc;
121
+
122
+ // Set globalThis so Building Blocks attach directly to this stack
123
+ (globalThis as any).CURRENT_BLOCKS_STACK = this;
124
+
125
+ // Initialize VPC context before the default compute is created and before
126
+ // BBs are constructed, so both can discover it: the default compute
127
+ // (LambdaCompute) reads it via getVpcContext(this) to place its function in
128
+ // the VPC, and BBs (e.g. bb-data) read it to co-locate their resources.
129
+ if (this._vpcOptions) {
130
+ initializeVpc(this, this._vpcOptions);
131
+ }
132
+
133
+ const infra = setupBlocksInfra(this, props, id);
134
+ this.executionRole = infra.executionRole;
135
+ }
136
+
137
+ static async create(scope: Construct, id: string, props: CoreBlocksStackProps) {
138
+ assertCdkConditionActive();
139
+
140
+ // Detect ambient pipeline stage scope set by Pipeline appFile imports
141
+ const pipelineScope = (globalThis as any)[__PIPELINE_STAGE_SCOPE__];
142
+ const actualScope = pipelineScope || scope;
143
+
144
+ const stack = new BlocksStack(actualScope, id, props);
145
+ // Create the default compute before importing the backend: it OWNS the
146
+ // Lambda function + API Gateway (which back .handler/.gateway/.apiUrl), and
147
+ // a block reading `this.compute` in its constructor (during that import)
148
+ // must resolve to it. The factory is supplied by the umbrella
149
+ // @aws-blocks/blocks (which injects LambdaCompute) via props, so core never
150
+ // imports the concrete compute class.
151
+ stack._defaultCompute = props.defaultComputeFactory(stack);
152
+ // file:// URL (not a raw path) so the cache-busting query works on Windows,
153
+ // where an absolute path like `D:\...` is rejected as URL scheme `d:`.
154
+ const backendUrl = pathToFileURL(props.backendCDKPath);
155
+ backendUrl.searchParams.set('stack', id);
156
+ const mod = await import(backendUrl.href);
157
+ if (typeof mod.default === 'function') {
158
+ try {
159
+ await mod.default(stack);
160
+ } catch (error) {
161
+ throw new Error(
162
+ `Error executing default export function for stack "${id}": ${error instanceof Error ? error.message : error}`,
163
+ { cause: error },
164
+ );
165
+ }
166
+ }
167
+ // Finalize BB config → S3 (after all BBs have registered their config)
168
+ finalizeConfigRegistry(stack, stack.executionRole, getComputes(stack));
169
+
170
+ // Tracing is presence-gated: if the app contains a Tracer, enable X-Ray on
171
+ // every compute. Runs before the dashboard finalize so tracingEnabled is
172
+ // set when the dashboard reads it.
173
+ finalizeTracing(stack, stack.executionRole);
174
+
175
+ // Build any deferred Dashboards now that every compute's observability
176
+ // state is settled — so the dashboard is order-independent.
177
+ finalizeDashboards(stack);
178
+
179
+ // Finalize VPC. A VPC is a derived resource: use the customer's if they
180
+ // brought one, else lazily create one only if a Building Block genuinely
181
+ // requires it (requiresVpc). Most apps need neither — Lambda reaches AWS
182
+ // services from the managed network without a VPC.
183
+ if (stack._vpcOptions) {
184
+ finalizeVpc(stack, stack._vpcOptions);
185
+ } else if (anyRequirementNeedsVpc(stack)) {
186
+ const derived = getOrCreateVpc(stack);
187
+ const options = { network: derived };
188
+ initializeVpc(stack, options);
189
+ finalizeVpc(stack, options);
190
+ cdk.Annotations.of(stack).addInfoV2(
191
+ 'blocks:vpc:derived',
192
+ 'A Building Block required a VPC and none was provided, so Blocks created one ' +
193
+ '(with a NAT gateway, which has an ongoing cost). Pass `defaults.vpc: { network }` to ' +
194
+ 'BlocksStack.create to bring your own. See packages/blocks/VPC.md.',
195
+ );
196
+ }
197
+
198
+ new cdk.CfnOutput(stack, 'ApiUrl', { value: stack.apiUrl });
199
+
200
+ addBlocksStackMetadata(stack);
201
+
202
+ return stack;
203
+ }
135
204
  }
136
205
 
137
206
  export class Scope extends Construct {
138
- public readonly id: string;
139
- public readonly parent: ScopeParent;
140
-
141
- readonly bbName?: string;
142
- readonly bbVersion?: string;
143
-
144
- /**
145
- * The owning stack/backend (the root of the Blocks construct tree), resolved
146
- * once at construction: the nearest BlocksStack/BlocksBackend up the construct
147
- * tree, or the ambient `globalThis.CURRENT_BLOCKS_STACK` fallback. All
148
- * root-derived accessors below read from this instead of each repeating the
149
- * tree walk.
150
- */
151
- private readonly root: BlocksStack | BlocksBackend;
152
-
153
- /**
154
- * Compute assigned at this node. Applies to this block and is inherited by
155
- * descendants (a nearer assignment wins). Covers both a handler assigned to a
156
- * specific compute and a scope-level default for its subtree. Internal until
157
- * the customer-facing surface exists.
158
- * @internal
159
- */
160
- _compute?: Compute;
161
-
162
- constructor(id: string, options?: ScopeOptions) {
163
- const parent = options?.parent || (globalThis as any).CURRENT_BLOCKS_STACK;
164
- super(parent, id);
165
- this.id = id;
166
- this.parent = parent;
167
- this.root = this.resolveRoot();
168
- }
169
-
170
- /**
171
- * Walk up the construct tree to the nearest owning BlocksStack/BlocksBackend;
172
- * fall back to the ambient `globalThis.CURRENT_BLOCKS_STACK`. Called once from
173
- * the constructor; the result is cached in {@link root}.
174
- */
175
- private resolveRoot(): BlocksStack | BlocksBackend {
176
- let current: Construct = this;
177
- while (current.node.scope) {
178
- current = current.node.scope as Construct;
179
- if (current instanceof BlocksStack || current instanceof BlocksBackend) {
180
- return current;
181
- }
182
- }
183
- // Fallback to the ambient stack. In production this is always a real
184
- // BlocksStack/BlocksBackend; the cast also admits the test doubles that set
185
- // globalThis.CURRENT_BLOCKS_STACK to a stub exposing the same surface.
186
- return (globalThis as any).CURRENT_BLOCKS_STACK as BlocksStack | BlocksBackend;
187
- }
188
-
189
- get handler() {
190
- return this.root.handler;
191
- }
192
-
193
- /**
194
- * The shared IAM role assumed by all Blocks compute. Building Blocks grant
195
- * their permissions to this role; CDK's `grant*()` / `addToPrincipalPolicy()`
196
- * route those grants to the role's default (inline) policy.
197
- */
198
- get executionRole(): cdk.aws_iam.IRole {
199
- return this.root.executionRole;
200
- }
201
-
202
- /**
203
- * The compute this block runs on: the nearest `_compute` assigned on this
204
- * block or an ancestor scope, else the owning stack/backend's default compute.
205
- *
206
- * For any app that doesn't assign a compute, this always resolves to the
207
- * default — so reads are a no-op refactor. `_compute` is internal
208
- * (test/framework) until the customer-facing surface exists; there is no
209
- * public option to set it yet.
210
- */
211
- get compute(): Compute {
212
- for (let current: ScopeParent | undefined = this; current; current = (current as Scope).parent) {
213
- const assigned = (current as Scope)._compute;
214
- if (assigned) return assigned;
215
- }
216
- const defaultCompute = this.root._defaultCompute;
217
- if (!defaultCompute) {
218
- throw new Error('Default compute not initialized — BlocksStack/BlocksBackend.create() must run before resolving `compute`.');
219
- }
220
- return defaultCompute;
221
- }
222
-
223
- /**
224
- * The backend entry file the owning BlocksStack/BlocksBackend runs — the
225
- * single handler entry shared across the whole app.
226
- */
227
- get backendHandlerPath(): string {
228
- return this.root.backendHandlerPath;
229
- }
230
-
231
- /**
232
- * The owning stack/backend's token-free root identity. This is the value the
233
- * runtime receives as `BLOCKS_STACK_NAME` and rebuilds `fullId` from, so
234
- * physical resource names (DynamoDB tables, env-var keys, IAM ARNs) derived
235
- * from `fullId` match byte-for-byte between synth and runtime — otherwise the
236
- * runtime looks up names that were never created. `BlocksBackend` exposes this
237
- * as `fullId` ({@link BlocksBackend.fullId}); `BlocksStack` as `id`.
238
- */
239
- get backendStackName(): string {
240
- const name = this.root instanceof BlocksBackend ? this.root.fullId : this.root.id;
241
- if (!name) {
242
- throw new Error('Owning Blocks stack/backend has no id to derive BLOCKS_STACK_NAME');
243
- }
244
- return name;
245
- }
246
-
247
- get fullId(): string {
248
- return computeScopeFullId(this);
249
- }
250
-
251
- /**
252
- * The stack-wide infrastructure {@link BlocksDefaults} registered by
253
- * `BlocksStack.create` / `BlocksBackend.create`. Read these in a Building
254
- * Block's CDK constructor to resolve a durability value, letting a per-block
255
- * option override:
256
- *
257
- * ```ts
258
- * const removalPolicy = options?.removalPolicy ?? this.defaults.removalPolicy;
259
- * ```
260
- */
261
- get defaults(): BlocksDefaults {
262
- // Resolve the same way as handler/executionRole: walk up to the owning
263
- // BlocksStack/BlocksBackend and read its defaults, so several backends in
264
- // one stack each keep their own posture. Falls back to the ambient stack,
265
- // then to the production preset when none was registered.
266
- let current: Construct = this;
267
- while (current.node.scope) {
268
- current = current.node.scope as Construct;
269
- if (current instanceof BlocksStack || current instanceof BlocksBackend) {
270
- return current.defaults;
271
- }
272
- }
273
- const ambient = ((globalThis as any).CURRENT_BLOCKS_STACK as { defaults?: BlocksDefaults } | undefined)?.defaults;
274
- if (ambient) return ambient;
275
- // No owning BlocksStack/BlocksBackend in the tree and none ambient — this is
276
- // usually a deliberate test stub, but could be a real misconfiguration (a
277
- // block built outside any Blocks backend). Fall back to the safe production
278
- // posture, and log so it's debuggable if it fires unexpectedly.
279
- console.warn(
280
- `[Blocks] Scope "${this.id}" resolved infrastructure defaults with no owning ` +
281
- 'BlocksStack/BlocksBackend in scope; falling back to BlocksPresets.production.',
282
- );
283
- return BlocksPresets.production;
284
- }
285
-
286
- protected buildUserAgentChain(): [string, string][] {
287
- return [];
288
- }
289
-
290
- // Plugin registration — no-ops in CDK context (plugins are only used at dev/build time)
291
- registerClientMiddleware(_packageSpecifier: string): void {}
292
- registerDevAttachment(_packageSpecifier: string): void {}
293
- registerLambdaEventHandler(_eventSource: string, _identifier: string, _handler: (record: any) => Promise<void>): void {}
294
- get clientMiddleware(): readonly string[] { return []; }
295
- get devAttachments(): readonly string[] { return []; }
207
+ public readonly id: string;
208
+ public readonly parent: ScopeParent;
209
+
210
+ readonly bbName?: string;
211
+ readonly bbVersion?: string;
212
+
213
+ /**
214
+ * The owning stack/backend (the root of the Blocks construct tree), resolved
215
+ * once at construction: the nearest BlocksStack/BlocksBackend up the construct
216
+ * tree, or the ambient `globalThis.CURRENT_BLOCKS_STACK` fallback. All
217
+ * root-derived accessors below read from this instead of each repeating the
218
+ * tree walk.
219
+ */
220
+ private readonly root: BlocksStack | BlocksBackend;
221
+
222
+ /**
223
+ * Compute assigned at this node. Applies to this block and is inherited by
224
+ * descendants (a nearer assignment wins). Covers both a handler assigned to a
225
+ * specific compute and a scope-level default for its subtree. Internal until
226
+ * the customer-facing surface exists.
227
+ * @internal
228
+ */
229
+ _compute?: Compute;
230
+
231
+ constructor(id: string, options?: ScopeOptions) {
232
+ const parent = options?.parent || (globalThis as any).CURRENT_BLOCKS_STACK;
233
+ super(parent, id);
234
+ this.id = id;
235
+ this.parent = parent;
236
+ this.root = this.resolveRoot();
237
+ }
238
+
239
+ /**
240
+ * Walk up the construct tree to the nearest owning BlocksStack/BlocksBackend;
241
+ * fall back to the ambient `globalThis.CURRENT_BLOCKS_STACK`. Called once from
242
+ * the constructor; the result is cached in {@link root}.
243
+ */
244
+ private resolveRoot(): BlocksStack | BlocksBackend {
245
+ let current: Construct = this;
246
+ while (current.node.scope) {
247
+ current = current.node.scope as Construct;
248
+ if (current instanceof BlocksStack || current instanceof BlocksBackend) {
249
+ return current;
250
+ }
251
+ }
252
+ // Fallback to the ambient stack. In production this is always a real
253
+ // BlocksStack/BlocksBackend; the cast also admits the test doubles that set
254
+ // globalThis.CURRENT_BLOCKS_STACK to a stub exposing the same surface.
255
+ return (globalThis as any).CURRENT_BLOCKS_STACK as BlocksStack | BlocksBackend;
256
+ }
257
+
258
+ get handler() {
259
+ return this.root.handler;
260
+ }
261
+
262
+ /**
263
+ * The shared IAM role assumed by all Blocks compute. Building Blocks grant
264
+ * their permissions to this role; CDK's `grant*()` / `addToPrincipalPolicy()`
265
+ * route those grants to the role's default (inline) policy.
266
+ */
267
+ get executionRole(): cdk.aws_iam.IRole {
268
+ return this.root.executionRole;
269
+ }
270
+
271
+ /**
272
+ * The compute this block runs on: the nearest `_compute` assigned on this
273
+ * block or an ancestor scope, else the owning stack/backend's default compute.
274
+ *
275
+ * For any app that doesn't assign a compute, this always resolves to the
276
+ * default — so reads are a no-op refactor. `_compute` is internal
277
+ * (test/framework) until the customer-facing surface exists; there is no
278
+ * public option to set it yet.
279
+ */
280
+ get compute(): Compute {
281
+ for (let current: ScopeParent | undefined = this; current; current = (current as Scope).parent) {
282
+ const assigned = (current as Scope)._compute;
283
+ if (assigned) return assigned;
284
+ }
285
+ const defaultCompute = this.root._defaultCompute;
286
+ if (!defaultCompute) {
287
+ throw new Error(
288
+ 'Default compute not initialized — BlocksStack/BlocksBackend.create() must run before resolving `compute`.',
289
+ );
290
+ }
291
+ return defaultCompute;
292
+ }
293
+
294
+ /**
295
+ * The stack's default compute, **ignoring** any per-scope `_compute`
296
+ * assignment (unlike {@link compute}, which resolves the nearest assigned
297
+ * one). Use when a resource is a stack-level singleton that must bind to one
298
+ * deterministic compute regardless of the block's resolved compute — e.g.
299
+ * Realtime's shared WebSocket route integration, where one WebSocket API
300
+ * integrates to a single target and connection bookkeeping is compute-agnostic.
301
+ *
302
+ * @internal Not a customer surface; for framework/BB singleton infra only.
303
+ */
304
+ get defaultCompute(): Compute {
305
+ const defaultCompute = this.root._defaultCompute;
306
+ if (!defaultCompute) {
307
+ throw new Error(
308
+ 'Default compute not initialized — BlocksStack/BlocksBackend.create() must run before resolving `defaultCompute`.',
309
+ );
310
+ }
311
+ return defaultCompute;
312
+ }
313
+
314
+ /**
315
+ * The backend entry file the owning BlocksStack/BlocksBackend runs — the
316
+ * single handler entry shared across the whole app.
317
+ */
318
+ get backendHandlerPath(): string {
319
+ return this.root.backendHandlerPath;
320
+ }
321
+
322
+ /**
323
+ * The owning stack/backend's token-free root identity. This is the value the
324
+ * runtime receives as `BLOCKS_STACK_NAME` and rebuilds `fullId` from, so
325
+ * physical resource names (DynamoDB tables, env-var keys, IAM ARNs) derived
326
+ * from `fullId` match byte-for-byte between synth and runtime — otherwise the
327
+ * runtime looks up names that were never created. `BlocksBackend` exposes this
328
+ * as `fullId` ({@link BlocksBackend.fullId}); `BlocksStack` as `id`.
329
+ */
330
+ get backendStackName(): string {
331
+ const name = this.root instanceof BlocksBackend ? this.root.fullId : this.root.id;
332
+ if (!name) {
333
+ throw new Error('Owning Blocks stack/backend has no id to derive BLOCKS_STACK_NAME');
334
+ }
335
+ return name;
336
+ }
337
+
338
+ /**
339
+ * The shared handler Lambda's CloudWatch log group (the default compute's).
340
+ * Resolves the same way as {@link handler} — via the owning
341
+ * BlocksStack/BlocksBackend. Its retention comes from the compute's
342
+ * `logRetention` (falling back to `defaults.logRetention`); the `bb-logger`
343
+ * CDK construct is a no-op and no longer reconfigures it.
344
+ */
345
+ get handlerLogGroup(): cdk.aws_logs.ILogGroup {
346
+ return this.root.handlerLogGroup;
347
+ }
348
+
349
+ get fullId(): string {
350
+ return computeScopeFullId(this);
351
+ }
352
+
353
+ /**
354
+ * The stack-wide infrastructure {@link BlocksDefaults} registered by
355
+ * `BlocksStack.create` / `BlocksBackend.create`. Read these in a Building
356
+ * Block's CDK constructor to resolve a durability value, letting a per-block
357
+ * option override:
358
+ *
359
+ * ```ts
360
+ * const removalPolicy = options?.removalPolicy ?? this.defaults.removalPolicy;
361
+ * ```
362
+ */
363
+ get defaults(): BlocksDefaults {
364
+ // Resolve the same way as handler/executionRole: walk up to the owning
365
+ // BlocksStack/BlocksBackend and read its defaults, so several backends in
366
+ // one stack each keep their own posture. Falls back to the ambient stack,
367
+ // then to the production preset when none was registered.
368
+ let current: Construct = this;
369
+ while (current.node.scope) {
370
+ current = current.node.scope as Construct;
371
+ if (current instanceof BlocksStack || current instanceof BlocksBackend) {
372
+ return current.defaults;
373
+ }
374
+ }
375
+ const ambient = ((globalThis as any).CURRENT_BLOCKS_STACK as { defaults?: BlocksDefaults } | undefined)
376
+ ?.defaults;
377
+ if (ambient) return ambient;
378
+ // No owning BlocksStack/BlocksBackend in the tree and none ambient — this is
379
+ // usually a deliberate test stub, but could be a real misconfiguration (a
380
+ // block built outside any Blocks backend). Fall back to the safe production
381
+ // posture, and log so it's debuggable if it fires unexpectedly.
382
+ console.warn(
383
+ `[Blocks] Scope "${this.id}" resolved infrastructure defaults with no owning ` +
384
+ 'BlocksStack/BlocksBackend in scope; falling back to BlocksPresets.production.',
385
+ );
386
+ return BlocksPresets.production;
387
+ }
388
+
389
+ protected buildUserAgentChain(): [string, string][] {
390
+ return [];
391
+ }
392
+
393
+ // Plugin registration — no-ops in CDK context (plugins are only used at dev/build time)
394
+ registerClientMiddleware(_packageSpecifier: string): void {}
395
+ registerDevAttachment(_packageSpecifier: string): void {}
396
+ registerLambdaEventHandler(
397
+ _eventSource: string,
398
+ _identifier: string,
399
+ _handler: (record: any) => Promise<void>,
400
+ ): void {}
401
+ get clientMiddleware(): readonly string[] {
402
+ return [];
403
+ }
404
+ get devAttachments(): readonly string[] {
405
+ return [];
406
+ }
407
+ }
408
+
409
+ /**
410
+ * A VPC-requirements provider: either the requirements directly, or a callback
411
+ * that returns them. Use the callback form when the value depends on `fullId`
412
+ * or other post-construction state — it is evaluated by the base constructor
413
+ * *after* `super()` runs, so `this` is fully available.
414
+ */
415
+ export type VpcRequirementsProvider = VpcRequirements | (() => VpcRequirements);
416
+
417
+ /**
418
+ * Constructor options for a {@link BuildingBlockScope} — the {@link ScopeOptions}
419
+ * every Scope takes, plus the block's VPC requirements. `vpc` is **required** so
420
+ * a BB author can't silently omit it; pass `{}` when the block needs nothing
421
+ * VPC-specific.
422
+ */
423
+ export interface BuildingBlockScopeOptions extends ScopeOptions {
424
+ /**
425
+ * What this block needs from the VPC — endpoints, runtime egress, whether it
426
+ * requires a VPC at all. A value, or a callback (evaluated after `super()`,
427
+ * so it may read `this.fullId`). See {@link VpcRequirements}.
428
+ */
429
+ vpc: VpcRequirementsProvider;
430
+ }
431
+
432
+ /**
433
+ * Base class for Building Block CDK constructs.
434
+ *
435
+ * BBs extend this instead of `Scope` directly and **must** declare their VPC
436
+ * requirements as a constructor argument — the base registers them centrally
437
+ * (see `vpc-requirements-registry.ts`) so `finalizeVpc` can pull, deduplicate,
438
+ * and provision endpoints, and so the lazy VPC can answer "does anything here
439
+ * need a VPC?". Passing the requirements is required by the constructor
440
+ * signature, so a BB author cannot silently forget to declare them — the same
441
+ * forcing the previous `abstract getVpcRequirements()` gave, but without a
442
+ * standing method on every subclass.
443
+ *
444
+ * Declare `{}` when the BB needs nothing VPC-specific.
445
+ *
446
+ * @example
447
+ * ```ts
448
+ * export class KVStore extends BuildingBlockScope {
449
+ * constructor(scope: ScopeParent, id: string) {
450
+ * super(id, { parent: scope, vpc: { gatewayEndpoints: [ec2.GatewayVpcEndpointAwsService.DYNAMODB] } });
451
+ * // …
452
+ * }
453
+ * }
454
+ * ```
455
+ */
456
+ export class BuildingBlockScope extends Scope {
457
+ constructor(id: string, options: BuildingBlockScopeOptions) {
458
+ const { vpc, ...scopeOptions } = options;
459
+ super(id, scopeOptions);
460
+ // Resolve the provider (callback form is evaluated here, after super(), so
461
+ // values that depend on this.fullId are available) and self-register on the
462
+ // owning stack. Register even when empty so the registry is a faithful
463
+ // census of every BB — the lazy VPC and finalizeVpc both rely on that.
464
+ const requirements = typeof vpc === 'function' ? vpc() : vpc;
465
+ registerVpcRequirements(this, requirements);
466
+ }
296
467
  }