@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
@@ -2,19 +2,18 @@
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
4
  import * as cdk from 'aws-cdk-lib';
5
- import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs';
6
- import * as apigateway from 'aws-cdk-lib/aws-apigateway';
5
+ import type * as apigateway from 'aws-cdk-lib/aws-apigateway';
7
6
  import * as iam from 'aws-cdk-lib/aws-iam';
8
7
  import { CfnGroup } from 'aws-cdk-lib/aws-resourcegroups';
9
8
  import { Construct } from 'constructs';
10
9
  import { pathToFileURL } from 'node:url';
11
- import { DEFAULT_NODE_RUNTIME } from './node-version.js';
12
- import { blocksNodejsBundling } from './bundling.js';
13
10
  import { addBlocksStackMetadata } from './stack-metadata.js';
14
11
  import { finalizeConfigRegistry, registerConfig } from './config-registry.js';
15
12
  import type { BlocksDefaults } from './blocks-defaults.js';
16
- import { BLOCKS_NAMESPACE, BLOCKS_RPC_PREFIX } from '../constants.js';
17
13
  import { registerBuiltinRoutes } from '../builtin-routes.js';
14
+ import type { Compute } from './compute/compute.js';
15
+ import { getComputes } from './compute/compute-registry.js';
16
+ import type { DefaultComputeFactory, LambdaShapedCompute } from './compute/default-compute-factory.js';
18
17
 
19
18
  /**
20
19
  * Validate that the Node.js process was started with `--conditions=cdk`.
@@ -44,6 +43,13 @@ export function assertCdkConditionActive(): void {
44
43
  }
45
44
  }
46
45
 
46
+ /**
47
+ * Timeout of the shared Blocks handler Lambda, and therefore the ceiling on a
48
+ * single invocation. Exported because resources that feed the handler have to
49
+ * size their own timeouts against it (e.g. an SQS queue's visibility timeout).
50
+ */
51
+ export const SHARED_HANDLER_TIMEOUT_SECONDS = 60 * 15;
52
+
47
53
  export interface BlocksBackendProps {
48
54
  backendHandlerPath: string;
49
55
  backendCDKPath: string;
@@ -57,7 +63,23 @@ export interface BlocksBackendProps {
57
63
  defaults: BlocksDefaults;
58
64
  }
59
65
 
60
- /** Shared infra setup — creates Lambda + API Gateway on the given scope. */
66
+ /**
67
+ * Core's `create()` props: the public {@link BlocksBackendProps} plus the
68
+ * required `defaultComputeFactory`, spread on by the umbrella (`@aws-blocks/blocks`).
69
+ * Customers use {@link BlocksBackendProps} and never set the factory.
70
+ *
71
+ * @internal
72
+ */
73
+ export interface CoreBlocksBackendProps extends BlocksBackendProps {
74
+ /** Builds the backend's default compute. Injected by `@aws-blocks/blocks`. */
75
+ defaultComputeFactory: DefaultComputeFactory;
76
+ }
77
+
78
+ /**
79
+ * Shared infra setup — provisions the stack-level resources that are NOT owned
80
+ * by a compute: the shared execution role, resource groups, and console-redirect
81
+ * routes.
82
+ */
61
83
  export function setupBlocksInfra(scope: Construct, props: BlocksBackendProps, id?: string) {
62
84
  // Fail fast with an actionable message at the create() call site if `defaults`
63
85
  // is missing (e.g. a plain-JS caller, `as any`, or a dynamically-built props
@@ -70,79 +92,31 @@ export function setupBlocksInfra(scope: Construct, props: BlocksBackendProps, id
70
92
  );
71
93
  }
72
94
 
73
- // ── Shared execution role ──────────────────────────────────────────────
95
+ // ── Shared execution role ───────────────────────────────────────────────
74
96
  // A single IAM role that every Building Block grants to. Provisioned here so
75
97
  // it exists before the backend module is imported (Building Blocks reach it
76
98
  // via `scope.executionRole`). Block grants sit on the role's default (inline)
77
- // policy. AWSLambdaBasicExecutionRole is attached so the handler retains
99
+ // policy. AWSLambdaBasicExecutionRole is attached so compute functions retain
78
100
  // CloudWatch Logs permissions.
101
+ //
102
+ // INVARIANT: this must be a mutable, framework-owned `iam.Role` — never an
103
+ // imported role (`Role.fromRoleArn`/`fromRoleName`), which is immutable by
104
+ // default. On an immutable role, every Building Block's `grant*()` /
105
+ // `addToPrincipalPolicy()` silently becomes a no-op (returns false, no error),
106
+ // so permissions would quietly vanish. If a bring-your-own-role option is ever
107
+ // added, it must resolve to a mutable role (`{ mutable: true }`).
79
108
  const executionRole = new iam.Role(scope, 'BlocksRole', {
80
- // CompositePrincipal (rather than a bare ServicePrincipal) so additional
81
- // compute types can assume this same shared role as they are introduced
82
- // (e.g. ECS tasks via ecs-tasks.amazonaws.com), by adding principals here.
109
+ // CompositePrincipal (rather than a bare ServicePrincipal) so a Building Block
110
+ // whose compute runs AS this shared role can add its own trust principal here
111
+ // (e.g. the Agent BB adds bedrock-agentcore in its CDK construct) core stays
112
+ // agnostic and only Lambda is trusted by default.
83
113
  assumedBy: new iam.CompositePrincipal(new iam.ServicePrincipal('lambda.amazonaws.com')),
84
114
  managedPolicies: [
85
115
  iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'),
86
116
  ],
87
117
  });
88
118
 
89
- const handler = new lambda.NodejsFunction(scope, 'Handler', {
90
- entry: props.backendHandlerPath,
91
- runtime: DEFAULT_NODE_RUNTIME,
92
- handler: 'handler',
93
- role: executionRole,
94
- memorySize: 2048,
95
- timeout: cdk.Duration.seconds(60 * 15),
96
- environment: {
97
- NODE_ENV: 'production',
98
- /**
99
- * BLOCKS_STACK_NAME is used at runtime to derive physical resource names
100
- * (DynamoDB table names, env var prefixes). It must match the CDK-time
101
- * fullId of the BlocksStack/BlocksBackend so resource lookups work correctly.
102
- *
103
- * For BlocksStack: this equals the stack name (id).
104
- * For BlocksBackend: the caller overrides this after construction to include
105
- * the parent stack name for deployment uniqueness.
106
- */
107
- BLOCKS_STACK_NAME: id ?? cdk.Stack.of(scope).stackName,
108
- },
109
- // blocksNodejsBundling shims import.meta.* to CommonJS equivalents so a
110
- // CJS-bundled `fileURLToPath(import.meta.url)` resolves instead of throwing at
111
- // Lambda load. See ./bundling.ts.
112
- bundling: blocksNodejsBundling({
113
- minify: true,
114
- esbuildArgs: { '--conditions': 'aws-runtime' },
115
- }),
116
- });
117
-
118
- // In sandbox mode, allow localhost origins so the local dev frontend can
119
- // reach the deployed Lambda API via CORS.
120
- const isSandbox =
121
- scope.node.tryGetContext('sandboxMode') === 'true' ||
122
- scope.node.tryGetContext('sandboxMode') === true;
123
- if (isSandbox) {
124
- handler.addEnvironment('CORS_ALLOWED_ORIGINS', '^https?://(localhost|127\\.0\\.0\\.1)(:\\d+)?$');
125
- }
126
-
127
- const api = new apigateway.RestApi(scope, 'API', {
128
- restApiName: 'Blocks API',
129
- deployOptions: { cachingEnabled: false },
130
- });
131
-
132
- const integration = new apigateway.LambdaIntegration(handler);
133
-
134
- // Build the nested resource tree for /aws-blocks/api.
135
- // Intermediate resource gets a proxy so sub-paths (RawRoutes) still reach Lambda.
136
- const awsBlocksResource = api.root.addResource(BLOCKS_NAMESPACE.slice(1));
137
- awsBlocksResource.addProxy({ defaultIntegration: integration, anyMethod: true });
138
-
139
- const apiResource = awsBlocksResource.addResource('api');
140
- apiResource.addMethod('POST', integration);
141
- apiResource.addMethod('OPTIONS', integration);
142
-
143
- api.root.addProxy({ defaultIntegration: integration, anyMethod: true });
144
-
145
- // ── Resource Groups ────────────────────────────────────────────────────
119
+ // ── Resource Groups ───────────────────────────────────────────────────
146
120
  let rootStack = cdk.Stack.of(scope);
147
121
  while (rootStack.nestedStackParent) rootStack = rootStack.nestedStackParent;
148
122
  const groupPrefix = (id && id !== rootStack.stackName) ? `${rootStack.stackName}-${id}` : rootStack.stackName;
@@ -178,7 +152,7 @@ export function setupBlocksInfra(scope: Construct, props: BlocksBackendProps, id
178
152
  },
179
153
  });
180
154
 
181
- // ── Console redirect routes ────────────────────────────────────────────
155
+ // ── Console redirect routes ───────────────────────────────────────────
182
156
  const region = cdk.Fn.ref('AWS::Region');
183
157
  const resourcesUrl = cdk.Fn.join('', [
184
158
  'https://', region, '.console.aws.amazon.com/resource-groups/group/',
@@ -194,7 +168,7 @@ export function setupBlocksInfra(scope: Construct, props: BlocksBackendProps, id
194
168
 
195
169
  registerBuiltinRoutes();
196
170
 
197
- return { handler, gateway: api, apiUrl: `${api.url}${BLOCKS_RPC_PREFIX.slice(1)}`, executionRole };
171
+ return { executionRole };
198
172
  }
199
173
 
200
174
  /**
@@ -215,14 +189,43 @@ export function setupBlocksInfra(scope: Construct, props: BlocksBackendProps, id
215
189
  * ```
216
190
  */
217
191
  export class BlocksBackend extends Construct {
218
- public readonly apiUrl: string;
219
- public readonly gateway: apigateway.RestApi;
220
- public readonly handler: cdk.aws_lambda_nodejs.NodejsFunction;
221
192
  public readonly backendHandlerPath: string;
193
+ /**
194
+ * Path to the app's backend module (`props.backendCDKPath`). Exposed so Building Blocks that
195
+ * co-bundle the backend at synth (e.g. the Agent BB's AgentCore Runtime) can discover it via
196
+ * `globalThis.CURRENT_BLOCKS_STACK.backendModulePath`.
197
+ */
198
+ public readonly backendModulePath: string;
222
199
  /** Shared IAM role assumed by all Blocks compute. Building Blocks grant to this role. */
223
200
  public readonly executionRole: iam.IRole;
224
201
  /** Infrastructure defaults for Building Blocks created under this backend. */
225
202
  public readonly defaults: BlocksDefaults;
203
+ /** The default compute (owns the Lambda function + API Gateway); set in `create()`. @internal */
204
+ _defaultCompute?: Compute;
205
+
206
+ /** The default compute's Lambda function. To be removed once consumers move to the multi-compute model. */
207
+ get handler(): cdk.aws_lambda_nodejs.NodejsFunction {
208
+ return this.requireDefaultCompute().fn;
209
+ }
210
+ /** The default compute's API Gateway REST API. To be removed once consumers move to the multi-compute model. */
211
+ get gateway(): apigateway.RestApi {
212
+ return this.requireDefaultCompute().apiGateway;
213
+ }
214
+ /** The default compute's RPC endpoint URL. To be removed once consumers move to the multi-compute model. */
215
+ get apiUrl(): string {
216
+ return this.requireDefaultCompute().apiUrl;
217
+ }
218
+ /** The default compute's handler CloudWatch log group. `bb-logger` reconfigures its retention. */
219
+ get handlerLogGroup(): cdk.aws_logs.ILogGroup {
220
+ return this.requireDefaultCompute().logGroup;
221
+ }
222
+
223
+ private requireDefaultCompute(): LambdaShapedCompute {
224
+ if (!this._defaultCompute) {
225
+ throw new Error('Blocks backend not fully initialized — access .handler/.gateway/.apiUrl after BlocksBackend.create() resolves.');
226
+ }
227
+ return this._defaultCompute as LambdaShapedCompute;
228
+ }
226
229
 
227
230
  /**
228
231
  * The fullId used by child Scopes to compute their env var names,
@@ -259,6 +262,7 @@ export class BlocksBackend extends Construct {
259
262
  super(scope, id);
260
263
 
261
264
  this.backendHandlerPath = props.backendHandlerPath;
265
+ this.backendModulePath = props.backendCDKPath;
262
266
 
263
267
  // Expose self to Building Blocks at CDK time
264
268
  (globalThis as any).CURRENT_BLOCKS_STACK = this;
@@ -269,20 +273,21 @@ export class BlocksBackend extends Construct {
269
273
  this.defaults = props.defaults;
270
274
 
271
275
  const infra = setupBlocksInfra(this, props, id);
272
- this.handler = infra.handler;
273
- this.gateway = infra.gateway;
274
- this.apiUrl = infra.apiUrl;
275
276
  this.executionRole = infra.executionRole;
276
-
277
- // Override BLOCKS_STACK_NAME to include the parent stack name so runtime
278
- // resource lookups (DynamoDB table names) match the CDK-time fullId
279
- // and are unique per deployment.
280
- this.handler.addEnvironment('BLOCKS_STACK_NAME', this.fullId);
277
+ // The default compute (and thus handler/gateway) is created in create(),
278
+ // after construction it derives BLOCKS_STACK_NAME from this.fullId.
281
279
  }
282
280
 
283
- static async create(scope: Construct, id: string, props: BlocksBackendProps) {
281
+ static async create(scope: Construct, id: string, props: CoreBlocksBackendProps) {
284
282
  assertCdkConditionActive();
285
283
  const backend = new BlocksBackend(scope, id, props);
284
+ // Create the default compute before importing the backend: it OWNS the
285
+ // Lambda function + API Gateway (which back .handler/.gateway/.apiUrl), and
286
+ // a block reading `this.compute` in its constructor (during that import)
287
+ // must resolve to it. The factory is supplied by the umbrella
288
+ // @aws-blocks/blocks (which injects LambdaCompute) via props, so core never
289
+ // imports the concrete compute class.
290
+ backend._defaultCompute = props.defaultComputeFactory(backend);
286
291
  // file:// URL (not a raw path) so the cache-busting query works on Windows,
287
292
  // where an absolute path like `D:\...` is rejected as URL scheme `d:`.
288
293
  const backendUrl = pathToFileURL(props.backendCDKPath);
@@ -298,7 +303,7 @@ export class BlocksBackend extends Construct {
298
303
  addBlocksStackMetadata(cdk.Stack.of(backend));
299
304
 
300
305
  // Finalize BB config → S3 (after all BBs have registered their config)
301
- finalizeConfigRegistry(backend, backend.handler);
306
+ finalizeConfigRegistry(backend, backend.executionRole, getComputes(backend));
302
307
 
303
308
  return backend;
304
309
  }
@@ -10,16 +10,39 @@
10
10
  import { test, describe } from 'node:test';
11
11
  import assert from 'node:assert';
12
12
  import { RemovalPolicy } from 'aws-cdk-lib';
13
+ import { RetentionDays } from 'aws-cdk-lib/aws-logs';
13
14
  import { BlocksPresets } from './blocks-defaults.js';
14
15
 
15
16
  describe('BlocksPresets', () => {
16
- test('sandbox is disposable: DESTROY + deletion protection off', () => {
17
+ test('sandbox is disposable: DESTROY + deletion protection off + no PITR', () => {
17
18
  assert.strictEqual(BlocksPresets.sandbox.removalPolicy, RemovalPolicy.DESTROY);
18
19
  assert.strictEqual(BlocksPresets.sandbox.deletionProtection, false);
20
+ assert.strictEqual(BlocksPresets.sandbox.pointInTimeRecovery, false);
19
21
  });
20
22
 
21
- test('production is durable: RETAIN + deletion protection on', () => {
23
+ test('production is durable: RETAIN + deletion protection on + PITR on', () => {
22
24
  assert.strictEqual(BlocksPresets.production.removalPolicy, RemovalPolicy.RETAIN);
23
25
  assert.strictEqual(BlocksPresets.production.deletionProtection, true);
26
+ assert.strictEqual(BlocksPresets.production.pointInTimeRecovery, true);
27
+ });
28
+
29
+ test('sandbox keeps logs briefly', () => {
30
+ assert.strictEqual(BlocksPresets.sandbox.logRetention, RetentionDays.ONE_WEEK);
31
+ });
32
+
33
+ test('production keeps logs a year', () => {
34
+ assert.strictEqual(BlocksPresets.production.logRetention, RetentionDays.ONE_YEAR);
35
+ });
36
+
37
+ test('access logging is off by default in BOTH presets (opt-in; mutates an account singleton)', () => {
38
+ assert.strictEqual(BlocksPresets.sandbox.accessLogging, false);
39
+ assert.strictEqual(BlocksPresets.production.accessLogging, false);
40
+ });
41
+
42
+ test('sandbox caps tighter (200/400) than production (1000/2000)', () => {
43
+ assert.strictEqual(BlocksPresets.sandbox.throttling.rateLimit, 200);
44
+ assert.strictEqual(BlocksPresets.sandbox.throttling.burstLimit, 400);
45
+ assert.strictEqual(BlocksPresets.production.throttling.rateLimit, 1000);
46
+ assert.strictEqual(BlocksPresets.production.throttling.burstLimit, 2000);
24
47
  });
25
48
  });
@@ -2,6 +2,20 @@
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
4
  import { RemovalPolicy } from 'aws-cdk-lib';
5
+ import { RetentionDays } from 'aws-cdk-lib/aws-logs';
6
+
7
+ /**
8
+ * Request-rate limits applied to an API Gateway stage. On a REST API these are
9
+ * requests/second; on a WebSocket API the unit is messages/second across the
10
+ * connection. `rateLimit` is the steady-state ceiling and `burstLimit` the
11
+ * token-bucket size for short spikes.
12
+ */
13
+ export interface BlocksThrottling {
14
+ /** Steady-state request (or WebSocket message) rate ceiling, per second. */
15
+ rateLimit: number;
16
+ /** Token-bucket burst size for short spikes above `rateLimit`. */
17
+ burstLimit: number;
18
+ }
5
19
 
6
20
  /**
7
21
  * Default values for every Amazon-authored Building Block created within a
@@ -25,6 +39,65 @@ export interface BlocksDefaults {
25
39
  * well as the CLI, API, and AWS console — until it is turned off.
26
40
  */
27
41
  deletionProtection: boolean;
42
+
43
+ /**
44
+ * CORS origins the compute's API accepts, as regular-expression patterns
45
+ * matched against the request `Origin` header. Empty means no cross-origin
46
+ * requests are allowed. The `sandbox` preset allows localhost so a local dev
47
+ * frontend can reach a deployed API; `production` allows none by default.
48
+ */
49
+ allowedOrigins: string[];
50
+
51
+ /**
52
+ * Whether stateful resources that support continuous backups keep them on by
53
+ * default — e.g. DynamoDB Point-in-Time Recovery, letting you restore to any
54
+ * second in the retention window. On in `production`, off in `sandbox` (where
55
+ * throwaway data isn't worth the backup-storage cost). Blocks whose service
56
+ * has no equivalent simply ignore it.
57
+ *
58
+ * `true` enables backups with the service's default window; `false` disables
59
+ * them; `{ retentionDays: n }` enables them and pins the window (a block
60
+ * clamps/validates `n` to its service's supported range — DynamoDB PITR is
61
+ * 1–35 days). Backups only have a window when on, so the two are one field.
62
+ */
63
+ pointInTimeRecovery: boolean | { retentionDays: number };
64
+
65
+ /**
66
+ * How long CloudWatch Logs keeps log events written by Blocks-managed log
67
+ * groups (the core handler Lambda, migration/GSI Lambdas, hosting compute,
68
+ * `bb-logger`, and API Gateway access logs) before expiring them. Without a
69
+ * retention set, AWS keeps log events forever, which grows cost unbounded —
70
+ * every Blocks-managed log group reads this default so retention is applied
71
+ * consistently.
72
+ */
73
+ logRetention: RetentionDays;
74
+
75
+ /**
76
+ * Request-rate limits applied to every Blocks-managed API Gateway stage: the
77
+ * core REST API, the SSR/hosting REST API, and the `bb-realtime` WebSocket
78
+ * stage. Protects the backend from runaway clients and caps blast radius.
79
+ * The sandbox preset caps tighter (200/400) than production (1000/2000) so a
80
+ * disposable stack is well-protected without throttling real production
81
+ * traffic. See {@link BlocksThrottling}.
82
+ */
83
+ throttling: BlocksThrottling;
84
+
85
+ /**
86
+ * Whether to emit structured JSON access logs from every Blocks-managed API
87
+ * Gateway stage to a dedicated CloudWatch log group (retention follows
88
+ * {@link logRetention}).
89
+ *
90
+ * **Off by default in both presets** — opt in with a per-stack override
91
+ * (`defaults: { ...BlocksPresets.production, accessLogging: true }`). It is
92
+ * off by default (rather than on for production) because enabling it
93
+ * provisions the account-level API Gateway CloudWatch Logs role, which is an
94
+ * account/region-level singleton: a second Blocks stack in the same
95
+ * account+region that also enables access logging can repoint that role on
96
+ * deploy or, on teardown, leave the survivor's access logging broken.
97
+ * Enabling it is therefore safe for **one Blocks stack per region** — see
98
+ * `ensureApiGatewayAccount` for the full multi-stack teardown caveat.
99
+ */
100
+ accessLogging: boolean;
28
101
  }
29
102
 
30
103
  /**
@@ -36,14 +109,33 @@ export interface BlocksDefaults {
36
109
  * ```
37
110
  */
38
111
  export const BlocksPresets = {
39
- /** Disposable development stacks: tear down cleanly, no delete guard. */
112
+ /** Disposable development stacks: tear down cleanly, no delete guard, no backups. */
40
113
  sandbox: {
41
114
  removalPolicy: RemovalPolicy.DESTROY,
42
115
  deletionProtection: false,
116
+ allowedOrigins: ['^https?://(localhost|127\\.0\\.0\\.1)(:\\d+)?$'],
117
+ pointInTimeRecovery: false,
118
+ logRetention: RetentionDays.ONE_WEEK,
119
+ throttling: { rateLimit: 200, burstLimit: 400 },
120
+ accessLogging: false,
43
121
  },
44
- /** Durable, protected posture for permanent deployments. */
122
+ /**
123
+ * Durable, protected posture for permanent deployments. A higher throttle
124
+ * ceiling (1000/2000) than sandbox so the default doesn't 429 real
125
+ * production traffic — raise it via a per-stack `throttling` override for
126
+ * higher-volume APIs.
127
+ */
45
128
  production: {
46
129
  removalPolicy: RemovalPolicy.RETAIN,
47
130
  deletionProtection: true,
131
+ allowedOrigins: [],
132
+ pointInTimeRecovery: true,
133
+ logRetention: RetentionDays.ONE_YEAR,
134
+ throttling: { rateLimit: 1000, burstLimit: 2000 },
135
+ // Off by default even in production: enabling access logging mutates the
136
+ // account/region-level API Gateway CloudWatch role (a singleton). Opt in
137
+ // per stack once you've confirmed a single Blocks stack owns it in the
138
+ // region — see the `accessLogging` field doc.
139
+ accessLogging: false,
48
140
  },
49
141
  } satisfies Record<string, BlocksDefaults>;