@aws-blocks/core 0.1.18 → 0.3.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.
- package/dist/cdk/__fixtures__/import-meta-handler.d.ts +6 -0
- package/dist/cdk/__fixtures__/import-meta-handler.d.ts.map +1 -0
- package/dist/cdk/__fixtures__/import-meta-handler.js +13 -0
- package/dist/cdk/blocks-backend.d.ts +46 -9
- package/dist/cdk/blocks-backend.d.ts.map +1 -1
- package/dist/cdk/blocks-backend.js +66 -67
- package/dist/cdk/blocks-backend.test.js +114 -56
- package/dist/cdk/blocks-defaults.d.ts +70 -0
- package/dist/cdk/blocks-defaults.d.ts.map +1 -0
- package/dist/cdk/blocks-defaults.js +27 -0
- package/dist/cdk/blocks-defaults.test.d.ts +2 -0
- package/dist/cdk/blocks-defaults.test.d.ts.map +1 -0
- package/dist/cdk/blocks-defaults.test.js +24 -0
- package/dist/cdk/blocks-stack.test.js +74 -31
- package/dist/cdk/bundling.d.ts +42 -0
- package/dist/cdk/bundling.d.ts.map +1 -0
- package/dist/cdk/bundling.js +72 -0
- package/dist/cdk/bundling.test.d.ts +2 -0
- package/dist/cdk/bundling.test.d.ts.map +1 -0
- package/dist/cdk/bundling.test.js +80 -0
- package/dist/cdk/compute/compute.d.ts +33 -0
- package/dist/cdk/compute/compute.d.ts.map +1 -0
- package/dist/cdk/compute/compute.js +28 -0
- package/dist/cdk/compute/default-compute-factory.d.ts +33 -0
- package/dist/cdk/compute/default-compute-factory.d.ts.map +1 -0
- package/dist/cdk/compute/default-compute-factory.js +3 -0
- package/dist/cdk/index.d.ts +92 -12
- package/dist/cdk/index.d.ts.map +1 -1
- package/dist/cdk/index.js +141 -25
- package/dist/cdk/internal.d.ts +25 -0
- package/dist/cdk/internal.d.ts.map +1 -0
- package/dist/cdk/internal.js +27 -0
- package/dist/cdk/mixins.d.ts +18 -4
- package/dist/cdk/mixins.d.ts.map +1 -1
- package/dist/cdk/mixins.js +31 -7
- package/dist/cdk/mixins.test.js +45 -0
- package/dist/common/index.d.ts +9 -0
- package/dist/common/index.d.ts.map +1 -1
- package/dist/hosting-secrets.d.ts +48 -0
- package/dist/hosting-secrets.d.ts.map +1 -0
- package/dist/hosting-secrets.js +57 -0
- package/dist/hosting-secrets.test.d.ts +2 -0
- package/dist/hosting-secrets.test.d.ts.map +1 -0
- package/dist/hosting-secrets.test.js +81 -0
- package/dist/hosting.d.ts +88 -6
- package/dist/hosting.d.ts.map +1 -1
- package/dist/hosting.js +83 -24
- package/dist/hosting.test.js +158 -10
- package/dist/index.cdk.d.ts +13 -11
- package/dist/index.cdk.d.ts.map +1 -1
- package/dist/index.cdk.js +13 -8
- package/dist/index.d.ts +8 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -5
- package/dist/lambda-handler.test.js +66 -0
- package/dist/pipeline/index.d.ts +24 -2
- package/dist/pipeline/index.d.ts.map +1 -1
- package/dist/pipeline/index.js +41 -1
- package/dist/pipeline.test.d.ts +2 -0
- package/dist/pipeline.test.d.ts.map +1 -0
- package/dist/pipeline.test.js +47 -0
- package/dist/rpc.d.ts +21 -0
- package/dist/rpc.d.ts.map +1 -1
- package/dist/rpc.js +46 -3
- package/dist/rpc.test.js +58 -1
- package/dist/scripts/config.d.ts +15 -0
- package/dist/scripts/config.d.ts.map +1 -0
- package/dist/scripts/config.js +29 -0
- package/dist/scripts/deploy.d.ts.map +1 -1
- package/dist/scripts/deploy.js +4 -0
- package/dist/scripts/dev-server.d.ts +8 -0
- package/dist/scripts/dev-server.d.ts.map +1 -1
- package/dist/scripts/dev-server.js +31 -0
- package/dist/scripts/generate-client.d.ts.map +1 -1
- package/dist/scripts/generate-client.js +9 -4
- package/dist/scripts/generate-client.test.js +19 -4
- package/dist/scripts/index.d.ts +13 -10
- package/dist/scripts/index.d.ts.map +1 -1
- package/dist/scripts/index.js +11 -8
- package/dist/scripts/preflight-credentials.d.ts +30 -0
- package/dist/scripts/preflight-credentials.d.ts.map +1 -0
- package/dist/scripts/preflight-credentials.js +108 -0
- package/dist/scripts/preflight-credentials.test.d.ts +2 -0
- package/dist/scripts/preflight-credentials.test.d.ts.map +1 -0
- package/dist/scripts/preflight-credentials.test.js +77 -0
- package/dist/scripts/sandbox-args.test.d.ts +2 -0
- package/dist/scripts/sandbox-args.test.d.ts.map +1 -0
- package/dist/scripts/sandbox-args.test.js +77 -0
- package/dist/scripts/sandbox.d.ts +52 -0
- package/dist/scripts/sandbox.d.ts.map +1 -1
- package/dist/scripts/sandbox.js +92 -15
- package/dist/scripts/secret.d.ts +15 -0
- package/dist/scripts/secret.d.ts.map +1 -0
- package/dist/scripts/secret.js +29 -0
- package/dist/scripts/secret.test.d.ts +2 -0
- package/dist/scripts/secret.test.d.ts.map +1 -0
- package/dist/scripts/secret.test.js +28 -0
- package/dist/scripts/telemetry.test.js +2 -0
- package/dist/scripts/typegen.d.ts +15 -0
- package/dist/scripts/typegen.d.ts.map +1 -0
- package/dist/scripts/typegen.js +16 -0
- package/dist/secret-naming.d.ts +58 -0
- package/dist/secret-naming.d.ts.map +1 -0
- package/dist/secret-naming.js +67 -0
- package/dist/secret-naming.test.d.ts +2 -0
- package/dist/secret-naming.test.d.ts.map +1 -0
- package/dist/secret-naming.test.js +40 -0
- package/dist/telemetry/environment.d.ts.map +1 -1
- package/dist/telemetry/environment.js +3 -1
- package/dist/telemetry/telemetry.test.js +10 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +7 -1
- package/src/cdk/__fixtures__/import-meta-handler.ts +16 -0
- package/src/cdk/blocks-backend.test.ts +308 -237
- package/src/cdk/blocks-backend.ts +98 -77
- package/src/cdk/blocks-defaults.test.ts +27 -0
- package/src/cdk/blocks-defaults.ts +75 -0
- package/src/cdk/blocks-stack.test.ts +160 -110
- package/src/cdk/bundling.test.ts +90 -0
- package/src/cdk/bundling.ts +76 -0
- package/src/cdk/compute/compute.ts +37 -0
- package/src/cdk/compute/default-compute-factory.ts +37 -0
- package/src/cdk/index.ts +178 -27
- package/src/cdk/internal.ts +30 -0
- package/src/cdk/mixins.test.ts +56 -1
- package/src/cdk/mixins.ts +32 -7
- package/src/common/index.ts +9 -0
- package/src/hosting-secrets.test.ts +106 -0
- package/src/hosting-secrets.ts +106 -0
- package/src/hosting.test.ts +244 -63
- package/src/hosting.ts +180 -68
- package/src/index.cdk.ts +79 -36
- package/src/index.ts +35 -16
- package/src/lambda-handler.test.ts +74 -0
- package/src/pipeline/index.ts +64 -11
- package/src/pipeline.test.ts +55 -0
- package/src/rpc.test.ts +66 -1
- package/src/rpc.ts +60 -3
- package/src/scripts/config.ts +35 -0
- package/src/scripts/deploy.ts +5 -0
- package/src/scripts/dev-server.ts +40 -0
- package/src/scripts/generate-client.test.ts +43 -8
- package/src/scripts/generate-client.ts +70 -65
- package/src/scripts/index.ts +20 -17
- package/src/scripts/preflight-credentials.test.ts +110 -0
- package/src/scripts/preflight-credentials.ts +131 -0
- package/src/scripts/sandbox-args.test.ts +92 -0
- package/src/scripts/sandbox.ts +102 -15
- package/src/scripts/secret.test.ts +35 -0
- package/src/scripts/secret.ts +35 -0
- package/src/scripts/telemetry.test.ts +2 -0
- package/src/scripts/typegen.ts +18 -0
- package/src/secret-naming.test.ts +59 -0
- package/src/secret-naming.ts +99 -0
- package/src/telemetry/environment.ts +3 -1
- package/src/telemetry/telemetry.test.ts +12 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { test, describe } from 'node:test';
|
|
5
|
+
import assert from 'node:assert';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { dirname, join } from 'node:path';
|
|
8
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
9
|
+
import { tmpdir } from 'node:os';
|
|
10
|
+
import { createRequire } from 'node:module';
|
|
11
|
+
import { build } from 'esbuild';
|
|
12
|
+
import { OutputFormat } from 'aws-cdk-lib/aws-lambda-nodejs';
|
|
13
|
+
import { blocksNodejsBundling } from './bundling.js';
|
|
14
|
+
|
|
15
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const importMetaFixture = join(__dirname, '__fixtures__', 'import-meta-handler.js');
|
|
17
|
+
const requireCjs = createRequire(import.meta.url);
|
|
18
|
+
|
|
19
|
+
describe('blocksNodejsBundling', () => {
|
|
20
|
+
test('injects the import.meta.* CJS shim for the default (CJS) output', () => {
|
|
21
|
+
const out = blocksNodejsBundling({ minify: true, esbuildArgs: { '--conditions': 'aws-runtime' } });
|
|
22
|
+
|
|
23
|
+
// Caller options are preserved.
|
|
24
|
+
assert.equal(out.minify, true);
|
|
25
|
+
assert.equal(out.esbuildArgs?.['--conditions'], 'aws-runtime');
|
|
26
|
+
|
|
27
|
+
// All three import.meta path properties are substituted.
|
|
28
|
+
assert.ok(out.esbuildArgs?.['--define:import.meta.url']);
|
|
29
|
+
assert.ok(out.esbuildArgs?.['--define:import.meta.dirname']);
|
|
30
|
+
assert.ok(out.esbuildArgs?.['--define:import.meta.filename']);
|
|
31
|
+
|
|
32
|
+
// The banner defines the substituted identifiers via CommonJS primitives.
|
|
33
|
+
assert.match(out.banner ?? '', /pathToFileURL\(__filename\)/);
|
|
34
|
+
assert.match(out.banner ?? '', /__dirname/);
|
|
35
|
+
assert.match(out.banner ?? '', /__filename/);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('leaves ESM output untouched (import.meta works natively there)', () => {
|
|
39
|
+
const input = { format: OutputFormat.ESM, esbuildArgs: { '--conditions': 'aws-runtime' } };
|
|
40
|
+
const out = blocksNodejsBundling(input);
|
|
41
|
+
|
|
42
|
+
assert.deepEqual(out, input);
|
|
43
|
+
assert.equal(out.esbuildArgs?.['--define:import.meta.url'], undefined);
|
|
44
|
+
assert.equal(out.banner, undefined);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('prepends the shim while keeping a caller-supplied banner', () => {
|
|
48
|
+
const out = blocksNodejsBundling({ banner: '// caller banner' });
|
|
49
|
+
assert.match(out.banner ?? '', /pathToFileURL\(__filename\)/);
|
|
50
|
+
assert.ok((out.banner ?? '').includes('// caller banner'));
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('a CJS bundle built with the shim resolves import.meta.url at load (no crash)', async () => {
|
|
54
|
+
// Bundle the fixture exactly as NodejsFunction would: apply the helper's `banner`
|
|
55
|
+
// and its `--define:import.meta.*` esbuildArgs. Without the shim this fixture's
|
|
56
|
+
// top-level `fileURLToPath(import.meta.url)` becomes `fileURLToPath(undefined)`
|
|
57
|
+
// and throws when the module is loaded.
|
|
58
|
+
const opts = blocksNodejsBundling({ minify: true });
|
|
59
|
+
const define: Record<string, string> = {};
|
|
60
|
+
for (const [key, value] of Object.entries(opts.esbuildArgs ?? {})) {
|
|
61
|
+
const m = key.match(/^--define:(.+)$/);
|
|
62
|
+
if (m) define[m[1]] = String(value);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const tmp = mkdtempSync(join(tmpdir(), 'bb-shim-'));
|
|
66
|
+
const outfile = join(tmp, 'out.cjs');
|
|
67
|
+
try {
|
|
68
|
+
await build({
|
|
69
|
+
entryPoints: [importMetaFixture],
|
|
70
|
+
bundle: true,
|
|
71
|
+
platform: 'node',
|
|
72
|
+
format: 'cjs',
|
|
73
|
+
minify: true,
|
|
74
|
+
banner: { js: opts.banner ?? '' },
|
|
75
|
+
define,
|
|
76
|
+
outfile,
|
|
77
|
+
logLevel: 'silent',
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Loading the bundle must not throw, and import.meta.url must resolve to a real
|
|
81
|
+
// (file-URL-derived) path rather than being undefined.
|
|
82
|
+
const mod = requireCjs(outfile);
|
|
83
|
+
assert.equal(typeof mod.moduleDir, 'string');
|
|
84
|
+
assert.ok(mod.moduleDir.length > 0, 'moduleDir should resolve to a non-empty path');
|
|
85
|
+
assert.equal(typeof mod.handler, 'function');
|
|
86
|
+
} finally {
|
|
87
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { OutputFormat, type BundlingOptions } from 'aws-cdk-lib/aws-lambda-nodejs';
|
|
5
|
+
|
|
6
|
+
/** Banner-defined identifiers the shim substitutes `import.meta.*` with (CJS only). */
|
|
7
|
+
const IMPORT_META_SHIM = {
|
|
8
|
+
url: '__blocksImportMetaUrl',
|
|
9
|
+
dirname: '__blocksImportMetaDirname',
|
|
10
|
+
filename: '__blocksImportMetaFilename',
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Wrap a `NodejsFunction` `bundling` config with the framework's hardened esbuild
|
|
15
|
+
* defaults, so every Lambda the framework bundles behaves consistently.
|
|
16
|
+
*
|
|
17
|
+
* **What it fixes.** `NodejsFunction` bundles to **CommonJS**, where `import.meta` is
|
|
18
|
+
* empty. Any bundled code that does `fileURLToPath(import.meta.url)` (a customer
|
|
19
|
+
* handler, a Building Block's `aws-runtime` code, or a dependency) would otherwise
|
|
20
|
+
* become `fileURLToPath(undefined)` and throw at Lambda load — esbuild only *warns*
|
|
21
|
+
* (`empty-import-meta`), so the broken bundle deploys and 502s on first invocation.
|
|
22
|
+
*
|
|
23
|
+
* **How.** For CJS output this shims `import.meta.url` / `import.meta.dirname` /
|
|
24
|
+
* `import.meta.filename` to their CommonJS equivalents (`pathToFileURL(__filename)`,
|
|
25
|
+
* `__dirname`, `__filename`) via an esbuild `--define` + `banner`. This is the same
|
|
26
|
+
* approach esbuild blesses (defining `import.meta` also suppresses the warning) and
|
|
27
|
+
* that Rollup applies by default, so:
|
|
28
|
+
* - a handler that reads `import.meta.url` no longer crashes at load, and
|
|
29
|
+
* - a bundled dependency that merely *contains* `import.meta` (even in dead code) no
|
|
30
|
+
* longer trips a build failure.
|
|
31
|
+
*
|
|
32
|
+
* The value resolves to the **bundled output file** (esbuild flattens the module tree),
|
|
33
|
+
* which is correct for the common cases — a value computed at synth (e.g. a
|
|
34
|
+
* `migrationsPath`) or dead interop fallbacks — but note it does not point at your
|
|
35
|
+
* source layout. Runtime code that must read a file relative to itself should not rely
|
|
36
|
+
* on `import.meta.url` inside a bundle; resolve such paths at synth time or ship the
|
|
37
|
+
* file as an asset. ESM output (`OutputFormat.ESM`) supports `import.meta` natively and
|
|
38
|
+
* is left untouched.
|
|
39
|
+
*
|
|
40
|
+
* All other options (`minify`, `commandHooks`, `externalModules`, other `esbuildArgs`
|
|
41
|
+
* such as `--conditions`, and any caller `banner`) are preserved.
|
|
42
|
+
*
|
|
43
|
+
* @param options - The site-specific `NodejsFunction` bundling options (optional).
|
|
44
|
+
* @returns The same options with the CJS `import.meta` shim merged in.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* new lambda.NodejsFunction(scope, 'Handler', {
|
|
48
|
+
* entry,
|
|
49
|
+
* bundling: blocksNodejsBundling({ minify: true, esbuildArgs: { '--conditions': 'aws-runtime' } }),
|
|
50
|
+
* });
|
|
51
|
+
*/
|
|
52
|
+
export function blocksNodejsBundling(options: BundlingOptions = {}): BundlingOptions {
|
|
53
|
+
// ESM output has real `import.meta` — nothing to shim, and `require` in the banner
|
|
54
|
+
// wouldn't resolve. Only the CommonJS bundle needs the shim.
|
|
55
|
+
if (options.format === OutputFormat.ESM) return options;
|
|
56
|
+
|
|
57
|
+
const shimBanner = [
|
|
58
|
+
`const ${IMPORT_META_SHIM.url}=require('url').pathToFileURL(__filename).href;`,
|
|
59
|
+
`const ${IMPORT_META_SHIM.dirname}=__dirname;`,
|
|
60
|
+
`const ${IMPORT_META_SHIM.filename}=__filename;`,
|
|
61
|
+
].join('');
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
...options,
|
|
65
|
+
// Prepend the shim definitions; keep any caller-supplied banner after them.
|
|
66
|
+
banner: options.banner ? `${shimBanner}\n${options.banner}` : shimBanner,
|
|
67
|
+
esbuildArgs: {
|
|
68
|
+
...options.esbuildArgs,
|
|
69
|
+
// Substitute import.meta.* with the banner identifiers. Also suppresses esbuild's
|
|
70
|
+
// empty-import-meta warning, so import.meta anywhere in the graph is safe.
|
|
71
|
+
'--define:import.meta.url': IMPORT_META_SHIM.url,
|
|
72
|
+
'--define:import.meta.dirname': IMPORT_META_SHIM.dirname,
|
|
73
|
+
'--define:import.meta.filename': IMPORT_META_SHIM.filename,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { Scope } from '../index.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Base class for a Blocks *compute* — a runtime that executes handler code
|
|
8
|
+
* (Lambda today; containers later). A compute owns the physical function/service
|
|
9
|
+
* plus its ingress, and receives config via {@link setEnv}.
|
|
10
|
+
*
|
|
11
|
+
* The backend entry and stack name a compute needs are inherited from
|
|
12
|
+
* {@link Scope} (`backendHandlerPath` / `backendStackName`), which resolve them
|
|
13
|
+
* from the owning BlocksStack/BlocksBackend — never caller-supplied, so every
|
|
14
|
+
* compute in an app runs the same backend and agrees on the resource-name
|
|
15
|
+
* namespace.
|
|
16
|
+
*
|
|
17
|
+
* The abstract base lives in core (a framework primitive); concrete computes
|
|
18
|
+
* live in their own packages (e.g. `LambdaCompute` in `@aws-blocks/bb-lambda-compute`).
|
|
19
|
+
*
|
|
20
|
+
* @internal Not exported from the package's public entry points. Customers
|
|
21
|
+
* cannot instantiate a compute until the customer-facing surface exists.
|
|
22
|
+
*/
|
|
23
|
+
export abstract class Compute extends Scope {
|
|
24
|
+
/**
|
|
25
|
+
* API namespaces assigned to run on this compute — recorded so request
|
|
26
|
+
* routing can map a namespace to the compute that hosts it. Currently
|
|
27
|
+
* unpopulated (no compute assignment surface yet).
|
|
28
|
+
*/
|
|
29
|
+
readonly namespaces: string[] = [];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Inject a runtime configuration value (an environment variable) into this
|
|
33
|
+
* compute. The framework calls this instead of `handler.addEnvironment()`
|
|
34
|
+
* directly so config targets the right compute.
|
|
35
|
+
*/
|
|
36
|
+
abstract setEnv(key: string, value: string): void;
|
|
37
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import type * as cdk from 'aws-cdk-lib';
|
|
5
|
+
import type { Compute } from './compute.js';
|
|
6
|
+
import type { BlocksStack } from '../index.js';
|
|
7
|
+
import type { BlocksBackend } from '../blocks-backend.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Lambda-shaped surface of the default compute that the legacy
|
|
11
|
+
* `handler` / `gateway` / `apiUrl` accessors on BlocksStack/BlocksBackend read.
|
|
12
|
+
* The default compute (`LambdaCompute` from `@aws-blocks/bb-lambda-compute`)
|
|
13
|
+
* satisfies this structurally, so core exposes those accessors without
|
|
14
|
+
* importing the concrete class. To be removed with those accessors once
|
|
15
|
+
* consumers move to the multi-compute model.
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
export interface LambdaShapedCompute extends Compute {
|
|
20
|
+
readonly fn: cdk.aws_lambda_nodejs.NodejsFunction;
|
|
21
|
+
readonly apiGateway: cdk.aws_apigateway.RestApi;
|
|
22
|
+
readonly apiUrl: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Builds the default {@link Compute} for a stack/backend. `create()` reads it
|
|
27
|
+
* from its props (`CoreBlocksStackProps.defaultComputeFactory`) and calls it to
|
|
28
|
+
* build the default without importing a concrete compute class — the factory is
|
|
29
|
+
* supplied by whoever owns both core and a concrete compute package (the
|
|
30
|
+
* umbrella `@aws-blocks/blocks`, which injects `LambdaCompute`). It lives on
|
|
31
|
+
* `CoreBlocksStackProps`/`CoreBlocksBackendProps`, which the umbrella derives
|
|
32
|
+
* from the customer-facing props, so it is absent from `BlocksStackProps` and
|
|
33
|
+
* customers cannot set it.
|
|
34
|
+
*
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export type DefaultComputeFactory = (root: BlocksStack | BlocksBackend) => Compute;
|
package/src/cdk/index.ts
CHANGED
|
@@ -15,40 +15,88 @@ import {
|
|
|
15
15
|
import { setupBlocksInfra, BlocksBackend, assertCdkConditionActive } from './blocks-backend.js';
|
|
16
16
|
import { addBlocksStackMetadata } from './stack-metadata.js';
|
|
17
17
|
import { finalizeConfigRegistry } from './config-registry.js';
|
|
18
|
+
import { type BlocksDefaults, BlocksPresets } from './blocks-defaults.js';
|
|
19
|
+
import type { Compute } from './compute/compute.js';
|
|
20
|
+
import type { DefaultComputeFactory, LambdaShapedCompute } from './compute/default-compute-factory.js';
|
|
18
21
|
|
|
19
|
-
export {
|
|
22
|
+
export {
|
|
23
|
+
BlocksBackend,
|
|
24
|
+
type BlocksBackendProps,
|
|
25
|
+
type CoreBlocksBackendProps,
|
|
26
|
+
SHARED_HANDLER_TIMEOUT_SECONDS,
|
|
27
|
+
} from './blocks-backend.js';
|
|
20
28
|
export { DEFAULT_NODE_RUNTIME } from './node-version.js';
|
|
29
|
+
export { blocksNodejsBundling } from './bundling.js';
|
|
21
30
|
export { SandboxDisableDeletionProtection } from './mixins.js';
|
|
22
31
|
export { registerConfig, finalizeConfigRegistry } from './config-registry.js';
|
|
32
|
+
export {
|
|
33
|
+
type BlocksDefaults,
|
|
34
|
+
BlocksPresets,
|
|
35
|
+
} from './blocks-defaults.js';
|
|
23
36
|
export { synthGuard } from './synth-guard.js';
|
|
24
37
|
export type { ScopeOptions } from '../index.js';
|
|
25
38
|
export { ApiError, isBlocksError, hasAuthError, DEFAULT_API_ERROR_NAME } from '../errors.js';
|
|
26
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Core's `create()` props: the public {@link BlocksStackProps} plus the required
|
|
42
|
+
* `defaultComputeFactory`. The umbrella (`@aws-blocks/blocks`) supplies the
|
|
43
|
+
* factory (which builds a `LambdaCompute`) by spreading it onto the customer's
|
|
44
|
+
* props; customers use {@link BlocksStackProps} and never set the factory.
|
|
45
|
+
*
|
|
46
|
+
* Kept separate (rather than a `create()` argument) so the factory travels with
|
|
47
|
+
* the props object and core stays free of any concrete compute class.
|
|
48
|
+
*
|
|
49
|
+
* @internal
|
|
50
|
+
*/
|
|
51
|
+
export interface CoreBlocksStackProps extends BlocksStackProps {
|
|
52
|
+
/** Builds the stack's default compute. Injected by `@aws-blocks/blocks`. */
|
|
53
|
+
defaultComputeFactory: DefaultComputeFactory;
|
|
54
|
+
}
|
|
55
|
+
|
|
27
56
|
export class BlocksStack extends cdk.Stack implements BaseBlocksStack {
|
|
28
57
|
public readonly id: string;
|
|
29
|
-
public readonly apiUrl: string;
|
|
30
|
-
public readonly gateway: cdk.aws_apigateway.RestApi;
|
|
31
|
-
public readonly handler: cdk.aws_lambda_nodejs.NodejsFunction;
|
|
32
58
|
public readonly backendHandlerPath: string;
|
|
33
59
|
/** Shared IAM role assumed by all Blocks compute. Building Blocks grant to this role. */
|
|
34
60
|
public readonly executionRole: cdk.aws_iam.IRole;
|
|
61
|
+
/** Infrastructure defaults for Building Blocks created under this stack. */
|
|
62
|
+
public readonly defaults: BlocksDefaults;
|
|
63
|
+
/** The default compute (owns the Lambda function + API Gateway); set in `create()`. @internal */
|
|
64
|
+
_defaultCompute?: Compute;
|
|
65
|
+
|
|
66
|
+
/** The default compute's Lambda function. To be removed once consumers move to the multi-compute model. */
|
|
67
|
+
get handler(): cdk.aws_lambda_nodejs.NodejsFunction {
|
|
68
|
+
return this.requireDefaultCompute().fn;
|
|
69
|
+
}
|
|
70
|
+
/** The default compute's API Gateway REST API. To be removed once consumers move to the multi-compute model. */
|
|
71
|
+
get gateway(): cdk.aws_apigateway.RestApi {
|
|
72
|
+
return this.requireDefaultCompute().apiGateway;
|
|
73
|
+
}
|
|
74
|
+
/** The default compute's RPC endpoint URL. To be removed once consumers move to the multi-compute model. */
|
|
75
|
+
get apiUrl(): string {
|
|
76
|
+
return this.requireDefaultCompute().apiUrl;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private requireDefaultCompute(): LambdaShapedCompute {
|
|
80
|
+
if (!this._defaultCompute) {
|
|
81
|
+
throw new Error('Blocks stack not fully initialized — access .handler/.gateway/.apiUrl after BlocksStack.create() resolves.');
|
|
82
|
+
}
|
|
83
|
+
return this._defaultCompute as LambdaShapedCompute;
|
|
84
|
+
}
|
|
35
85
|
|
|
36
86
|
private constructor(scope: Construct, id: string, props: BlocksStackProps) {
|
|
37
87
|
super(scope, id, props);
|
|
38
88
|
this.id = id;
|
|
39
89
|
this.backendHandlerPath = props.backendHandlerPath;
|
|
90
|
+
this.defaults = props.defaults;
|
|
40
91
|
|
|
41
92
|
// Set globalThis so Building Blocks attach directly to this stack
|
|
42
93
|
(globalThis as any).CURRENT_BLOCKS_STACK = this;
|
|
43
94
|
|
|
44
95
|
const infra = setupBlocksInfra(this, props, id);
|
|
45
|
-
this.handler = infra.handler;
|
|
46
|
-
this.gateway = infra.gateway;
|
|
47
|
-
this.apiUrl = infra.apiUrl;
|
|
48
96
|
this.executionRole = infra.executionRole;
|
|
49
97
|
}
|
|
50
98
|
|
|
51
|
-
static async create(scope: Construct, id: string, props:
|
|
99
|
+
static async create(scope: Construct, id: string, props: CoreBlocksStackProps) {
|
|
52
100
|
assertCdkConditionActive();
|
|
53
101
|
|
|
54
102
|
// Detect ambient pipeline stage scope set by Pipeline appFile imports
|
|
@@ -56,6 +104,13 @@ export class BlocksStack extends cdk.Stack implements BaseBlocksStack {
|
|
|
56
104
|
const actualScope = pipelineScope || scope;
|
|
57
105
|
|
|
58
106
|
const stack = new BlocksStack(actualScope, id, props);
|
|
107
|
+
// Create the default compute before importing the backend: it OWNS the
|
|
108
|
+
// Lambda function + API Gateway (which back .handler/.gateway/.apiUrl), and
|
|
109
|
+
// a block reading `this.compute` in its constructor (during that import)
|
|
110
|
+
// must resolve to it. The factory is supplied by the umbrella
|
|
111
|
+
// @aws-blocks/blocks (which injects LambdaCompute) via props, so core never
|
|
112
|
+
// imports the concrete compute class.
|
|
113
|
+
stack._defaultCompute = props.defaultComputeFactory(stack);
|
|
59
114
|
// file:// URL (not a raw path) so the cache-busting query works on Windows,
|
|
60
115
|
// where an absolute path like `D:\...` is rejected as URL scheme `d:`.
|
|
61
116
|
const backendUrl = pathToFileURL(props.backendCDKPath);
|
|
@@ -86,50 +141,146 @@ export class Scope extends Construct {
|
|
|
86
141
|
readonly bbName?: string;
|
|
87
142
|
readonly bbVersion?: string;
|
|
88
143
|
|
|
144
|
+
/**
|
|
145
|
+
* The owning stack/backend (the root of the Blocks construct tree), resolved
|
|
146
|
+
* once at construction: the nearest BlocksStack/BlocksBackend up the construct
|
|
147
|
+
* tree, or the ambient `globalThis.CURRENT_BLOCKS_STACK` fallback. All
|
|
148
|
+
* root-derived accessors below read from this instead of each repeating the
|
|
149
|
+
* tree walk.
|
|
150
|
+
*/
|
|
151
|
+
private readonly root: BlocksStack | BlocksBackend;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Compute assigned at this node. Applies to this block and is inherited by
|
|
155
|
+
* descendants (a nearer assignment wins). Covers both a handler assigned to a
|
|
156
|
+
* specific compute and a scope-level default for its subtree. Internal until
|
|
157
|
+
* the customer-facing surface exists.
|
|
158
|
+
* @internal
|
|
159
|
+
*/
|
|
160
|
+
_compute?: Compute;
|
|
161
|
+
|
|
89
162
|
constructor(id: string, options?: ScopeOptions) {
|
|
90
163
|
const parent = options?.parent || (globalThis as any).CURRENT_BLOCKS_STACK;
|
|
91
164
|
super(parent, id);
|
|
92
165
|
this.id = id;
|
|
93
166
|
this.parent = parent;
|
|
167
|
+
this.root = this.resolveRoot();
|
|
94
168
|
}
|
|
95
169
|
|
|
96
|
-
|
|
97
|
-
|
|
170
|
+
/**
|
|
171
|
+
* Walk up the construct tree to the nearest owning BlocksStack/BlocksBackend;
|
|
172
|
+
* fall back to the ambient `globalThis.CURRENT_BLOCKS_STACK`. Called once from
|
|
173
|
+
* the constructor; the result is cached in {@link root}.
|
|
174
|
+
*/
|
|
175
|
+
private resolveRoot(): BlocksStack | BlocksBackend {
|
|
98
176
|
let current: Construct = this;
|
|
99
177
|
while (current.node.scope) {
|
|
100
178
|
current = current.node.scope as Construct;
|
|
101
179
|
if (current instanceof BlocksStack || current instanceof BlocksBackend) {
|
|
102
|
-
return current
|
|
180
|
+
return current;
|
|
103
181
|
}
|
|
104
182
|
}
|
|
105
|
-
// Fallback to
|
|
106
|
-
|
|
183
|
+
// Fallback to the ambient stack. In production this is always a real
|
|
184
|
+
// BlocksStack/BlocksBackend; the cast also admits the test doubles that set
|
|
185
|
+
// globalThis.CURRENT_BLOCKS_STACK to a stub exposing the same surface.
|
|
186
|
+
return (globalThis as any).CURRENT_BLOCKS_STACK as BlocksStack | BlocksBackend;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
get handler() {
|
|
190
|
+
return this.root.handler;
|
|
107
191
|
}
|
|
108
192
|
|
|
109
193
|
/**
|
|
110
194
|
* The shared IAM role assumed by all Blocks compute. Building Blocks grant
|
|
111
|
-
* their permissions to this role
|
|
112
|
-
*
|
|
113
|
-
* to the role's default (inline) policy — exactly where they landed on the
|
|
114
|
-
* auto-generated role before.
|
|
115
|
-
*
|
|
116
|
-
* Resolves the same way as {@link handler}: walk up to the owning
|
|
117
|
-
* BlocksStack/BlocksBackend, falling back to the ambient stack.
|
|
195
|
+
* their permissions to this role; CDK's `grant*()` / `addToPrincipalPolicy()`
|
|
196
|
+
* route those grants to the role's default (inline) policy.
|
|
118
197
|
*/
|
|
119
198
|
get executionRole(): cdk.aws_iam.IRole {
|
|
199
|
+
return this.root.executionRole;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* The compute this block runs on: the nearest `_compute` assigned on this
|
|
204
|
+
* block or an ancestor scope, else the owning stack/backend's default compute.
|
|
205
|
+
*
|
|
206
|
+
* For any app that doesn't assign a compute, this always resolves to the
|
|
207
|
+
* default — so reads are a no-op refactor. `_compute` is internal
|
|
208
|
+
* (test/framework) until the customer-facing surface exists; there is no
|
|
209
|
+
* public option to set it yet.
|
|
210
|
+
*/
|
|
211
|
+
get compute(): Compute {
|
|
212
|
+
for (let current: ScopeParent | undefined = this; current; current = (current as Scope).parent) {
|
|
213
|
+
const assigned = (current as Scope)._compute;
|
|
214
|
+
if (assigned) return assigned;
|
|
215
|
+
}
|
|
216
|
+
const defaultCompute = this.root._defaultCompute;
|
|
217
|
+
if (!defaultCompute) {
|
|
218
|
+
throw new Error('Default compute not initialized — BlocksStack/BlocksBackend.create() must run before resolving `compute`.');
|
|
219
|
+
}
|
|
220
|
+
return defaultCompute;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* The backend entry file the owning BlocksStack/BlocksBackend runs — the
|
|
225
|
+
* single handler entry shared across the whole app.
|
|
226
|
+
*/
|
|
227
|
+
get backendHandlerPath(): string {
|
|
228
|
+
return this.root.backendHandlerPath;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* The owning stack/backend's token-free root identity. This is the value the
|
|
233
|
+
* runtime receives as `BLOCKS_STACK_NAME` and rebuilds `fullId` from, so
|
|
234
|
+
* physical resource names (DynamoDB tables, env-var keys, IAM ARNs) derived
|
|
235
|
+
* from `fullId` match byte-for-byte between synth and runtime — otherwise the
|
|
236
|
+
* runtime looks up names that were never created. `BlocksBackend` exposes this
|
|
237
|
+
* as `fullId` ({@link BlocksBackend.fullId}); `BlocksStack` as `id`.
|
|
238
|
+
*/
|
|
239
|
+
get backendStackName(): string {
|
|
240
|
+
const name = this.root instanceof BlocksBackend ? this.root.fullId : this.root.id;
|
|
241
|
+
if (!name) {
|
|
242
|
+
throw new Error('Owning Blocks stack/backend has no id to derive BLOCKS_STACK_NAME');
|
|
243
|
+
}
|
|
244
|
+
return name;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
get fullId(): string {
|
|
248
|
+
return computeScopeFullId(this);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* The stack-wide infrastructure {@link BlocksDefaults} registered by
|
|
253
|
+
* `BlocksStack.create` / `BlocksBackend.create`. Read these in a Building
|
|
254
|
+
* Block's CDK constructor to resolve a durability value, letting a per-block
|
|
255
|
+
* option override:
|
|
256
|
+
*
|
|
257
|
+
* ```ts
|
|
258
|
+
* const removalPolicy = options?.removalPolicy ?? this.defaults.removalPolicy;
|
|
259
|
+
* ```
|
|
260
|
+
*/
|
|
261
|
+
get defaults(): BlocksDefaults {
|
|
262
|
+
// Resolve the same way as handler/executionRole: walk up to the owning
|
|
263
|
+
// BlocksStack/BlocksBackend and read its defaults, so several backends in
|
|
264
|
+
// one stack each keep their own posture. Falls back to the ambient stack,
|
|
265
|
+
// then to the production preset when none was registered.
|
|
120
266
|
let current: Construct = this;
|
|
121
267
|
while (current.node.scope) {
|
|
122
268
|
current = current.node.scope as Construct;
|
|
123
269
|
if (current instanceof BlocksStack || current instanceof BlocksBackend) {
|
|
124
|
-
return current.
|
|
270
|
+
return current.defaults;
|
|
125
271
|
}
|
|
126
272
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
273
|
+
const ambient = ((globalThis as any).CURRENT_BLOCKS_STACK as { defaults?: BlocksDefaults } | undefined)?.defaults;
|
|
274
|
+
if (ambient) return ambient;
|
|
275
|
+
// No owning BlocksStack/BlocksBackend in the tree and none ambient — this is
|
|
276
|
+
// usually a deliberate test stub, but could be a real misconfiguration (a
|
|
277
|
+
// block built outside any Blocks backend). Fall back to the safe production
|
|
278
|
+
// posture, and log so it's debuggable if it fires unexpectedly.
|
|
279
|
+
console.warn(
|
|
280
|
+
`[Blocks] Scope "${this.id}" resolved infrastructure defaults with no owning ` +
|
|
281
|
+
'BlocksStack/BlocksBackend in scope; falling back to BlocksPresets.production.',
|
|
282
|
+
);
|
|
283
|
+
return BlocksPresets.production;
|
|
133
284
|
}
|
|
134
285
|
|
|
135
286
|
protected buildUserAgentChain(): [string, string][] {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Internal CDK entry point — framework- and test-only surface that is
|
|
6
|
+
* intentionally NOT part of the public API (`@aws-blocks/core` /
|
|
7
|
+
* `@aws-blocks/core/cdk`).
|
|
8
|
+
*
|
|
9
|
+
* The compute abstraction lives behind this path while it has no public,
|
|
10
|
+
* customer-facing surface. Importing from here is a signal that you are inside
|
|
11
|
+
* the framework or a test, not a customer.
|
|
12
|
+
*
|
|
13
|
+
* Planned removal: once a customer can assign a compute and have it actually
|
|
14
|
+
* take effect — i.e. `this.compute` resolution and request routing to the
|
|
15
|
+
* chosen compute both exist, plus a synth-time guard that rejects an assignment
|
|
16
|
+
* with no route — these exports move to the public CDK entry point
|
|
17
|
+
* (`@aws-blocks/core/cdk`, re-exported from `index.cdk.ts`) and this file is
|
|
18
|
+
* deleted. It must NOT be made public before then: a compute a customer can
|
|
19
|
+
* declare but that is silently ignored is a worse experience than not having
|
|
20
|
+
* the feature. Until that flip, treat everything here as unstable — no
|
|
21
|
+
* backward-compatibility guarantee.
|
|
22
|
+
*
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export { Compute } from './compute/compute.js';
|
|
27
|
+
export type { DefaultComputeFactory } from './compute/default-compute-factory.js';
|
|
28
|
+
// Reserved `/aws-blocks` path segment, needed by concrete computes (e.g.
|
|
29
|
+
// LambdaCompute in @aws-blocks/bb-lambda-compute) to build their API route tree.
|
|
30
|
+
export { BLOCKS_NAMESPACE } from '../constants.js';
|
package/src/cdk/mixins.test.ts
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
4
|
import { test, describe } from 'node:test';
|
|
5
|
-
import assert from 'node:assert';
|
|
6
5
|
import * as cdk from 'aws-cdk-lib';
|
|
7
6
|
import { Template, Match } from 'aws-cdk-lib/assertions';
|
|
8
7
|
import { Table, AttributeType, BillingMode } from 'aws-cdk-lib/aws-dynamodb';
|
|
@@ -110,6 +109,62 @@ describe('sandbox removal policy', () => {
|
|
|
110
109
|
});
|
|
111
110
|
});
|
|
112
111
|
|
|
112
|
+
test('SandboxDisableDeletionProtection mixin: deletionProtection disabled on DynamoDB table', () => {
|
|
113
|
+
const app = new cdk.App({ context: { sandboxMode: 'true' } });
|
|
114
|
+
const stack = new cdk.Stack(app, 'TestStack');
|
|
115
|
+
|
|
116
|
+
new Table(stack, 'MyTable', {
|
|
117
|
+
partitionKey: { name: 'pk', type: AttributeType.STRING },
|
|
118
|
+
billingMode: BillingMode.PAY_PER_REQUEST,
|
|
119
|
+
deletionProtection: true,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
RemovalPolicies.of(stack).destroy();
|
|
123
|
+
Mixins.of(stack).apply(new SandboxDisableDeletionProtection());
|
|
124
|
+
|
|
125
|
+
const template = Template.fromStack(stack);
|
|
126
|
+
// DynamoDB refuses DeleteTable while protection is on, regardless of DeletionPolicy
|
|
127
|
+
template.hasResourceProperties('AWS::DynamoDB::Table', {
|
|
128
|
+
DeletionProtectionEnabled: false,
|
|
129
|
+
});
|
|
130
|
+
template.hasResource('AWS::DynamoDB::Table', {
|
|
131
|
+
DeletionPolicy: 'Delete',
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('production mode: deletionProtection stays enabled on DynamoDB table when mixin is not applied', () => {
|
|
136
|
+
const app = new cdk.App();
|
|
137
|
+
const stack = new cdk.Stack(app, 'TestStack');
|
|
138
|
+
|
|
139
|
+
new Table(stack, 'MyTable', {
|
|
140
|
+
partitionKey: { name: 'pk', type: AttributeType.STRING },
|
|
141
|
+
billingMode: BillingMode.PAY_PER_REQUEST,
|
|
142
|
+
deletionProtection: true,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const template = Template.fromStack(stack);
|
|
146
|
+
template.hasResourceProperties('AWS::DynamoDB::Table', {
|
|
147
|
+
DeletionProtectionEnabled: true,
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('SandboxDisableDeletionProtection mixin: does not set DeletionProtectionEnabled on unprotected tables', () => {
|
|
152
|
+
const app = new cdk.App({ context: { sandboxMode: 'true' } });
|
|
153
|
+
const stack = new cdk.Stack(app, 'TestStack');
|
|
154
|
+
|
|
155
|
+
new Table(stack, 'MyTable', {
|
|
156
|
+
partitionKey: { name: 'pk', type: AttributeType.STRING },
|
|
157
|
+
billingMode: BillingMode.PAY_PER_REQUEST,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
Mixins.of(stack).apply(new SandboxDisableDeletionProtection());
|
|
161
|
+
|
|
162
|
+
const template = Template.fromStack(stack);
|
|
163
|
+
template.hasResourceProperties('AWS::DynamoDB::Table', {
|
|
164
|
+
DeletionProtectionEnabled: Match.absent(),
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
113
168
|
test('SandboxDisableDeletionProtection mixin: skips constructs without deletionProtection', () => {
|
|
114
169
|
const app = new cdk.App();
|
|
115
170
|
const stack = new cdk.Stack(app, 'TestStack');
|