@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
package/README.md CHANGED
@@ -76,6 +76,18 @@ The body is JSON-RPC 2.0:
76
76
  - `params` is a POSITIONAL array of the method's arguments. A named object also works (its values are used in order).
77
77
  - Errors come back as HTTP `200` with an `error` object in the body (per JSON-RPC), not as a non-2xx status.
78
78
 
79
+ Error responses use these JSON-RPC codes:
80
+
81
+ | Code | Meaning | When Blocks uses it |
82
+ |------|---------|---------------------|
83
+ | `-32700` | Parse error | Request body is not valid JSON. |
84
+ | `-32600` | Invalid Request | Body is not a valid Blocks JSON-RPC request, such as missing `jsonrpc: "2.0"` or a `method` that is not `namespace.method`. |
85
+ | `-32601` | Method not found | The requested namespace or method is not exported by the backend. |
86
+ | `-32602` | Invalid params | Reserved for JSON-RPC parameter validation failures. Application-level validation should throw an `ApiError` with the appropriate HTTP status instead. |
87
+ | `-32603` | Internal error | Reserved for generic JSON-RPC internal errors. Unhandled application errors currently use code `500`. |
88
+
89
+ Errors thrown with `ApiError` use the positive HTTP status as the JSON-RPC `error.code` (for example, `401`, `404`, or `409`) and include `error.data.name` when an error name is available. The typed client converts JSON-RPC errors back into `ApiError`.
90
+
79
91
  Working example:
80
92
 
81
93
  ```bash
@@ -88,7 +88,9 @@ export declare class BlocksBackend extends Construct {
88
88
  get gateway(): apigateway.RestApi;
89
89
  /** The default compute's RPC endpoint URL. To be removed once consumers move to the multi-compute model. */
90
90
  get apiUrl(): string;
91
- /** The default compute's handler CloudWatch log group. `bb-logger` reconfigures its retention. */
91
+ /** The default compute's handler CloudWatch log group. Its retention comes from
92
+ * the compute's `logRetention` (falling back to `defaults.logRetention`); the
93
+ * `bb-logger` CDK construct is a no-op and no longer touches it. */
92
94
  get handlerLogGroup(): cdk.aws_logs.ILogGroup;
93
95
  private requireDefaultCompute;
94
96
  /**
@@ -111,6 +113,7 @@ export declare class BlocksBackend extends Construct {
111
113
  * fallback to the (token-free) construct id should no resolvable name exist.
112
114
  */
113
115
  get fullId(): string;
116
+ private _vpcOptions?;
114
117
  private constructor();
115
118
  static create(scope: Construct, id: string, props: CoreBlocksBackendProps): Promise<BlocksBackend>;
116
119
  }
@@ -1 +1 @@
1
- {"version":3,"file":"blocks-backend.d.ts","sourceRoot":"","sources":["../../src/cdk/blocks-backend.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,GAAG,MAAM,aAAa,CAAC;AACnC,OAAO,KAAK,KAAK,UAAU,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,GAAG,MAAM,qBAAqB,CAAC;AAE3C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAIvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,KAAK,EAAE,qBAAqB,EAAuB,MAAM,sCAAsC,CAAC;AAEvG;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAkB/C;AAED;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,QAAU,CAAC;AAEtD,MAAM,WAAW,kBAAkB;IACjC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,QAAQ,EAAE,cAAc,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;IAChE,8EAA8E;IAC9E,qBAAqB,EAAE,qBAAqB,CAAC;CAC9C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,kBAAkB,EAAE,EAAE,CAAC,EAAE,MAAM;;EAyFxF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,aAAc,SAAQ,SAAS;IAC1C,SAAgB,kBAAkB,EAAE,MAAM,CAAC;IAC3C;;;;OAIG;IACH,SAAgB,iBAAiB,EAAE,MAAM,CAAC;IAC1C,yFAAyF;IACzF,SAAgB,aAAa,EAAE,GAAG,CAAC,KAAK,CAAC;IACzC,8EAA8E;IAC9E,SAAgB,QAAQ,EAAE,cAAc,CAAC;IACzC,iGAAiG;IACjG,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B,2GAA2G;IAC3G,IAAI,OAAO,IAAI,GAAG,CAAC,iBAAiB,CAAC,cAAc,CAElD;IACD,gHAAgH;IAChH,IAAI,OAAO,IAAI,UAAU,CAAC,OAAO,CAEhC;IACD,4GAA4G;IAC5G,IAAI,MAAM,IAAI,MAAM,CAEnB;IACD,kGAAkG;IAClG,IAAI,eAAe,IAAI,GAAG,CAAC,QAAQ,CAAC,SAAS,CAE5C;IAED,OAAO,CAAC,qBAAqB;IAO7B;;;;;;;;;;;;;;;;;;OAkBG;IACH,IAAI,MAAM,IAAI,MAAM,CAUnB;IAED,OAAO;WAoBM,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,sBAAsB;CA6BhF"}
1
+ {"version":3,"file":"blocks-backend.d.ts","sourceRoot":"","sources":["../../src/cdk/blocks-backend.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,GAAG,MAAM,aAAa,CAAC;AACnC,OAAO,KAAK,KAAK,UAAU,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,GAAG,MAAM,qBAAqB,CAAC;AAE3C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,KAAK,EAAE,qBAAqB,EAAuB,MAAM,sCAAsC,CAAC;AAQvG;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAkB/C;AAED;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,QAAU,CAAC;AAEtD,MAAM,WAAW,kBAAkB;IAClC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,QAAQ,EAAE,cAAc,CAAC;CACzB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;IACjE,8EAA8E;IAC9E,qBAAqB,EAAE,qBAAqB,CAAC;CAC7C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,kBAAkB,EAAE,EAAE,CAAC,EAAE,MAAM;;EAqGxF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,aAAc,SAAQ,SAAS;IAC3C,SAAgB,kBAAkB,EAAE,MAAM,CAAC;IAC3C;;;;OAIG;IACH,SAAgB,iBAAiB,EAAE,MAAM,CAAC;IAC1C,yFAAyF;IACzF,SAAgB,aAAa,EAAE,GAAG,CAAC,KAAK,CAAC;IACzC,8EAA8E;IAC9E,SAAgB,QAAQ,EAAE,cAAc,CAAC;IACzC,iGAAiG;IACjG,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B,2GAA2G;IAC3G,IAAI,OAAO,IAAI,GAAG,CAAC,iBAAiB,CAAC,cAAc,CAElD;IACD,gHAAgH;IAChH,IAAI,OAAO,IAAI,UAAU,CAAC,OAAO,CAEhC;IACD,4GAA4G;IAC5G,IAAI,MAAM,IAAI,MAAM,CAEnB;IACD;;wEAEoE;IACpE,IAAI,eAAe,IAAI,GAAG,CAAC,QAAQ,CAAC,SAAS,CAE5C;IAED,OAAO,CAAC,qBAAqB;IAS7B;;;;;;;;;;;;;;;;;;OAkBG;IACH,IAAI,MAAM,IAAI,MAAM,CAUnB;IAED,OAAO,CAAC,WAAW,CAAC,CAAmB;IAEvC,OAAO;WA6BM,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,sBAAsB;CAyD/E"}
@@ -1,14 +1,17 @@
1
1
  // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
+ import { pathToFileURL } from 'node:url';
3
4
  import * as cdk from 'aws-cdk-lib';
4
5
  import * as iam from 'aws-cdk-lib/aws-iam';
5
6
  import { CfnGroup } from 'aws-cdk-lib/aws-resourcegroups';
6
7
  import { Construct } from 'constructs';
7
- import { pathToFileURL } from 'node:url';
8
- import { addBlocksStackMetadata } from './stack-metadata.js';
9
- import { finalizeConfigRegistry, registerConfig } from './config-registry.js';
10
8
  import { registerBuiltinRoutes } from '../builtin-routes.js';
11
9
  import { getComputes } from './compute/compute-registry.js';
10
+ import { finalizeConfigRegistry, registerConfig } from './config-registry.js';
11
+ import { finalizeDashboards } from './dashboard-registry.js';
12
+ import { finalizeTracing } from './tracer-registry.js';
13
+ import { addBlocksStackMetadata } from './stack-metadata.js';
14
+ import { anyRequirementNeedsVpc, finalizeVpc, getOrCreateVpc, initializeVpc } from './vpc.js';
12
15
  /**
13
16
  * Validate that the Node.js process was started with `--conditions=cdk`.
14
17
  *
@@ -20,7 +23,7 @@ import { getComputes } from './compute/compute-registry.js';
20
23
  export function assertCdkConditionActive() {
21
24
  const nodeOptions = process.env.NODE_OPTIONS ?? '';
22
25
  const execArgv = process.execArgv ?? [];
23
- const hasCdkCondition = execArgv.some(arg => arg === '--conditions=cdk') ||
26
+ const hasCdkCondition = execArgv.some((arg) => arg === '--conditions=cdk') ||
24
27
  execArgv.some((arg, i) => (arg === '--conditions' || arg === '-C') && execArgv[i + 1] === 'cdk') ||
25
28
  nodeOptions.includes('--conditions=cdk') ||
26
29
  /(?:--conditions|-C)\s+cdk/.test(nodeOptions);
@@ -72,13 +75,17 @@ export function setupBlocksInfra(scope, props, id) {
72
75
  assumedBy: new iam.CompositePrincipal(new iam.ServicePrincipal('lambda.amazonaws.com')),
73
76
  managedPolicies: [
74
77
  iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'),
78
+ // When Lambda is placed in a VPC it needs ENI management permissions
79
+ ...(props.defaults.vpc
80
+ ? [iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaVPCAccessExecutionRole')]
81
+ : []),
75
82
  ],
76
83
  });
77
84
  // ── Resource Groups ───────────────────────────────────────────────────
78
85
  let rootStack = cdk.Stack.of(scope);
79
86
  while (rootStack.nestedStackParent)
80
87
  rootStack = rootStack.nestedStackParent;
81
- const groupPrefix = (id && id !== rootStack.stackName) ? `${rootStack.stackName}-${id}` : rootStack.stackName;
88
+ const groupPrefix = id && id !== rootStack.stackName ? `${rootStack.stackName}-${id}` : rootStack.stackName;
82
89
  new CfnGroup(scope, 'StackResources', {
83
90
  name: `${groupPrefix}-resources`,
84
91
  resourceQuery: {
@@ -111,12 +118,20 @@ export function setupBlocksInfra(scope, props, id) {
111
118
  // ── Console redirect routes ───────────────────────────────────────────
112
119
  const region = cdk.Fn.ref('AWS::Region');
113
120
  const resourcesUrl = cdk.Fn.join('', [
114
- 'https://', region, '.console.aws.amazon.com/resource-groups/group/',
115
- `${groupPrefix}-resources`, '?region=', region,
121
+ 'https://',
122
+ region,
123
+ '.console.aws.amazon.com/resource-groups/group/',
124
+ `${groupPrefix}-resources`,
125
+ '?region=',
126
+ region,
116
127
  ]);
117
128
  const settingsUrl = cdk.Fn.join('', [
118
- 'https://', region, '.console.aws.amazon.com/resource-groups/group/',
119
- `${groupPrefix}-settings`, '?region=', region,
129
+ 'https://',
130
+ region,
131
+ '.console.aws.amazon.com/resource-groups/group/',
132
+ `${groupPrefix}-settings`,
133
+ '?region=',
134
+ region,
120
135
  ]);
121
136
  registerConfig(scope, 'BB_RESOURCES_GROUP_URL', resourcesUrl);
122
137
  registerConfig(scope, 'BB_SETTINGS_GROUP_URL', settingsUrl);
@@ -166,7 +181,9 @@ export class BlocksBackend extends Construct {
166
181
  get apiUrl() {
167
182
  return this.requireDefaultCompute().apiUrl;
168
183
  }
169
- /** The default compute's handler CloudWatch log group. `bb-logger` reconfigures its retention. */
184
+ /** The default compute's handler CloudWatch log group. Its retention comes from
185
+ * the compute's `logRetention` (falling back to `defaults.logRetention`); the
186
+ * `bb-logger` CDK construct is a no-op and no longer touches it. */
170
187
  get handlerLogGroup() {
171
188
  return this.requireDefaultCompute().logGroup;
172
189
  }
@@ -206,16 +223,25 @@ export class BlocksBackend extends Construct {
206
223
  }
207
224
  return `${stackName}-${this.node.id}`;
208
225
  }
226
+ _vpcOptions;
209
227
  constructor(scope, id, props) {
210
228
  super(scope, id);
211
229
  this.backendHandlerPath = props.backendHandlerPath;
212
230
  this.backendModulePath = props.backendCDKPath;
231
+ this._vpcOptions = props.defaults.vpc;
213
232
  // Expose self to Building Blocks at CDK time
214
233
  globalThis.CURRENT_BLOCKS_STACK = this;
215
234
  // Store defaults on the backend (not the stack) so several BlocksBackends
216
235
  // in one stack each keep their own posture; Building Blocks resolve them by
217
236
  // walking up to their owning backend (see Scope.defaults).
218
237
  this.defaults = props.defaults;
238
+ // Initialize VPC context before the default compute is created and before
239
+ // BBs are constructed, so both can discover it: the default compute
240
+ // (LambdaCompute) reads it via getVpcContext(this) to place its function in
241
+ // the VPC, and BBs (e.g. bb-data) read it to co-locate their resources.
242
+ if (this._vpcOptions) {
243
+ initializeVpc(this, this._vpcOptions);
244
+ }
219
245
  const infra = setupBlocksInfra(this, props, id);
220
246
  this.executionRole = infra.executionRole;
221
247
  // The default compute (and thus handler/gateway) is created in create(),
@@ -247,6 +273,26 @@ export class BlocksBackend extends Construct {
247
273
  addBlocksStackMetadata(cdk.Stack.of(backend));
248
274
  // Finalize BB config → S3 (after all BBs have registered their config)
249
275
  finalizeConfigRegistry(backend, backend.executionRole, getComputes(backend));
276
+ // Tracing is presence-gated: if the app contains a Tracer, enable X-Ray on
277
+ // every compute. Runs before the dashboard finalize.
278
+ finalizeTracing(backend, backend.executionRole);
279
+ // Build any deferred Dashboards now that every compute's observability
280
+ // state is settled — so the dashboard is order-independent.
281
+ finalizeDashboards(backend);
282
+ // Finalize VPC. Derived resource: use the customer's if provided, else
283
+ // lazily create one only if a Building Block requires it.
284
+ if (backend._vpcOptions) {
285
+ finalizeVpc(backend, backend._vpcOptions);
286
+ }
287
+ else if (anyRequirementNeedsVpc(backend)) {
288
+ const derived = getOrCreateVpc(backend);
289
+ const options = { network: derived };
290
+ initializeVpc(backend, options);
291
+ finalizeVpc(backend, options);
292
+ cdk.Annotations.of(backend).addInfoV2('blocks:vpc:derived', 'A Building Block required a VPC and none was provided, so Blocks created one ' +
293
+ '(with a NAT gateway, which has an ongoing cost). Pass `defaults.vpc: { network }` to ' +
294
+ 'bring your own. See packages/blocks/VPC.md.');
295
+ }
250
296
  return backend;
251
297
  }
252
298
  }
@@ -14,6 +14,7 @@ import { BlocksBackend } from './blocks-backend.js';
14
14
  import { BlocksPresets } from './blocks-defaults.js';
15
15
  import { Compute } from './compute/compute.js';
16
16
  import { Scope } from './index.js';
17
+ import { getVpcContext } from './vpc.js';
17
18
  // A real app gets its default compute from @aws-blocks/bb-lambda-compute (via
18
19
  // @aws-blocks/blocks), which core's own tests can't depend on. Use an
19
20
  // equivalent inline stub: a Compute that owns a NodejsFunction + API Gateway,
@@ -31,6 +32,11 @@ class StubLambdaCompute extends Compute {
31
32
  this.logGroup = new cdk.aws_logs.LogGroup(this, 'HandlerLogGroup', {
32
33
  removalPolicy: cdk.RemovalPolicy.DESTROY,
33
34
  });
35
+ // Discover the shared VPC context from the owning stack/backend, mirroring
36
+ // LambdaCompute — so the core-side VPC placement plumbing (initializeVpc →
37
+ // getVpcContext) is exercised by these tests without depending on
38
+ // @aws-blocks/bb-lambda-compute.
39
+ const vpcContext = getVpcContext(this);
34
40
  this.fn = new lambda.NodejsFunction(this, 'Handler', {
35
41
  entry: this.backendHandlerPath,
36
42
  runtime: cdk.aws_lambda.Runtime.NODEJS_22_X,
@@ -39,6 +45,13 @@ class StubLambdaCompute extends Compute {
39
45
  logGroup: this.logGroup,
40
46
  environment: { BLOCKS_STACK_NAME: this.backendStackName },
41
47
  bundling: { minify: true, esbuildArgs: { '--conditions': 'aws-runtime' } },
48
+ ...(vpcContext
49
+ ? {
50
+ vpc: vpcContext.vpc,
51
+ vpcSubnets: vpcContext.computeSubnets,
52
+ securityGroups: [vpcContext.computeSecurityGroup],
53
+ }
54
+ : {}),
42
55
  });
43
56
  this.apiGateway = new apigateway.RestApi(this, 'API', { restApiName: 'Blocks API' });
44
57
  this.apiGateway.root.addProxy({
@@ -50,6 +63,16 @@ class StubLambdaCompute extends Compute {
50
63
  setEnv(key, value) {
51
64
  this.fn.addEnvironment(key, value);
52
65
  }
66
+ applyTracing() { }
67
+ healthWidgets(_region) {
68
+ return [];
69
+ }
70
+ loggingWidgets(_region) {
71
+ return [];
72
+ }
73
+ tracingWidgets(_region) {
74
+ return [];
75
+ }
53
76
  }
54
77
  const stubComputeFactory = (root) => new StubLambdaCompute(root, 'DefaultCompute');
55
78
  // Simulate the CDK condition being active (tests import CDK files directly)
@@ -65,7 +88,12 @@ const EXECUTION_ROLE_MARKER_ACTION = 'blocks-test:MarkerAction';
65
88
  const importMetaHandlerPath = join(__dirname, '__fixtures__', 'import-meta-handler.js');
66
89
  // Wraps BlocksBackend.create, injecting the stub default-compute factory the way
67
90
  // @aws-blocks/blocks injects LambdaCompute — so tests don't repeat it 15 times.
68
- const makeBackend = (scope, id, backendCDKPath) => BlocksBackend.create(scope, id, { backendHandlerPath: handlerPath, backendCDKPath, defaults: BlocksPresets.production, defaultComputeFactory: stubComputeFactory });
91
+ const makeBackend = (scope, id, backendCDKPath) => BlocksBackend.create(scope, id, {
92
+ backendHandlerPath: handlerPath,
93
+ backendCDKPath,
94
+ defaults: BlocksPresets.production,
95
+ defaultComputeFactory: stubComputeFactory,
96
+ });
69
97
  describe('ESM cache-busting (multi-stage)', () => {
70
98
  test('BlocksBackend.create() with same backendCDKPath but different IDs produces constructs in each', async () => {
71
99
  const app = new cdk.App();
@@ -294,3 +322,45 @@ describe('infrastructure defaults (backend-anchored)', () => {
294
322
  assert.strictEqual(inner.defaults, BlocksPresets.sandbox);
295
323
  });
296
324
  });
325
+ describe('VPC placement', () => {
326
+ test('places the handler Lambda in the VPC with a security group when vpc is provided', async () => {
327
+ const app = new cdk.App();
328
+ const parent = new cdk.Stack(app, 'VpcParent', {
329
+ env: { account: '123456789012', region: 'us-east-1' },
330
+ });
331
+ const vpc = new cdk.aws_ec2.Vpc(parent, 'AppVpc', { maxAzs: 2, natGateways: 1 });
332
+ await BlocksBackend.create(parent, 'Blocks', {
333
+ backendHandlerPath: handlerPath,
334
+ backendCDKPath: sideEffectBackendPath,
335
+ defaults: { ...BlocksPresets.production, vpc: { network: vpc } },
336
+ defaultComputeFactory: stubComputeFactory,
337
+ });
338
+ const template = Template.fromStack(parent);
339
+ // The handler Lambda is VPC-attached: it has a VpcConfig with subnets + SGs.
340
+ template.hasResourceProperties('AWS::Lambda::Function', {
341
+ VpcConfig: Match.objectLike({
342
+ SubnetIds: Match.anyValue(),
343
+ SecurityGroupIds: Match.anyValue(),
344
+ }),
345
+ });
346
+ // The VPC-access managed policy is attached to the execution role.
347
+ const policies = template.findResources('AWS::IAM::Role');
348
+ const hasVpcManagedPolicy = Object.values(policies).some((role) => JSON.stringify(role.Properties?.ManagedPolicyArns ?? []).includes('AWSLambdaVPCAccessExecutionRole'));
349
+ assert.ok(hasVpcManagedPolicy, 'execution role should have the VPC access managed policy');
350
+ });
351
+ test('no VpcConfig on the handler when vpc is omitted', async () => {
352
+ const app = new cdk.App();
353
+ const parent = new cdk.Stack(app, 'NoVpcParent');
354
+ await BlocksBackend.create(parent, 'Blocks', {
355
+ backendHandlerPath: handlerPath,
356
+ backendCDKPath: sideEffectBackendPath,
357
+ defaults: BlocksPresets.production,
358
+ defaultComputeFactory: stubComputeFactory,
359
+ });
360
+ const template = Template.fromStack(parent);
361
+ const fns = template.findResources('AWS::Lambda::Function');
362
+ for (const fn of Object.values(fns)) {
363
+ assert.strictEqual(fn.Properties?.VpcConfig, undefined, 'handler must not have VpcConfig when no VPC is configured');
364
+ }
365
+ });
366
+ });
@@ -1,5 +1,6 @@
1
1
  import { RemovalPolicy } from 'aws-cdk-lib';
2
2
  import { RetentionDays } from 'aws-cdk-lib/aws-logs';
3
+ import type { BlocksVpcOptions } from './vpc-types.js';
3
4
  /**
4
5
  * Request-rate limits applied to an API Gateway stage. On a REST API these are
5
6
  * requests/second; on a WebSocket API the unit is messages/second across the
@@ -89,6 +90,16 @@ export interface BlocksDefaults {
89
90
  * `ensureApiGatewayAccount` for the full multi-stack teardown caveat.
90
91
  */
91
92
  accessLogging: boolean;
93
+ /**
94
+ * Place the app's compute and VPC-resident resources in a VPC. Pass a
95
+ * standard CDK VPC — Blocks handles Lambda placement, endpoint provisioning
96
+ * (based on which Building Blocks are in scope), and security-group wiring.
97
+ *
98
+ * Omit for no VPC (the preset default — Lambda runs in the AWS-managed
99
+ * network). A Building Block that can't function without a VPC causes Blocks
100
+ * to derive one on demand even when this is unset.
101
+ */
102
+ vpc?: BlocksVpcOptions;
92
103
  }
93
104
  /**
94
105
  * Prepared starting points for {@link BlocksDefaults}. Pick one and override
@@ -1 +1 @@
1
- {"version":3,"file":"blocks-defaults.d.ts","sourceRoot":"","sources":["../../src/cdk/blocks-defaults.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAChC,4EAA4E;IAC5E,SAAS,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,UAAU,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC9B;;;;;OAKG;IACH,aAAa,EAAE,aAAa,CAAC;IAE7B;;;;OAIG;IACH,kBAAkB,EAAE,OAAO,CAAC;IAE5B;;;;;OAKG;IACH,cAAc,EAAE,MAAM,EAAE,CAAC;IAEzB;;;;;;;;;;;OAWG;IACH,mBAAmB,EAAE,OAAO,GAAG;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;IAEzD;;;;;;;OAOG;IACH,YAAY,EAAE,aAAa,CAAC;IAE5B;;;;;;;OAOG;IACH,UAAU,EAAE,gBAAgB,CAAC;IAE7B;;;;;;;;;;;;;;OAcG;IACH,aAAa,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa;IACzB,qFAAqF;;;;;;;;;;;;;IAUrF;;;;;OAKG;;;;;;;;;;;;;CAcsC,CAAC"}
1
+ {"version":3,"file":"blocks-defaults.d.ts","sourceRoot":"","sources":["../../src/cdk/blocks-defaults.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAChC,4EAA4E;IAC5E,SAAS,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,UAAU,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC9B;;;;;OAKG;IACH,aAAa,EAAE,aAAa,CAAC;IAE7B;;;;OAIG;IACH,kBAAkB,EAAE,OAAO,CAAC;IAE5B;;;;;OAKG;IACH,cAAc,EAAE,MAAM,EAAE,CAAC;IAEzB;;;;;;;;;;;OAWG;IACH,mBAAmB,EAAE,OAAO,GAAG;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;IAEzD;;;;;;;OAOG;IACH,YAAY,EAAE,aAAa,CAAC;IAE5B;;;;;;;OAOG;IACH,UAAU,EAAE,gBAAgB,CAAC;IAE7B;;;;;;;;;;;;;;OAcG;IACH,aAAa,EAAE,OAAO,CAAC;IAEvB;;;;;;;;OAQG;IACH,GAAG,CAAC,EAAE,gBAAgB,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa;IACzB,qFAAqF;;;;;;;;;;;;;IAUrF;;;;;OAKG;;;;;;;;;;;;;CAcsC,CAAC"}
@@ -11,7 +11,7 @@ import { BLOCKS_RPC_PREFIX } from '../constants.js';
11
11
  import { BlocksBackend } from './blocks-backend.js';
12
12
  import { Compute } from './compute/compute.js';
13
13
  import { getComputes } from './compute/compute-registry.js';
14
- import { BlocksStack, BlocksPresets, Scope } from './index.js';
14
+ import { BlocksPresets, BlocksStack, Scope } from './index.js';
15
15
  // A real app gets its default compute from @aws-blocks/bb-lambda-compute (via
16
16
  // @aws-blocks/blocks), which core's own tests can't depend on. Use an
17
17
  // equivalent inline stub: a Compute that owns a NodejsFunction + API Gateway,
@@ -48,6 +48,16 @@ class StubLambdaCompute extends Compute {
48
48
  setEnv(key, value) {
49
49
  this.fn.addEnvironment(key, value);
50
50
  }
51
+ applyTracing() { }
52
+ healthWidgets(_region) {
53
+ return [];
54
+ }
55
+ loggingWidgets(_region) {
56
+ return [];
57
+ }
58
+ tracingWidgets(_region) {
59
+ return [];
60
+ }
51
61
  }
52
62
  const stubComputeFactory = (root) => new StubLambdaCompute(root, 'DefaultCompute');
53
63
  // Simulate the CDK condition being active (tests import CDK files directly)
@@ -60,8 +70,18 @@ const sideEffectBackendPath = join(__dirname, '__fixtures__', 'side-effect-backe
60
70
  const factoryBackendPath = join(__dirname, '__fixtures__', 'factory-backend.js');
61
71
  // Wrap create(), injecting the stub default-compute factory the way
62
72
  // @aws-blocks/blocks injects LambdaCompute — so tests don't repeat it.
63
- const makeStack = (scope, id, backendCDKPath) => BlocksStack.create(scope, id, { backendHandlerPath: handlerPath, backendCDKPath, defaults: BlocksPresets.production, defaultComputeFactory: stubComputeFactory });
64
- const makeBackend = (scope, id, backendCDKPath) => BlocksBackend.create(scope, id, { backendHandlerPath: handlerPath, backendCDKPath, defaults: BlocksPresets.production, defaultComputeFactory: stubComputeFactory });
73
+ const makeStack = (scope, id, backendCDKPath) => BlocksStack.create(scope, id, {
74
+ backendHandlerPath: handlerPath,
75
+ backendCDKPath,
76
+ defaults: BlocksPresets.production,
77
+ defaultComputeFactory: stubComputeFactory,
78
+ });
79
+ const makeBackend = (scope, id, backendCDKPath) => BlocksBackend.create(scope, id, {
80
+ backendHandlerPath: handlerPath,
81
+ backendCDKPath,
82
+ defaults: BlocksPresets.production,
83
+ defaultComputeFactory: stubComputeFactory,
84
+ });
65
85
  describe('ESM cache-busting (multi-stage)', () => {
66
86
  test('BlocksStack.create() with same backendCDKPath but different IDs produces constructs in each', async () => {
67
87
  const app = new cdk.App();
@@ -1,3 +1,4 @@
1
+ import type { IWidget } from 'aws-cdk-lib/aws-cloudwatch';
1
2
  import type { ScopeOptions } from '../../common/index.js';
2
3
  import { Scope } from '../index.js';
3
4
  /**
@@ -11,11 +12,17 @@ import { Scope } from '../index.js';
11
12
  * compute in an app runs the same backend and agrees on the resource-name
12
13
  * namespace.
13
14
  *
15
+ * **Observability is compute-owned at deploy time.** Logging is always on (a
16
+ * compute owns its log group and captures stdout; retention is a compute-level
17
+ * setting), so there is no "enable logging" — logs always exist. Tracing, by
18
+ * contrast, provisions real infrastructure (X-Ray) and has cost, so it is
19
+ * enabled explicitly via {@link enableTracing} — which the framework calls on
20
+ * every compute when the app contains a `Tracer` (presence-gated).
21
+ *
14
22
  * The abstract base lives in core (a framework primitive); concrete computes
15
23
  * live in their own packages (e.g. `LambdaCompute` in `@aws-blocks/bb-lambda-compute`).
16
24
  *
17
- * @internal Not exported from the package's public entry points. Customers
18
- * cannot instantiate a compute until the customer-facing surface exists.
25
+ * @internal Not exported from the package's public entry points.
19
26
  */
20
27
  export declare abstract class Compute extends Scope {
21
28
  /**
@@ -24,6 +31,16 @@ export declare abstract class Compute extends Scope {
24
31
  * unpopulated (no compute assignment surface yet).
25
32
  */
26
33
  readonly namespaces: string[];
34
+ /**
35
+ * Whether tracing has been enabled on this compute — flipped by
36
+ * {@link enableTracing}. Private so it can't be set independently of the
37
+ * infra; read internally by {@link dashboardSection} to decide whether to
38
+ * render the traces section.
39
+ */
40
+ private tracerEnabled;
41
+ /** Read-only view of {@link tracerEnabled} for subclasses (e.g. to guard
42
+ * their `tracingWidgets` builder). Subclasses can read but not set it. */
43
+ protected get isTracerEnabled(): boolean;
27
44
  constructor(id: string, options?: ScopeOptions);
28
45
  /**
29
46
  * Inject a runtime configuration value (an environment variable) into this
@@ -31,5 +48,66 @@ export declare abstract class Compute extends Scope {
31
48
  * directly so config targets the right compute.
32
49
  */
33
50
  abstract setEnv(key: string, value: string): void;
51
+ /**
52
+ * Enable distributed tracing on this compute: mark it traced (so the Dashboard
53
+ * renders its traces section) and turn on the compute's active tracing via
54
+ * {@link applyTracing}. Idempotent — the framework calls this on **every**
55
+ * compute when the app contains a `Tracer` (tracing is presence-gated, not
56
+ * per-compute), so calling it more than once is a no-op.
57
+ */
58
+ enableTracing(): void;
59
+ /**
60
+ * Turn on this compute's active tracing (e.g. X-Ray) and grant its role the
61
+ * permission to publish trace segments. Called by {@link enableTracing};
62
+ * `protected` so tracing can't be turned on without marking the compute
63
+ * traced.
64
+ */
65
+ protected abstract applyTracing(): void;
66
+ /**
67
+ * Build this compute's CloudWatch Dashboard section: health widgets and log
68
+ * widgets **always** (logs are always captured for a compute), plus trace
69
+ * widgets **only when** tracing is enabled on this compute (via
70
+ * {@link enableTracing}).
71
+ *
72
+ * This is the single public entry the Dashboard Building Block uses; the
73
+ * per-kind builders below are `protected`. Whether the logs / traces sections
74
+ * are actually shown is a display choice the Dashboard makes on top (its
75
+ * `logs` / `traces` options) — this returns what the compute *has*.
76
+ *
77
+ * @param region - AWS region the widgets query metrics in.
78
+ */
79
+ dashboardSection(region: string): ComputeDashboardSection;
80
+ /**
81
+ * Build this compute's health widget rows. Implemented by a concrete compute;
82
+ * obtained only via {@link dashboardSection}.
83
+ */
84
+ protected abstract healthWidgets(region: string): IWidget[][];
85
+ /**
86
+ * Build this compute's **log** widget rows for its own log group. Logs always
87
+ * exist, so this is always available; the Dashboard's `logs` option decides
88
+ * whether to render it.
89
+ */
90
+ protected abstract loggingWidgets(region: string): IWidget[][];
91
+ /**
92
+ * Build this compute's **trace** widget rows. Gated behind
93
+ * {@link dashboardSection} so it is only used when tracing is enabled.
94
+ */
95
+ protected abstract tracingWidgets(region: string): IWidget[][];
96
+ }
97
+ /**
98
+ * A compute's self-reported CloudWatch Dashboard section. `health` and `logging`
99
+ * are always present; `tracing` is populated only when tracing is enabled on the
100
+ * compute. (The Dashboard may still hide `logging` / `tracing` via its display
101
+ * options.)
102
+ */
103
+ export interface ComputeDashboardSection {
104
+ /** Display label used as the compute's group header. */
105
+ label: string;
106
+ /** Health widget rows — always present. */
107
+ health: IWidget[][];
108
+ /** Log widget rows — always present (logs are always captured). */
109
+ logging?: IWidget[][];
110
+ /** Trace widget rows — present only when tracing is enabled. */
111
+ tracing?: IWidget[][];
34
112
  }
35
113
  //# sourceMappingURL=compute.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"compute.d.ts","sourceRoot":"","sources":["../../../src/cdk/compute/compute.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAGpC;;;;;;;;;;;;;;;;GAgBG;AACH,8BAAsB,OAAQ,SAAQ,KAAK;IAC1C;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,CAAM;gBAEvB,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;IAQ9C;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;CACjD"}
1
+ {"version":3,"file":"compute.d.ts","sourceRoot":"","sources":["../../../src/cdk/compute/compute.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAGpC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,8BAAsB,OAAQ,SAAQ,KAAK;IAC1C;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,CAAM;IAEnC;;;;;OAKG;IACH,OAAO,CAAC,aAAa,CAAS;IAE9B;8EAC0E;IAC1E,SAAS,KAAK,eAAe,IAAI,OAAO,CAEvC;gBAEW,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;IAQ9C;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAEjD;;;;;;OAMG;IACH,aAAa,IAAI,IAAI;IAMrB;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,CAAC,YAAY,IAAI,IAAI;IAEvC;;;;;;;;;;;;OAYG;IACH,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,uBAAuB;IAWzD;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,EAAE,EAAE;IAE7D;;;;OAIG;IACH,SAAS,CAAC,QAAQ,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,EAAE,EAAE;IAE9D;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,EAAE,EAAE;CAC9D;AAED;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACvC,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC;IACpB,mEAAmE;IACnE,OAAO,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC;IACtB,gEAAgE;IAChE,OAAO,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC;CACtB"}
@@ -13,11 +13,17 @@ import { registerCompute } from './compute-registry.js';
13
13
  * compute in an app runs the same backend and agrees on the resource-name
14
14
  * namespace.
15
15
  *
16
+ * **Observability is compute-owned at deploy time.** Logging is always on (a
17
+ * compute owns its log group and captures stdout; retention is a compute-level
18
+ * setting), so there is no "enable logging" — logs always exist. Tracing, by
19
+ * contrast, provisions real infrastructure (X-Ray) and has cost, so it is
20
+ * enabled explicitly via {@link enableTracing} — which the framework calls on
21
+ * every compute when the app contains a `Tracer` (presence-gated).
22
+ *
16
23
  * The abstract base lives in core (a framework primitive); concrete computes
17
24
  * live in their own packages (e.g. `LambdaCompute` in `@aws-blocks/bb-lambda-compute`).
18
25
  *
19
- * @internal Not exported from the package's public entry points. Customers
20
- * cannot instantiate a compute until the customer-facing surface exists.
26
+ * @internal Not exported from the package's public entry points.
21
27
  */
22
28
  export class Compute extends Scope {
23
29
  /**
@@ -26,11 +32,59 @@ export class Compute extends Scope {
26
32
  * unpopulated (no compute assignment surface yet).
27
33
  */
28
34
  namespaces = [];
35
+ /**
36
+ * Whether tracing has been enabled on this compute — flipped by
37
+ * {@link enableTracing}. Private so it can't be set independently of the
38
+ * infra; read internally by {@link dashboardSection} to decide whether to
39
+ * render the traces section.
40
+ */
41
+ tracerEnabled = false;
42
+ /** Read-only view of {@link tracerEnabled} for subclasses (e.g. to guard
43
+ * their `tracingWidgets` builder). Subclasses can read but not set it. */
44
+ get isTracerEnabled() {
45
+ return this.tracerEnabled;
46
+ }
29
47
  constructor(id, options) {
30
48
  super(id, options);
31
- // Self-register on the owning stack so finalize steps (config, routing,
49
+ // Self-register on the owning stack so finalize steps (tracing, routing,
32
50
  // dashboards) can enumerate every compute without a separate discovery
33
51
  // pass. Scoped per stack, so a multi-stack synth keeps lists isolated.
34
52
  registerCompute(this);
35
53
  }
54
+ /**
55
+ * Enable distributed tracing on this compute: mark it traced (so the Dashboard
56
+ * renders its traces section) and turn on the compute's active tracing via
57
+ * {@link applyTracing}. Idempotent — the framework calls this on **every**
58
+ * compute when the app contains a `Tracer` (tracing is presence-gated, not
59
+ * per-compute), so calling it more than once is a no-op.
60
+ */
61
+ enableTracing() {
62
+ if (this.tracerEnabled)
63
+ return;
64
+ this.tracerEnabled = true;
65
+ this.applyTracing();
66
+ }
67
+ /**
68
+ * Build this compute's CloudWatch Dashboard section: health widgets and log
69
+ * widgets **always** (logs are always captured for a compute), plus trace
70
+ * widgets **only when** tracing is enabled on this compute (via
71
+ * {@link enableTracing}).
72
+ *
73
+ * This is the single public entry the Dashboard Building Block uses; the
74
+ * per-kind builders below are `protected`. Whether the logs / traces sections
75
+ * are actually shown is a display choice the Dashboard makes on top (its
76
+ * `logs` / `traces` options) — this returns what the compute *has*.
77
+ *
78
+ * @param region - AWS region the widgets query metrics in.
79
+ */
80
+ dashboardSection(region) {
81
+ return {
82
+ // The scope id (e.g. 'DefaultCompute', 'api') — short and readable for a
83
+ // section header, and distinct per compute within a stack.
84
+ label: this.id,
85
+ health: this.healthWidgets(region),
86
+ logging: this.loggingWidgets(region),
87
+ tracing: this.tracerEnabled ? this.tracingWidgets(region) : undefined,
88
+ };
89
+ }
36
90
  }
@@ -37,6 +37,18 @@ class TestCompute extends Compute {
37
37
  setEnv(key, value) {
38
38
  this.fn.addEnvironment(key, value);
39
39
  }
40
+ // Observability hooks are irrelevant to config-registry tests — stub them so
41
+ // this test double satisfies Compute's abstract contract.
42
+ applyTracing() { }
43
+ healthWidgets() {
44
+ return [];
45
+ }
46
+ loggingWidgets() {
47
+ return [];
48
+ }
49
+ tracingWidgets() {
50
+ return [];
51
+ }
40
52
  }
41
53
  function stackWithCompute(id) {
42
54
  const app = new cdk.App();
@@ -0,0 +1,41 @@
1
+ import type { Construct } from 'constructs';
2
+ /** A Dashboard's deferred widget-body build, run once after the app is constructed. */
3
+ type DashboardFinalizer = () => void;
4
+ /**
5
+ * Register a Dashboard's deferred body-build, to run after every Building Block
6
+ * in the app has been constructed (the end of `BlocksStack`/`BlocksBackend`
7
+ * `create()`, once the backend module has fully imported).
8
+ *
9
+ * The Dashboard builds its widget body here rather than in its constructor
10
+ * because the body depends on which computes are traced, and a `Tracer` may be
11
+ * constructed *after* the Dashboard. Deferring makes the Dashboard observe the
12
+ * complete app, so it never depends on construction order. (The Dashboard's
13
+ * CloudWatch resource itself is created eagerly in the constructor, so its URL /
14
+ * redirect route never dangle — only the body is deferred.)
15
+ *
16
+ * This is intentionally scoped to the Dashboard (the only deferred-build case
17
+ * today) rather than a generic finalizer mechanism; generalize it only if a
18
+ * second use case appears.
19
+ *
20
+ * @param scope - Any construct in the stack (used to locate the stack).
21
+ * @param finalize - The deferred body-build; run once (in registration order)
22
+ * by {@link finalizeDashboards}.
23
+ */
24
+ export declare function registerDashboardFinalizer(scope: Construct, finalize: DashboardFinalizer): void;
25
+ /**
26
+ * Run — and clear — every registered Dashboard finalizer on `scope`'s stack, in
27
+ * registration order. Called once from `create()` after the backend module has
28
+ * imported. Clearing the list makes a repeated call a no-op, so a dashboard's
29
+ * body is never built twice.
30
+ *
31
+ * A Dashboard constructed outside `create()` (e.g. directly in a unit test) must
32
+ * call this explicitly before synth — the same way `config-registry.test.ts`
33
+ * drives `finalizeConfigRegistry`. (A Dashboard's CloudWatch resource is created
34
+ * eagerly in its constructor, so even if its finalizer never runs its URL /
35
+ * redirect never dangle — only the widget body is missing.)
36
+ *
37
+ * @param scope - Any construct in the stack (used to locate the stack).
38
+ */
39
+ export declare function finalizeDashboards(scope: Construct): void;
40
+ export {};
41
+ //# sourceMappingURL=dashboard-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard-registry.d.ts","sourceRoot":"","sources":["../../src/cdk/dashboard-registry.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAI5C,uFAAuF;AACvF,KAAK,kBAAkB,GAAG,MAAM,IAAI,CAAC;AAgBrC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,kBAAkB,GAAG,IAAI,CAE/F;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAKzD"}