@aws-blocks/core 0.3.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.
- package/README.md +13 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +32 -0
- package/dist/api.test.js +22 -0
- package/dist/bb-utils.d.ts +1 -0
- package/dist/bb-utils.d.ts.map +1 -1
- package/dist/bb-utils.js +3 -0
- package/dist/cdk/apigateway-account.d.ts +33 -0
- package/dist/cdk/apigateway-account.d.ts.map +1 -0
- package/dist/cdk/apigateway-account.js +60 -0
- package/dist/cdk/blocks-backend.d.ts +8 -0
- package/dist/cdk/blocks-backend.d.ts.map +1 -1
- package/dist/cdk/blocks-backend.js +17 -4
- package/dist/cdk/blocks-backend.test.js +17 -0
- package/dist/cdk/blocks-defaults.d.ts +65 -1
- package/dist/cdk/blocks-defaults.d.ts.map +1 -1
- package/dist/cdk/blocks-defaults.js +17 -1
- package/dist/cdk/blocks-defaults.test.js +17 -0
- package/dist/cdk/blocks-stack.test.js +21 -0
- package/dist/cdk/compute/compute-registry.d.ts +19 -0
- package/dist/cdk/compute/compute-registry.d.ts.map +1 -0
- package/dist/cdk/compute/compute-registry.js +38 -0
- package/dist/cdk/compute/compute.d.ts +2 -0
- package/dist/cdk/compute/compute.d.ts.map +1 -1
- package/dist/cdk/compute/compute.js +8 -0
- package/dist/cdk/compute/default-compute-factory.d.ts +1 -0
- package/dist/cdk/compute/default-compute-factory.d.ts.map +1 -1
- package/dist/cdk/config-registry.d.ts +34 -4
- package/dist/cdk/config-registry.d.ts.map +1 -1
- package/dist/cdk/config-registry.js +83 -25
- package/dist/cdk/config-registry.test.d.ts +2 -0
- package/dist/cdk/config-registry.test.d.ts.map +1 -0
- package/dist/cdk/config-registry.test.js +115 -0
- package/dist/cdk/index.d.ts +30 -2
- package/dist/cdk/index.d.ts.map +1 -1
- package/dist/cdk/index.js +42 -2
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/common/config.d.ts +34 -0
- package/dist/common/config.d.ts.map +1 -1
- package/dist/common/config.js +45 -3
- package/dist/common/config.test.js +19 -0
- package/dist/common/index.d.ts +8 -0
- package/dist/common/index.d.ts.map +1 -1
- package/dist/errors.d.ts +16 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +20 -0
- package/dist/hosting.d.ts +9 -0
- package/dist/hosting.d.ts.map +1 -1
- package/dist/hosting.js +10 -1
- package/dist/hosting.test.js +24 -0
- package/dist/index.cdk.d.ts +2 -2
- package/dist/index.cdk.d.ts.map +1 -1
- package/dist/index.cdk.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/lambda-handler.d.ts +17 -0
- package/dist/lambda-handler.d.ts.map +1 -1
- package/dist/lambda-handler.js +52 -3
- package/dist/lambda-handler.test.js +130 -1
- package/dist/scripts/sandbox-empty-buckets.test.d.ts +2 -0
- package/dist/scripts/sandbox-empty-buckets.test.d.ts.map +1 -0
- package/dist/scripts/sandbox-empty-buckets.test.js +171 -0
- package/dist/scripts/sandbox.d.ts +54 -0
- package/dist/scripts/sandbox.d.ts.map +1 -1
- package/dist/scripts/sandbox.js +163 -25
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -1
- package/src/api.test.ts +25 -0
- package/src/api.ts +39 -0
- package/src/bb-utils.ts +3 -0
- package/src/cdk/apigateway-account.ts +66 -0
- package/src/cdk/blocks-backend.test.ts +24 -0
- package/src/cdk/blocks-backend.ts +17 -4
- package/src/cdk/blocks-defaults.test.ts +21 -0
- package/src/cdk/blocks-defaults.ts +67 -1
- package/src/cdk/blocks-stack.test.ts +25 -0
- package/src/cdk/compute/compute-registry.ts +45 -0
- package/src/cdk/compute/compute.ts +10 -0
- package/src/cdk/compute/default-compute-factory.ts +1 -0
- package/src/cdk/config-registry.test.ts +135 -0
- package/src/cdk/config-registry.ts +92 -34
- package/src/cdk/index.ts +45 -2
- package/src/client/index.ts +1 -1
- package/src/common/config.test.ts +21 -0
- package/src/common/config.ts +47 -3
- package/src/common/index.ts +8 -0
- package/src/errors.ts +21 -0
- package/src/hosting.test.ts +28 -0
- package/src/hosting.ts +21 -1
- package/src/index.cdk.ts +4 -1
- package/src/index.ts +1 -1
- package/src/lambda-handler.test.ts +141 -1
- package/src/lambda-handler.ts +54 -2
- package/src/scripts/sandbox-empty-buckets.test.ts +191 -0
- package/src/scripts/sandbox.ts +185 -24
- package/src/version.ts +1 -1
package/dist/common/config.js
CHANGED
|
@@ -53,9 +53,13 @@ async function loadConfigFromS3() {
|
|
|
53
53
|
|| error?.Code === 'NoSuchKey'
|
|
54
54
|
|| error?.$metadata?.httpStatusCode === 404;
|
|
55
55
|
if (isNotFound) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
// Do NOT cache the empty result: a 404 right after deploy is usually the config file
|
|
57
|
+
// not being readable yet (the transient window before the BucketDeployment settles), not
|
|
58
|
+
// a genuinely config-less app. Caching {} here would poison the container for its whole
|
|
59
|
+
// lifetime. Returning without caching lets the next invocation re-fetch and pick up the
|
|
60
|
+
// real config once it's present. (Apps that truly have no config just re-check cheaply.)
|
|
61
|
+
console.warn(`[Blocks] Config file not found in S3 (${bucket}/${key}), proceeding with empty config (will retry on next request)`);
|
|
62
|
+
return {};
|
|
59
63
|
}
|
|
60
64
|
const msg = error instanceof Error ? error.message : String(error);
|
|
61
65
|
throw new Error(`[Blocks] Failed to load config from S3 (${bucket}/${key}): ${msg}`);
|
|
@@ -138,6 +142,44 @@ export async function loadConfigToProcessEnv() {
|
|
|
138
142
|
}
|
|
139
143
|
}
|
|
140
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Whether a config load has resolved and cached a result.
|
|
147
|
+
*
|
|
148
|
+
* Returns `true` once `loadConfigFromS3()` has cached a config object — either a
|
|
149
|
+
* successful S3 load, a genuinely-empty `{}` config, or the no-bucket local-dev
|
|
150
|
+
* path (both of which cache `{}`). Returns `false` in the initial state AND
|
|
151
|
+
* after a transient not-found miss: the 404 path deliberately does NOT cache its
|
|
152
|
+
* empty result (see `loadConfigFromS3`), so a `false` return immediately after
|
|
153
|
+
* awaiting a load uniquely identifies the post-deploy S3 window where
|
|
154
|
+
* blocks-config.json isn't readable yet.
|
|
155
|
+
*
|
|
156
|
+
* Callers use this to tell a transient-empty load (retry on the next request)
|
|
157
|
+
* apart from a legitimately-config-less app (do nothing) without reaching into
|
|
158
|
+
* the module's private cache.
|
|
159
|
+
*/
|
|
160
|
+
export function isConfigResolved() {
|
|
161
|
+
return configCache !== null;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Sanitize a Blocks identifier into a valid config/env-var key segment:
|
|
165
|
+
* uppercase, with every non-alphanumeric character replaced by `_`.
|
|
166
|
+
*
|
|
167
|
+
* Config entries are loaded into `process.env` at runtime (see
|
|
168
|
+
* {@link loadConfigToProcessEnv}), so a key must be a valid env-var name. This
|
|
169
|
+
* is the single rule both sides of a key share: whoever *writes* a config key
|
|
170
|
+
* (`registerConfig(\`PREFIX_${sanitizeConfigKey(id)}\`, …)`) and whoever *reads*
|
|
171
|
+
* it (`getConfigSync(\`PREFIX_${sanitizeConfigKey(id)}\`)`) MUST go through this
|
|
172
|
+
* function so they reconstruct a byte-identical key — a hand-inlined variant
|
|
173
|
+
* that drifts silently misses the lookup at runtime.
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```typescript
|
|
177
|
+
* registerConfig(this, `BLOCKS_QUEUE_URL_${sanitizeConfigKey(this.fullId)}`, url);
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
export function sanitizeConfigKey(id) {
|
|
181
|
+
return id.toUpperCase().replace(/[^A-Z0-9]/g, '_');
|
|
182
|
+
}
|
|
141
183
|
/**
|
|
142
184
|
* Reset the config cache. **For testing only.**
|
|
143
185
|
*/
|
|
@@ -101,6 +101,25 @@ describe('config — S3 errors', () => {
|
|
|
101
101
|
const result = await getConfig('ANY_KEY');
|
|
102
102
|
assert.strictEqual(result, '', 'Should return empty string (empty config) on 404');
|
|
103
103
|
});
|
|
104
|
+
it('does not cache a not-found result — a later call re-fetches once the config exists', async () => {
|
|
105
|
+
process.env.BLOCKS_CONFIG_BUCKET = 'test-bucket';
|
|
106
|
+
process.env.BLOCKS_CONFIG_KEY = 'blocks-config.json';
|
|
107
|
+
// Simulate the transient post-deploy window: the object isn't written yet on the first
|
|
108
|
+
// fetch, then becomes available. A not-found result must NOT be cached, or the container
|
|
109
|
+
// would be poisoned with empty config for its lifetime.
|
|
110
|
+
let call = 0;
|
|
111
|
+
const notFound = new Error('The specified key does not exist.');
|
|
112
|
+
notFound.name = 'NoSuchKey';
|
|
113
|
+
_setS3Fetcher(async () => {
|
|
114
|
+
call++;
|
|
115
|
+
if (call === 1)
|
|
116
|
+
throw notFound;
|
|
117
|
+
return JSON.stringify({ READY_KEY: 'ready-value' });
|
|
118
|
+
});
|
|
119
|
+
assert.strictEqual(await getConfig('READY_KEY'), '', 'first call sees the not-yet-written config as empty');
|
|
120
|
+
assert.strictEqual(await getConfig('READY_KEY'), 'ready-value', 'later call re-fetches once the config exists');
|
|
121
|
+
assert.strictEqual(call, 2, 'a not-found result must not be cached — S3 is retried');
|
|
122
|
+
});
|
|
104
123
|
it('throws with clear message on S3 network error', async () => {
|
|
105
124
|
process.env.BLOCKS_CONFIG_BUCKET = 'test-bucket';
|
|
106
125
|
process.env.BLOCKS_CONFIG_KEY = 'blocks-config.json';
|
package/dist/common/index.d.ts
CHANGED
|
@@ -224,6 +224,14 @@ export interface BlocksStackProps extends StackProps {
|
|
|
224
224
|
}
|
|
225
225
|
export declare class BlocksStack {
|
|
226
226
|
readonly id: string;
|
|
227
|
+
/**
|
|
228
|
+
* Path to the app's backend module (`props.backendCDKPath`). A typed contract for Building
|
|
229
|
+
* Blocks that co-bundle the backend at synth time and read it off
|
|
230
|
+
* `globalThis.CURRENT_BLOCKS_STACK` (the CDK `BlocksStack` / `BlocksBackend` set it). Declared
|
|
231
|
+
* here so those consumers can type against this shared `BlocksStack` contract — which the CDK
|
|
232
|
+
* `BlocksStack` / `BlocksBackend` implements — instead of `any`.
|
|
233
|
+
*/
|
|
234
|
+
readonly backendModulePath: string;
|
|
227
235
|
constructor(scope: Construct, id: string, props: BlocksStackProps);
|
|
228
236
|
}
|
|
229
237
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAGhE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAErE,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsGG;AAEH;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IACjC,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B;AAED,qBAAa,KAAK;IAChB,SAAgB,EAAE,EAAE,MAAM,CAAC;IAC3B,SAAgB,MAAM,EAAE,WAAW,CAAC;IAEpC,qEAAqE;IACrE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAE5B,kEAAkE;IAClE,OAAO,CAAC,MAAM,CAAC,WAAW,CAA8D;gBAE5E,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;IAiB9C;;;;;;;;OAQG;IACH,MAAM,CAAC,mBAAmB,IAAI;QAAE,MAAM,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,iBAAiB,EAAE,MAAM,CAAA;KAAE;IAejI,2CAA2C;IAC3C,MAAM,CAAC,cAAc,IAAI,IAAI;IAI7B,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED;;;;;;;;;;OAUG;IACH,0BAA0B,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAOlH;;;;OAIG;IACH,wBAAwB,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI;IAIxD;;;;;;;;;;OAUG;IACH,qBAAqB,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI;IAIrD;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,SAAS,CAAC,mBAAmB,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE;CAapD;AA0CD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,GAAG,CAAA;CAAE,UAUrE;AAED,MAAM,WAAW,gBAAiB,SAAQ,UAAU;IAClD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,QAAQ,EAAE,cAAc,CAAC;CAC1B;AAED,qBAAa,WAAW;IACvB,SAAgB,EAAE,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAGhE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAErE,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsGG;AAEH;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IACjC,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B;AAED,qBAAa,KAAK;IAChB,SAAgB,EAAE,EAAE,MAAM,CAAC;IAC3B,SAAgB,MAAM,EAAE,WAAW,CAAC;IAEpC,qEAAqE;IACrE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAE5B,kEAAkE;IAClE,OAAO,CAAC,MAAM,CAAC,WAAW,CAA8D;gBAE5E,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;IAiB9C;;;;;;;;OAQG;IACH,MAAM,CAAC,mBAAmB,IAAI;QAAE,MAAM,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,iBAAiB,EAAE,MAAM,CAAA;KAAE;IAejI,2CAA2C;IAC3C,MAAM,CAAC,cAAc,IAAI,IAAI;IAI7B,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED;;;;;;;;;;OAUG;IACH,0BAA0B,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAOlH;;;;OAIG;IACH,wBAAwB,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI;IAIxD;;;;;;;;;;OAUG;IACH,qBAAqB,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI;IAIrD;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,SAAS,CAAC,mBAAmB,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE;CAapD;AA0CD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,GAAG,CAAA;CAAE,UAUrE;AAED,MAAM,WAAW,gBAAiB,SAAQ,UAAU;IAClD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,QAAQ,EAAE,cAAc,CAAC;CAC1B;AAED,qBAAa,WAAW;IACvB,SAAgB,EAAE,EAAE,MAAM,CAAC;IAC3B;;;;;;OAMG;IACH,SAAiB,iBAAiB,EAAE,MAAM,CAAC;gBAC/B,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB;CAGjE"}
|
package/dist/errors.d.ts
CHANGED
|
@@ -70,6 +70,22 @@ export declare class ApiError extends Error {
|
|
|
70
70
|
export declare function isBlocksError<N extends string>(e: unknown, name: N): e is Error & {
|
|
71
71
|
name: N;
|
|
72
72
|
};
|
|
73
|
+
/**
|
|
74
|
+
* Build a named `Error` whose `name` is a BB error constant, so it is matchable
|
|
75
|
+
* with {@link isBlocksError} on both server and client. The name is also
|
|
76
|
+
* prefixed into the message for readable logs.
|
|
77
|
+
*
|
|
78
|
+
* This is the producer half of the {@link isBlocksError} contract: throw via
|
|
79
|
+
* this helper so the `name` a consumer matches on is set consistently. It has
|
|
80
|
+
* no runtime dependencies, so it is safe to use in every bundle — mock,
|
|
81
|
+
* aws-runtime, and CDK synth.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* throw blocksError(KVStoreErrors.ConditionalCheckFailed, 'Key already exists');
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export declare function blocksError(name: string, message: string): Error;
|
|
73
89
|
/**
|
|
74
90
|
* Type guard for branching on a failed `AuthState` (the recommended
|
|
75
91
|
* `setAuthState` client path) by its structured `errorName`.
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,aAAa,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,QAAS,SAAQ,KAAK;IAClC,wBAAwB;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;;;;;OAQG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;gBAEhB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE;CAM9G;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,GAAG;IAAE,IAAI,EAAE,CAAC,CAAA;CAAE,CAE7F;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,SAAS,MAAM,EAC9E,KAAK,EAAE,CAAC,GAAG,IAAI,GAAG,SAAS,EAC3B,IAAI,EAAE,CAAC,GACL,KAAK,IAAI,CAAC,GAAG;IAAE,SAAS,EAAE,CAAC,CAAA;CAAE,CAE/B"}
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,aAAa,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,QAAS,SAAQ,KAAK;IAClC,wBAAwB;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;;;;;OAQG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;gBAEhB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE;CAM9G;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,GAAG;IAAE,IAAI,EAAE,CAAC,CAAA;CAAE,CAE7F;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,KAAK,CAIhE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,SAAS,MAAM,EAC9E,KAAK,EAAE,CAAC,GAAG,IAAI,GAAG,SAAS,EAC3B,IAAI,EAAE,CAAC,GACL,KAAK,IAAI,CAAC,GAAG;IAAE,SAAS,EAAE,CAAC,CAAA;CAAE,CAE/B"}
|
package/dist/errors.js
CHANGED
|
@@ -73,6 +73,26 @@ export class ApiError extends Error {
|
|
|
73
73
|
export function isBlocksError(e, name) {
|
|
74
74
|
return e instanceof Error && e.name === name;
|
|
75
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Build a named `Error` whose `name` is a BB error constant, so it is matchable
|
|
78
|
+
* with {@link isBlocksError} on both server and client. The name is also
|
|
79
|
+
* prefixed into the message for readable logs.
|
|
80
|
+
*
|
|
81
|
+
* This is the producer half of the {@link isBlocksError} contract: throw via
|
|
82
|
+
* this helper so the `name` a consumer matches on is set consistently. It has
|
|
83
|
+
* no runtime dependencies, so it is safe to use in every bundle — mock,
|
|
84
|
+
* aws-runtime, and CDK synth.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* throw blocksError(KVStoreErrors.ConditionalCheckFailed, 'Key already exists');
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export function blocksError(name, message) {
|
|
92
|
+
const err = new Error(`${name}: ${message}`);
|
|
93
|
+
err.name = name;
|
|
94
|
+
return err;
|
|
95
|
+
}
|
|
76
96
|
/**
|
|
77
97
|
* Type guard for branching on a failed `AuthState` (the recommended
|
|
78
98
|
* `setAuthState` client path) by its structured `errorName`.
|
package/dist/hosting.d.ts
CHANGED
|
@@ -209,6 +209,15 @@ export interface HostingProps {
|
|
|
209
209
|
waf?: HostingWafConfig;
|
|
210
210
|
/** Retain the S3 bucket when the stack is deleted. Default: false. */
|
|
211
211
|
retainOnDelete?: boolean;
|
|
212
|
+
/**
|
|
213
|
+
* Days to retain SUPERSEDED build artifacts in S3 before they are expired.
|
|
214
|
+
* Default: 30. The build currently referenced by CloudFront KVS (`meta.b`)
|
|
215
|
+
* is NEVER expired regardless of this value — only builds that have been
|
|
216
|
+
* replaced by a newer deploy are cleaned up (issue #480). Raise this to keep
|
|
217
|
+
* a longer rollback window. Must be at least `skewProtection.maxAge`
|
|
218
|
+
* (converted to days), or synth throws `InvalidSkewProtectionMaxAgeError`.
|
|
219
|
+
*/
|
|
220
|
+
buildRetentionDays?: number;
|
|
212
221
|
/** Custom Content-Security-Policy header value. */
|
|
213
222
|
contentSecurityPolicy?: string;
|
|
214
223
|
/** CloudFront price class. Default: PRICE_CLASS_100. */
|
package/dist/hosting.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hosting.d.ts","sourceRoot":"","sources":["../src/hosting.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"hosting.d.ts","sourceRoot":"","sources":["../src/hosting.ts"],"names":[],"mappings":"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"}
|
package/dist/hosting.js
CHANGED
|
@@ -267,7 +267,16 @@ export class Hosting extends Construct {
|
|
|
267
267
|
environment: Object.keys(plainEnv).length ? plainEnv : undefined,
|
|
268
268
|
domain: resolvedDomain,
|
|
269
269
|
waf: props.waf,
|
|
270
|
-
storage: props.retainOnDelete != null
|
|
270
|
+
storage: props.retainOnDelete != null || props.buildRetentionDays != null
|
|
271
|
+
? {
|
|
272
|
+
...(props.retainOnDelete != null
|
|
273
|
+
? { retainOnDelete: props.retainOnDelete }
|
|
274
|
+
: {}),
|
|
275
|
+
...(props.buildRetentionDays != null
|
|
276
|
+
? { buildRetentionDays: props.buildRetentionDays }
|
|
277
|
+
: {}),
|
|
278
|
+
}
|
|
279
|
+
: undefined,
|
|
271
280
|
cdn: props.contentSecurityPolicy || props.priceClass || props.geoRestriction || props.quotas
|
|
272
281
|
? {
|
|
273
282
|
contentSecurityPolicy: props.contentSecurityPolicy,
|
package/dist/hosting.test.js
CHANGED
|
@@ -372,6 +372,30 @@ describe('Hosting', () => {
|
|
|
372
372
|
}
|
|
373
373
|
assert.ok(foundRetain, 'At least one bucket should have Retain deletion policy');
|
|
374
374
|
});
|
|
375
|
+
it('forwards buildRetentionDays to the hosting bucket lifecycle rule (#480)', () => {
|
|
376
|
+
createSpaBuildOutput(tmpDir);
|
|
377
|
+
const app = new App();
|
|
378
|
+
const stack = new Stack(app, 'RetentionStack');
|
|
379
|
+
new Hosting(stack, 'Hosting', {
|
|
380
|
+
root: tmpDir,
|
|
381
|
+
api: MOCK_API,
|
|
382
|
+
buildRetentionDays: 90,
|
|
383
|
+
});
|
|
384
|
+
const template = Template.fromStack(stack);
|
|
385
|
+
// Before the fix, core dropped buildRetentionDays (only retainOnDelete was
|
|
386
|
+
// forwarded), so this asserted 30. It must now reach the DeleteOldBuilds
|
|
387
|
+
// rule as 90.
|
|
388
|
+
template.hasResourceProperties('AWS::S3::Bucket', {
|
|
389
|
+
LifecycleConfiguration: Match.objectLike({
|
|
390
|
+
Rules: Match.arrayWith([
|
|
391
|
+
Match.objectLike({
|
|
392
|
+
Id: 'DeleteOldBuilds',
|
|
393
|
+
ExpirationInDays: 90,
|
|
394
|
+
}),
|
|
395
|
+
]),
|
|
396
|
+
}),
|
|
397
|
+
});
|
|
398
|
+
});
|
|
375
399
|
});
|
|
376
400
|
// ── API integration tests ────────────────────────────────────
|
|
377
401
|
describe('API integration', () => {
|
package/dist/index.cdk.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export { type ConfigValue, config, DEFAULT_CONFIG_PARAMETER_PREFIX, DEFAULT_SECRET_PARAMETER_PREFIX, isConfig, isManagedValue, isSecret, type ManagedValue, type SecretValue, secret, type ValueKind, } from '@aws-blocks/hosting';
|
|
2
2
|
export { type ApiHandler, ApiNamespace, type BlocksContext } from './api.js';
|
|
3
|
-
export { BlocksBackend, type BlocksBackendProps, type BlocksDefaults, BlocksPresets, BlocksStack, blocksNodejsBundling, type CoreBlocksBackendProps, type CoreBlocksStackProps, DEFAULT_NODE_RUNTIME, finalizeConfigRegistry, registerConfig, SandboxDisableDeletionProtection, Scope, SHARED_HANDLER_TIMEOUT_SECONDS, synthGuard, } from './cdk/index.js';
|
|
3
|
+
export { BlocksBackend, type BlocksBackendProps, type BlocksDefaults, BlocksPresets, BlocksStack, type BlocksThrottling, blocksNodejsBundling, type CoreBlocksBackendProps, type CoreBlocksStackProps, DEFAULT_NODE_RUNTIME, ensureApiGatewayAccount, finalizeConfigRegistry, getConfigLocation, registerConfig, SandboxDisableDeletionProtection, Scope, SHARED_HANDLER_TIMEOUT_SECONDS, synthGuard, } from './cdk/index.js';
|
|
4
4
|
export { _resetConfigCache, getConfig, getConfigSync, loadConfigToProcessEnv, preloadConfig } from './common/config.js';
|
|
5
5
|
export { BlocksStackProps } from './common/index.js';
|
|
6
6
|
export { _resetSdkRegistry, getAllSdkIdentifiers, getSdkIdentifiers, registerSdkIdentifiers, } from './common/sdk-registry.js';
|
|
7
7
|
export { BLOCKS_AUTH_PREFIX, BLOCKS_RPC_PREFIX } from './constants.js';
|
|
8
|
-
export { ApiError, DEFAULT_API_ERROR_NAME, hasAuthError, isBlocksError } from './errors.js';
|
|
8
|
+
export { ApiError, blocksError, DEFAULT_API_ERROR_NAME, hasAuthError, isBlocksError } from './errors.js';
|
|
9
9
|
export { type BlocksStackApi, type ComputeConfig, type FrameworkType, Hosting, type HostingDomainConfig, type HostingProps, type HostingWafConfig, } from './hosting.js';
|
|
10
10
|
export { EventSourceMapping } from './lambda-handler.js';
|
|
11
11
|
export { __PIPELINE_STAGE_SCOPE__, type BranchConfig, DeployStage, type DeployStageProps, Pipeline, type PipelineProps, type PipelineSourceConfig, type PipelineStageConfig, type PipelineSynthConfig, } from './pipeline/index.js';
|
package/dist/index.cdk.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cdk.d.ts","sourceRoot":"","sources":["../src/index.cdk.ts"],"names":[],"mappings":"AAMA,OAAO,EACN,KAAK,WAAW,EAChB,MAAM,EACN,+BAA+B,EAC/B,+BAA+B,EAC/B,QAAQ,EACR,cAAc,EACd,QAAQ,EACR,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,MAAM,EACN,KAAK,SAAS,GACd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,UAAU,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AAC7E,OAAO,EACN,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,aAAa,EACb,WAAW,EACX,oBAAoB,EACpB,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,oBAAoB,EACpB,sBAAsB,EACtB,cAAc,EACd,gCAAgC,EAChC,KAAK,EACL,8BAA8B,EAC9B,UAAU,GACV,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,aAAa,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxH,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EACN,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EACjB,sBAAsB,GACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,sBAAsB,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.cdk.d.ts","sourceRoot":"","sources":["../src/index.cdk.ts"],"names":[],"mappings":"AAMA,OAAO,EACN,KAAK,WAAW,EAChB,MAAM,EACN,+BAA+B,EAC/B,+BAA+B,EAC/B,QAAQ,EACR,cAAc,EACd,QAAQ,EACR,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,MAAM,EACN,KAAK,SAAS,GACd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,UAAU,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AAC7E,OAAO,EACN,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,aAAa,EACb,WAAW,EACX,KAAK,gBAAgB,EACrB,oBAAoB,EACpB,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,oBAAoB,EACpB,uBAAuB,EACvB,sBAAsB,EACtB,iBAAiB,EACjB,cAAc,EACd,gCAAgC,EAChC,KAAK,EACL,8BAA8B,EAC9B,UAAU,GACV,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,aAAa,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxH,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EACN,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EACjB,sBAAsB,GACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,sBAAsB,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACzG,OAAO,EACN,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,OAAO,EACP,KAAK,mBAAmB,EACxB,KAAK,YAAY,EACjB,KAAK,gBAAgB,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EACN,wBAAwB,EACxB,KAAK,YAAY,EACjB,WAAW,EACX,KAAK,gBAAgB,EACrB,QAAQ,EACR,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,GACxB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACN,KAAK,UAAU,EACf,QAAQ,EACR,cAAc,EACd,KAAK,eAAe,GACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACN,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,UAAU,EACV,KAAK,eAAe,EACpB,aAAa,EACb,mBAAmB,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACN,8BAA8B,EAC9B,8BAA8B,EAC9B,yBAAyB,EACzB,yBAAyB,GACzB,MAAM,oBAAoB,CAAC"}
|
package/dist/index.cdk.js
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
// getConfig (backend config loader, above) owns that name here.
|
|
6
6
|
export { config, DEFAULT_CONFIG_PARAMETER_PREFIX, DEFAULT_SECRET_PARAMETER_PREFIX, isConfig, isManagedValue, isSecret, secret, } from '@aws-blocks/hosting';
|
|
7
7
|
export { ApiNamespace } from './api.js';
|
|
8
|
-
export { BlocksBackend, BlocksPresets, BlocksStack, blocksNodejsBundling, DEFAULT_NODE_RUNTIME, finalizeConfigRegistry, registerConfig, SandboxDisableDeletionProtection, Scope, SHARED_HANDLER_TIMEOUT_SECONDS, synthGuard, } from './cdk/index.js';
|
|
8
|
+
export { BlocksBackend, BlocksPresets, BlocksStack, blocksNodejsBundling, DEFAULT_NODE_RUNTIME, ensureApiGatewayAccount, finalizeConfigRegistry, getConfigLocation, registerConfig, SandboxDisableDeletionProtection, Scope, SHARED_HANDLER_TIMEOUT_SECONDS, synthGuard, } from './cdk/index.js';
|
|
9
9
|
export { _resetConfigCache, getConfig, getConfigSync, loadConfigToProcessEnv, preloadConfig } from './common/config.js';
|
|
10
10
|
export { _resetSdkRegistry, getAllSdkIdentifiers, getSdkIdentifiers, registerSdkIdentifiers, } from './common/sdk-registry.js';
|
|
11
11
|
export { BLOCKS_AUTH_PREFIX, BLOCKS_RPC_PREFIX } from './constants.js';
|
|
12
|
-
export { ApiError, DEFAULT_API_ERROR_NAME, hasAuthError, isBlocksError } from './errors.js';
|
|
12
|
+
export { ApiError, blocksError, DEFAULT_API_ERROR_NAME, hasAuthError, isBlocksError } from './errors.js';
|
|
13
13
|
export { Hosting, } from './hosting.js';
|
|
14
14
|
export { EventSourceMapping } from './lambda-handler.js';
|
|
15
15
|
export { __PIPELINE_STAGE_SCOPE__, DeployStage, Pipeline, } from './pipeline/index.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { _resetConfigCache, getConfig, getConfigSync, loadConfigToProcessEnv, pr
|
|
|
4
4
|
export { type BuildingBlockMeta, Scope, type ScopeOptions, type ScopeParent } from './common/index.js';
|
|
5
5
|
export { _resetSdkRegistry, getAllSdkIdentifiers, getSdkIdentifiers, registerSdkIdentifiers, } from './common/sdk-registry.js';
|
|
6
6
|
export { BLOCKS_AUTH_PREFIX, BLOCKS_RPC_PREFIX } from './constants.js';
|
|
7
|
-
export { ApiError, DEFAULT_API_ERROR_NAME, hasAuthError, isBlocksError } from './errors.js';
|
|
7
|
+
export { ApiError, blocksError, DEFAULT_API_ERROR_NAME, hasAuthError, isBlocksError } from './errors.js';
|
|
8
8
|
export { clearRouteRegistry, getRegisteredRoutes, type HttpMethod, lockRouteRegistry, matchRoute, RawRouteErrors, type RawRouteOptions, type RegisteredRoute, registerRoute, unlockRouteRegistry, } from './raw-route.js';
|
|
9
9
|
export { RawRoute } from './raw-route.mock.js';
|
|
10
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EACN,KAAK,WAAW,EAChB,MAAM,EACN,QAAQ,EACR,cAAc,EACd,QAAQ,EACR,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,MAAM,EACN,KAAK,SAAS,GACd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,UAAU,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,aAAa,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxH,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,EAAE,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACvG,OAAO,EACN,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EACjB,sBAAsB,GACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,sBAAsB,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EACN,KAAK,WAAW,EAChB,MAAM,EACN,QAAQ,EACR,cAAc,EACd,QAAQ,EACR,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,MAAM,EACN,KAAK,SAAS,GACd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,UAAU,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,aAAa,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxH,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,EAAE,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACvG,OAAO,EACN,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EACjB,sBAAsB,GACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,sBAAsB,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACzG,OAAO,EACN,kBAAkB,EAClB,mBAAmB,EACnB,KAAK,UAAU,EACf,iBAAiB,EACjB,UAAU,EACV,cAAc,EACd,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,aAAa,EACb,mBAAmB,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,6 @@ export { _resetConfigCache, getConfig, getConfigSync, loadConfigToProcessEnv, pr
|
|
|
9
9
|
export { Scope } from './common/index.js';
|
|
10
10
|
export { _resetSdkRegistry, getAllSdkIdentifiers, getSdkIdentifiers, registerSdkIdentifiers, } from './common/sdk-registry.js';
|
|
11
11
|
export { BLOCKS_AUTH_PREFIX, BLOCKS_RPC_PREFIX } from './constants.js';
|
|
12
|
-
export { ApiError, DEFAULT_API_ERROR_NAME, hasAuthError, isBlocksError } from './errors.js';
|
|
12
|
+
export { ApiError, blocksError, DEFAULT_API_ERROR_NAME, hasAuthError, isBlocksError } from './errors.js';
|
|
13
13
|
export { clearRouteRegistry, getRegisteredRoutes, lockRouteRegistry, matchRoute, RawRouteErrors, registerRoute, unlockRouteRegistry, } from './raw-route.js';
|
|
14
14
|
export { RawRoute } from './raw-route.mock.js';
|
package/dist/lambda-handler.d.ts
CHANGED
|
@@ -128,4 +128,21 @@ export interface LambdaContext {
|
|
|
128
128
|
* @param backendFactory - Async function returning the backend module (typically `() => import('./index.js')`)
|
|
129
129
|
*/
|
|
130
130
|
export declare function createLambdaHandler(backendFactory: () => Promise<any>): (event: any, context?: LambdaContext) => Promise<any>;
|
|
131
|
+
/**
|
|
132
|
+
* Thrown by `initialize()` when the app has config coordinates
|
|
133
|
+
* (BLOCKS_CONFIG_BUCKET/KEY) but the S3 config load resolved empty because
|
|
134
|
+
* blocks-config.json wasn't readable yet — the transient window right after a
|
|
135
|
+
* deploy, before the BucketDeployment settles.
|
|
136
|
+
*
|
|
137
|
+
* It flows through the `createLambdaHandler()` init catch, which resets
|
|
138
|
+
* `initPromise` so the NEXT request re-runs `initialize()` and picks up the
|
|
139
|
+
* now-present config (config.ts does not cache a not-found result, so the retry
|
|
140
|
+
* re-fetches). A distinct type keeps the recovery path greppable and testable
|
|
141
|
+
* and separates it from real init failures, which surface with their own error.
|
|
142
|
+
*
|
|
143
|
+
* @internal Exported for testing only.
|
|
144
|
+
*/
|
|
145
|
+
export declare class TransientConfigError extends Error {
|
|
146
|
+
constructor();
|
|
147
|
+
}
|
|
131
148
|
//# sourceMappingURL=lambda-handler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lambda-handler.d.ts","sourceRoot":"","sources":["../src/lambda-handler.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAerD,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAElE;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,2BAAkC,CAAC;AAG9D;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;CAErB,CAAC;AAqCX;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAWlF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,GAAG,GAAG,GAAG,CAwD7C;AAiBD;;;;;;;GAOG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;AAErE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,GAAG,GAAG,UAAU,CAWpD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO,CAEzD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,CAIrE;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,wBAAwB,IAAI,MAAM,CAAC;CACpC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,mBAAmB,CAAC,cAAc,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"lambda-handler.d.ts","sourceRoot":"","sources":["../src/lambda-handler.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAerD,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAElE;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,2BAAkC,CAAC;AAG9D;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;CAErB,CAAC;AAqCX;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAWlF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,GAAG,GAAG,GAAG,CAwD7C;AAiBD;;;;;;;GAOG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;AAErE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,GAAG,GAAG,UAAU,CAWpD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO,CAEzD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,CAIrE;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,wBAAwB,IAAI,MAAM,CAAC;CACpC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,mBAAmB,CAAC,cAAc,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,IA8CtD,OAAO,GAAG,EAAE,UAAU,aAAa,kBA4ElD;AASD;;;;;;;;;;;;;GAaG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;;CAK9C"}
|
package/dist/lambda-handler.js
CHANGED
|
@@ -6,7 +6,7 @@ import { ApiError } from './errors.js';
|
|
|
6
6
|
import { BLOCKS_RPC_PREFIX } from './constants.js';
|
|
7
7
|
import { matchRoute, lockRouteRegistry } from './raw-route.js';
|
|
8
8
|
import { registerBuiltinRoutes } from './builtin-routes.js';
|
|
9
|
-
import { loadConfigToProcessEnv } from './common/config.js';
|
|
9
|
+
import { loadConfigToProcessEnv, isConfigResolved } from './common/config.js';
|
|
10
10
|
import { parseRpcRequest, successResponse, errorResponse, errorResponseFromCatch, methodNotFoundResponse, } from './rpc.js';
|
|
11
11
|
import { getCorsPatterns, isOriginAllowed, corsRejection, buildCorsHeaders, CORS_MAX_AGE } from './cors.js';
|
|
12
12
|
export { parseCorsPatterns, _resetCorsPatterns } from './cors.js';
|
|
@@ -257,6 +257,25 @@ export function createLambdaHandler(backendFactory) {
|
|
|
257
257
|
let initPromise = null;
|
|
258
258
|
async function initialize() {
|
|
259
259
|
await loadConfigToProcessEnv();
|
|
260
|
+
// If the app has config coordinates (BLOCKS_CONFIG_BUCKET/KEY set) but the
|
|
261
|
+
// load didn't actually resolve the config, we're in the transient post-deploy
|
|
262
|
+
// S3 window where blocks-config.json isn't readable yet. loadConfigFromS3()
|
|
263
|
+
// deliberately does NOT cache that empty result, so throw a typed transient
|
|
264
|
+
// error here — BEFORE importing the backend — so the createLambdaHandler()
|
|
265
|
+
// catch resets initPromise and the next request re-runs initialize(), which
|
|
266
|
+
// re-fetches from S3 and picks up the now-present config. Without this throw,
|
|
267
|
+
// initialize() would succeed with empty config, `handler` would be assigned,
|
|
268
|
+
// and the `if (!handler)` guard below would never fire again, poisoning the
|
|
269
|
+
// container for its whole life (it would import the backend against empty
|
|
270
|
+
// process.env and serve "not configured" 500s forever).
|
|
271
|
+
//
|
|
272
|
+
// A genuinely config-less app does NOT throw: the no-bucket local-dev path
|
|
273
|
+
// and a real empty `{}` config both cache their result, so isConfigResolved()
|
|
274
|
+
// is true and the retry never spins. Once the blob is readable the successful
|
|
275
|
+
// load caches, so there is no unbounded per-request S3 re-fetch either.
|
|
276
|
+
if (process.env.BLOCKS_CONFIG_BUCKET && process.env.BLOCKS_CONFIG_KEY && !isConfigResolved()) {
|
|
277
|
+
throw new TransientConfigError();
|
|
278
|
+
}
|
|
260
279
|
// Merge hosting-provided CORS origins into the main env var so the lazy
|
|
261
280
|
// getCorsPatterns() sees a combined value on first access.
|
|
262
281
|
// loadConfigToProcessEnv() won't override CORS_ALLOWED_ORIGINS if it's
|
|
@@ -276,8 +295,18 @@ export function createLambdaHandler(backendFactory) {
|
|
|
276
295
|
}
|
|
277
296
|
return async (event, context) => {
|
|
278
297
|
if (!handler) {
|
|
279
|
-
|
|
280
|
-
|
|
298
|
+
// Retry init on failure instead of caching the rejection. If initialize() throws (most often
|
|
299
|
+
// because loadConfigToProcessEnv() couldn't read blocks-config.json during the brief
|
|
300
|
+
// post-deploy window before it's readable), a memoized rejected promise would poison this
|
|
301
|
+
// container for its whole lifetime — every later request re-awaits the same rejection and 500s.
|
|
302
|
+
// Resetting initPromise lets the next invocation re-run initialize() (config.ts re-fetches,
|
|
303
|
+
// since it doesn't cache failures), so the handler self-heals once config is available.
|
|
304
|
+
if (!initPromise) {
|
|
305
|
+
initPromise = initialize().catch((err) => {
|
|
306
|
+
initPromise = null;
|
|
307
|
+
throw err;
|
|
308
|
+
});
|
|
309
|
+
}
|
|
281
310
|
await initPromise;
|
|
282
311
|
}
|
|
283
312
|
// Only apply the HTTP timeout guard to API Gateway requests; async event
|
|
@@ -344,6 +373,26 @@ class HandlerTimeoutError extends Error {
|
|
|
344
373
|
this.name = 'HandlerTimeoutError';
|
|
345
374
|
}
|
|
346
375
|
}
|
|
376
|
+
/**
|
|
377
|
+
* Thrown by `initialize()` when the app has config coordinates
|
|
378
|
+
* (BLOCKS_CONFIG_BUCKET/KEY) but the S3 config load resolved empty because
|
|
379
|
+
* blocks-config.json wasn't readable yet — the transient window right after a
|
|
380
|
+
* deploy, before the BucketDeployment settles.
|
|
381
|
+
*
|
|
382
|
+
* It flows through the `createLambdaHandler()` init catch, which resets
|
|
383
|
+
* `initPromise` so the NEXT request re-runs `initialize()` and picks up the
|
|
384
|
+
* now-present config (config.ts does not cache a not-found result, so the retry
|
|
385
|
+
* re-fetches). A distinct type keeps the recovery path greppable and testable
|
|
386
|
+
* and separates it from real init failures, which surface with their own error.
|
|
387
|
+
*
|
|
388
|
+
* @internal Exported for testing only.
|
|
389
|
+
*/
|
|
390
|
+
export class TransientConfigError extends Error {
|
|
391
|
+
constructor() {
|
|
392
|
+
super('[Blocks] Config not readable yet (transient post-deploy S3 window); will retry on next request');
|
|
393
|
+
this.name = 'TransientConfigError';
|
|
394
|
+
}
|
|
395
|
+
}
|
|
347
396
|
/**
|
|
348
397
|
* Extract the request path from a Lambda event, normalizing between
|
|
349
398
|
* API Gateway v1 (REST) and v2 (HTTP API) event shapes.
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
import { describe, it, beforeEach } from 'node:test';
|
|
4
4
|
import assert from 'node:assert';
|
|
5
|
-
import { createLambdaHandler, _resetCorsPatterns, requestCookies, isApiGatewayHttpEvent, computeHttpDeadlineMs, classifyEvent, buildEventUrl, isLoopbackForwardedHost } from './lambda-handler.js';
|
|
5
|
+
import { createLambdaHandler, _resetCorsPatterns, requestCookies, isApiGatewayHttpEvent, computeHttpDeadlineMs, classifyEvent, buildEventUrl, isLoopbackForwardedHost, TransientConfigError } from './lambda-handler.js';
|
|
6
6
|
import { registerRoute, clearRouteRegistry } from './raw-route.js';
|
|
7
7
|
import { decodeRpcResponse } from './rpc.js';
|
|
8
|
+
import { _resetConfigCache, _setS3Fetcher } from './common/config.js';
|
|
8
9
|
beforeEach(() => {
|
|
9
10
|
clearRouteRegistry();
|
|
10
11
|
});
|
|
@@ -22,6 +23,134 @@ async function invoke(backend, event) {
|
|
|
22
23
|
const handler = createLambdaHandler(async () => backend);
|
|
23
24
|
return handler(event);
|
|
24
25
|
}
|
|
26
|
+
// ── init self-heal (retry on failed initialization) ─────────────────────────
|
|
27
|
+
describe('createLambdaHandler — init self-heal', () => {
|
|
28
|
+
it('retries initialize() on a later request instead of caching a failed init', async () => {
|
|
29
|
+
let initCalls = 0;
|
|
30
|
+
const backend = {
|
|
31
|
+
api: (_ctx) => ({
|
|
32
|
+
async echo(msg) {
|
|
33
|
+
return { msg };
|
|
34
|
+
},
|
|
35
|
+
}),
|
|
36
|
+
};
|
|
37
|
+
// Fail the first initialization (e.g. config not readable yet in the brief post-deploy
|
|
38
|
+
// window), then succeed. The SAME handler instance must recover — a cached rejected
|
|
39
|
+
// initPromise would poison the container and fail every subsequent request.
|
|
40
|
+
const backendFactory = async () => {
|
|
41
|
+
initCalls++;
|
|
42
|
+
if (initCalls === 1)
|
|
43
|
+
throw new Error('transient init failure');
|
|
44
|
+
return backend;
|
|
45
|
+
};
|
|
46
|
+
const handler = createLambdaHandler(backendFactory);
|
|
47
|
+
// 1st request: init fails, so the handler rejects.
|
|
48
|
+
await assert.rejects(() => handler(makeEvent()), /transient init failure/);
|
|
49
|
+
// 2nd request: init is retried and succeeds → 200 (not a re-thrown cached rejection).
|
|
50
|
+
const res = (await handler(makeEvent()));
|
|
51
|
+
assert.strictEqual(res.statusCode, 200);
|
|
52
|
+
assert.strictEqual(initCalls, 2, 'initialize() must be retried on the next request, not cached');
|
|
53
|
+
});
|
|
54
|
+
it('recovers from a transient-empty (post-deploy 404) config load on the next request', async () => {
|
|
55
|
+
// Reproduces the poisoned-container bug: the first S3 load hits the transient
|
|
56
|
+
// post-deploy window (NoSuchKey), so config resolves empty; the handler must
|
|
57
|
+
// NOT lock in that empty config, and the NEXT request must re-fetch and pick
|
|
58
|
+
// up the now-present config.
|
|
59
|
+
_resetConfigCache();
|
|
60
|
+
process.env.BLOCKS_CONFIG_BUCKET = 'test-bucket';
|
|
61
|
+
process.env.BLOCKS_CONFIG_KEY = 'blocks-config.json';
|
|
62
|
+
delete process.env.SELFHEAL_KEY;
|
|
63
|
+
let fetchCall = 0;
|
|
64
|
+
const notFound = new Error('The specified key does not exist.');
|
|
65
|
+
notFound.name = 'NoSuchKey';
|
|
66
|
+
_setS3Fetcher(async () => {
|
|
67
|
+
fetchCall++;
|
|
68
|
+
if (fetchCall === 1)
|
|
69
|
+
throw notFound; // config not readable yet
|
|
70
|
+
return JSON.stringify({ SELFHEAL_KEY: 'ready' });
|
|
71
|
+
});
|
|
72
|
+
let backendImports = 0;
|
|
73
|
+
const handler = createLambdaHandler(async () => {
|
|
74
|
+
backendImports++;
|
|
75
|
+
return {
|
|
76
|
+
api: (_ctx) => ({
|
|
77
|
+
async echo(msg) { return { msg, cfg: process.env.SELFHEAL_KEY }; },
|
|
78
|
+
}),
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
try {
|
|
82
|
+
// 1st request: transient-empty load → initialize() throws TransientConfigError
|
|
83
|
+
// BEFORE importing the backend (so we never lock in empty config), and the
|
|
84
|
+
// request rejects.
|
|
85
|
+
await assert.rejects(() => handler(makeEvent()), TransientConfigError);
|
|
86
|
+
assert.strictEqual(backendImports, 0, 'backend must NOT be imported while config is unresolved');
|
|
87
|
+
// 2nd request: config now present → initialize() re-runs, re-fetches, and
|
|
88
|
+
// injects the config into process.env before importing the backend.
|
|
89
|
+
const res = (await handler(makeEvent()));
|
|
90
|
+
assert.strictEqual(res.statusCode, 200);
|
|
91
|
+
const body = JSON.parse(res.body);
|
|
92
|
+
assert.strictEqual(body.result.cfg, 'ready', 'now-present config was injected on the retry');
|
|
93
|
+
assert.strictEqual(backendImports, 1, 'backend imported exactly once, on the successful retry');
|
|
94
|
+
assert.strictEqual(fetchCall, 2, 'S3 was re-fetched on the retry (transient miss is not cached)');
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
_resetConfigCache();
|
|
98
|
+
delete process.env.BLOCKS_CONFIG_BUCKET;
|
|
99
|
+
delete process.env.BLOCKS_CONFIG_KEY;
|
|
100
|
+
delete process.env.SELFHEAL_KEY;
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
it('does NOT re-init for a config-less app (no bucket, local dev) — initialize runs once', async () => {
|
|
104
|
+
_resetConfigCache();
|
|
105
|
+
delete process.env.BLOCKS_CONFIG_BUCKET;
|
|
106
|
+
delete process.env.BLOCKS_CONFIG_KEY;
|
|
107
|
+
let backendImports = 0;
|
|
108
|
+
const handler = createLambdaHandler(async () => {
|
|
109
|
+
backendImports++;
|
|
110
|
+
return { api: (_ctx) => ({ async echo(msg) { return { msg }; } }) };
|
|
111
|
+
});
|
|
112
|
+
try {
|
|
113
|
+
const r1 = (await handler(makeEvent()));
|
|
114
|
+
const r2 = (await handler(makeEvent()));
|
|
115
|
+
assert.strictEqual(r1.statusCode, 200);
|
|
116
|
+
assert.strictEqual(r2.statusCode, 200);
|
|
117
|
+
assert.strictEqual(backendImports, 1, 'no re-init for a genuinely config-less (local dev) app');
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
_resetConfigCache();
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
it('does NOT re-init or spin for a genuinely-empty ({}) S3 config', async () => {
|
|
124
|
+
// A real, readable empty config must be treated as resolved — distinct from
|
|
125
|
+
// the transient 404 miss — so the container does not throw/retry forever nor
|
|
126
|
+
// re-fetch S3 on every request.
|
|
127
|
+
_resetConfigCache();
|
|
128
|
+
process.env.BLOCKS_CONFIG_BUCKET = 'test-bucket';
|
|
129
|
+
process.env.BLOCKS_CONFIG_KEY = 'blocks-config.json';
|
|
130
|
+
let fetchCall = 0;
|
|
131
|
+
_setS3Fetcher(async () => { fetchCall++; return JSON.stringify({}); });
|
|
132
|
+
let backendImports = 0;
|
|
133
|
+
const handler = createLambdaHandler(async () => {
|
|
134
|
+
backendImports++;
|
|
135
|
+
return { api: (_ctx) => ({ async echo(msg) { return { msg }; } }) };
|
|
136
|
+
});
|
|
137
|
+
try {
|
|
138
|
+
const r1 = (await handler(makeEvent()));
|
|
139
|
+
const r2 = (await handler(makeEvent()));
|
|
140
|
+
const r3 = (await handler(makeEvent()));
|
|
141
|
+
assert.strictEqual(r1.statusCode, 200);
|
|
142
|
+
assert.strictEqual(r2.statusCode, 200);
|
|
143
|
+
assert.strictEqual(r3.statusCode, 200);
|
|
144
|
+
assert.strictEqual(backendImports, 1, 'genuinely-empty config resolves; no re-init');
|
|
145
|
+
assert.strictEqual(fetchCall, 1, 'S3 fetched once and cached — no per-request re-fetch spin');
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
_resetConfigCache();
|
|
149
|
+
delete process.env.BLOCKS_CONFIG_BUCKET;
|
|
150
|
+
delete process.env.BLOCKS_CONFIG_KEY;
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
});
|
|
25
154
|
// ── RPC body tests ──────────────────────────────────────────────────────────
|
|
26
155
|
describe('createLambdaHandler — RPC body handling', () => {
|
|
27
156
|
it('populates ctx.request.body as a ReadableStream for RPC calls', async () => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sandbox-empty-buckets.test.d.ts","sourceRoot":"","sources":["../../src/scripts/sandbox-empty-buckets.test.ts"],"names":[],"mappings":""}
|