@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,15 +1,57 @@
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 { Template, Match } from 'aws-cdk-lib/assertions';
8
+ import { Match, Template } from 'aws-cdk-lib/assertions';
9
+ import * as apigateway from 'aws-cdk-lib/aws-apigateway';
9
10
  import { PolicyStatement } from 'aws-cdk-lib/aws-iam';
11
+ import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs';
12
+ import { BLOCKS_RPC_PREFIX } from '../constants.js';
10
13
  import { BlocksBackend } from './blocks-backend.js';
11
14
  import { BlocksPresets } from './blocks-defaults.js';
15
+ import { Compute } from './compute/compute.js';
12
16
  import { Scope } from './index.js';
17
+ // A real app gets its default compute from @aws-blocks/bb-lambda-compute (via
18
+ // @aws-blocks/blocks), which core's own tests can't depend on. Use an
19
+ // equivalent inline stub: a Compute that owns a NodejsFunction + API Gateway,
20
+ // so create() can build the default and the handler/gateway/apiUrl accessors
21
+ // and synth-shape assertions have something real to resolve to. It is passed to
22
+ // each create() via the internal `defaultComputeFactory` option (see
23
+ // makeBackend), exactly as @aws-blocks/blocks injects LambdaCompute.
24
+ class StubLambdaCompute extends Compute {
25
+ fn;
26
+ apiGateway;
27
+ apiUrl;
28
+ logGroup;
29
+ constructor(scope, id) {
30
+ super(id, { parent: scope });
31
+ this.logGroup = new cdk.aws_logs.LogGroup(this, 'HandlerLogGroup', {
32
+ removalPolicy: cdk.RemovalPolicy.DESTROY,
33
+ });
34
+ this.fn = new lambda.NodejsFunction(this, 'Handler', {
35
+ entry: this.backendHandlerPath,
36
+ runtime: cdk.aws_lambda.Runtime.NODEJS_22_X,
37
+ handler: 'handler',
38
+ role: this.executionRole,
39
+ logGroup: this.logGroup,
40
+ environment: { BLOCKS_STACK_NAME: this.backendStackName },
41
+ bundling: { minify: true, esbuildArgs: { '--conditions': 'aws-runtime' } },
42
+ });
43
+ this.apiGateway = new apigateway.RestApi(this, 'API', { restApiName: 'Blocks API' });
44
+ this.apiGateway.root.addProxy({
45
+ defaultIntegration: new apigateway.LambdaIntegration(this.fn),
46
+ anyMethod: true,
47
+ });
48
+ this.apiUrl = `${this.apiGateway.url}${BLOCKS_RPC_PREFIX.slice(1)}`;
49
+ }
50
+ setEnv(key, value) {
51
+ this.fn.addEnvironment(key, value);
52
+ }
53
+ }
54
+ const stubComputeFactory = (root) => new StubLambdaCompute(root, 'DefaultCompute');
13
55
  // Simulate the CDK condition being active (tests import CDK files directly)
14
56
  before(() => {
15
57
  process.env.NODE_OPTIONS = (process.env.NODE_OPTIONS ?? '') + ' --conditions=cdk';
@@ -21,20 +63,15 @@ const factoryBackendPath = join(__dirname, '__fixtures__', 'factory-backend.js')
21
63
  const fullIdConstructBackendPath = join(__dirname, '__fixtures__', 'fullid-construct-backend.js');
22
64
  const EXECUTION_ROLE_MARKER_ACTION = 'blocks-test:MarkerAction';
23
65
  const importMetaHandlerPath = join(__dirname, '__fixtures__', 'import-meta-handler.js');
66
+ // Wraps BlocksBackend.create, injecting the stub default-compute factory the way
67
+ // @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 });
24
69
  describe('ESM cache-busting (multi-stage)', () => {
25
70
  test('BlocksBackend.create() with same backendCDKPath but different IDs produces constructs in each', async () => {
26
71
  const app = new cdk.App();
27
72
  const stack = new cdk.Stack(app, 'TestStack');
28
- const backend1 = await BlocksBackend.create(stack, 'Stage1', {
29
- backendHandlerPath: handlerPath,
30
- backendCDKPath: sideEffectBackendPath,
31
- defaults: BlocksPresets.production,
32
- });
33
- const backend2 = await BlocksBackend.create(stack, 'Stage2', {
34
- backendHandlerPath: handlerPath,
35
- backendCDKPath: sideEffectBackendPath,
36
- defaults: BlocksPresets.production,
37
- });
73
+ const backend1 = await makeBackend(stack, 'Stage1', sideEffectBackendPath);
74
+ const backend2 = await makeBackend(stack, 'Stage2', sideEffectBackendPath);
38
75
  const findMarker = (scope) => scope.node.tryFindChild('SideEffectMarker');
39
76
  assert.ok(findMarker(backend1), 'Stage1 backend should have SideEffectMarker construct from module side effect');
40
77
  assert.ok(findMarker(backend2), 'Stage2 backend should have SideEffectMarker construct from re-executed module');
@@ -44,11 +81,7 @@ describe('synth shape (drop into existing stack)', () => {
44
81
  test('BlocksBackend lives inside the parent stack and synthesizes Lambda + API Gateway', async () => {
45
82
  const app = new cdk.App();
46
83
  const parent = new cdk.Stack(app, 'MyExistingStack');
47
- const backend = await BlocksBackend.create(parent, 'Blocks', {
48
- backendHandlerPath: handlerPath,
49
- backendCDKPath: sideEffectBackendPath,
50
- defaults: BlocksPresets.production,
51
- });
84
+ const backend = await makeBackend(parent, 'Blocks', sideEffectBackendPath);
52
85
  // Public surface mirrors BlocksStack.
53
86
  assert.ok(backend.handler, 'BlocksBackend should expose .handler');
54
87
  assert.ok(backend.gateway, 'BlocksBackend should expose .gateway');
@@ -62,16 +95,8 @@ describe('synth shape (drop into existing stack)', () => {
62
95
  test('multiple BlocksBackends in the same parent stack do not collide', async () => {
63
96
  const app = new cdk.App();
64
97
  const parent = new cdk.Stack(app, 'MultiBackendStack');
65
- await BlocksBackend.create(parent, 'BackendA', {
66
- backendHandlerPath: handlerPath,
67
- backendCDKPath: sideEffectBackendPath,
68
- defaults: BlocksPresets.production,
69
- });
70
- await BlocksBackend.create(parent, 'BackendB', {
71
- backendHandlerPath: handlerPath,
72
- backendCDKPath: sideEffectBackendPath,
73
- defaults: BlocksPresets.production,
74
- });
98
+ await makeBackend(parent, 'BackendA', sideEffectBackendPath);
99
+ await makeBackend(parent, 'BackendB', sideEffectBackendPath);
75
100
  const template = Template.fromStack(parent);
76
101
  template.resourceCountIs('AWS::ApiGateway::RestApi', 2);
77
102
  });
@@ -80,21 +105,13 @@ describe('shared execution role', () => {
80
105
  test('exposes executionRole on the backend', async () => {
81
106
  const app = new cdk.App();
82
107
  const parent = new cdk.Stack(app, 'RoleSurfaceStack');
83
- const backend = await BlocksBackend.create(parent, 'Blocks', {
84
- backendHandlerPath: handlerPath,
85
- backendCDKPath: sideEffectBackendPath,
86
- defaults: BlocksPresets.production,
87
- });
108
+ const backend = await makeBackend(parent, 'Blocks', sideEffectBackendPath);
88
109
  assert.ok(backend.executionRole, 'BlocksBackend should expose .executionRole');
89
110
  });
90
111
  test('synth produces a Lambda-assumable role with basic execution, and the handler uses it', async () => {
91
112
  const app = new cdk.App();
92
113
  const parent = new cdk.Stack(app, 'RoleSynthStack');
93
- await BlocksBackend.create(parent, 'Blocks', {
94
- backendHandlerPath: handlerPath,
95
- backendCDKPath: sideEffectBackendPath,
96
- defaults: BlocksPresets.production,
97
- });
114
+ await makeBackend(parent, 'Blocks', sideEffectBackendPath);
98
115
  const template = Template.fromStack(parent);
99
116
  // The shared role (logical id derived from the 'BlocksRole' construct id)
100
117
  // is assumable by Lambda and carries AWSLambdaBasicExecutionRole (so
@@ -102,7 +119,7 @@ describe('shared execution role', () => {
102
119
  // roles exist (API Gateway CloudWatch role, config BucketDeployment role),
103
120
  // so we target ours by logical id.
104
121
  const roles = template.findResources('AWS::IAM::Role');
105
- const blocksRoleId = Object.keys(roles).find(k => k.includes('BlocksRole'));
122
+ const blocksRoleId = Object.keys(roles).find((k) => k.includes('BlocksRole'));
106
123
  assert.ok(blocksRoleId, 'expected a role from the BlocksRole construct');
107
124
  const blocksRole = roles[blocksRoleId];
108
125
  assert.deepStrictEqual(blocksRole.Properties.AssumeRolePolicyDocument.Statement[0], {
@@ -111,19 +128,27 @@ describe('shared execution role', () => {
111
128
  Principal: { Service: 'lambda.amazonaws.com' },
112
129
  });
113
130
  assert.ok(JSON.stringify(blocksRole.Properties.ManagedPolicyArns ?? []).includes('AWSLambdaBasicExecutionRole'), 'BlocksRole should attach AWSLambdaBasicExecutionRole');
131
+ // Core is BB-agnostic: with no Building Block that runs AS the shared role, the trust policy
132
+ // must NOT allow any compute principal beyond Lambda (e.g. bedrock-agentcore is added by the
133
+ // Agent BB's own construct, not here — see packages/bb-agent).
134
+ assert.ok(!JSON.stringify(blocksRole.Properties.AssumeRolePolicyDocument).includes('bedrock-agentcore'), 'core must not trust bedrock-agentcore by default (no agent → no AgentCore trust)');
114
135
  // The Blocks handler references the shared role, not an auto-generated one.
115
136
  template.hasResourceProperties('AWS::Lambda::Function', {
116
137
  Role: { 'Fn::GetAtt': [blocksRoleId, 'Arn'] },
117
138
  });
118
139
  });
140
+ test('exposes backendModulePath (props.backendCDKPath) for co-bundling BBs', async () => {
141
+ const app = new cdk.App();
142
+ const parent = new cdk.Stack(app, 'BackendPathStack');
143
+ const backend = await makeBackend(parent, 'Blocks', sideEffectBackendPath);
144
+ // Building Blocks that co-bundle the app backend at synth time (e.g. the Agent BB's AgentCore
145
+ // Runtime) discover it via globalThis.CURRENT_BLOCKS_STACK.backendModulePath.
146
+ assert.strictEqual(backend.backendModulePath, sideEffectBackendPath);
147
+ });
119
148
  test('a nested block resolves executionRole via the construct-tree walk', async () => {
120
149
  const app = new cdk.App();
121
150
  const parent = new cdk.Stack(app, 'RoleResolveStack');
122
- const backend = await BlocksBackend.create(parent, 'Blocks', {
123
- backendHandlerPath: handlerPath,
124
- backendCDKPath: sideEffectBackendPath,
125
- defaults: BlocksPresets.production,
126
- });
151
+ const backend = await makeBackend(parent, 'Blocks', sideEffectBackendPath);
127
152
  // Build nested Scopes under the backend (outer → inner), the same shape a
128
153
  // real Building Block tree has, and grant a uniquely-named marker action to
129
154
  // `this.executionRole` from the innermost scope. If the getter's tree-walk
@@ -160,6 +185,7 @@ describe('CJS bundle: import.meta.url in the handler is shimmed (no Lambda-load
160
185
  backendHandlerPath: importMetaHandlerPath,
161
186
  backendCDKPath: sideEffectBackendPath,
162
187
  defaults: BlocksPresets.production,
188
+ defaultComputeFactory: stubComputeFactory,
163
189
  }));
164
190
  });
165
191
  });
@@ -167,11 +193,7 @@ describe('factory function support', () => {
167
193
  test('BlocksBackend.create() calls default export function with the backend instance', async () => {
168
194
  const app = new cdk.App();
169
195
  const stack = new cdk.Stack(app, 'FactoryTestStack');
170
- const backend = await BlocksBackend.create(stack, 'FactoryStage', {
171
- backendHandlerPath: handlerPath,
172
- backendCDKPath: factoryBackendPath,
173
- defaults: BlocksPresets.production,
174
- });
196
+ const backend = await makeBackend(stack, 'FactoryStage', factoryBackendPath);
175
197
  const marker = backend.node.tryFindChild('FactoryMarker');
176
198
  assert.ok(marker, 'Factory function should have created FactoryMarker on the backend');
177
199
  });
@@ -180,11 +202,7 @@ describe('fullId is token-free (construct IDs / env-var keys)', () => {
180
202
  test('top-level stack: fullId is {stackName}-{id} and resolvable', async () => {
181
203
  const app = new cdk.App();
182
204
  const stack = new cdk.Stack(app, 'TopLevelStack');
183
- const backend = await BlocksBackend.create(stack, 'blocks', {
184
- backendHandlerPath: handlerPath,
185
- backendCDKPath: sideEffectBackendPath,
186
- defaults: BlocksPresets.production,
187
- });
205
+ const backend = await makeBackend(stack, 'blocks', sideEffectBackendPath);
188
206
  assert.strictEqual(backend.fullId, 'TopLevelStack-blocks');
189
207
  assert.ok(!cdk.Token.isUnresolved(backend.fullId), 'fullId must not contain a token');
190
208
  });
@@ -198,11 +216,7 @@ describe('fullId is token-free (construct IDs / env-var keys)', () => {
198
216
  const nested = new cdk.NestedStack(parent, 'blocks');
199
217
  // Sanity: the nested stack's own name really is a token.
200
218
  assert.ok(cdk.Token.isUnresolved(nested.stackName), 'precondition: NestedStack.stackName should be an unresolved token');
201
- const backend = await BlocksBackend.create(nested, 'blocks', {
202
- backendHandlerPath: handlerPath,
203
- backendCDKPath: sideEffectBackendPath,
204
- defaults: BlocksPresets.production,
205
- });
219
+ const backend = await makeBackend(nested, 'blocks', sideEffectBackendPath);
206
220
  assert.ok(!cdk.Token.isUnresolved(backend.fullId), `fullId must be token-free inside a nested stack, got: ${backend.fullId}`);
207
221
  // Falls back to the top-level (concrete) stack name, keeping uniqueness.
208
222
  assert.strictEqual(backend.fullId, 'ParentStack-blocks');
@@ -213,11 +227,7 @@ describe('fullId is token-free (construct IDs / env-var keys)', () => {
213
227
  const app = new cdk.App();
214
228
  const parent = new cdk.Stack(app, 'Gen2ParentStack');
215
229
  const nested = new cdk.NestedStack(parent, 'blocks');
216
- const backend = await BlocksBackend.create(nested, 'blocks', {
217
- backendHandlerPath: handlerPath,
218
- backendCDKPath: fullIdConstructBackendPath,
219
- defaults: BlocksPresets.production,
220
- });
230
+ const backend = await makeBackend(nested, 'blocks', fullIdConstructBackendPath);
221
231
  // The construct ID is `${scope.fullId}Marker` → `ParentStack-blocks-blocks-dbMarker`.
222
232
  const expectedId = `${backend.fullId}-dbMarker`;
223
233
  assert.ok(nested.node.tryFindChild(expectedId), `expected a child construct with id "${expectedId}"`);
@@ -234,11 +244,7 @@ describe('fullId is token-free (construct IDs / env-var keys)', () => {
234
244
  const app = new cdk.App();
235
245
  const parent = new cdk.Stack(app, 'InvariantParent');
236
246
  const nested = new cdk.NestedStack(parent, 'blocks');
237
- const backend = await BlocksBackend.create(nested, 'blocks', {
238
- backendHandlerPath: handlerPath,
239
- backendCDKPath: sideEffectBackendPath,
240
- defaults: BlocksPresets.production,
241
- });
247
+ const backend = await makeBackend(nested, 'blocks', sideEffectBackendPath);
242
248
  const template = Template.fromStack(nested);
243
249
  const fns = template.findResources('AWS::Lambda::Function');
244
250
  const envValues = Object.values(fns)
@@ -259,11 +265,13 @@ describe('infrastructure defaults (backend-anchored)', () => {
259
265
  backendHandlerPath: handlerPath,
260
266
  backendCDKPath: sideEffectBackendPath,
261
267
  defaults: BlocksPresets.production,
268
+ defaultComputeFactory: stubComputeFactory,
262
269
  });
263
270
  const b = await BlocksBackend.create(stack, 'B', {
264
271
  backendHandlerPath: handlerPath,
265
272
  backendCDKPath: sideEffectBackendPath,
266
273
  defaults: BlocksPresets.sandbox,
274
+ defaultComputeFactory: stubComputeFactory,
267
275
  });
268
276
  // Two backends in one stack must NOT clobber each other — defaults are
269
277
  // anchored on the backend, not the shared stack.
@@ -277,6 +285,7 @@ describe('infrastructure defaults (backend-anchored)', () => {
277
285
  backendHandlerPath: handlerPath,
278
286
  backendCDKPath: sideEffectBackendPath,
279
287
  defaults: BlocksPresets.sandbox,
288
+ defaultComputeFactory: stubComputeFactory,
280
289
  });
281
290
  // A Scope under the backend resolves scope.defaults by walking up to it.
282
291
  const outer = new Scope('outer');
@@ -1,4 +1,17 @@
1
1
  import { RemovalPolicy } from 'aws-cdk-lib';
2
+ import { RetentionDays } from 'aws-cdk-lib/aws-logs';
3
+ /**
4
+ * Request-rate limits applied to an API Gateway stage. On a REST API these are
5
+ * requests/second; on a WebSocket API the unit is messages/second across the
6
+ * connection. `rateLimit` is the steady-state ceiling and `burstLimit` the
7
+ * token-bucket size for short spikes.
8
+ */
9
+ export interface BlocksThrottling {
10
+ /** Steady-state request (or WebSocket message) rate ceiling, per second. */
11
+ rateLimit: number;
12
+ /** Token-bucket burst size for short spikes above `rateLimit`. */
13
+ burstLimit: number;
14
+ }
2
15
  /**
3
16
  * Default values for every Amazon-authored Building Block created within a
4
17
  * `BlocksStack` or `BlocksBackend`. A per-block option always overrides the
@@ -20,6 +33,62 @@ export interface BlocksDefaults {
20
33
  * well as the CLI, API, and AWS console — until it is turned off.
21
34
  */
22
35
  deletionProtection: boolean;
36
+ /**
37
+ * CORS origins the compute's API accepts, as regular-expression patterns
38
+ * matched against the request `Origin` header. Empty means no cross-origin
39
+ * requests are allowed. The `sandbox` preset allows localhost so a local dev
40
+ * frontend can reach a deployed API; `production` allows none by default.
41
+ */
42
+ allowedOrigins: string[];
43
+ /**
44
+ * Whether stateful resources that support continuous backups keep them on by
45
+ * default — e.g. DynamoDB Point-in-Time Recovery, letting you restore to any
46
+ * second in the retention window. On in `production`, off in `sandbox` (where
47
+ * throwaway data isn't worth the backup-storage cost). Blocks whose service
48
+ * has no equivalent simply ignore it.
49
+ *
50
+ * `true` enables backups with the service's default window; `false` disables
51
+ * them; `{ retentionDays: n }` enables them and pins the window (a block
52
+ * clamps/validates `n` to its service's supported range — DynamoDB PITR is
53
+ * 1–35 days). Backups only have a window when on, so the two are one field.
54
+ */
55
+ pointInTimeRecovery: boolean | {
56
+ retentionDays: number;
57
+ };
58
+ /**
59
+ * How long CloudWatch Logs keeps log events written by Blocks-managed log
60
+ * groups (the core handler Lambda, migration/GSI Lambdas, hosting compute,
61
+ * `bb-logger`, and API Gateway access logs) before expiring them. Without a
62
+ * retention set, AWS keeps log events forever, which grows cost unbounded —
63
+ * every Blocks-managed log group reads this default so retention is applied
64
+ * consistently.
65
+ */
66
+ logRetention: RetentionDays;
67
+ /**
68
+ * Request-rate limits applied to every Blocks-managed API Gateway stage: the
69
+ * core REST API, the SSR/hosting REST API, and the `bb-realtime` WebSocket
70
+ * stage. Protects the backend from runaway clients and caps blast radius.
71
+ * The sandbox preset caps tighter (200/400) than production (1000/2000) so a
72
+ * disposable stack is well-protected without throttling real production
73
+ * traffic. See {@link BlocksThrottling}.
74
+ */
75
+ throttling: BlocksThrottling;
76
+ /**
77
+ * Whether to emit structured JSON access logs from every Blocks-managed API
78
+ * Gateway stage to a dedicated CloudWatch log group (retention follows
79
+ * {@link logRetention}).
80
+ *
81
+ * **Off by default in both presets** — opt in with a per-stack override
82
+ * (`defaults: { ...BlocksPresets.production, accessLogging: true }`). It is
83
+ * off by default (rather than on for production) because enabling it
84
+ * provisions the account-level API Gateway CloudWatch Logs role, which is an
85
+ * account/region-level singleton: a second Blocks stack in the same
86
+ * account+region that also enables access logging can repoint that role on
87
+ * deploy or, on teardown, leave the survivor's access logging broken.
88
+ * Enabling it is therefore safe for **one Blocks stack per region** — see
89
+ * `ensureApiGatewayAccount` for the full multi-stack teardown caveat.
90
+ */
91
+ accessLogging: boolean;
23
92
  }
24
93
  /**
25
94
  * Prepared starting points for {@link BlocksDefaults}. Pick one and override
@@ -30,15 +99,36 @@ export interface BlocksDefaults {
30
99
  * ```
31
100
  */
32
101
  export declare const BlocksPresets: {
33
- /** Disposable development stacks: tear down cleanly, no delete guard. */
102
+ /** Disposable development stacks: tear down cleanly, no delete guard, no backups. */
34
103
  sandbox: {
35
104
  removalPolicy: RemovalPolicy.DESTROY;
36
105
  deletionProtection: false;
106
+ allowedOrigins: string[];
107
+ pointInTimeRecovery: false;
108
+ logRetention: RetentionDays.ONE_WEEK;
109
+ throttling: {
110
+ rateLimit: number;
111
+ burstLimit: number;
112
+ };
113
+ accessLogging: false;
37
114
  };
38
- /** Durable, protected posture for permanent deployments. */
115
+ /**
116
+ * Durable, protected posture for permanent deployments. A higher throttle
117
+ * ceiling (1000/2000) than sandbox so the default doesn't 429 real
118
+ * production traffic — raise it via a per-stack `throttling` override for
119
+ * higher-volume APIs.
120
+ */
39
121
  production: {
40
122
  removalPolicy: RemovalPolicy.RETAIN;
41
123
  deletionProtection: true;
124
+ allowedOrigins: never[];
125
+ pointInTimeRecovery: true;
126
+ logRetention: RetentionDays.ONE_YEAR;
127
+ throttling: {
128
+ rateLimit: number;
129
+ burstLimit: number;
130
+ };
131
+ accessLogging: false;
42
132
  };
43
133
  };
44
134
  //# sourceMappingURL=blocks-defaults.d.ts.map
@@ -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;AAE5C;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC9B;;;;;OAKG;IACH,aAAa,EAAE,aAAa,CAAC;IAE7B;;;;OAIG;IACH,kBAAkB,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa;IACzB,yEAAyE;;;;;IAKzE,4DAA4D;;;;;CAKnB,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;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,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 { RemovalPolicy } from 'aws-cdk-lib';
4
+ import { RetentionDays } from 'aws-cdk-lib/aws-logs';
4
5
  /**
5
6
  * Prepared starting points for {@link BlocksDefaults}. Pick one and override
6
7
  * individual fields with a spread:
@@ -10,14 +11,33 @@ import { RemovalPolicy } from 'aws-cdk-lib';
10
11
  * ```
11
12
  */
12
13
  export const BlocksPresets = {
13
- /** Disposable development stacks: tear down cleanly, no delete guard. */
14
+ /** Disposable development stacks: tear down cleanly, no delete guard, no backups. */
14
15
  sandbox: {
15
16
  removalPolicy: RemovalPolicy.DESTROY,
16
17
  deletionProtection: false,
18
+ allowedOrigins: ['^https?://(localhost|127\\.0\\.0\\.1)(:\\d+)?$'],
19
+ pointInTimeRecovery: false,
20
+ logRetention: RetentionDays.ONE_WEEK,
21
+ throttling: { rateLimit: 200, burstLimit: 400 },
22
+ accessLogging: false,
17
23
  },
18
- /** Durable, protected posture for permanent deployments. */
24
+ /**
25
+ * Durable, protected posture for permanent deployments. A higher throttle
26
+ * ceiling (1000/2000) than sandbox so the default doesn't 429 real
27
+ * production traffic — raise it via a per-stack `throttling` override for
28
+ * higher-volume APIs.
29
+ */
19
30
  production: {
20
31
  removalPolicy: RemovalPolicy.RETAIN,
21
32
  deletionProtection: true,
33
+ allowedOrigins: [],
34
+ pointInTimeRecovery: true,
35
+ logRetention: RetentionDays.ONE_YEAR,
36
+ throttling: { rateLimit: 1000, burstLimit: 2000 },
37
+ // Off by default even in production: enabling access logging mutates the
38
+ // account/region-level API Gateway CloudWatch role (a singleton). Opt in
39
+ // per stack once you've confirmed a single Blocks stack owns it in the
40
+ // region — see the `accessLogging` field doc.
41
+ accessLogging: false,
22
42
  },
23
43
  };
@@ -9,14 +9,33 @@
9
9
  import { test, describe } from 'node:test';
10
10
  import assert from 'node:assert';
11
11
  import { RemovalPolicy } from 'aws-cdk-lib';
12
+ import { RetentionDays } from 'aws-cdk-lib/aws-logs';
12
13
  import { BlocksPresets } from './blocks-defaults.js';
13
14
  describe('BlocksPresets', () => {
14
- test('sandbox is disposable: DESTROY + deletion protection off', () => {
15
+ test('sandbox is disposable: DESTROY + deletion protection off + no PITR', () => {
15
16
  assert.strictEqual(BlocksPresets.sandbox.removalPolicy, RemovalPolicy.DESTROY);
16
17
  assert.strictEqual(BlocksPresets.sandbox.deletionProtection, false);
18
+ assert.strictEqual(BlocksPresets.sandbox.pointInTimeRecovery, false);
17
19
  });
18
- test('production is durable: RETAIN + deletion protection on', () => {
20
+ test('production is durable: RETAIN + deletion protection on + PITR on', () => {
19
21
  assert.strictEqual(BlocksPresets.production.removalPolicy, RemovalPolicy.RETAIN);
20
22
  assert.strictEqual(BlocksPresets.production.deletionProtection, true);
23
+ assert.strictEqual(BlocksPresets.production.pointInTimeRecovery, true);
24
+ });
25
+ test('sandbox keeps logs briefly', () => {
26
+ assert.strictEqual(BlocksPresets.sandbox.logRetention, RetentionDays.ONE_WEEK);
27
+ });
28
+ test('production keeps logs a year', () => {
29
+ assert.strictEqual(BlocksPresets.production.logRetention, RetentionDays.ONE_YEAR);
30
+ });
31
+ test('access logging is off by default in BOTH presets (opt-in; mutates an account singleton)', () => {
32
+ assert.strictEqual(BlocksPresets.sandbox.accessLogging, false);
33
+ assert.strictEqual(BlocksPresets.production.accessLogging, false);
34
+ });
35
+ test('sandbox caps tighter (200/400) than production (1000/2000)', () => {
36
+ assert.strictEqual(BlocksPresets.sandbox.throttling.rateLimit, 200);
37
+ assert.strictEqual(BlocksPresets.sandbox.throttling.burstLimit, 400);
38
+ assert.strictEqual(BlocksPresets.production.throttling.rateLimit, 1000);
39
+ assert.strictEqual(BlocksPresets.production.throttling.burstLimit, 2000);
21
40
  });
22
41
  });