@aws-blocks/core 0.2.0 → 0.4.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 (178) hide show
  1. package/README.md +13 -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 +43 -9
  12. package/dist/cdk/blocks-backend.d.ts.map +1 -1
  13. package/dist/cdk/blocks-backend.js +69 -73
  14. package/dist/cdk/blocks-backend.test.js +78 -69
  15. package/dist/cdk/blocks-defaults.d.ts +92 -2
  16. package/dist/cdk/blocks-defaults.d.ts.map +1 -1
  17. package/dist/cdk/blocks-defaults.js +22 -2
  18. package/dist/cdk/blocks-defaults.test.js +21 -2
  19. package/dist/cdk/blocks-stack.test.js +94 -37
  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 +2 -0
  24. package/dist/cdk/compute/compute.d.ts.map +1 -1
  25. package/dist/cdk/compute/compute.js +8 -0
  26. package/dist/cdk/compute/default-compute-factory.d.ts +34 -0
  27. package/dist/cdk/compute/default-compute-factory.d.ts.map +1 -0
  28. package/dist/cdk/compute/default-compute-factory.js +3 -0
  29. package/dist/cdk/config-registry.d.ts +34 -4
  30. package/dist/cdk/config-registry.d.ts.map +1 -1
  31. package/dist/cdk/config-registry.js +83 -25
  32. package/dist/cdk/config-registry.test.d.ts +2 -0
  33. package/dist/cdk/config-registry.test.d.ts.map +1 -0
  34. package/dist/cdk/config-registry.test.js +115 -0
  35. package/dist/cdk/index.d.ts +76 -7
  36. package/dist/cdk/index.d.ts.map +1 -1
  37. package/dist/cdk/index.js +99 -9
  38. package/dist/cdk/internal.d.ts +1 -0
  39. package/dist/cdk/internal.d.ts.map +1 -1
  40. package/dist/client/index.d.ts +1 -1
  41. package/dist/client/index.d.ts.map +1 -1
  42. package/dist/client/index.js +1 -1
  43. package/dist/common/config.d.ts +34 -0
  44. package/dist/common/config.d.ts.map +1 -1
  45. package/dist/common/config.js +45 -3
  46. package/dist/common/config.test.js +19 -0
  47. package/dist/common/index.d.ts +8 -0
  48. package/dist/common/index.d.ts.map +1 -1
  49. package/dist/errors.d.ts +16 -0
  50. package/dist/errors.d.ts.map +1 -1
  51. package/dist/errors.js +20 -0
  52. package/dist/hosting-secrets.d.ts +48 -0
  53. package/dist/hosting-secrets.d.ts.map +1 -0
  54. package/dist/hosting-secrets.js +57 -0
  55. package/dist/hosting-secrets.test.d.ts +2 -0
  56. package/dist/hosting-secrets.test.d.ts.map +1 -0
  57. package/dist/hosting-secrets.test.js +81 -0
  58. package/dist/hosting.d.ts +97 -6
  59. package/dist/hosting.d.ts.map +1 -1
  60. package/dist/hosting.js +91 -23
  61. package/dist/hosting.test.js +182 -10
  62. package/dist/index.cdk.d.ts +13 -12
  63. package/dist/index.cdk.d.ts.map +1 -1
  64. package/dist/index.cdk.js +13 -9
  65. package/dist/index.d.ts +8 -7
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +9 -5
  68. package/dist/lambda-handler.d.ts +17 -0
  69. package/dist/lambda-handler.d.ts.map +1 -1
  70. package/dist/lambda-handler.js +52 -3
  71. package/dist/lambda-handler.test.js +196 -1
  72. package/dist/pipeline/index.d.ts +24 -2
  73. package/dist/pipeline/index.d.ts.map +1 -1
  74. package/dist/pipeline/index.js +41 -1
  75. package/dist/pipeline.test.d.ts +2 -0
  76. package/dist/pipeline.test.d.ts.map +1 -0
  77. package/dist/pipeline.test.js +47 -0
  78. package/dist/rpc.d.ts +21 -0
  79. package/dist/rpc.d.ts.map +1 -1
  80. package/dist/rpc.js +34 -0
  81. package/dist/rpc.test.js +38 -1
  82. package/dist/scripts/config.d.ts +15 -0
  83. package/dist/scripts/config.d.ts.map +1 -0
  84. package/dist/scripts/config.js +29 -0
  85. package/dist/scripts/deploy.d.ts.map +1 -1
  86. package/dist/scripts/deploy.js +4 -0
  87. package/dist/scripts/dev-server.d.ts +8 -0
  88. package/dist/scripts/dev-server.d.ts.map +1 -1
  89. package/dist/scripts/dev-server.js +31 -0
  90. package/dist/scripts/generate-client.d.ts.map +1 -1
  91. package/dist/scripts/generate-client.js +9 -4
  92. package/dist/scripts/generate-client.test.js +19 -4
  93. package/dist/scripts/index.d.ts +13 -10
  94. package/dist/scripts/index.d.ts.map +1 -1
  95. package/dist/scripts/index.js +11 -8
  96. package/dist/scripts/preflight-credentials.d.ts +30 -0
  97. package/dist/scripts/preflight-credentials.d.ts.map +1 -0
  98. package/dist/scripts/preflight-credentials.js +108 -0
  99. package/dist/scripts/preflight-credentials.test.d.ts +2 -0
  100. package/dist/scripts/preflight-credentials.test.d.ts.map +1 -0
  101. package/dist/scripts/preflight-credentials.test.js +77 -0
  102. package/dist/scripts/sandbox-args.test.d.ts +2 -0
  103. package/dist/scripts/sandbox-args.test.d.ts.map +1 -0
  104. package/dist/scripts/sandbox-args.test.js +77 -0
  105. package/dist/scripts/sandbox-empty-buckets.test.d.ts +2 -0
  106. package/dist/scripts/sandbox-empty-buckets.test.d.ts.map +1 -0
  107. package/dist/scripts/sandbox-empty-buckets.test.js +171 -0
  108. package/dist/scripts/sandbox.d.ts +106 -0
  109. package/dist/scripts/sandbox.d.ts.map +1 -1
  110. package/dist/scripts/sandbox.js +255 -40
  111. package/dist/scripts/secret.d.ts +15 -0
  112. package/dist/scripts/secret.d.ts.map +1 -0
  113. package/dist/scripts/secret.js +29 -0
  114. package/dist/scripts/secret.test.d.ts +2 -0
  115. package/dist/scripts/secret.test.d.ts.map +1 -0
  116. package/dist/scripts/secret.test.js +28 -0
  117. package/dist/scripts/typegen.d.ts +15 -0
  118. package/dist/scripts/typegen.d.ts.map +1 -0
  119. package/dist/scripts/typegen.js +16 -0
  120. package/dist/secret-naming.d.ts +58 -0
  121. package/dist/secret-naming.d.ts.map +1 -0
  122. package/dist/secret-naming.js +67 -0
  123. package/dist/secret-naming.test.d.ts +2 -0
  124. package/dist/secret-naming.test.d.ts.map +1 -0
  125. package/dist/secret-naming.test.js +40 -0
  126. package/dist/version.d.ts +1 -1
  127. package/dist/version.js +1 -1
  128. package/package.json +4 -1
  129. package/src/api.test.ts +25 -0
  130. package/src/api.ts +39 -0
  131. package/src/bb-utils.ts +3 -0
  132. package/src/cdk/apigateway-account.ts +66 -0
  133. package/src/cdk/blocks-backend.test.ts +291 -272
  134. package/src/cdk/blocks-backend.ts +88 -83
  135. package/src/cdk/blocks-defaults.test.ts +25 -2
  136. package/src/cdk/blocks-defaults.ts +94 -2
  137. package/src/cdk/blocks-stack.test.ts +186 -118
  138. package/src/cdk/compute/compute-registry.ts +45 -0
  139. package/src/cdk/compute/compute.ts +10 -0
  140. package/src/cdk/compute/default-compute-factory.ts +38 -0
  141. package/src/cdk/config-registry.test.ts +135 -0
  142. package/src/cdk/config-registry.ts +92 -34
  143. package/src/cdk/index.ts +129 -10
  144. package/src/cdk/internal.ts +1 -0
  145. package/src/client/index.ts +1 -1
  146. package/src/common/config.test.ts +21 -0
  147. package/src/common/config.ts +47 -3
  148. package/src/common/index.ts +8 -0
  149. package/src/errors.ts +21 -0
  150. package/src/hosting-secrets.test.ts +106 -0
  151. package/src/hosting-secrets.ts +106 -0
  152. package/src/hosting.test.ts +272 -63
  153. package/src/hosting.ts +200 -68
  154. package/src/index.cdk.ts +80 -38
  155. package/src/index.ts +35 -16
  156. package/src/lambda-handler.test.ts +215 -1
  157. package/src/lambda-handler.ts +54 -2
  158. package/src/pipeline/index.ts +64 -11
  159. package/src/pipeline.test.ts +55 -0
  160. package/src/rpc.test.ts +44 -1
  161. package/src/rpc.ts +41 -0
  162. package/src/scripts/config.ts +35 -0
  163. package/src/scripts/deploy.ts +5 -0
  164. package/src/scripts/dev-server.ts +40 -0
  165. package/src/scripts/generate-client.test.ts +43 -8
  166. package/src/scripts/generate-client.ts +70 -65
  167. package/src/scripts/index.ts +20 -17
  168. package/src/scripts/preflight-credentials.test.ts +110 -0
  169. package/src/scripts/preflight-credentials.ts +131 -0
  170. package/src/scripts/sandbox-args.test.ts +92 -0
  171. package/src/scripts/sandbox-empty-buckets.test.ts +191 -0
  172. package/src/scripts/sandbox.ts +287 -39
  173. package/src/scripts/secret.test.ts +35 -0
  174. package/src/scripts/secret.ts +35 -0
  175. package/src/scripts/typegen.ts +18 -0
  176. package/src/secret-naming.test.ts +59 -0
  177. package/src/secret-naming.ts +99 -0
  178. package/src/version.ts +1 -1
@@ -1,12 +1,55 @@
1
1
  // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
- import { test, describe, before } from 'node:test';
4
3
  import assert from 'node:assert';
5
- import { fileURLToPath } from 'node:url';
6
4
  import { dirname, join } from 'node:path';
5
+ import { before, describe, test } from 'node:test';
6
+ import { fileURLToPath } from 'node:url';
7
7
  import * as cdk from 'aws-cdk-lib';
8
+ import * as apigateway from 'aws-cdk-lib/aws-apigateway';
9
+ import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs';
10
+ import { BLOCKS_RPC_PREFIX } from '../constants.js';
8
11
  import { BlocksBackend } from './blocks-backend.js';
12
+ import { Compute } from './compute/compute.js';
13
+ import { getComputes } from './compute/compute-registry.js';
9
14
  import { BlocksStack, BlocksPresets, Scope } from './index.js';
15
+ // A real app gets its default compute from @aws-blocks/bb-lambda-compute (via
16
+ // @aws-blocks/blocks), which core's own tests can't depend on. Use an
17
+ // equivalent inline stub: a Compute that owns a NodejsFunction + API Gateway,
18
+ // so create() can build the default and the handler/gateway/apiUrl accessors
19
+ // and synth-shape assertions have something real to resolve to. It is passed to
20
+ // each create() via the internal `defaultComputeFactory` option (see makeStack /
21
+ // makeBackend), exactly as @aws-blocks/blocks injects LambdaCompute.
22
+ class StubLambdaCompute extends Compute {
23
+ fn;
24
+ apiGateway;
25
+ apiUrl;
26
+ logGroup;
27
+ constructor(scope, id) {
28
+ super(id, { parent: scope });
29
+ this.logGroup = new cdk.aws_logs.LogGroup(this, 'HandlerLogGroup', {
30
+ removalPolicy: cdk.RemovalPolicy.DESTROY,
31
+ });
32
+ this.fn = new lambda.NodejsFunction(this, 'Handler', {
33
+ entry: this.backendHandlerPath,
34
+ runtime: cdk.aws_lambda.Runtime.NODEJS_22_X,
35
+ handler: 'handler',
36
+ role: this.executionRole,
37
+ logGroup: this.logGroup,
38
+ environment: { BLOCKS_STACK_NAME: this.backendStackName },
39
+ bundling: { minify: true, esbuildArgs: { '--conditions': 'aws-runtime' } },
40
+ });
41
+ this.apiGateway = new apigateway.RestApi(this, 'API', { restApiName: 'Blocks API' });
42
+ this.apiGateway.root.addProxy({
43
+ defaultIntegration: new apigateway.LambdaIntegration(this.fn),
44
+ anyMethod: true,
45
+ });
46
+ this.apiUrl = `${this.apiGateway.url}${BLOCKS_RPC_PREFIX.slice(1)}`;
47
+ }
48
+ setEnv(key, value) {
49
+ this.fn.addEnvironment(key, value);
50
+ }
51
+ }
52
+ const stubComputeFactory = (root) => new StubLambdaCompute(root, 'DefaultCompute');
10
53
  // Simulate the CDK condition being active (tests import CDK files directly)
11
54
  before(() => {
12
55
  process.env.NODE_OPTIONS = (process.env.NODE_OPTIONS ?? '') + ' --conditions=cdk';
@@ -15,19 +58,15 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
15
58
  const handlerPath = join(__dirname, '__fixtures__', 'handler.js');
16
59
  const sideEffectBackendPath = join(__dirname, '__fixtures__', 'side-effect-backend.js');
17
60
  const factoryBackendPath = join(__dirname, '__fixtures__', 'factory-backend.js');
61
+ // Wrap create(), injecting the stub default-compute factory the way
62
+ // @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 });
18
65
  describe('ESM cache-busting (multi-stage)', () => {
19
66
  test('BlocksStack.create() with same backendCDKPath but different IDs produces constructs in each', async () => {
20
67
  const app = new cdk.App();
21
- const stack1 = await BlocksStack.create(app, 'PipelineStage1', {
22
- backendHandlerPath: handlerPath,
23
- backendCDKPath: sideEffectBackendPath,
24
- defaults: BlocksPresets.production,
25
- });
26
- const stack2 = await BlocksStack.create(app, 'PipelineStage2', {
27
- backendHandlerPath: handlerPath,
28
- backendCDKPath: sideEffectBackendPath,
29
- defaults: BlocksPresets.production,
30
- });
68
+ const stack1 = await makeStack(app, 'PipelineStage1', sideEffectBackendPath);
69
+ const stack2 = await makeStack(app, 'PipelineStage2', sideEffectBackendPath);
31
70
  const findMarker = (scope) => scope.node.tryFindChild('SideEffectMarker');
32
71
  assert.ok(findMarker(stack1), 'First stack should have SideEffectMarker from module side effect');
33
72
  assert.ok(findMarker(stack2), 'Second stack should have SideEffectMarker from re-executed module (cache busted)');
@@ -36,11 +75,7 @@ describe('ESM cache-busting (multi-stage)', () => {
36
75
  describe('factory function support', () => {
37
76
  test('BlocksStack.create() calls default export function with the stack instance', async () => {
38
77
  const app = new cdk.App();
39
- const stack = await BlocksStack.create(app, 'FactoryBlocksStack', {
40
- backendHandlerPath: handlerPath,
41
- backendCDKPath: factoryBackendPath,
42
- defaults: BlocksPresets.production,
43
- });
78
+ const stack = await makeStack(app, 'FactoryBlocksStack', factoryBackendPath);
44
79
  const marker = stack.node.tryFindChild('FactoryMarker');
45
80
  assert.ok(marker, 'Factory function should have created FactoryMarker on the stack');
46
81
  });
@@ -49,11 +84,7 @@ describe('legacy side-effect mode (no default export)', () => {
49
84
  test('module with only side effects still registers constructs via globalThis', async () => {
50
85
  const app = new cdk.App();
51
86
  const stack = new cdk.Stack(app, 'LegacyTestStack');
52
- const backend = await BlocksBackend.create(stack, 'LegacyStage', {
53
- backendHandlerPath: handlerPath,
54
- backendCDKPath: sideEffectBackendPath,
55
- defaults: BlocksPresets.production,
56
- });
87
+ const backend = await makeBackend(stack, 'LegacyStage', sideEffectBackendPath);
57
88
  const marker = backend.node.tryFindChild('SideEffectMarker');
58
89
  assert.ok(marker, 'Side-effect-only module should register construct via globalThis.CURRENT_BLOCKS_STACK');
59
90
  });
@@ -65,22 +96,14 @@ describe('shared execution role (BlocksStack)', () => {
65
96
  // executionRole — a separate code path from BlocksBackend's constructor.
66
97
  test('BlocksStack wires executionRole via its constructor', async () => {
67
98
  const app = new cdk.App();
68
- const stack = await BlocksStack.create(app, 'StackRoleStack', {
69
- backendHandlerPath: handlerPath,
70
- backendCDKPath: sideEffectBackendPath,
71
- defaults: BlocksPresets.production,
72
- });
99
+ const stack = await makeStack(app, 'StackRoleStack', sideEffectBackendPath);
73
100
  assert.ok(stack.executionRole, 'BlocksStack should expose a populated .executionRole');
74
101
  });
75
102
  });
76
103
  describe('executionRole globalThis fallback', () => {
77
104
  test('resolves via globalThis.CURRENT_BLOCKS_STACK when no owner is in the tree', async () => {
78
105
  const app = new cdk.App();
79
- const stack = await BlocksStack.create(app, 'FallbackStack', {
80
- backendHandlerPath: handlerPath,
81
- backendCDKPath: sideEffectBackendPath,
82
- defaults: BlocksPresets.production,
83
- });
106
+ const stack = await makeStack(app, 'FallbackStack', sideEffectBackendPath);
84
107
  // A Scope whose construct-tree ancestry has no BlocksStack/BlocksBackend
85
108
  // (parented under a plain cdk.Stack) exhausts the tree-walk and falls back
86
109
  // to globalThis.CURRENT_BLOCKS_STACK. The `as any` is test plumbing — a
@@ -91,6 +114,44 @@ describe('executionRole globalThis fallback', () => {
91
114
  assert.strictEqual(orphan.executionRole, stack.executionRole, 'fallback resolves to the ambient stack role');
92
115
  });
93
116
  });
117
+ describe('root is bound to the owning stack (multi-stack synth)', () => {
118
+ test('a block under each of two stacks resolves its OWN stack, not the last globalThis', async () => {
119
+ // `root` is resolved once at Scope construction (not re-walked per getter),
120
+ // and `create()` mutates globalThis.CURRENT_BLOCKS_STACK for each stack it
121
+ // builds. With two stacks in one synth, a block constructed under the first
122
+ // must stay bound to the first even after the second stack is created and
123
+ // overwrites globalThis — otherwise its root-derived accessors (handler,
124
+ // executionRole, backendStackName) would silently point at the wrong stack.
125
+ const app = new cdk.App();
126
+ const stackA = await makeStack(app, 'RootBindingA', sideEffectBackendPath);
127
+ // A block explicitly parented under stackA (construct-tree walk resolves
128
+ // stackA regardless of the ambient globalThis).
129
+ const blockA = new Scope('blockA', { parent: stackA });
130
+ // Building the second stack overwrites globalThis.CURRENT_BLOCKS_STACK.
131
+ const stackB = await makeStack(app, 'RootBindingB', sideEffectBackendPath);
132
+ const blockB = new Scope('blockB', { parent: stackB });
133
+ assert.strictEqual(blockA.executionRole, stackA.executionRole, 'blockA stays bound to stackA');
134
+ assert.strictEqual(blockB.executionRole, stackB.executionRole, 'blockB binds to stackB');
135
+ assert.notStrictEqual(stackA.executionRole, stackB.executionRole, 'the two stacks have distinct roles');
136
+ assert.strictEqual(blockA.backendStackName, 'RootBindingA', 'blockA derives its own stack name');
137
+ assert.strictEqual(blockB.backendStackName, 'RootBindingB', 'blockB derives its own stack name');
138
+ });
139
+ test('each stack owns an isolated compute registry (no cross-stack bleed)', async () => {
140
+ // Computes self-register on their owning stack (keyed per stack, not a
141
+ // process-global list), so a multi-stack synth keeps each stack's computes
142
+ // separate — finalize steps for one stack never see another's compute.
143
+ const app = new cdk.App();
144
+ const stackA = await makeStack(app, 'ComputeRegistryA', sideEffectBackendPath);
145
+ const stackB = await makeStack(app, 'ComputeRegistryB', sideEffectBackendPath);
146
+ const computesA = getComputes(stackA);
147
+ const computesB = getComputes(stackB);
148
+ assert.strictEqual(computesA.length, 1, 'stackA registered exactly its default compute');
149
+ assert.strictEqual(computesB.length, 1, 'stackB registered exactly its default compute');
150
+ assert.strictEqual(computesA[0], stackA._defaultCompute, 'stackA lists its own default');
151
+ assert.strictEqual(computesB[0], stackB._defaultCompute, 'stackB lists its own default');
152
+ assert.notStrictEqual(computesA[0], computesB[0], 'the two stacks hold distinct computes');
153
+ });
154
+ });
94
155
  describe('assertCdkConditionActive', () => {
95
156
  test('BlocksStack.create() throws when --conditions=cdk is missing', async () => {
96
157
  const origNodeOptions = process.env.NODE_OPTIONS;
@@ -99,11 +160,7 @@ describe('assertCdkConditionActive', () => {
99
160
  process.execArgv = [];
100
161
  try {
101
162
  const app = new cdk.App();
102
- await assert.rejects(BlocksStack.create(app, 'MissingConditionStack', {
103
- backendHandlerPath: handlerPath,
104
- backendCDKPath: sideEffectBackendPath,
105
- defaults: BlocksPresets.production,
106
- }), (err) => {
163
+ await assert.rejects(makeStack(app, 'MissingConditionStack', sideEffectBackendPath), (err) => {
107
164
  assert.ok(err.message.includes('Missing --conditions=cdk'), `Expected condition error, got: ${err.message}`);
108
165
  return true;
109
166
  });
@@ -0,0 +1,19 @@
1
+ import type { Construct } from 'constructs';
2
+ import type { Compute } from './compute.js';
3
+ /**
4
+ * Register a compute on its owning stack. Called from the {@link Compute} base
5
+ * constructor, so every compute self-registers the moment it is constructed —
6
+ * the finalize steps then enumerate them without a separate discovery pass
7
+ * (mirrors how `registerConfig` accumulates config during the backend import).
8
+ *
9
+ * @param compute - The compute to register (used to locate its stack).
10
+ */
11
+ export declare function registerCompute(compute: Compute): void;
12
+ /**
13
+ * The computes registered on the stack that owns `scope`, in construction
14
+ * order. Returns an empty array before any compute is constructed.
15
+ *
16
+ * @param scope - Any construct in the stack (used to locate the stack).
17
+ */
18
+ export declare function getComputes(scope: Construct): readonly Compute[];
19
+ //# sourceMappingURL=compute-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compute-registry.d.ts","sourceRoot":"","sources":["../../../src/cdk/compute/compute-registry.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAmB5C;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAEtD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,OAAO,EAAE,CAEhE"}
@@ -0,0 +1,38 @@
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
+ const REGISTRY_KEY = Symbol.for('BLOCKS_COMPUTE_REGISTRY');
5
+ /**
6
+ * Get or create the compute list for a given stack. The list is stored on the
7
+ * stack object (keyed by a Symbol), so each stack in a multi-stack synth gets
8
+ * its own — a compute never leaks into another stack's list. Mirrors the config
9
+ * registry (`config-registry.ts`), which scopes its state the same way.
10
+ */
11
+ function getRegistry(stack) {
12
+ let list = stack[REGISTRY_KEY];
13
+ if (!list) {
14
+ list = [];
15
+ stack[REGISTRY_KEY] = list;
16
+ }
17
+ return list;
18
+ }
19
+ /**
20
+ * Register a compute on its owning stack. Called from the {@link Compute} base
21
+ * constructor, so every compute self-registers the moment it is constructed —
22
+ * the finalize steps then enumerate them without a separate discovery pass
23
+ * (mirrors how `registerConfig` accumulates config during the backend import).
24
+ *
25
+ * @param compute - The compute to register (used to locate its stack).
26
+ */
27
+ export function registerCompute(compute) {
28
+ getRegistry(cdk.Stack.of(compute)).push(compute);
29
+ }
30
+ /**
31
+ * The computes registered on the stack that owns `scope`, in construction
32
+ * order. Returns an empty array before any compute is constructed.
33
+ *
34
+ * @param scope - Any construct in the stack (used to locate the stack).
35
+ */
36
+ export function getComputes(scope) {
37
+ return getRegistry(cdk.Stack.of(scope));
38
+ }
@@ -1,3 +1,4 @@
1
+ import type { ScopeOptions } from '../../common/index.js';
1
2
  import { Scope } from '../index.js';
2
3
  /**
3
4
  * Base class for a Blocks *compute* — a runtime that executes handler code
@@ -23,6 +24,7 @@ export declare abstract class Compute extends Scope {
23
24
  * unpopulated (no compute assignment surface yet).
24
25
  */
25
26
  readonly namespaces: string[];
27
+ constructor(id: string, options?: ScopeOptions);
26
28
  /**
27
29
  * Inject a runtime configuration value (an environment variable) into this
28
30
  * compute. The framework calls this instead of `handler.addEnvironment()`
@@ -1 +1 @@
1
- {"version":3,"file":"compute.d.ts","sourceRoot":"","sources":["../../../src/cdk/compute/compute.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC;;;;;;;;;;;;;;;;GAgBG;AACH,8BAAsB,OAAQ,SAAQ,KAAK;IAC1C;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,CAAM;IAEnC;;;;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,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,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
  import { Scope } from '../index.js';
4
+ import { registerCompute } from './compute-registry.js';
4
5
  /**
5
6
  * Base class for a Blocks *compute* — a runtime that executes handler code
6
7
  * (Lambda today; containers later). A compute owns the physical function/service
@@ -25,4 +26,11 @@ export class Compute extends Scope {
25
26
  * unpopulated (no compute assignment surface yet).
26
27
  */
27
28
  namespaces = [];
29
+ constructor(id, options) {
30
+ super(id, options);
31
+ // Self-register on the owning stack so finalize steps (config, routing,
32
+ // dashboards) can enumerate every compute without a separate discovery
33
+ // pass. Scoped per stack, so a multi-stack synth keeps lists isolated.
34
+ registerCompute(this);
35
+ }
28
36
  }
@@ -0,0 +1,34 @@
1
+ import type * as cdk from 'aws-cdk-lib';
2
+ import type { Compute } from './compute.js';
3
+ import type { BlocksStack } from '../index.js';
4
+ import type { BlocksBackend } from '../blocks-backend.js';
5
+ /**
6
+ * Lambda-shaped surface of the default compute that the legacy
7
+ * `handler` / `gateway` / `apiUrl` accessors on BlocksStack/BlocksBackend read.
8
+ * The default compute (`LambdaCompute` from `@aws-blocks/bb-lambda-compute`)
9
+ * satisfies this structurally, so core exposes those accessors without
10
+ * importing the concrete class. To be removed with those accessors once
11
+ * consumers move to the multi-compute model.
12
+ *
13
+ * @internal
14
+ */
15
+ export interface LambdaShapedCompute extends Compute {
16
+ readonly fn: cdk.aws_lambda_nodejs.NodejsFunction;
17
+ readonly apiGateway: cdk.aws_apigateway.RestApi;
18
+ readonly apiUrl: string;
19
+ readonly logGroup: cdk.aws_logs.ILogGroup;
20
+ }
21
+ /**
22
+ * Builds the default {@link Compute} for a stack/backend. `create()` reads it
23
+ * from its props (`CoreBlocksStackProps.defaultComputeFactory`) and calls it to
24
+ * build the default without importing a concrete compute class — the factory is
25
+ * supplied by whoever owns both core and a concrete compute package (the
26
+ * umbrella `@aws-blocks/blocks`, which injects `LambdaCompute`). It lives on
27
+ * `CoreBlocksStackProps`/`CoreBlocksBackendProps`, which the umbrella derives
28
+ * from the customer-facing props, so it is absent from `BlocksStackProps` and
29
+ * customers cannot set it.
30
+ *
31
+ * @internal
32
+ */
33
+ export type DefaultComputeFactory = (root: BlocksStack | BlocksBackend) => Compute;
34
+ //# sourceMappingURL=default-compute-factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-compute-factory.d.ts","sourceRoot":"","sources":["../../../src/cdk/compute/default-compute-factory.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,GAAG,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D;;;;;;;;;GASG;AACH,MAAM,WAAW,mBAAoB,SAAQ,OAAO;IACnD,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAC,iBAAiB,CAAC,cAAc,CAAC;IAClD,QAAQ,CAAC,UAAU,EAAE,GAAG,CAAC,cAAc,CAAC,OAAO,CAAC;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC;CAC1C;AAED;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,IAAI,EAAE,WAAW,GAAG,aAAa,KAAK,OAAO,CAAC"}
@@ -0,0 +1,3 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ export {};
@@ -1,5 +1,6 @@
1
1
  import * as cdk from 'aws-cdk-lib';
2
2
  import type { Construct } from 'constructs';
3
+ import type { Compute } from './compute/compute.js';
3
4
  /**
4
5
  * Register a config entry for a Building Block. This replaces
5
6
  * `handler.addEnvironment(key, value)` for BB resource mappings.
@@ -12,15 +13,44 @@ import type { Construct } from 'constructs';
12
13
  * @param value - The config value (can be a CDK token that resolves at deploy time)
13
14
  */
14
15
  export declare function registerConfig(scope: Construct, key: string, value: unknown): void;
16
+ /**
17
+ * Ensure the shared config bucket exists and return where the config JSON lives
18
+ * (`{ bucketName, key }`). The bucket is created **once per stack** (memoized on
19
+ * the registry) and this is idempotent — the first caller creates it, later
20
+ * callers get the same bucket regardless of order.
21
+ *
22
+ * Any compute that loads config at runtime (`loadConfigToProcessEnv()`) injects
23
+ * these two values as `BLOCKS_CONFIG_BUCKET` / `BLOCKS_CONFIG_KEY`. The Lambda
24
+ * handler gets them from {@link finalizeConfigRegistry}; other compute that runs
25
+ * as the shared execution role (e.g. the Agent BB's AgentCore Runtime) calls this
26
+ * at construction to inject them too, so it loads the same app config the handler
27
+ * does. IAM is not granted here — `finalizeConfigRegistry` grants read on the config
28
+ * object to the shared execution role, which such compute inherits.
29
+ *
30
+ * @param scope - Any construct in the stack; the bucket is created under the stack.
31
+ */
32
+ export declare function getConfigLocation(scope: Construct): {
33
+ bucketName: string;
34
+ key: string;
35
+ };
15
36
  /**
16
37
  * Finalize the config registry: create an S3 bucket, upload the config JSON,
17
- * set env vars on the handler, and grant read access.
38
+ * grant read to the shared execution role, and stamp the config coordinates
39
+ * (`BLOCKS_CONFIG_BUCKET` / `BLOCKS_CONFIG_KEY`) onto every compute.
40
+ *
41
+ * Read access is granted once to the shared role (`root.executionRole`) rather
42
+ * than to a single function, so every compute that assumes the role can read
43
+ * the object. The bucket/key coordinates can't live on a role (env vars are
44
+ * per-compute), so they are set on each compute via `setEnv`.
18
45
  *
19
46
  * Must be called after all BBs are constructed (i.e., after the backendCDKPath
20
47
  * import completes in BlocksStack.create() / BlocksBackend.create()).
21
48
  *
22
- * @param scope - The CDK construct to create resources under
23
- * @param handler - The Lambda function that needs to read the config
49
+ * @param root - The construct to create the config resources under (also used
50
+ * to locate the owning stack).
51
+ * @param executionRole - The shared role every compute assumes; config read is
52
+ * granted to it once.
53
+ * @param computes - The computes to stamp `BLOCKS_CONFIG_BUCKET` / `BLOCKS_CONFIG_KEY` on.
24
54
  */
25
- export declare function finalizeConfigRegistry(scope: Construct, handler: cdk.aws_lambda.IFunction): void;
55
+ export declare function finalizeConfigRegistry(root: Construct, executionRole: cdk.aws_iam.IRole, computes: readonly Compute[]): void;
26
56
  //# sourceMappingURL=config-registry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config-registry.d.ts","sourceRoot":"","sources":["../../src/cdk/config-registry.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,GAAG,MAAM,aAAa,CAAC;AAInC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAuB5C;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAIlF;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACrC,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,GAAG,CAAC,UAAU,CAAC,SAAS,GAC/B,IAAI,CA6CN"}
1
+ {"version":3,"file":"config-registry.d.ts","sourceRoot":"","sources":["../../src/cdk/config-registry.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,GAAG,MAAM,aAAa,CAAC;AAGnC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AA4BpD;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAIlF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,SAAS,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAEvF;AA6BD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CACrC,IAAI,EAAE,SAAS,EACf,aAAa,EAAE,GAAG,CAAC,OAAO,CAAC,KAAK,EAChC,QAAQ,EAAE,SAAS,OAAO,EAAE,GAC1B,IAAI,CAyCN"}
@@ -3,8 +3,9 @@
3
3
  import * as cdk from 'aws-cdk-lib';
4
4
  import * as s3 from 'aws-cdk-lib/aws-s3';
5
5
  import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment';
6
- import * as iam from 'aws-cdk-lib/aws-iam';
7
6
  const REGISTRY_KEY = Symbol.for('BLOCKS_CONFIG_REGISTRY');
7
+ /** The object key of the config JSON under {@link getConfigLocation}'s bucket. */
8
+ const CONFIG_KEY = 'blocks-config.json';
8
9
  /**
9
10
  * Get or create the config registry for a given stack.
10
11
  * The registry collects BB config entries (env var key → CDK token value)
@@ -34,47 +35,104 @@ export function registerConfig(scope, key, value) {
34
35
  const registry = getRegistry(stack);
35
36
  registry.entries.set(key, value);
36
37
  }
38
+ /**
39
+ * Ensure the shared config bucket exists and return where the config JSON lives
40
+ * (`{ bucketName, key }`). The bucket is created **once per stack** (memoized on
41
+ * the registry) and this is idempotent — the first caller creates it, later
42
+ * callers get the same bucket regardless of order.
43
+ *
44
+ * Any compute that loads config at runtime (`loadConfigToProcessEnv()`) injects
45
+ * these two values as `BLOCKS_CONFIG_BUCKET` / `BLOCKS_CONFIG_KEY`. The Lambda
46
+ * handler gets them from {@link finalizeConfigRegistry}; other compute that runs
47
+ * as the shared execution role (e.g. the Agent BB's AgentCore Runtime) calls this
48
+ * at construction to inject them too, so it loads the same app config the handler
49
+ * does. IAM is not granted here — `finalizeConfigRegistry` grants read on the config
50
+ * object to the shared execution role, which such compute inherits.
51
+ *
52
+ * @param scope - Any construct in the stack; the bucket is created under the stack.
53
+ */
54
+ export function getConfigLocation(scope) {
55
+ return { bucketName: ensureConfigBucket(scope).bucketName, key: CONFIG_KEY };
56
+ }
57
+ /**
58
+ * Create-or-return the shared config bucket (memoized on the per-stack registry). Created under the
59
+ * owning `BlocksStack`/`BlocksBackend` (`globalThis.CURRENT_BLOCKS_STACK` — the construct finalize
60
+ * historically used), so its logical ID is stable regardless of which caller creates it first: a
61
+ * co-located BB (e.g. the AgentCore Runtime, a deep construct) may be the first to call it, and a
62
+ * `BlocksBackend` embedded in a customer stack must keep `Blocks/BlocksConfigBucket` (no replacement).
63
+ * Falls back to the stack when no owner is registered (isolated unit tests). Returns a concrete
64
+ * `s3.Bucket` so callers don't need a non-null assertion.
65
+ */
66
+ function ensureConfigBucket(scope) {
67
+ const stack = cdk.Stack.of(scope);
68
+ const registry = getRegistry(stack);
69
+ if (!registry.bucket) {
70
+ const owner = globalThis.CURRENT_BLOCKS_STACK ?? stack;
71
+ registry.bucket = new s3.Bucket(owner, 'BlocksConfigBucket', {
72
+ removalPolicy: cdk.RemovalPolicy.DESTROY,
73
+ autoDeleteObjects: true,
74
+ encryption: s3.BucketEncryption.S3_MANAGED,
75
+ blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
76
+ lifecycleRules: [
77
+ { noncurrentVersionExpiration: cdk.Duration.days(1) },
78
+ ],
79
+ });
80
+ }
81
+ return registry.bucket;
82
+ }
37
83
  /**
38
84
  * Finalize the config registry: create an S3 bucket, upload the config JSON,
39
- * set env vars on the handler, and grant read access.
85
+ * grant read to the shared execution role, and stamp the config coordinates
86
+ * (`BLOCKS_CONFIG_BUCKET` / `BLOCKS_CONFIG_KEY`) onto every compute.
87
+ *
88
+ * Read access is granted once to the shared role (`root.executionRole`) rather
89
+ * than to a single function, so every compute that assumes the role can read
90
+ * the object. The bucket/key coordinates can't live on a role (env vars are
91
+ * per-compute), so they are set on each compute via `setEnv`.
40
92
  *
41
93
  * Must be called after all BBs are constructed (i.e., after the backendCDKPath
42
94
  * import completes in BlocksStack.create() / BlocksBackend.create()).
43
95
  *
44
- * @param scope - The CDK construct to create resources under
45
- * @param handler - The Lambda function that needs to read the config
96
+ * @param root - The construct to create the config resources under (also used
97
+ * to locate the owning stack).
98
+ * @param executionRole - The shared role every compute assumes; config read is
99
+ * granted to it once.
100
+ * @param computes - The computes to stamp `BLOCKS_CONFIG_BUCKET` / `BLOCKS_CONFIG_KEY` on.
46
101
  */
47
- export function finalizeConfigRegistry(scope, handler) {
48
- const stack = cdk.Stack.of(scope);
102
+ export function finalizeConfigRegistry(root, executionRole, computes) {
103
+ const stack = cdk.Stack.of(root);
49
104
  const registry = getRegistry(stack);
50
105
  if (registry.finalized)
51
106
  return;
52
107
  registry.finalized = true;
53
- if (registry.entries.size === 0)
108
+ // Nothing to do only if no config was registered AND no bucket was created (via
109
+ // getConfigLocation). If a co-located BB created the bucket, still upload (even an empty {}) and
110
+ // wire the handler so that compute's loadConfigToProcessEnv() resolves instead of 404-ing forever.
111
+ if (registry.entries.size === 0 && !registry.bucket)
54
112
  return;
55
- const configBucket = new s3.Bucket(scope, 'BlocksConfigBucket', {
56
- removalPolicy: cdk.RemovalPolicy.DESTROY,
57
- autoDeleteObjects: true,
58
- encryption: s3.BucketEncryption.S3_MANAGED,
59
- blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
60
- lifecycleRules: [
61
- { noncurrentVersionExpiration: cdk.Duration.days(1) },
62
- ],
63
- });
64
- const configKey = 'blocks-config.json';
113
+ // Ensure the bucket exists (a co-located BB may already have created it via getConfigLocation).
114
+ const configBucket = ensureConfigBucket(root);
115
+ const configKey = CONFIG_KEY;
65
116
  const configObject = cdk.Lazy.any({
66
117
  produce: () => Object.fromEntries(registry.entries),
67
118
  });
68
- const deployment = new s3deploy.BucketDeployment(scope, 'BlocksConfigDeployment', {
119
+ new s3deploy.BucketDeployment(root, 'BlocksConfigDeployment', {
69
120
  sources: [s3deploy.Source.jsonData(configKey, configObject)],
70
121
  destinationBucket: configBucket,
71
122
  prune: false,
72
123
  });
73
- handler.addEnvironment('BLOCKS_CONFIG_BUCKET', configBucket.bucketName);
74
- handler.addEnvironment('BLOCKS_CONFIG_KEY', configKey);
75
- // Scope IAM grant to the specific config key
76
- handler.addToRolePolicy(new iam.PolicyStatement({
77
- actions: ['s3:GetObject'],
78
- resources: [`${configBucket.bucketArn}/${configKey}`],
79
- }));
124
+ // Grant read once to the shared role (scoped to the config key), so every
125
+ // compute assuming the role can read it. We intentionally use `grantRead`
126
+ // (which also adds s3:GetBucket*/s3:List* alongside s3:GetObject*) rather
127
+ // than a hand-rolled GetObject-only statement: this is a dedicated,
128
+ // block-all-public, config-only bucket, so the broader action set carries
129
+ // negligible exposure, and grantRead stays correct automatically if the
130
+ // bucket ever moves to KMS encryption (it would add kms:Decrypt).
131
+ configBucket.grantRead(executionRole, configKey);
132
+ // Stamp the config coordinates on every compute — env vars can't live on a
133
+ // role, so each compute needs them to locate the object at runtime.
134
+ for (const compute of computes) {
135
+ compute.setEnv('BLOCKS_CONFIG_BUCKET', configBucket.bucketName);
136
+ compute.setEnv('BLOCKS_CONFIG_KEY', configKey);
137
+ }
80
138
  }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=config-registry.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-registry.test.d.ts","sourceRoot":"","sources":["../../src/cdk/config-registry.test.ts"],"names":[],"mappings":""}