@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
@@ -7,6 +7,7 @@ import { before, describe, test } from 'node:test';
7
7
  import { fileURLToPath } from 'node:url';
8
8
  import * as cdk from 'aws-cdk-lib';
9
9
  import * as apigateway from 'aws-cdk-lib/aws-apigateway';
10
+ import type { IWidget } from 'aws-cdk-lib/aws-cloudwatch';
10
11
  import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs';
11
12
  import type { Construct } from 'constructs';
12
13
  import type { ScopeParent } from '../common/index.js';
@@ -15,7 +16,7 @@ import { BlocksBackend } from './blocks-backend.js';
15
16
  import { Compute } from './compute/compute.js';
16
17
  import { getComputes } from './compute/compute-registry.js';
17
18
  import type { DefaultComputeFactory } from './compute/default-compute-factory.js';
18
- import { BlocksStack, BlocksPresets, Scope } from './index.js';
19
+ import { BlocksPresets, BlocksStack, Scope } from './index.js';
19
20
 
20
21
  // A real app gets its default compute from @aws-blocks/bb-lambda-compute (via
21
22
  // @aws-blocks/blocks), which core's own tests can't depend on. Use an
@@ -55,6 +56,17 @@ class StubLambdaCompute extends Compute {
55
56
  setEnv(key: string, value: string): void {
56
57
  this.fn.addEnvironment(key, value);
57
58
  }
59
+
60
+ protected applyTracing(): void {}
61
+ protected healthWidgets(_region: string): IWidget[][] {
62
+ return [];
63
+ }
64
+ protected loggingWidgets(_region: string): IWidget[][] {
65
+ return [];
66
+ }
67
+ protected tracingWidgets(_region: string): IWidget[][] {
68
+ return [];
69
+ }
58
70
  }
59
71
 
60
72
  const stubComputeFactory: DefaultComputeFactory = (root) => new StubLambdaCompute(root as never, 'DefaultCompute');
@@ -72,9 +84,19 @@ const factoryBackendPath = join(__dirname, '__fixtures__', 'factory-backend.js')
72
84
  // Wrap create(), injecting the stub default-compute factory the way
73
85
  // @aws-blocks/blocks injects LambdaCompute — so tests don't repeat it.
74
86
  const makeStack = (scope: Construct, id: string, backendCDKPath: string) =>
75
- BlocksStack.create(scope, id, { backendHandlerPath: handlerPath, backendCDKPath, defaults: BlocksPresets.production, defaultComputeFactory: stubComputeFactory });
87
+ BlocksStack.create(scope, id, {
88
+ backendHandlerPath: handlerPath,
89
+ backendCDKPath,
90
+ defaults: BlocksPresets.production,
91
+ defaultComputeFactory: stubComputeFactory,
92
+ });
76
93
  const makeBackend = (scope: Construct, id: string, backendCDKPath: string) =>
77
- BlocksBackend.create(scope, id, { backendHandlerPath: handlerPath, backendCDKPath, defaults: BlocksPresets.production, defaultComputeFactory: stubComputeFactory });
94
+ BlocksBackend.create(scope, id, {
95
+ backendHandlerPath: handlerPath,
96
+ backendCDKPath,
97
+ defaults: BlocksPresets.production,
98
+ defaultComputeFactory: stubComputeFactory,
99
+ });
78
100
 
79
101
  describe('ESM cache-busting (multi-stage)', () => {
80
102
  test('BlocksStack.create() with same backendCDKPath but different IDs produces constructs in each', async () => {
@@ -203,16 +225,13 @@ describe('assertCdkConditionActive', () => {
203
225
  try {
204
226
  const app = new cdk.App();
205
227
 
206
- await assert.rejects(
207
- makeStack(app, 'MissingConditionStack', sideEffectBackendPath),
208
- (err: Error) => {
209
- assert.ok(
210
- err.message.includes('Missing --conditions=cdk'),
211
- `Expected condition error, got: ${err.message}`,
212
- );
213
- return true;
214
- },
215
- );
228
+ await assert.rejects(makeStack(app, 'MissingConditionStack', sideEffectBackendPath), (err: Error) => {
229
+ assert.ok(
230
+ err.message.includes('Missing --conditions=cdk'),
231
+ `Expected condition error, got: ${err.message}`,
232
+ );
233
+ return true;
234
+ });
216
235
  } finally {
217
236
  process.env.NODE_OPTIONS = origNodeOptions;
218
237
  process.execArgv = origExecArgv;
@@ -1,6 +1,7 @@
1
1
  // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
+ import type { IWidget } from 'aws-cdk-lib/aws-cloudwatch';
4
5
  import type { ScopeOptions } from '../../common/index.js';
5
6
  import { Scope } from '../index.js';
6
7
  import { registerCompute } from './compute-registry.js';
@@ -16,11 +17,17 @@ import { registerCompute } from './compute-registry.js';
16
17
  * compute in an app runs the same backend and agrees on the resource-name
17
18
  * namespace.
18
19
  *
20
+ * **Observability is compute-owned at deploy time.** Logging is always on (a
21
+ * compute owns its log group and captures stdout; retention is a compute-level
22
+ * setting), so there is no "enable logging" — logs always exist. Tracing, by
23
+ * contrast, provisions real infrastructure (X-Ray) and has cost, so it is
24
+ * enabled explicitly via {@link enableTracing} — which the framework calls on
25
+ * every compute when the app contains a `Tracer` (presence-gated).
26
+ *
19
27
  * The abstract base lives in core (a framework primitive); concrete computes
20
28
  * live in their own packages (e.g. `LambdaCompute` in `@aws-blocks/bb-lambda-compute`).
21
29
  *
22
- * @internal Not exported from the package's public entry points. Customers
23
- * cannot instantiate a compute until the customer-facing surface exists.
30
+ * @internal Not exported from the package's public entry points.
24
31
  */
25
32
  export abstract class Compute extends Scope {
26
33
  /**
@@ -30,9 +37,23 @@ export abstract class Compute extends Scope {
30
37
  */
31
38
  readonly namespaces: string[] = [];
32
39
 
40
+ /**
41
+ * Whether tracing has been enabled on this compute — flipped by
42
+ * {@link enableTracing}. Private so it can't be set independently of the
43
+ * infra; read internally by {@link dashboardSection} to decide whether to
44
+ * render the traces section.
45
+ */
46
+ private tracerEnabled = false;
47
+
48
+ /** Read-only view of {@link tracerEnabled} for subclasses (e.g. to guard
49
+ * their `tracingWidgets` builder). Subclasses can read but not set it. */
50
+ protected get isTracerEnabled(): boolean {
51
+ return this.tracerEnabled;
52
+ }
53
+
33
54
  constructor(id: string, options?: ScopeOptions) {
34
55
  super(id, options);
35
- // Self-register on the owning stack so finalize steps (config, routing,
56
+ // Self-register on the owning stack so finalize steps (tracing, routing,
36
57
  // dashboards) can enumerate every compute without a separate discovery
37
58
  // pass. Scoped per stack, so a multi-stack synth keeps lists isolated.
38
59
  registerCompute(this);
@@ -44,4 +65,85 @@ export abstract class Compute extends Scope {
44
65
  * directly so config targets the right compute.
45
66
  */
46
67
  abstract setEnv(key: string, value: string): void;
68
+
69
+ /**
70
+ * Enable distributed tracing on this compute: mark it traced (so the Dashboard
71
+ * renders its traces section) and turn on the compute's active tracing via
72
+ * {@link applyTracing}. Idempotent — the framework calls this on **every**
73
+ * compute when the app contains a `Tracer` (tracing is presence-gated, not
74
+ * per-compute), so calling it more than once is a no-op.
75
+ */
76
+ enableTracing(): void {
77
+ if (this.tracerEnabled) return;
78
+ this.tracerEnabled = true;
79
+ this.applyTracing();
80
+ }
81
+
82
+ /**
83
+ * Turn on this compute's active tracing (e.g. X-Ray) and grant its role the
84
+ * permission to publish trace segments. Called by {@link enableTracing};
85
+ * `protected` so tracing can't be turned on without marking the compute
86
+ * traced.
87
+ */
88
+ protected abstract applyTracing(): void;
89
+
90
+ /**
91
+ * Build this compute's CloudWatch Dashboard section: health widgets and log
92
+ * widgets **always** (logs are always captured for a compute), plus trace
93
+ * widgets **only when** tracing is enabled on this compute (via
94
+ * {@link enableTracing}).
95
+ *
96
+ * This is the single public entry the Dashboard Building Block uses; the
97
+ * per-kind builders below are `protected`. Whether the logs / traces sections
98
+ * are actually shown is a display choice the Dashboard makes on top (its
99
+ * `logs` / `traces` options) — this returns what the compute *has*.
100
+ *
101
+ * @param region - AWS region the widgets query metrics in.
102
+ */
103
+ dashboardSection(region: string): ComputeDashboardSection {
104
+ return {
105
+ // The scope id (e.g. 'DefaultCompute', 'api') — short and readable for a
106
+ // section header, and distinct per compute within a stack.
107
+ label: this.id,
108
+ health: this.healthWidgets(region),
109
+ logging: this.loggingWidgets(region),
110
+ tracing: this.tracerEnabled ? this.tracingWidgets(region) : undefined,
111
+ };
112
+ }
113
+
114
+ /**
115
+ * Build this compute's health widget rows. Implemented by a concrete compute;
116
+ * obtained only via {@link dashboardSection}.
117
+ */
118
+ protected abstract healthWidgets(region: string): IWidget[][];
119
+
120
+ /**
121
+ * Build this compute's **log** widget rows for its own log group. Logs always
122
+ * exist, so this is always available; the Dashboard's `logs` option decides
123
+ * whether to render it.
124
+ */
125
+ protected abstract loggingWidgets(region: string): IWidget[][];
126
+
127
+ /**
128
+ * Build this compute's **trace** widget rows. Gated behind
129
+ * {@link dashboardSection} so it is only used when tracing is enabled.
130
+ */
131
+ protected abstract tracingWidgets(region: string): IWidget[][];
132
+ }
133
+
134
+ /**
135
+ * A compute's self-reported CloudWatch Dashboard section. `health` and `logging`
136
+ * are always present; `tracing` is populated only when tracing is enabled on the
137
+ * compute. (The Dashboard may still hide `logging` / `tracing` via its display
138
+ * options.)
139
+ */
140
+ export interface ComputeDashboardSection {
141
+ /** Display label used as the compute's group header. */
142
+ label: string;
143
+ /** Health widget rows — always present. */
144
+ health: IWidget[][];
145
+ /** Log widget rows — always present (logs are always captured). */
146
+ logging?: IWidget[][];
147
+ /** Trace widget rows — present only when tracing is enabled. */
148
+ tracing?: IWidget[][];
47
149
  }
@@ -13,6 +13,7 @@ import assert from 'node:assert';
13
13
  import { afterEach, test } from 'node:test';
14
14
  import * as cdk from 'aws-cdk-lib';
15
15
  import { Match, Template } from 'aws-cdk-lib/assertions';
16
+ import type { IWidget } from 'aws-cdk-lib/aws-cloudwatch';
16
17
  import { Construct } from 'constructs';
17
18
  import { Compute } from './compute/compute.js';
18
19
  import { finalizeConfigRegistry, getConfigLocation, registerConfig } from './config-registry.js';
@@ -42,6 +43,19 @@ class TestCompute extends Compute {
42
43
  setEnv(key: string, value: string): void {
43
44
  this.fn.addEnvironment(key, value);
44
45
  }
46
+
47
+ // Observability hooks are irrelevant to config-registry tests — stub them so
48
+ // this test double satisfies Compute's abstract contract.
49
+ protected applyTracing(): void {}
50
+ protected healthWidgets(): IWidget[][] {
51
+ return [];
52
+ }
53
+ protected loggingWidgets(): IWidget[][] {
54
+ return [];
55
+ }
56
+ protected tracingWidgets(): IWidget[][] {
57
+ return [];
58
+ }
45
59
  }
46
60
 
47
61
  function stackWithCompute(id: string): {
@@ -0,0 +1,68 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import * as cdk from 'aws-cdk-lib';
4
+ import type { Construct } from 'constructs';
5
+
6
+ const REGISTRY_KEY = Symbol.for('BLOCKS_DASHBOARD_REGISTRY');
7
+
8
+ /** A Dashboard's deferred widget-body build, run once after the app is constructed. */
9
+ type DashboardFinalizer = () => void;
10
+
11
+ /**
12
+ * Get or create the deferred-dashboard list for a given stack. Stored on the
13
+ * stack object (keyed by a Symbol), so each stack in a multi-stack synth gets
14
+ * its own — mirrors the config + compute registries.
15
+ */
16
+ function getRegistry(stack: cdk.Stack): DashboardFinalizer[] {
17
+ let list = (stack as unknown as Record<symbol, DashboardFinalizer[] | undefined>)[REGISTRY_KEY];
18
+ if (!list) {
19
+ list = [];
20
+ (stack as unknown as Record<symbol, DashboardFinalizer[]>)[REGISTRY_KEY] = list;
21
+ }
22
+ return list;
23
+ }
24
+
25
+ /**
26
+ * Register a Dashboard's deferred body-build, to run after every Building Block
27
+ * in the app has been constructed (the end of `BlocksStack`/`BlocksBackend`
28
+ * `create()`, once the backend module has fully imported).
29
+ *
30
+ * The Dashboard builds its widget body here rather than in its constructor
31
+ * because the body depends on which computes are traced, and a `Tracer` may be
32
+ * constructed *after* the Dashboard. Deferring makes the Dashboard observe the
33
+ * complete app, so it never depends on construction order. (The Dashboard's
34
+ * CloudWatch resource itself is created eagerly in the constructor, so its URL /
35
+ * redirect route never dangle — only the body is deferred.)
36
+ *
37
+ * This is intentionally scoped to the Dashboard (the only deferred-build case
38
+ * today) rather than a generic finalizer mechanism; generalize it only if a
39
+ * second use case appears.
40
+ *
41
+ * @param scope - Any construct in the stack (used to locate the stack).
42
+ * @param finalize - The deferred body-build; run once (in registration order)
43
+ * by {@link finalizeDashboards}.
44
+ */
45
+ export function registerDashboardFinalizer(scope: Construct, finalize: DashboardFinalizer): void {
46
+ getRegistry(cdk.Stack.of(scope)).push(finalize);
47
+ }
48
+
49
+ /**
50
+ * Run — and clear — every registered Dashboard finalizer on `scope`'s stack, in
51
+ * registration order. Called once from `create()` after the backend module has
52
+ * imported. Clearing the list makes a repeated call a no-op, so a dashboard's
53
+ * body is never built twice.
54
+ *
55
+ * A Dashboard constructed outside `create()` (e.g. directly in a unit test) must
56
+ * call this explicitly before synth — the same way `config-registry.test.ts`
57
+ * drives `finalizeConfigRegistry`. (A Dashboard's CloudWatch resource is created
58
+ * eagerly in its constructor, so even if its finalizer never runs its URL /
59
+ * redirect never dangle — only the widget body is missing.)
60
+ *
61
+ * @param scope - Any construct in the stack (used to locate the stack).
62
+ */
63
+ export function finalizeDashboards(scope: Construct): void {
64
+ const list = getRegistry(cdk.Stack.of(scope));
65
+ // Drain the list so a repeated call can't rebuild an already-built dashboard.
66
+ const pending = list.splice(0, list.length);
67
+ for (const finalize of pending) finalize();
68
+ }