@aws-blocks/core 0.1.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/LICENSE +174 -0
- package/dist/api.d.ts +147 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +10 -0
- package/dist/api.test.d.ts +2 -0
- package/dist/api.test.d.ts.map +1 -0
- package/dist/api.test.js +19 -0
- package/dist/bb-utils.d.ts +11 -0
- package/dist/bb-utils.d.ts.map +1 -0
- package/dist/bb-utils.js +12 -0
- package/dist/builtin-routes.d.ts +6 -0
- package/dist/builtin-routes.d.ts.map +1 -0
- package/dist/builtin-routes.js +57 -0
- package/dist/cdk/__fixtures__/factory-backend.d.ts +3 -0
- package/dist/cdk/__fixtures__/factory-backend.d.ts.map +1 -0
- package/dist/cdk/__fixtures__/factory-backend.js +6 -0
- package/dist/cdk/__fixtures__/fullid-construct-backend.d.ts +2 -0
- package/dist/cdk/__fixtures__/fullid-construct-backend.d.ts.map +1 -0
- package/dist/cdk/__fixtures__/fullid-construct-backend.js +37 -0
- package/dist/cdk/__fixtures__/handler.d.ts +5 -0
- package/dist/cdk/__fixtures__/handler.d.ts.map +1 -0
- package/dist/cdk/__fixtures__/handler.js +3 -0
- package/dist/cdk/__fixtures__/side-effect-backend.d.ts +2 -0
- package/dist/cdk/__fixtures__/side-effect-backend.d.ts.map +1 -0
- package/dist/cdk/__fixtures__/side-effect-backend.js +5 -0
- package/dist/cdk/blocks-backend.d.ts +68 -0
- package/dist/cdk/blocks-backend.d.ts.map +1 -0
- package/dist/cdk/blocks-backend.js +214 -0
- package/dist/cdk/blocks-backend.test.d.ts +2 -0
- package/dist/cdk/blocks-backend.test.d.ts.map +1 -0
- package/dist/cdk/blocks-backend.test.js +151 -0
- package/dist/cdk/blocks-stack.test.d.ts +2 -0
- package/dist/cdk/blocks-stack.test.d.ts.map +1 -0
- package/dist/cdk/blocks-stack.test.js +78 -0
- package/dist/cdk/config-registry.d.ts +26 -0
- package/dist/cdk/config-registry.d.ts.map +1 -0
- package/dist/cdk/config-registry.js +80 -0
- package/dist/cdk/index.d.ts +35 -0
- package/dist/cdk/index.d.ts.map +1 -0
- package/dist/cdk/index.js +90 -0
- package/dist/cdk/mixins.d.ts +28 -0
- package/dist/cdk/mixins.d.ts.map +1 -0
- package/dist/cdk/mixins.js +37 -0
- package/dist/cdk/mixins.test.d.ts +2 -0
- package/dist/cdk/mixins.test.d.ts.map +1 -0
- package/dist/cdk/mixins.test.js +108 -0
- package/dist/cdk/node-version.d.ts +14 -0
- package/dist/cdk/node-version.d.ts.map +1 -0
- package/dist/cdk/node-version.js +15 -0
- package/dist/cdk/stack-metadata.d.ts +12 -0
- package/dist/cdk/stack-metadata.d.ts.map +1 -0
- package/dist/cdk/stack-metadata.js +22 -0
- package/dist/cdk/stack-metadata.test.d.ts +2 -0
- package/dist/cdk/stack-metadata.test.d.ts.map +1 -0
- package/dist/cdk/stack-metadata.test.js +56 -0
- package/dist/cdk/synth-guard.d.ts +14 -0
- package/dist/cdk/synth-guard.d.ts.map +1 -0
- package/dist/cdk/synth-guard.js +20 -0
- package/dist/cdk/synth-guard.test.d.ts +2 -0
- package/dist/cdk/synth-guard.test.d.ts.map +1 -0
- package/dist/cdk/synth-guard.test.js +15 -0
- package/dist/client/index.d.ts +97 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +238 -0
- package/dist/client/index.test.d.ts +2 -0
- package/dist/client/index.test.d.ts.map +1 -0
- package/dist/client/index.test.js +113 -0
- package/dist/common/config.d.ts +45 -0
- package/dist/common/config.d.ts.map +1 -0
- package/dist/common/config.js +154 -0
- package/dist/common/config.test.d.ts +2 -0
- package/dist/common/config.test.d.ts.map +1 -0
- package/dist/common/config.test.js +173 -0
- package/dist/common/constants.d.ts +11 -0
- package/dist/common/constants.d.ts.map +1 -0
- package/dist/common/constants.js +12 -0
- package/dist/common/crypto.d.ts +6 -0
- package/dist/common/crypto.d.ts.map +1 -0
- package/dist/common/crypto.js +14 -0
- package/dist/common/index.d.ts +220 -0
- package/dist/common/index.d.ts.map +1 -0
- package/dist/common/index.js +180 -0
- package/dist/common/index.test.d.ts +2 -0
- package/dist/common/index.test.d.ts.map +1 -0
- package/dist/common/index.test.js +195 -0
- package/dist/common/mock-data.d.ts +40 -0
- package/dist/common/mock-data.d.ts.map +1 -0
- package/dist/common/mock-data.js +39 -0
- package/dist/common/official-bb-names.generated.d.ts +7 -0
- package/dist/common/official-bb-names.generated.d.ts.map +1 -0
- package/dist/common/official-bb-names.generated.js +28 -0
- package/dist/common/sdk-registry.d.ts +136 -0
- package/dist/common/sdk-registry.d.ts.map +1 -0
- package/dist/common/sdk-registry.js +125 -0
- package/dist/constants.d.ts +27 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +28 -0
- package/dist/cors.d.ts +39 -0
- package/dist/cors.d.ts.map +1 -0
- package/dist/cors.js +85 -0
- package/dist/cors.test.d.ts +2 -0
- package/dist/cors.test.d.ts.map +1 -0
- package/dist/cors.test.js +241 -0
- package/dist/db-naming.d.ts +21 -0
- package/dist/db-naming.d.ts.map +1 -0
- package/dist/db-naming.js +38 -0
- package/dist/db-naming.test.d.ts +2 -0
- package/dist/db-naming.test.d.ts.map +1 -0
- package/dist/db-naming.test.js +29 -0
- package/dist/errors.d.ts +67 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +69 -0
- package/dist/hosting.d.ts +258 -0
- package/dist/hosting.d.ts.map +1 -0
- package/dist/hosting.js +340 -0
- package/dist/hosting.test.d.ts +2 -0
- package/dist/hosting.test.d.ts.map +1 -0
- package/dist/hosting.test.js +1077 -0
- package/dist/index.cdk.d.ts +13 -0
- package/dist/index.cdk.d.ts.map +1 -0
- package/dist/index.cdk.js +13 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/lambda-handler.d.ts +131 -0
- package/dist/lambda-handler.d.ts.map +1 -0
- package/dist/lambda-handler.js +610 -0
- package/dist/lambda-handler.test.d.ts +2 -0
- package/dist/lambda-handler.test.d.ts.map +1 -0
- package/dist/lambda-handler.test.js +835 -0
- package/dist/pipeline/index.d.ts +4 -0
- package/dist/pipeline/index.d.ts.map +1 -0
- package/dist/pipeline/index.js +3 -0
- package/dist/pipeline/pipeline-construct.d.ts +130 -0
- package/dist/pipeline/pipeline-construct.d.ts.map +1 -0
- package/dist/pipeline/pipeline-construct.js +484 -0
- package/dist/pipeline/pipeline.test.d.ts +2 -0
- package/dist/pipeline/pipeline.test.d.ts.map +1 -0
- package/dist/pipeline/pipeline.test.js +1231 -0
- package/dist/pipeline/types.d.ts +351 -0
- package/dist/pipeline/types.d.ts.map +1 -0
- package/dist/pipeline/types.js +3 -0
- package/dist/raw-route.cdk.d.ts +47 -0
- package/dist/raw-route.cdk.d.ts.map +1 -0
- package/dist/raw-route.cdk.js +51 -0
- package/dist/raw-route.d.ts +119 -0
- package/dist/raw-route.d.ts.map +1 -0
- package/dist/raw-route.js +239 -0
- package/dist/raw-route.mock.d.ts +47 -0
- package/dist/raw-route.mock.d.ts.map +1 -0
- package/dist/raw-route.mock.js +51 -0
- package/dist/raw-route.test.d.ts +2 -0
- package/dist/raw-route.test.d.ts.map +1 -0
- package/dist/raw-route.test.js +597 -0
- package/dist/redact.d.ts +51 -0
- package/dist/redact.d.ts.map +1 -0
- package/dist/redact.js +134 -0
- package/dist/redact.test.d.ts +2 -0
- package/dist/redact.test.d.ts.map +1 -0
- package/dist/redact.test.js +199 -0
- package/dist/rpc.d.ts +54 -0
- package/dist/rpc.d.ts.map +1 -0
- package/dist/rpc.js +127 -0
- package/dist/rpc.test.d.ts +2 -0
- package/dist/rpc.test.d.ts.map +1 -0
- package/dist/rpc.test.js +60 -0
- package/dist/scripts/cdk-telemetry-env.d.ts +13 -0
- package/dist/scripts/cdk-telemetry-env.d.ts.map +1 -0
- package/dist/scripts/cdk-telemetry-env.js +21 -0
- package/dist/scripts/cdk-telemetry-env.test.d.ts +2 -0
- package/dist/scripts/cdk-telemetry-env.test.d.ts.map +1 -0
- package/dist/scripts/cdk-telemetry-env.test.js +56 -0
- package/dist/scripts/cleanup.d.ts +3 -0
- package/dist/scripts/cleanup.d.ts.map +1 -0
- package/dist/scripts/cleanup.js +35 -0
- package/dist/scripts/console.d.ts +6 -0
- package/dist/scripts/console.d.ts.map +1 -0
- package/dist/scripts/console.js +25 -0
- package/dist/scripts/deploy.d.ts +6 -0
- package/dist/scripts/deploy.d.ts.map +1 -0
- package/dist/scripts/deploy.js +82 -0
- package/dist/scripts/destroy.d.ts +6 -0
- package/dist/scripts/destroy.d.ts.map +1 -0
- package/dist/scripts/destroy.js +30 -0
- package/dist/scripts/dev-server-cors.test.d.ts +2 -0
- package/dist/scripts/dev-server-cors.test.d.ts.map +1 -0
- package/dist/scripts/dev-server-cors.test.js +23 -0
- package/dist/scripts/dev-server-telemetry.test.d.ts +2 -0
- package/dist/scripts/dev-server-telemetry.test.d.ts.map +1 -0
- package/dist/scripts/dev-server-telemetry.test.js +113 -0
- package/dist/scripts/dev-server.d.ts +21 -0
- package/dist/scripts/dev-server.d.ts.map +1 -0
- package/dist/scripts/dev-server.js +456 -0
- package/dist/scripts/ensure-secrets.d.ts +31 -0
- package/dist/scripts/ensure-secrets.d.ts.map +1 -0
- package/dist/scripts/ensure-secrets.js +100 -0
- package/dist/scripts/ensure-secrets.test.d.ts +2 -0
- package/dist/scripts/ensure-secrets.test.d.ts.map +1 -0
- package/dist/scripts/ensure-secrets.test.js +44 -0
- package/dist/scripts/external-migrations-step.d.ts +62 -0
- package/dist/scripts/external-migrations-step.d.ts.map +1 -0
- package/dist/scripts/external-migrations-step.js +243 -0
- package/dist/scripts/external-migrations-step.test.d.ts +2 -0
- package/dist/scripts/external-migrations-step.test.d.ts.map +1 -0
- package/dist/scripts/external-migrations-step.test.js +47 -0
- package/dist/scripts/extract-ts-types.d.ts +65 -0
- package/dist/scripts/extract-ts-types.d.ts.map +1 -0
- package/dist/scripts/extract-ts-types.js +664 -0
- package/dist/scripts/extract-ts-types.test.d.ts +2 -0
- package/dist/scripts/extract-ts-types.test.d.ts.map +1 -0
- package/dist/scripts/extract-ts-types.test.js +497 -0
- package/dist/scripts/generate-client-worker.d.ts +2 -0
- package/dist/scripts/generate-client-worker.d.ts.map +1 -0
- package/dist/scripts/generate-client-worker.js +13 -0
- package/dist/scripts/generate-client.d.ts +14 -0
- package/dist/scripts/generate-client.d.ts.map +1 -0
- package/dist/scripts/generate-client.js +73 -0
- package/dist/scripts/generate-client.test.d.ts +2 -0
- package/dist/scripts/generate-client.test.d.ts.map +1 -0
- package/dist/scripts/generate-client.test.js +48 -0
- package/dist/scripts/generate-spec-cli.d.ts +3 -0
- package/dist/scripts/generate-spec-cli.d.ts.map +1 -0
- package/dist/scripts/generate-spec-cli.js +77 -0
- package/dist/scripts/generate-spec-cli.test.d.ts +14 -0
- package/dist/scripts/generate-spec-cli.test.d.ts.map +1 -0
- package/dist/scripts/generate-spec-cli.test.js +90 -0
- package/dist/scripts/generate-spec.d.ts +61 -0
- package/dist/scripts/generate-spec.d.ts.map +1 -0
- package/dist/scripts/generate-spec.js +746 -0
- package/dist/scripts/generate-spec.test.d.ts +12 -0
- package/dist/scripts/generate-spec.test.d.ts.map +1 -0
- package/dist/scripts/generate-spec.test.js +88 -0
- package/dist/scripts/index.d.ts +12 -0
- package/dist/scripts/index.d.ts.map +1 -0
- package/dist/scripts/index.js +13 -0
- package/dist/scripts/sandbox.d.ts +11 -0
- package/dist/scripts/sandbox.d.ts.map +1 -0
- package/dist/scripts/sandbox.js +200 -0
- package/dist/scripts/telemetry-cli.d.ts +3 -0
- package/dist/scripts/telemetry-cli.d.ts.map +1 -0
- package/dist/scripts/telemetry-cli.js +8 -0
- package/dist/scripts/telemetry.d.ts +17 -0
- package/dist/scripts/telemetry.d.ts.map +1 -0
- package/dist/scripts/telemetry.js +150 -0
- package/dist/scripts/telemetry.test.d.ts +2 -0
- package/dist/scripts/telemetry.test.d.ts.map +1 -0
- package/dist/scripts/telemetry.test.js +203 -0
- package/dist/scripts/validate-spec.d.ts +22 -0
- package/dist/scripts/validate-spec.d.ts.map +1 -0
- package/dist/scripts/validate-spec.js +159 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +3 -0
- package/dist/server/withAuth.d.ts +142 -0
- package/dist/server/withAuth.d.ts.map +1 -0
- package/dist/server/withAuth.js +261 -0
- package/dist/server/withAuth.test.d.ts +2 -0
- package/dist/server/withAuth.test.d.ts.map +1 -0
- package/dist/server/withAuth.test.js +278 -0
- package/dist/telemetry/client.d.ts +59 -0
- package/dist/telemetry/client.d.ts.map +1 -0
- package/dist/telemetry/client.js +215 -0
- package/dist/telemetry/config-writer.d.ts +16 -0
- package/dist/telemetry/config-writer.d.ts.map +1 -0
- package/dist/telemetry/config-writer.js +36 -0
- package/dist/telemetry/consent-commands.test.d.ts +2 -0
- package/dist/telemetry/consent-commands.test.d.ts.map +1 -0
- package/dist/telemetry/consent-commands.test.js +161 -0
- package/dist/telemetry/consent.d.ts +60 -0
- package/dist/telemetry/consent.d.ts.map +1 -0
- package/dist/telemetry/consent.js +122 -0
- package/dist/telemetry/environment.d.ts +33 -0
- package/dist/telemetry/environment.d.ts.map +1 -0
- package/dist/telemetry/environment.js +87 -0
- package/dist/telemetry/identifiers.d.ts +20 -0
- package/dist/telemetry/identifiers.d.ts.map +1 -0
- package/dist/telemetry/identifiers.js +86 -0
- package/dist/telemetry/index.d.ts +6 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +4 -0
- package/dist/telemetry/telemetry.test.d.ts +2 -0
- package/dist/telemetry/telemetry.test.d.ts.map +1 -0
- package/dist/telemetry/telemetry.test.js +1131 -0
- package/dist/telemetry/trackCommand.d.ts +32 -0
- package/dist/telemetry/trackCommand.d.ts.map +1 -0
- package/dist/telemetry/trackCommand.js +102 -0
- package/dist/telemetry/types.d.ts +92 -0
- package/dist/telemetry/types.d.ts.map +1 -0
- package/dist/telemetry/types.js +1 -0
- package/dist/types.d.ts +24 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/package.json +83 -0
- package/src/api.test.ts +25 -0
- package/src/api.ts +163 -0
- package/src/bb-utils.ts +13 -0
- package/src/builtin-routes.ts +62 -0
- package/src/cdk/__fixtures__/factory-backend.ts +8 -0
- package/src/cdk/__fixtures__/fullid-construct-backend.ts +42 -0
- package/src/cdk/__fixtures__/handler.ts +4 -0
- package/src/cdk/__fixtures__/side-effect-backend.ts +7 -0
- package/src/cdk/blocks-backend.test.ts +207 -0
- package/src/cdk/blocks-backend.ts +249 -0
- package/src/cdk/blocks-stack.test.ts +106 -0
- package/src/cdk/config-registry.ts +106 -0
- package/src/cdk/index.ts +115 -0
- package/src/cdk/mixins.test.ts +129 -0
- package/src/cdk/mixins.ts +41 -0
- package/src/cdk/node-version.ts +17 -0
- package/src/cdk/stack-metadata.test.ts +71 -0
- package/src/cdk/stack-metadata.ts +29 -0
- package/src/cdk/synth-guard.test.ts +21 -0
- package/src/cdk/synth-guard.ts +23 -0
- package/src/client/index.test.ts +118 -0
- package/src/client/index.ts +320 -0
- package/src/common/config.test.ts +236 -0
- package/src/common/config.ts +169 -0
- package/src/common/constants.ts +15 -0
- package/src/common/crypto.ts +15 -0
- package/src/common/index.test.ts +242 -0
- package/src/common/index.ts +335 -0
- package/src/common/mock-data.ts +52 -0
- package/src/common/official-bb-names.generated.ts +29 -0
- package/src/common/sdk-registry.ts +150 -0
- package/src/constants.ts +31 -0
- package/src/cors.test.ts +279 -0
- package/src/cors.ts +91 -0
- package/src/db-naming.test.ts +48 -0
- package/src/db-naming.ts +41 -0
- package/src/errors.ts +72 -0
- package/src/hosting.test.ts +1431 -0
- package/src/hosting.ts +626 -0
- package/src/index.cdk.ts +45 -0
- package/src/index.ts +22 -0
- package/src/lambda-handler.test.ts +1005 -0
- package/src/lambda-handler.ts +706 -0
- package/src/pipeline/index.ts +12 -0
- package/src/pipeline/pipeline-construct.ts +662 -0
- package/src/pipeline/pipeline.test.ts +1565 -0
- package/src/pipeline/types.ts +384 -0
- package/src/raw-route.cdk.ts +56 -0
- package/src/raw-route.mock.ts +56 -0
- package/src/raw-route.test.ts +819 -0
- package/src/raw-route.ts +316 -0
- package/src/redact.test.ts +222 -0
- package/src/redact.ts +143 -0
- package/src/rpc.test.ts +73 -0
- package/src/rpc.ts +174 -0
- package/src/scripts/cdk-telemetry-env.test.ts +75 -0
- package/src/scripts/cdk-telemetry-env.ts +23 -0
- package/src/scripts/cleanup.ts +39 -0
- package/src/scripts/console.ts +34 -0
- package/src/scripts/deploy.ts +106 -0
- package/src/scripts/destroy.ts +42 -0
- package/src/scripts/dev-server-cors.test.ts +28 -0
- package/src/scripts/dev-server-telemetry.test.ts +127 -0
- package/src/scripts/dev-server.ts +515 -0
- package/src/scripts/ensure-secrets.test.ts +50 -0
- package/src/scripts/ensure-secrets.ts +113 -0
- package/src/scripts/external-migrations-step.test.ts +64 -0
- package/src/scripts/external-migrations-step.ts +269 -0
- package/src/scripts/extract-ts-types.test.ts +516 -0
- package/src/scripts/extract-ts-types.ts +773 -0
- package/src/scripts/generate-client-worker.ts +15 -0
- package/src/scripts/generate-client.test.ts +69 -0
- package/src/scripts/generate-client.ts +86 -0
- package/src/scripts/generate-spec-cli.test.ts +106 -0
- package/src/scripts/generate-spec-cli.ts +92 -0
- package/src/scripts/generate-spec.test.ts +99 -0
- package/src/scripts/generate-spec.ts +833 -0
- package/src/scripts/index.ts +21 -0
- package/src/scripts/sandbox.ts +230 -0
- package/src/scripts/telemetry-cli.ts +10 -0
- package/src/scripts/telemetry.test.ts +239 -0
- package/src/scripts/telemetry.ts +173 -0
- package/src/scripts/validate-spec.ts +199 -0
- package/src/server/index.ts +5 -0
- package/src/server/withAuth.test.ts +348 -0
- package/src/server/withAuth.ts +323 -0
- package/src/telemetry/client.ts +226 -0
- package/src/telemetry/config-writer.ts +52 -0
- package/src/telemetry/consent-commands.test.ts +196 -0
- package/src/telemetry/consent.ts +154 -0
- package/src/telemetry/environment.ts +100 -0
- package/src/telemetry/identifiers.ts +94 -0
- package/src/telemetry/index.ts +11 -0
- package/src/telemetry/telemetry.test.ts +1343 -0
- package/src/telemetry/trackCommand.ts +110 -0
- package/src/telemetry/types.ts +92 -0
- package/src/types.ts +33 -0
- package/src/version.ts +2 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
export type BlocksContext = any;
|
|
5
|
+
export type ApiHandler<T extends Record<string, (...args: any[]) => any>> = any;
|
|
6
|
+
import { encodeRpcRequest, decodeRpcResponse } from '../rpc.js';
|
|
7
|
+
|
|
8
|
+
const IS_SSR = typeof window === 'undefined';
|
|
9
|
+
|
|
10
|
+
// SSR cookie forwarding: reads from a global AsyncLocalStorage that SSR
|
|
11
|
+
// frameworks populate with the inbound request's cookies. The store is
|
|
12
|
+
// accessed via a well-known global key so the client bundle never imports
|
|
13
|
+
// node:async_hooks (which would break browser/webpack builds).
|
|
14
|
+
function getSsrCookies(): string | undefined {
|
|
15
|
+
if (!IS_SSR) return undefined;
|
|
16
|
+
if (typeof globalThis === 'undefined') return undefined;
|
|
17
|
+
const store = (globalThis as any).__BLOCKS_REQUEST_COOKIES_STORE__;
|
|
18
|
+
if (!store || typeof store.getStore !== 'function') return undefined;
|
|
19
|
+
return store.getStore();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type AsyncAPI<T extends Record<string, (...args: any[]) => any>> = {
|
|
23
|
+
[K in keyof T]: T[K] extends (...args: infer Args) => infer R
|
|
24
|
+
? (...args: Args) => Promise<Awaited<R>>
|
|
25
|
+
: never;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
let API_URL: string | null = null;
|
|
29
|
+
let apiUrlPromise: Promise<string> | null = null;
|
|
30
|
+
|
|
31
|
+
async function getApiUrl(): Promise<string> {
|
|
32
|
+
if (API_URL) return API_URL;
|
|
33
|
+
if (apiUrlPromise) return apiUrlPromise;
|
|
34
|
+
apiUrlPromise = resolveApiUrl().catch((err) => {
|
|
35
|
+
// Don't cache failures — config.json may not exist yet during startup
|
|
36
|
+
apiUrlPromise = null;
|
|
37
|
+
throw err;
|
|
38
|
+
});
|
|
39
|
+
return apiUrlPromise;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function resolveApiUrl(): Promise<string> {
|
|
43
|
+
if (API_URL) return API_URL;
|
|
44
|
+
|
|
45
|
+
function isInvalidUrl(url: string): boolean {
|
|
46
|
+
if (!url || typeof url !== 'string' || !url.trim()) return true;
|
|
47
|
+
if (url === 'undefined' || url.startsWith('undefined')) return true;
|
|
48
|
+
// Relative URLs (e.g. '/aws-blocks/api') are valid for browser fetch
|
|
49
|
+
if (url.startsWith('/')) return false;
|
|
50
|
+
try {
|
|
51
|
+
const parsed = new URL(url);
|
|
52
|
+
return parsed.hostname === 'undefined' ||
|
|
53
|
+
parsed.pathname === '/undefined' ||
|
|
54
|
+
parsed.pathname.startsWith('/undefined/');
|
|
55
|
+
} catch {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function validateAndCache(url: unknown, source: string): string {
|
|
61
|
+
if (!url || typeof url !== 'string' || isInvalidUrl(url)) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Blocks API URL is not configured (source: ${source}). Ensure BLOCKS_API_URL environment variable is set ` +
|
|
64
|
+
'or config.json is deployed. Run with --conditions=cdk during CDK synthesis.',
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
API_URL = url;
|
|
68
|
+
return url;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 1. SSR Lambda: env vars injected by Hosting construct
|
|
72
|
+
if (typeof process !== 'undefined' && process.env?.BLOCKS_API_URL) {
|
|
73
|
+
const url = process.env.BLOCKS_API_URL;
|
|
74
|
+
|
|
75
|
+
// CDK tokens (e.g. ${Token[TOKEN.1228]}) are present at build time but
|
|
76
|
+
// only resolve at CloudFormation deploy time. If we detect them, the
|
|
77
|
+
// build is running inside `cdk synth` and the URL is not usable yet.
|
|
78
|
+
if (/\$\{Token\[/.test(url)) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
'Blocks API URL contains unresolved CDK tokens. This usually means a ' +
|
|
81
|
+
'Server Component is being statically prerendered during `next build` ' +
|
|
82
|
+
'inside `cdk deploy`.\n' +
|
|
83
|
+
'Fix: add `export const dynamic = \'force-dynamic\';` to any page ' +
|
|
84
|
+
'that calls the Blocks API so Next.js skips prerendering it.',
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const validated = validateAndCache(url, 'env BLOCKS_API_URL');
|
|
89
|
+
console.log('[Blocks] Using API (env BLOCKS_API_URL):', validated);
|
|
90
|
+
return validated;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 2. SSR Lambda: full config as JSON env var
|
|
94
|
+
if (typeof process !== 'undefined' && process.env?.BLOCKS_CONFIG) {
|
|
95
|
+
try {
|
|
96
|
+
const config = JSON.parse(process.env.BLOCKS_CONFIG);
|
|
97
|
+
const validated = validateAndCache(config.apiUrl, 'env BLOCKS_CONFIG');
|
|
98
|
+
console.log('[Blocks] Using API (env BLOCKS_CONFIG):', validated);
|
|
99
|
+
return validated;
|
|
100
|
+
} catch {
|
|
101
|
+
// Malformed BLOCKS_CONFIG — fall through
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 3. Node.js: read config file from filesystem
|
|
106
|
+
if (typeof process !== 'undefined' && process.versions?.node) {
|
|
107
|
+
try {
|
|
108
|
+
const fs = await import(/* webpackIgnore: true */ 'node:fs');
|
|
109
|
+
const config = JSON.parse(fs.readFileSync('.blocks-sandbox/config.json', 'utf-8'));
|
|
110
|
+
const validated = validateAndCache(config.apiUrl, 'config.json file');
|
|
111
|
+
console.log('[Blocks] Using API (config.json file):', validated);
|
|
112
|
+
return validated;
|
|
113
|
+
} catch {
|
|
114
|
+
// Config doesn't exist or not in Node.js
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 4. Browser: fetch config.json from hosting origin
|
|
119
|
+
try {
|
|
120
|
+
const response = await fetch('/.blocks-sandbox/config.json');
|
|
121
|
+
if (response.ok) {
|
|
122
|
+
const config = await response.json();
|
|
123
|
+
const validated = validateAndCache(config.apiUrl, 'config.json fetch');
|
|
124
|
+
console.log('[Blocks] Using API (config.json fetch):', validated);
|
|
125
|
+
return validated;
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
// Config doesn't exist
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Fallback — no config found
|
|
132
|
+
throw new Error(
|
|
133
|
+
'Blocks API URL not configured. Ensure:\n' +
|
|
134
|
+
'1. You ran `npm run deploy` (deploys config.json)\n' +
|
|
135
|
+
'2. SSR Lambda has BLOCKS_API_URL env var, OR\n' +
|
|
136
|
+
'3. config.json exists at /.blocks-sandbox/config.json'
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// --- Middleware ---
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* A Blocks request object passed through middleware before being sent.
|
|
144
|
+
* Middleware can inspect or modify any of these properties before the
|
|
145
|
+
* request is dispatched to the server.
|
|
146
|
+
*/
|
|
147
|
+
export interface KitRequest {
|
|
148
|
+
/** The namespace name this call targets (e.g., 'api', 'auth'). Corresponds to the second argument of `new ApiNamespace(scope, name, handler)` in the backend. */
|
|
149
|
+
apiNamespace: string;
|
|
150
|
+
/** The method being called on the namespace (e.g., 'getUser', 'kvSet'). */
|
|
151
|
+
method: string;
|
|
152
|
+
/** The arguments passed to the method call. */
|
|
153
|
+
args: any[];
|
|
154
|
+
/** HTTP headers that will be sent with the request. Middleware can add or modify headers (e.g., inject auth tokens). */
|
|
155
|
+
headers: Record<string, string>;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Client middleware that can process requests before they are sent
|
|
160
|
+
* and/or responses after they are received. Both hooks are optional.
|
|
161
|
+
*
|
|
162
|
+
* ```typescript
|
|
163
|
+
* // Request middleware — inject auth token
|
|
164
|
+
* registerMiddleware({
|
|
165
|
+
* onRequest(req) {
|
|
166
|
+
* req.headers['authorization'] = `Bearer ${getToken()}`;
|
|
167
|
+
* }
|
|
168
|
+
* });
|
|
169
|
+
*
|
|
170
|
+
* // Response middleware — hydrate transferable descriptors
|
|
171
|
+
* registerMiddleware({
|
|
172
|
+
* onResponse(data) {
|
|
173
|
+
* if (isMyDescriptor(data)) return hydrate(data);
|
|
174
|
+
* return data;
|
|
175
|
+
* }
|
|
176
|
+
* });
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
export interface KitMiddleware {
|
|
180
|
+
/** Transform the request before it's sent. Modify the request in place or return a new one. Can be async. */
|
|
181
|
+
onRequest?: (request: KitRequest) => KitRequest | void | Promise<KitRequest | void>;
|
|
182
|
+
/** Transform the response data after it's received. Used to hydrate __blocks descriptors. */
|
|
183
|
+
onResponse?: (data: unknown) => unknown;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const middlewares: KitMiddleware[] = [];
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Register client middleware for request/response processing.
|
|
190
|
+
*
|
|
191
|
+
* Building Block middleware packages call this on import to register
|
|
192
|
+
* hooks that run on every API call. Request hooks can inject headers
|
|
193
|
+
* (e.g., auth tokens). Response hooks hydrate `{ __blocks: '...' }`
|
|
194
|
+
* descriptors into live client-side objects (e.g., WebSocket channels).
|
|
195
|
+
*
|
|
196
|
+
* Client middleware uses a **self-registering** pattern: the middleware
|
|
197
|
+
* module imports this function and calls it as a side effect of being
|
|
198
|
+
* imported. The generated `client.js` simply includes
|
|
199
|
+
* `import 'bb-realtime/mock-middleware'` — no explicit registration
|
|
200
|
+
* code is needed. This is idiomatic for browser/bundler contexts where
|
|
201
|
+
* side-effect imports are standard (similar to CSS imports or polyfills).
|
|
202
|
+
*
|
|
203
|
+
* This differs intentionally from **dev server attachments**, which use
|
|
204
|
+
* an explicit `attach(server)` pattern because they need the HTTP server
|
|
205
|
+
* instance passed to them — something unavailable at import time.
|
|
206
|
+
*/
|
|
207
|
+
export function registerMiddleware(middleware: KitMiddleware): void {
|
|
208
|
+
middlewares.push(middleware);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function processRequest(request: KitRequest): Promise<KitRequest> {
|
|
212
|
+
for (const mw of middlewares) {
|
|
213
|
+
if (mw.onRequest) {
|
|
214
|
+
const result = await mw.onRequest(request);
|
|
215
|
+
if (result) request = result;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return request;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function processResponse(data: unknown): unknown {
|
|
222
|
+
for (const mw of middlewares) {
|
|
223
|
+
if (mw.onResponse) {
|
|
224
|
+
data = mw.onResponse(data);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return data;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Options for `ApiNamespaceClient`.
|
|
232
|
+
*/
|
|
233
|
+
export interface ApiNamespaceClientOptions {
|
|
234
|
+
/** Explicit API URL. When provided, skips automatic config.json discovery. */
|
|
235
|
+
url?: string;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Client-side API proxy. Creates a typed proxy that sends method calls
|
|
240
|
+
* to the backend API over HTTP and hydrates Transferable descriptors
|
|
241
|
+
* in responses via registered middleware.
|
|
242
|
+
*
|
|
243
|
+
* This is the browser counterpart of the server-side `ApiNamespace` in
|
|
244
|
+
* `core/api.ts`. The generated `client.js` emits one of these per
|
|
245
|
+
* API namespace exported from the backend.
|
|
246
|
+
*
|
|
247
|
+
* ```typescript
|
|
248
|
+
* // Generated client.js
|
|
249
|
+
* import { ApiNamespaceClient } from '@aws-blocks/core/client';
|
|
250
|
+
* export const api = ApiNamespaceClient('api');
|
|
251
|
+
*
|
|
252
|
+
* // Frontend usage — fully typed via the 'aws-blocks' types export
|
|
253
|
+
* import { api } from 'aws-blocks';
|
|
254
|
+
* const user = await api.getUser('123');
|
|
255
|
+
* ```
|
|
256
|
+
*/
|
|
257
|
+
export function ApiNamespaceClient<T extends Record<string, (...args: any[]) => any>>(
|
|
258
|
+
name: string,
|
|
259
|
+
options?: ApiNamespaceClientOptions,
|
|
260
|
+
): AsyncAPI<T> {
|
|
261
|
+
const urlOverride = options?.url;
|
|
262
|
+
return new Proxy({} as any, {
|
|
263
|
+
get(target, method: string | symbol) {
|
|
264
|
+
if (typeof method === 'symbol') return undefined;
|
|
265
|
+
return async (...args: any[]) => {
|
|
266
|
+
const apiUrl = urlOverride ?? await getApiUrl();
|
|
267
|
+
|
|
268
|
+
let request: KitRequest = {
|
|
269
|
+
apiNamespace: name,
|
|
270
|
+
method,
|
|
271
|
+
args,
|
|
272
|
+
headers: { 'Content-Type': 'application/json' },
|
|
273
|
+
};
|
|
274
|
+
request = await processRequest(request);
|
|
275
|
+
|
|
276
|
+
// SSR cookie forwarding: when running server-side, forward the
|
|
277
|
+
// inbound request cookies so auth sessions propagate to API calls.
|
|
278
|
+
// Deduplicates by cookie name — existing cookies (from middleware)
|
|
279
|
+
// take precedence over SSR-forwarded ones.
|
|
280
|
+
const ssrCookies = getSsrCookies();
|
|
281
|
+
if (ssrCookies) {
|
|
282
|
+
const existingKey = 'Cookie' in request.headers ? 'Cookie' : 'cookie' in request.headers ? 'cookie' : null;
|
|
283
|
+
const existing = existingKey ? request.headers[existingKey] : undefined;
|
|
284
|
+
if (existingKey && existingKey !== 'Cookie') {
|
|
285
|
+
delete request.headers[existingKey];
|
|
286
|
+
}
|
|
287
|
+
if (existing) {
|
|
288
|
+
const existingNames = new Set(
|
|
289
|
+
existing.split(';').filter(Boolean).map((c: string) => c.trim().split('=')[0])
|
|
290
|
+
);
|
|
291
|
+
const newCookies = ssrCookies
|
|
292
|
+
.split(';')
|
|
293
|
+
.filter(Boolean)
|
|
294
|
+
.filter((c: string) => !existingNames.has(c.trim().split('=')[0]))
|
|
295
|
+
.join('; ');
|
|
296
|
+
request.headers['Cookie'] = newCookies
|
|
297
|
+
? `${existing}; ${newCookies}`
|
|
298
|
+
: existing;
|
|
299
|
+
} else {
|
|
300
|
+
request.headers['Cookie'] = ssrCookies;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const response = await fetch(apiUrl, {
|
|
305
|
+
method: 'POST',
|
|
306
|
+
headers: request.headers,
|
|
307
|
+
credentials: 'include',
|
|
308
|
+
body: encodeRpcRequest(request.apiNamespace, request.method, request.args),
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
const rpcBody = await response.json();
|
|
312
|
+
const result = decodeRpcResponse(rpcBody); // throws ApiError on RPC error
|
|
313
|
+
return processResponse(result);
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export { ApiError, isBlocksError } from '../errors.js';
|
|
320
|
+
export { Scope, type ScopeOptions } from '../common/index.js';
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { describe, it, beforeEach, afterEach } from 'node:test';
|
|
5
|
+
import assert from 'node:assert';
|
|
6
|
+
import { getConfig, getConfigSync, loadConfigToProcessEnv, preloadConfig, _resetConfigCache, _setS3Fetcher } from './config.js';
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
_resetConfigCache();
|
|
10
|
+
delete process.env.BLOCKS_CONFIG_BUCKET;
|
|
11
|
+
delete process.env.BLOCKS_CONFIG_KEY;
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
_resetConfigCache();
|
|
16
|
+
delete process.env.BLOCKS_CONFIG_BUCKET;
|
|
17
|
+
delete process.env.BLOCKS_CONFIG_KEY;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('config — no S3 env vars (local dev)', () => {
|
|
21
|
+
it('getConfig returns empty string when key not in env', async () => {
|
|
22
|
+
const result = await getConfig('SOME_KEY');
|
|
23
|
+
assert.strictEqual(result, '');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('getConfig returns env var value when set', async () => {
|
|
27
|
+
process.env.MY_KEY = 'my-value';
|
|
28
|
+
const result = await getConfig('MY_KEY');
|
|
29
|
+
assert.strictEqual(result, 'my-value');
|
|
30
|
+
delete process.env.MY_KEY;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('loadConfigToProcessEnv does not throw when no S3 vars', async () => {
|
|
34
|
+
await assert.doesNotReject(() => loadConfigToProcessEnv());
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('preloadConfig does not throw when no S3 vars', async () => {
|
|
38
|
+
await assert.doesNotReject(() => preloadConfig());
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('getConfigSync returns env var value', () => {
|
|
42
|
+
process.env.SYNC_KEY = 'sync-value';
|
|
43
|
+
const result = getConfigSync('SYNC_KEY');
|
|
44
|
+
assert.strictEqual(result, 'sync-value');
|
|
45
|
+
delete process.env.SYNC_KEY;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('getConfigSync returns empty string for unknown key', () => {
|
|
49
|
+
const result = getConfigSync('UNKNOWN');
|
|
50
|
+
assert.strictEqual(result, '');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('config — S3 load success', () => {
|
|
55
|
+
it('loads config from S3 and caches it', async () => {
|
|
56
|
+
process.env.BLOCKS_CONFIG_BUCKET = 'test-bucket';
|
|
57
|
+
process.env.BLOCKS_CONFIG_KEY = 'blocks-config.json';
|
|
58
|
+
|
|
59
|
+
let callCount = 0;
|
|
60
|
+
_setS3Fetcher(async () => {
|
|
61
|
+
callCount++;
|
|
62
|
+
return JSON.stringify({ DB_URL: 'postgres://localhost', QUEUE_ARN: 'arn:aws:sqs:us-east-1:123:queue' });
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const result = await getConfig('DB_URL');
|
|
66
|
+
assert.strictEqual(result, 'postgres://localhost');
|
|
67
|
+
|
|
68
|
+
const result2 = await getConfig('QUEUE_ARN');
|
|
69
|
+
assert.strictEqual(result2, 'arn:aws:sqs:us-east-1:123:queue');
|
|
70
|
+
|
|
71
|
+
assert.strictEqual(callCount, 1, 'S3 should only be called once (cached)');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('loadConfigToProcessEnv injects values into process.env', async () => {
|
|
75
|
+
process.env.BLOCKS_CONFIG_BUCKET = 'test-bucket';
|
|
76
|
+
process.env.BLOCKS_CONFIG_KEY = 'blocks-config.json';
|
|
77
|
+
|
|
78
|
+
_setS3Fetcher(async () => JSON.stringify({ INJECTED_KEY: 'injected-value' }));
|
|
79
|
+
|
|
80
|
+
await loadConfigToProcessEnv();
|
|
81
|
+
assert.strictEqual(process.env.INJECTED_KEY, 'injected-value');
|
|
82
|
+
delete process.env.INJECTED_KEY;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('loadConfigToProcessEnv does not override existing env vars', async () => {
|
|
86
|
+
process.env.BLOCKS_CONFIG_BUCKET = 'test-bucket';
|
|
87
|
+
process.env.BLOCKS_CONFIG_KEY = 'blocks-config.json';
|
|
88
|
+
process.env.EXISTING_KEY = 'original';
|
|
89
|
+
|
|
90
|
+
_setS3Fetcher(async () => JSON.stringify({ EXISTING_KEY: 'from-s3' }));
|
|
91
|
+
|
|
92
|
+
await loadConfigToProcessEnv();
|
|
93
|
+
assert.strictEqual(process.env.EXISTING_KEY, 'original');
|
|
94
|
+
delete process.env.EXISTING_KEY;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('getConfigSync returns cached value after load', async () => {
|
|
98
|
+
process.env.BLOCKS_CONFIG_BUCKET = 'test-bucket';
|
|
99
|
+
process.env.BLOCKS_CONFIG_KEY = 'blocks-config.json';
|
|
100
|
+
|
|
101
|
+
_setS3Fetcher(async () => JSON.stringify({ CACHED_KEY: 'cached-value' }));
|
|
102
|
+
|
|
103
|
+
await preloadConfig();
|
|
104
|
+
const result = getConfigSync('CACHED_KEY');
|
|
105
|
+
assert.strictEqual(result, 'cached-value');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('config — S3 errors', () => {
|
|
110
|
+
it('gracefully handles NoSuchKey (file not yet written)', async () => {
|
|
111
|
+
process.env.BLOCKS_CONFIG_BUCKET = 'test-bucket';
|
|
112
|
+
process.env.BLOCKS_CONFIG_KEY = 'blocks-config.json';
|
|
113
|
+
|
|
114
|
+
const noSuchKeyError = new Error('The specified key does not exist.');
|
|
115
|
+
(noSuchKeyError as any).name = 'NoSuchKey';
|
|
116
|
+
_setS3Fetcher(async () => { throw noSuchKeyError; });
|
|
117
|
+
|
|
118
|
+
const result = await getConfig('ANY_KEY');
|
|
119
|
+
assert.strictEqual(result, '', 'Should return empty string (empty config) on NoSuchKey');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('gracefully handles S3 404 status code', async () => {
|
|
123
|
+
process.env.BLOCKS_CONFIG_BUCKET = 'test-bucket';
|
|
124
|
+
process.env.BLOCKS_CONFIG_KEY = 'blocks-config.json';
|
|
125
|
+
|
|
126
|
+
const notFoundError = new Error('Not Found');
|
|
127
|
+
(notFoundError as any).$metadata = { httpStatusCode: 404 };
|
|
128
|
+
_setS3Fetcher(async () => { throw notFoundError; });
|
|
129
|
+
|
|
130
|
+
const result = await getConfig('ANY_KEY');
|
|
131
|
+
assert.strictEqual(result, '', 'Should return empty string (empty config) on 404');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('throws with clear message on S3 network error', async () => {
|
|
135
|
+
process.env.BLOCKS_CONFIG_BUCKET = 'test-bucket';
|
|
136
|
+
process.env.BLOCKS_CONFIG_KEY = 'blocks-config.json';
|
|
137
|
+
|
|
138
|
+
_setS3Fetcher(async () => { throw new Error('Network timeout'); });
|
|
139
|
+
|
|
140
|
+
await assert.rejects(
|
|
141
|
+
() => getConfig('ANY_KEY'),
|
|
142
|
+
(err: Error) => {
|
|
143
|
+
assert.ok(err.message.includes('[Blocks] Failed to load config from S3'));
|
|
144
|
+
assert.ok(err.message.includes('test-bucket/blocks-config.json'));
|
|
145
|
+
assert.ok(err.message.includes('Network timeout'));
|
|
146
|
+
return true;
|
|
147
|
+
},
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('throws on empty config file', async () => {
|
|
152
|
+
process.env.BLOCKS_CONFIG_BUCKET = 'test-bucket';
|
|
153
|
+
process.env.BLOCKS_CONFIG_KEY = 'blocks-config.json';
|
|
154
|
+
|
|
155
|
+
_setS3Fetcher(async () => { throw new Error('Empty config file'); });
|
|
156
|
+
|
|
157
|
+
await assert.rejects(
|
|
158
|
+
() => getConfig('ANY_KEY'),
|
|
159
|
+
(err: Error) => {
|
|
160
|
+
assert.ok(err.message.includes('[Blocks] Failed to load config from S3'));
|
|
161
|
+
assert.ok(err.message.includes('Empty config file'));
|
|
162
|
+
return true;
|
|
163
|
+
},
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('throws on malformed JSON', async () => {
|
|
168
|
+
process.env.BLOCKS_CONFIG_BUCKET = 'test-bucket';
|
|
169
|
+
process.env.BLOCKS_CONFIG_KEY = 'blocks-config.json';
|
|
170
|
+
|
|
171
|
+
_setS3Fetcher(async () => 'not valid json {{{');
|
|
172
|
+
|
|
173
|
+
await assert.rejects(
|
|
174
|
+
() => getConfig('ANY_KEY'),
|
|
175
|
+
(err: Error) => {
|
|
176
|
+
assert.ok(err.message.includes('[Blocks] Failed to load config from S3'));
|
|
177
|
+
return true;
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('throws when config is an array instead of object', async () => {
|
|
183
|
+
process.env.BLOCKS_CONFIG_BUCKET = 'test-bucket';
|
|
184
|
+
process.env.BLOCKS_CONFIG_KEY = 'blocks-config.json';
|
|
185
|
+
|
|
186
|
+
_setS3Fetcher(async () => '["not", "an", "object"]');
|
|
187
|
+
|
|
188
|
+
await assert.rejects(
|
|
189
|
+
() => getConfig('ANY_KEY'),
|
|
190
|
+
(err: Error) => {
|
|
191
|
+
assert.ok(err.message.includes('[Blocks] Config file must be a JSON object'));
|
|
192
|
+
return true;
|
|
193
|
+
},
|
|
194
|
+
);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('throws when config is null', async () => {
|
|
198
|
+
process.env.BLOCKS_CONFIG_BUCKET = 'test-bucket';
|
|
199
|
+
process.env.BLOCKS_CONFIG_KEY = 'blocks-config.json';
|
|
200
|
+
|
|
201
|
+
_setS3Fetcher(async () => 'null');
|
|
202
|
+
|
|
203
|
+
await assert.rejects(
|
|
204
|
+
() => getConfig('ANY_KEY'),
|
|
205
|
+
(err: Error) => {
|
|
206
|
+
assert.ok(err.message.includes('[Blocks] Config file must be a JSON object'));
|
|
207
|
+
return true;
|
|
208
|
+
},
|
|
209
|
+
);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe('config — concurrent access', () => {
|
|
214
|
+
it('deduplicates concurrent calls to the same S3 fetch', async () => {
|
|
215
|
+
process.env.BLOCKS_CONFIG_BUCKET = 'test-bucket';
|
|
216
|
+
process.env.BLOCKS_CONFIG_KEY = 'blocks-config.json';
|
|
217
|
+
|
|
218
|
+
let callCount = 0;
|
|
219
|
+
_setS3Fetcher(async () => {
|
|
220
|
+
callCount++;
|
|
221
|
+
await new Promise(r => setTimeout(r, 50));
|
|
222
|
+
return JSON.stringify({ KEY_A: 'value-a' });
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const [r1, r2, r3] = await Promise.all([
|
|
226
|
+
getConfig('KEY_A'),
|
|
227
|
+
getConfig('KEY_A'),
|
|
228
|
+
getConfig('KEY_A'),
|
|
229
|
+
]);
|
|
230
|
+
|
|
231
|
+
assert.strictEqual(r1, 'value-a');
|
|
232
|
+
assert.strictEqual(r2, 'value-a');
|
|
233
|
+
assert.strictEqual(r3, 'value-a');
|
|
234
|
+
assert.strictEqual(callCount, 1, 'S3 should only be called once even with concurrent access');
|
|
235
|
+
});
|
|
236
|
+
});
|