@bleedingdev/modern-js-app-tools-extensions 3.9.0-ultramodern.9 → 3.9.3-ultramodern.10
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 +23 -0
- package/dist/cjs/backend-federation-build/codegen.js +29 -18
- package/dist/cjs/backend-federation-build/config.js +84 -45
- package/dist/cjs/backend-federation-build/emit.js +63 -22
- package/dist/cjs/build-config/build-environment.js +30 -165
- package/dist/cjs/build-config/internal-effect-discovery.js +231 -0
- package/dist/cjs/build-config/public.js +2 -5
- package/dist/cjs/build-config/remote-address.js +58 -0
- package/dist/cjs/cloudflare/artifacts.js +8 -19
- package/dist/cjs/cloudflare/delivery-unit.js +125 -43
- package/dist/cjs/cloudflare/index.js +14 -4
- package/dist/cjs/cloudflare/native-renderer.js +100 -0
- package/dist/cjs/cloudflare/renderer-identity.js +106 -0
- package/dist/cjs/cloudflare/worker-manifest.js +54 -13
- package/dist/cjs/cloudflare/worker-options.js +48 -0
- package/dist/cjs/cloudflare/wrangler-config.js +210 -13
- package/dist/cjs/cloudflare-builder.js +307 -89
- package/dist/cjs/cloudflare-output-contract.js +36 -4
- package/dist/cjs/cloudflare-output-verifier/identity.js +112 -23
- package/dist/cjs/cloudflare-output-verifier/index.js +10 -0
- package/dist/cjs/cloudflare-output-verifier/manifest.js +46 -0
- package/dist/cjs/cloudflare-output-verifier/mutation-policy.js +7 -1
- package/dist/cjs/cloudflare-output-verifier/worker-bundles.js +10 -5
- package/dist/cjs/config.js +10 -5
- package/dist/cjs/deploy-output/npmAliases.js +92 -6
- package/dist/cjs/deploy-output/plugin.js +27 -6
- package/dist/cjs/deploy-output/public-assets.js +146 -0
- package/dist/cjs/deploy-output/target.js +57 -1
- package/dist/cjs/deploy-output/trace-files.js +99 -0
- package/dist/cjs/module-federation-config/exposes.js +70 -0
- package/dist/cjs/module-federation-config/index.js +73 -0
- package/dist/cjs/module-federation-config/inspect.js +103 -0
- package/dist/cjs/module-federation-config/object-literal.js +87 -0
- package/dist/cjs/module-federation-config/syntax.js +151 -0
- package/dist/cjs/module-federation-config/types.js +18 -0
- package/dist/cjs/native-type-checker.js +180 -17
- package/dist/cjs/policy-defaults.js +38 -29
- package/dist/cjs/release-envelope/canonical.js +2 -9
- package/dist/cjs/release-envelope/contract.js +68 -0
- package/dist/cjs/release-envelope/framework-output.js +296 -105
- package/dist/cjs/release-envelope/index.js +150 -36
- package/dist/cjs/release-envelope/plugin.js +24 -15
- package/dist/cjs/release-envelope/renderer-output-stamp.js +170 -0
- package/dist/cjs/release-envelope/types.js +10 -8
- package/dist/cjs/release-identity.js +9 -6
- package/dist/cjs/renderer-build-identity.js +144 -0
- package/dist/cjs/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
- package/dist/cjs/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
- package/dist/cjs/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
- package/dist/cjs/templates/cloudflare-entry.005-worker-dispatch.mjs +547 -103
- package/dist/cjs/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
- package/dist/cjs/templates/cloudflare-worker-route-data.mjs +1069 -0
- package/dist/cjs/workspace-topology.js +84 -0
- package/dist/esm/backend-federation-build/codegen.mjs +29 -18
- package/dist/esm/backend-federation-build/config.mjs +74 -38
- package/dist/esm/backend-federation-build/emit.mjs +64 -23
- package/dist/esm/build-config/build-environment.mjs +31 -163
- package/dist/esm/build-config/internal-effect-discovery.mjs +187 -0
- package/dist/esm/build-config/public.mjs +1 -1
- package/dist/esm/build-config/remote-address.mjs +20 -0
- package/dist/esm/cloudflare/artifacts.mjs +8 -19
- package/dist/esm/cloudflare/delivery-unit.mjs +124 -45
- package/dist/esm/cloudflare/index.mjs +15 -5
- package/dist/esm/cloudflare/native-renderer.mjs +48 -0
- package/dist/esm/cloudflare/renderer-identity.mjs +57 -0
- package/dist/esm/cloudflare/worker-manifest.mjs +55 -14
- package/dist/esm/cloudflare/worker-options.mjs +10 -0
- package/dist/esm/cloudflare/wrangler-config.mjs +203 -12
- package/dist/esm/cloudflare-builder.mjs +301 -89
- package/dist/esm/cloudflare-output-contract.mjs +29 -3
- package/dist/esm/cloudflare-output-verifier/identity.mjs +113 -24
- package/dist/esm/cloudflare-output-verifier/index.mjs +10 -0
- package/dist/esm/cloudflare-output-verifier/manifest.mjs +46 -0
- package/dist/esm/cloudflare-output-verifier/mutation-policy.mjs +7 -1
- package/dist/esm/cloudflare-output-verifier/worker-bundles.mjs +11 -6
- package/dist/esm/config.mjs +3 -1
- package/dist/esm/deploy-output/npmAliases.mjs +92 -6
- package/dist/esm/deploy-output/plugin.mjs +24 -6
- package/dist/esm/deploy-output/public-assets.mjs +82 -0
- package/dist/esm/deploy-output/target.mjs +50 -3
- package/dist/esm/deploy-output/trace-files.mjs +47 -0
- package/dist/esm/module-federation-config/exposes.mjs +18 -0
- package/dist/esm/module-federation-config/index.mjs +4 -0
- package/dist/esm/module-federation-config/inspect.mjs +65 -0
- package/dist/esm/module-federation-config/object-literal.mjs +43 -0
- package/dist/esm/module-federation-config/syntax.mjs +101 -0
- package/dist/esm/module-federation-config/types.mjs +0 -0
- package/dist/esm/native-type-checker.mjs +175 -18
- package/dist/esm/policy-defaults.mjs +36 -30
- package/dist/esm/release-envelope/canonical.mjs +2 -9
- package/dist/esm/release-envelope/contract.mjs +21 -0
- package/dist/esm/release-envelope/framework-output.mjs +297 -106
- package/dist/esm/release-envelope/index.mjs +149 -38
- package/dist/esm/release-envelope/plugin.mjs +24 -15
- package/dist/esm/release-envelope/renderer-output-stamp.mjs +112 -0
- package/dist/esm/release-envelope/types.mjs +2 -3
- package/dist/esm/release-identity.mjs +9 -6
- package/dist/esm/renderer-build-identity.mjs +92 -0
- package/dist/esm/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
- package/dist/esm/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
- package/dist/esm/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
- package/dist/esm/templates/cloudflare-entry.005-worker-dispatch.mjs +547 -103
- package/dist/esm/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
- package/dist/esm/templates/cloudflare-worker-route-data.mjs +1069 -0
- package/dist/esm/workspace-topology.mjs +21 -0
- package/dist/esm-node/backend-federation-build/codegen.mjs +29 -18
- package/dist/esm-node/backend-federation-build/config.mjs +74 -38
- package/dist/esm-node/backend-federation-build/emit.mjs +64 -23
- package/dist/esm-node/build-config/build-environment.mjs +31 -163
- package/dist/esm-node/build-config/internal-effect-discovery.mjs +188 -0
- package/dist/esm-node/build-config/public.mjs +1 -1
- package/dist/esm-node/build-config/remote-address.mjs +21 -0
- package/dist/esm-node/cloudflare/artifacts.mjs +8 -19
- package/dist/esm-node/cloudflare/delivery-unit.mjs +124 -45
- package/dist/esm-node/cloudflare/index.mjs +15 -5
- package/dist/esm-node/cloudflare/native-renderer.mjs +49 -0
- package/dist/esm-node/cloudflare/renderer-identity.mjs +58 -0
- package/dist/esm-node/cloudflare/worker-manifest.mjs +55 -14
- package/dist/esm-node/cloudflare/worker-options.mjs +11 -0
- package/dist/esm-node/cloudflare/wrangler-config.mjs +203 -12
- package/dist/esm-node/cloudflare-builder.mjs +301 -89
- package/dist/esm-node/cloudflare-output-contract.mjs +29 -3
- package/dist/esm-node/cloudflare-output-verifier/identity.mjs +113 -24
- package/dist/esm-node/cloudflare-output-verifier/index.mjs +10 -0
- package/dist/esm-node/cloudflare-output-verifier/manifest.mjs +46 -0
- package/dist/esm-node/cloudflare-output-verifier/mutation-policy.mjs +7 -1
- package/dist/esm-node/cloudflare-output-verifier/worker-bundles.mjs +11 -6
- package/dist/esm-node/config.mjs +3 -1
- package/dist/esm-node/deploy-output/npmAliases.mjs +92 -6
- package/dist/esm-node/deploy-output/plugin.mjs +24 -6
- package/dist/esm-node/deploy-output/public-assets.mjs +83 -0
- package/dist/esm-node/deploy-output/target.mjs +50 -3
- package/dist/esm-node/deploy-output/trace-files.mjs +48 -0
- package/dist/esm-node/module-federation-config/exposes.mjs +19 -0
- package/dist/esm-node/module-federation-config/index.mjs +5 -0
- package/dist/esm-node/module-federation-config/inspect.mjs +66 -0
- package/dist/esm-node/module-federation-config/object-literal.mjs +44 -0
- package/dist/esm-node/module-federation-config/syntax.mjs +102 -0
- package/dist/esm-node/module-federation-config/types.mjs +1 -0
- package/dist/esm-node/native-type-checker.mjs +175 -18
- package/dist/esm-node/policy-defaults.mjs +36 -30
- package/dist/esm-node/release-envelope/canonical.mjs +2 -9
- package/dist/esm-node/release-envelope/contract.mjs +22 -0
- package/dist/esm-node/release-envelope/framework-output.mjs +297 -106
- package/dist/esm-node/release-envelope/index.mjs +149 -38
- package/dist/esm-node/release-envelope/plugin.mjs +24 -15
- package/dist/esm-node/release-envelope/renderer-output-stamp.mjs +113 -0
- package/dist/esm-node/release-envelope/types.mjs +2 -3
- package/dist/esm-node/release-identity.mjs +9 -6
- package/dist/esm-node/renderer-build-identity.mjs +93 -0
- package/dist/esm-node/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
- package/dist/esm-node/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
- package/dist/esm-node/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
- package/dist/esm-node/templates/cloudflare-entry.005-worker-dispatch.mjs +547 -103
- package/dist/esm-node/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
- package/dist/esm-node/templates/cloudflare-worker-route-data.mjs +1069 -0
- package/dist/esm-node/workspace-topology.mjs +22 -0
- package/dist/types/backend-federation-build/codegen.d.ts +30 -0
- package/dist/types/backend-federation-build/config.d.ts +10 -57
- package/dist/types/backend-federation-build/emit.d.ts +12 -2
- package/dist/types/backend-federation-build/index.d.ts +1 -1
- package/dist/types/build-config/build-environment.d.ts +4 -10
- package/dist/types/build-config/internal-effect-discovery.d.ts +6 -0
- package/dist/types/build-config/public.d.ts +1 -1
- package/dist/types/build-config/remote-address.d.ts +3 -0
- package/dist/types/cloudflare/artifacts.d.ts +7 -6
- package/dist/types/cloudflare/constants.d.ts +1 -1
- package/dist/types/cloudflare/delivery-unit.d.ts +38 -7
- package/dist/types/cloudflare/native-renderer.d.ts +30 -0
- package/dist/types/cloudflare/renderer-identity.d.ts +25 -0
- package/dist/types/cloudflare/worker-manifest.d.ts +20 -4
- package/dist/types/cloudflare/worker-options.d.ts +20 -0
- package/dist/types/cloudflare/wrangler-config.d.ts +55 -2
- package/dist/types/cloudflare-builder.d.ts +51 -8
- package/dist/types/cloudflare-output-contract.d.ts +5 -3
- package/dist/types/cloudflare-output-verifier/identity.d.ts +7 -3
- package/dist/types/cloudflare-output-verifier/worker-bundles.d.ts +2 -2
- package/dist/types/config.d.ts +102 -13
- package/dist/types/deploy-output/plugin.d.ts +29 -9
- package/dist/types/deploy-output/public-assets.d.ts +51 -0
- package/dist/types/deploy-output/target.d.ts +28 -5
- package/dist/types/deploy-output/trace-files.d.ts +17 -0
- package/dist/types/module-federation-config/exposes.d.ts +11 -0
- package/dist/types/module-federation-config/index.d.ts +5 -0
- package/dist/types/module-federation-config/inspect.d.ts +2 -0
- package/dist/types/module-federation-config/object-literal.d.ts +4 -0
- package/dist/types/module-federation-config/syntax.d.ts +7 -0
- package/dist/types/module-federation-config/types.d.ts +20 -0
- package/dist/types/native-type-checker.d.ts +28 -3
- package/dist/types/policy-defaults.d.ts +12 -2
- package/dist/types/release-envelope/canonical.d.ts +5 -5
- package/dist/types/release-envelope/contract.d.mts +8 -0
- package/dist/types/release-envelope/framework-output.d.ts +23 -13
- package/dist/types/release-envelope/index.d.ts +17 -5
- package/dist/types/release-envelope/plugin.d.ts +10 -6
- package/dist/types/release-envelope/renderer-output-stamp.d.ts +34 -0
- package/dist/types/release-envelope/types.d.ts +42 -4
- package/dist/types/release-identity.d.ts +13 -2
- package/dist/types/renderer-build-identity.d.ts +41 -0
- package/dist/types/templates/cloudflare-worker-route-data.d.mts +14 -0
- package/dist/types/workspace-topology.d.ts +91 -0
- package/package.json +87 -27
- package/src/backend-federation-build/codegen.ts +28 -19
- package/src/backend-federation-build/config.ts +189 -123
- package/src/backend-federation-build/emit.ts +119 -38
- package/src/backend-federation-build/index.ts +4 -1
- package/src/build-config/build-environment.ts +69 -293
- package/src/build-config/internal-effect-discovery.ts +458 -0
- package/src/build-config/public.ts +0 -1
- package/src/build-config/remote-address.ts +33 -0
- package/src/cloudflare/artifacts.ts +22 -43
- package/src/cloudflare/delivery-unit.ts +283 -60
- package/src/cloudflare/index.ts +35 -19
- package/src/cloudflare/native-renderer.ts +117 -0
- package/src/cloudflare/renderer-identity.ts +171 -0
- package/src/cloudflare/worker-manifest.ts +115 -13
- package/src/cloudflare/worker-options.ts +60 -0
- package/src/cloudflare/wrangler-config.ts +390 -14
- package/src/cloudflare-builder.ts +543 -170
- package/src/cloudflare-output-contract.ts +45 -2
- package/src/cloudflare-output-verifier/identity.ts +138 -7
- package/src/cloudflare-output-verifier/index.ts +20 -0
- package/src/cloudflare-output-verifier/manifest.ts +77 -0
- package/src/cloudflare-output-verifier/mutation-policy.ts +11 -1
- package/src/cloudflare-output-verifier/worker-bundles.ts +27 -11
- package/src/config.ts +105 -19
- package/src/deploy-output/npmAliases.ts +141 -4
- package/src/deploy-output/plugin.ts +55 -18
- package/src/deploy-output/public-assets.ts +224 -0
- package/src/deploy-output/target.ts +97 -14
- package/src/deploy-output/trace-files.ts +80 -0
- package/src/module-federation-config/exposes.ts +46 -0
- package/src/module-federation-config/index.ts +22 -0
- package/src/module-federation-config/inspect.ts +157 -0
- package/src/module-federation-config/object-literal.ts +76 -0
- package/src/module-federation-config/syntax.ts +212 -0
- package/src/module-federation-config/types.ts +22 -0
- package/src/native-type-checker.ts +284 -27
- package/src/policy-defaults.ts +47 -17
- package/src/release-envelope/canonical.ts +6 -18
- package/src/release-envelope/contract.mjs +28 -0
- package/src/release-envelope/framework-output.ts +681 -156
- package/src/release-envelope/index.ts +315 -60
- package/src/release-envelope/plugin.ts +54 -38
- package/src/release-envelope/renderer-output-stamp.ts +210 -0
- package/src/release-envelope/types.ts +75 -6
- package/src/release-identity.ts +30 -4
- package/src/renderer-build-identity.ts +208 -0
- package/src/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
- package/src/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
- package/src/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
- package/src/templates/cloudflare-entry.005-worker-dispatch.mjs +547 -103
- package/src/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
- package/src/templates/cloudflare-worker-route-data.mjs +1069 -0
- package/src/workspace-topology.ts +125 -0
- package/dist/cjs/templates/cloudflare-worker-fs-promises.mjs +0 -11
- package/dist/cjs/templates/cloudflare-worker-loadable-server.mjs +0 -281
- package/dist/cjs/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
- package/dist/cjs/templates/cloudflare-worker-path.mjs +0 -98
- package/dist/esm/templates/cloudflare-worker-fs-promises.mjs +0 -11
- package/dist/esm/templates/cloudflare-worker-loadable-server.mjs +0 -281
- package/dist/esm/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
- package/dist/esm/templates/cloudflare-worker-path.mjs +0 -98
- package/dist/esm-node/templates/cloudflare-worker-fs-promises.mjs +0 -11
- package/dist/esm-node/templates/cloudflare-worker-loadable-server.mjs +0 -281
- package/dist/esm-node/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
- package/dist/esm-node/templates/cloudflare-worker-path.mjs +0 -98
- package/dist/types/templates/cloudflare-worker-fs-promises.d.mts +0 -5
- package/dist/types/templates/cloudflare-worker-loadable-server.d.mts +0 -48
- package/dist/types/templates/cloudflare-worker-mf-ssr-runtime-plugin.d.mts +0 -13
- package/dist/types/templates/cloudflare-worker-path.d.mts +0 -21
- package/src/templates/cloudflare-worker-fs-promises.mjs +0 -11
- package/src/templates/cloudflare-worker-loadable-server.mjs +0 -281
- package/src/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
- package/src/templates/cloudflare-worker-path.mjs +0 -98
|
@@ -12,6 +12,109 @@ async function loadWorkerModule(workerPath) {
|
|
|
12
12
|
return workerModulePromises.get(workerPath);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
function createWorkerRendererGuardResponse(request) {
|
|
16
|
+
const deliveryUnit = MODERN_WORKER_MANIFEST.deliveryUnit;
|
|
17
|
+
const surfaces = deliveryUnit?.surfaces;
|
|
18
|
+
// API identity is universal. Only an explicit UI surface selects a renderer.
|
|
19
|
+
if (!surfaces || !Object.hasOwn(surfaces, 'ui')) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const ui = surfaces.ui;
|
|
24
|
+
const identity = ui?.rendererIdentity;
|
|
25
|
+
const profile = ui?.rendererProfile;
|
|
26
|
+
// The built renderer's adapter decides whether documents are native.
|
|
27
|
+
const builtRenderer = MODERN_WORKER_MANIFEST.renderer;
|
|
28
|
+
const isNative =
|
|
29
|
+
builtRenderer?.nativeDocuments === true &&
|
|
30
|
+
[identity?.renderer, profile?.renderer].includes(builtRenderer.name);
|
|
31
|
+
if (
|
|
32
|
+
isNative &&
|
|
33
|
+
(request.headers.has('x-rsc-tree') || request.headers.has('x-rsc-action'))
|
|
34
|
+
) {
|
|
35
|
+
return Response.json(
|
|
36
|
+
{ code: 'unsupported-renderer-capability', capability: 'rsc' },
|
|
37
|
+
{ status: 400, headers: { 'cache-control': 'no-store' } },
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const isRecord = value =>
|
|
42
|
+
value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
43
|
+
const isCanonicalString = value =>
|
|
44
|
+
typeof value === 'string' && value.length > 0 && value.trim() === value;
|
|
45
|
+
const hasExactFields = (value, fields) =>
|
|
46
|
+
isRecord(value) &&
|
|
47
|
+
Object.keys(value).every(field => fields.includes(field)) &&
|
|
48
|
+
fields.every(field => Object.hasOwn(value, field));
|
|
49
|
+
const exactVersion =
|
|
50
|
+
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/u;
|
|
51
|
+
const isPackageIdentity = (value, router = false, hydration = false) => {
|
|
52
|
+
const fields = router
|
|
53
|
+
? ['name', 'version', 'coreName', 'coreVersion']
|
|
54
|
+
: ['name', 'version'];
|
|
55
|
+
return (
|
|
56
|
+
hasExactFields(value, fields) &&
|
|
57
|
+
isCanonicalString(value.name) &&
|
|
58
|
+
(!router || isCanonicalString(value.coreName)) &&
|
|
59
|
+
(router ? ['version', 'coreVersion'] : ['version']).every(
|
|
60
|
+
field =>
|
|
61
|
+
isCanonicalString(value[field]) &&
|
|
62
|
+
(exactVersion.test(value[field]) ||
|
|
63
|
+
(hydration && /^[1-9]\d*$/u.test(value[field]))),
|
|
64
|
+
)
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
const validMetadata =
|
|
68
|
+
isRecord(ui) &&
|
|
69
|
+
ui.surface === 'ui' &&
|
|
70
|
+
hasExactFields(identity, [
|
|
71
|
+
'renderer',
|
|
72
|
+
'appId',
|
|
73
|
+
'entryName',
|
|
74
|
+
'protocolVersion',
|
|
75
|
+
'buildId',
|
|
76
|
+
]) &&
|
|
77
|
+
identity.renderer === builtRenderer?.name &&
|
|
78
|
+
identity.protocolVersion === 1 &&
|
|
79
|
+
['appId', 'entryName', 'buildId'].every(field =>
|
|
80
|
+
isCanonicalString(identity[field]),
|
|
81
|
+
) &&
|
|
82
|
+
identity.appId === ui.appId &&
|
|
83
|
+
ui.appId === deliveryUnit.appId &&
|
|
84
|
+
identity.buildId === ui.buildMarker &&
|
|
85
|
+
ui.buildMarker === deliveryUnit.buildMarker &&
|
|
86
|
+
hasExactFields(profile, [
|
|
87
|
+
'renderer',
|
|
88
|
+
'protocolVersion',
|
|
89
|
+
'compiler',
|
|
90
|
+
'hydration',
|
|
91
|
+
'router',
|
|
92
|
+
]) &&
|
|
93
|
+
profile.renderer === identity.renderer &&
|
|
94
|
+
profile.protocolVersion === identity.protocolVersion &&
|
|
95
|
+
isPackageIdentity(profile.compiler) &&
|
|
96
|
+
isPackageIdentity(profile.hydration, false, true) &&
|
|
97
|
+
isPackageIdentity(profile.router, true);
|
|
98
|
+
|
|
99
|
+
if (!validMetadata) {
|
|
100
|
+
return Response.json(
|
|
101
|
+
{ code: 'invalid-renderer-metadata' },
|
|
102
|
+
{ status: 500, headers: { 'cache-control': 'no-store' } },
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
if (isNative && !hasNativeWorkerRenderer(identity.renderer)) {
|
|
106
|
+
return Response.json(
|
|
107
|
+
{
|
|
108
|
+
code: 'unsupported-renderer-capability',
|
|
109
|
+
capability: 'cloudflare-worker',
|
|
110
|
+
renderer: identity.renderer,
|
|
111
|
+
},
|
|
112
|
+
{ status: 501, headers: { 'cache-control': 'no-store' } },
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
15
118
|
function getRuntimeModule(workerModule) {
|
|
16
119
|
const defaultExport = workerModule.default;
|
|
17
120
|
const nestedDefaultExport =
|
|
@@ -52,7 +155,266 @@ async function getRequestHandler(workerModule) {
|
|
|
52
155
|
);
|
|
53
156
|
}
|
|
54
157
|
|
|
158
|
+
function hasNativeWorkerRenderer(renderer) {
|
|
159
|
+
return MODERN_WORKER_MANIFEST.nativeRenderer?.renderer === renderer;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function getNativeRouteIdentity(route) {
|
|
163
|
+
const identities = MODERN_WORKER_MANIFEST.rendererIdentities;
|
|
164
|
+
const identity =
|
|
165
|
+
identities && Object.hasOwn(identities, route.entryName)
|
|
166
|
+
? identities[route.entryName]
|
|
167
|
+
: undefined;
|
|
168
|
+
// The built native-document renderer, or one that shipped native resources.
|
|
169
|
+
const builtRenderer = MODERN_WORKER_MANIFEST.renderer;
|
|
170
|
+
return identity &&
|
|
171
|
+
((builtRenderer?.nativeDocuments === true &&
|
|
172
|
+
identity.renderer === builtRenderer.name) ||
|
|
173
|
+
hasNativeWorkerRenderer(identity.renderer))
|
|
174
|
+
? identity
|
|
175
|
+
: undefined;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const ROUTE_DATA_REQUEST_PARAM = '__loader';
|
|
179
|
+
|
|
180
|
+
// Route data requests (`?__loader=`) are answered by the entry's route data
|
|
181
|
+
// worker, like the Node server's data handler. Without a match the request
|
|
182
|
+
// falls through to page rendering, as it does on Node.
|
|
183
|
+
async function dispatchRouteDataRequest(route, request) {
|
|
184
|
+
const routeDataWorkerPath = route.routeDataWorker;
|
|
185
|
+
// Native entries answer their own data protocol through the native
|
|
186
|
+
// dispatcher; the legacy route-data worker speaks the React encoding.
|
|
187
|
+
if (
|
|
188
|
+
!routeDataWorkerPath ||
|
|
189
|
+
getNativeRouteIdentity(route) ||
|
|
190
|
+
!new URL(request.url).searchParams.has(ROUTE_DATA_REQUEST_PARAM)
|
|
191
|
+
) {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const routeDataWorkerModule = await loadWorkerModule(routeDataWorkerPath);
|
|
196
|
+
const handleRouteDataRequest = routeDataWorkerModule
|
|
197
|
+
? getRuntimeModule(routeDataWorkerModule).handleRouteDataRequest
|
|
198
|
+
: undefined;
|
|
199
|
+
|
|
200
|
+
if (typeof handleRouteDataRequest !== 'function') {
|
|
201
|
+
return new Response(
|
|
202
|
+
`Route data worker bundle has no handleRouteDataRequest export: ${routeDataWorkerPath}`,
|
|
203
|
+
{
|
|
204
|
+
status: 500,
|
|
205
|
+
headers: {
|
|
206
|
+
'content-type': 'text/plain; charset=utf-8',
|
|
207
|
+
'x-modern-js-route-data-worker': routeDataWorkerPath,
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return handleRouteDataRequest({
|
|
214
|
+
request,
|
|
215
|
+
serverRoutes: MODERN_WORKER_MANIFEST.routeSpec.routes,
|
|
216
|
+
context: {
|
|
217
|
+
loaderContext: new Map(),
|
|
218
|
+
monitors: createNoopMonitors(),
|
|
219
|
+
reporter: {
|
|
220
|
+
reportTiming: () => {},
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
onTiming() {},
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
55
227
|
async function dispatchRouteWorker(route, request, env, ctx) {
|
|
228
|
+
const rendererRejection = createWorkerRendererGuardResponse(request);
|
|
229
|
+
if (rendererRejection) return rendererRejection;
|
|
230
|
+
|
|
231
|
+
const nativeIdentity = getNativeRouteIdentity(route);
|
|
232
|
+
return withWorkerRendererIdentity(
|
|
233
|
+
nativeIdentity
|
|
234
|
+
? applyRouteResponseHeaders(
|
|
235
|
+
await invokeNativeRouteWorker(
|
|
236
|
+
route,
|
|
237
|
+
nativeIdentity,
|
|
238
|
+
request,
|
|
239
|
+
env,
|
|
240
|
+
ctx,
|
|
241
|
+
),
|
|
242
|
+
route,
|
|
243
|
+
)
|
|
244
|
+
: await invokeRouteWorker(route, request, env, ctx),
|
|
245
|
+
route,
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const CUMULATIVE_RESPONSE_HEADERS = new Set([
|
|
250
|
+
'content-security-policy',
|
|
251
|
+
'content-security-policy-report-only',
|
|
252
|
+
'link',
|
|
253
|
+
'server-timing',
|
|
254
|
+
'set-cookie',
|
|
255
|
+
]);
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Merge a route's configured `responseHeaders` into a native response, as the
|
|
259
|
+
* Node host does: CSP, Link, Server-Timing and cookies append, `Vary` unions, the
|
|
260
|
+
* renderer's Content-Type stays, and other fields take the configured value.
|
|
261
|
+
*/
|
|
262
|
+
function applyRouteResponseHeaders(response, route) {
|
|
263
|
+
const configured = Object.entries(route.responseHeaders ?? {});
|
|
264
|
+
if (configured.length === 0) return response;
|
|
265
|
+
const headers = new Headers(response.headers);
|
|
266
|
+
for (const [rawName, rawValue] of configured) {
|
|
267
|
+
const name = rawName.toLowerCase();
|
|
268
|
+
const value = String(rawValue);
|
|
269
|
+
// The renderer owns its body framing and content coding.
|
|
270
|
+
if (
|
|
271
|
+
name === 'content-length' ||
|
|
272
|
+
name === 'transfer-encoding' ||
|
|
273
|
+
name === 'content-encoding'
|
|
274
|
+
)
|
|
275
|
+
continue;
|
|
276
|
+
if (CUMULATIVE_RESPONSE_HEADERS.has(name)) headers.append(name, value);
|
|
277
|
+
else if (name === 'vary') {
|
|
278
|
+
const fields = new Map();
|
|
279
|
+
for (const field of `${headers.get('vary') ?? ''},${value}`.split(',')) {
|
|
280
|
+
const item = field.trim();
|
|
281
|
+
if (item && !fields.has(item.toLowerCase()))
|
|
282
|
+
fields.set(item.toLowerCase(), item);
|
|
283
|
+
}
|
|
284
|
+
headers.set('vary', [...fields.values()].join(', '));
|
|
285
|
+
} else if (name !== 'content-type' || !headers.has(name))
|
|
286
|
+
headers.set(name, value);
|
|
287
|
+
}
|
|
288
|
+
return new Response(response.body, {
|
|
289
|
+
headers,
|
|
290
|
+
status: response.status,
|
|
291
|
+
statusText: response.statusText,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function createWorkerRendererErrorResponse(code, entryName) {
|
|
296
|
+
return Response.json(
|
|
297
|
+
{ code, entryName: String(entryName) },
|
|
298
|
+
{ status: 500, headers: { 'cache-control': 'no-store' } },
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Native-document renderers bundle their server handler with the renderer-core
|
|
303
|
+
// worker dispatcher. Build-validated document inputs come from the manifest.
|
|
304
|
+
async function invokeNativeRouteWorker(route, identity, request, env, ctx) {
|
|
305
|
+
const nativeRenderer = MODERN_WORKER_MANIFEST.nativeRenderer;
|
|
306
|
+
const resources =
|
|
307
|
+
nativeRenderer?.renderer === identity.renderer &&
|
|
308
|
+
Object.hasOwn(nativeRenderer.entries, route.entryName)
|
|
309
|
+
? {
|
|
310
|
+
...nativeRenderer.entries[route.entryName],
|
|
311
|
+
...(Object.hasOwn(nativeRenderer, 'workerFederation')
|
|
312
|
+
? { workerFederation: nativeRenderer.workerFederation }
|
|
313
|
+
: {}),
|
|
314
|
+
...(Object.hasOwn(nativeRenderer, 'workerFederationManifestUrl')
|
|
315
|
+
? {
|
|
316
|
+
workerFederationManifestUrl:
|
|
317
|
+
nativeRenderer.workerFederationManifestUrl,
|
|
318
|
+
}
|
|
319
|
+
: {}),
|
|
320
|
+
...(Object.hasOwn(nativeRenderer, 'workerFederationServices')
|
|
321
|
+
? {
|
|
322
|
+
workerFederationServices:
|
|
323
|
+
nativeRenderer.workerFederationServices,
|
|
324
|
+
}
|
|
325
|
+
: {}),
|
|
326
|
+
}
|
|
327
|
+
: undefined;
|
|
328
|
+
// Native renderers have no Flight transport. Reject before the bundle loads.
|
|
329
|
+
if (
|
|
330
|
+
request.headers.has('x-rsc-tree') ||
|
|
331
|
+
request.headers.has('x-rsc-action')
|
|
332
|
+
) {
|
|
333
|
+
return Response.json(
|
|
334
|
+
{ code: 'unsupported-renderer-capability', capability: 'rsc' },
|
|
335
|
+
{ status: 400, headers: { 'cache-control': 'no-store' } },
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
if (!resources) {
|
|
339
|
+
return createWorkerRendererErrorResponse(
|
|
340
|
+
'missing-native-renderer-resources',
|
|
341
|
+
route.entryName,
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
try {
|
|
345
|
+
const workerModule = route.worker
|
|
346
|
+
? await loadWorkerModule(route.worker)
|
|
347
|
+
: undefined;
|
|
348
|
+
if (
|
|
349
|
+
!workerModule ||
|
|
350
|
+
typeof workerModule.dispatchNativeWorkerRequest !== 'function'
|
|
351
|
+
) {
|
|
352
|
+
return createWorkerRendererErrorResponse(
|
|
353
|
+
'missing-native-worker-bundle',
|
|
354
|
+
route.entryName,
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
return await workerModule.dispatchNativeWorkerRequest(request, {
|
|
358
|
+
identity,
|
|
359
|
+
bundle: workerModule,
|
|
360
|
+
resources,
|
|
361
|
+
bindings: env,
|
|
362
|
+
executionContext: ctx,
|
|
363
|
+
});
|
|
364
|
+
} catch (error) {
|
|
365
|
+
// A bundle evaluation or pre-commit native failure must not escape fetch.
|
|
366
|
+
console.error(error);
|
|
367
|
+
return createWorkerRendererErrorResponse(
|
|
368
|
+
'native-render-failed',
|
|
369
|
+
route.entryName,
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* The Node host's ASCII-only identity header bytes: fixed field order and
|
|
376
|
+
* non-ASCII code units escaped, since Fetch header values are ByteStrings.
|
|
377
|
+
*/
|
|
378
|
+
function serializeRendererIdentityHeader(identity) {
|
|
379
|
+
return JSON.stringify({
|
|
380
|
+
renderer: identity.renderer,
|
|
381
|
+
appId: identity.appId,
|
|
382
|
+
entryName: identity.entryName,
|
|
383
|
+
protocolVersion: identity.protocolVersion,
|
|
384
|
+
buildId: identity.buildId,
|
|
385
|
+
}).replace(/[\u007f-\u{10ffff}]/gu, character =>
|
|
386
|
+
Array.from(
|
|
387
|
+
{ length: character.length },
|
|
388
|
+
(_, index) =>
|
|
389
|
+
`\\u${character.charCodeAt(index).toString(16).padStart(4, '0')}`,
|
|
390
|
+
).join(''),
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function withWorkerRendererIdentity(response, route) {
|
|
395
|
+
const identities = MODERN_WORKER_MANIFEST.rendererIdentities;
|
|
396
|
+
if (!identities) return response;
|
|
397
|
+
if (!Object.hasOwn(identities, route.entryName)) {
|
|
398
|
+
// Do not let a manifest/route mismatch escape fetch and fail the isolate.
|
|
399
|
+
response.body?.cancel().catch(() => {});
|
|
400
|
+
return createWorkerRendererErrorResponse(
|
|
401
|
+
'missing-renderer-identity',
|
|
402
|
+
route.entryName,
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
const headers = new Headers(response.headers);
|
|
406
|
+
headers.set(
|
|
407
|
+
'x-ultramodern-renderer-identity',
|
|
408
|
+
serializeRendererIdentityHeader(identities[route.entryName]),
|
|
409
|
+
);
|
|
410
|
+
return new Response(response.body, {
|
|
411
|
+
headers,
|
|
412
|
+
status: response.status,
|
|
413
|
+
statusText: response.statusText,
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async function invokeRouteWorker(route, request, env, ctx) {
|
|
56
418
|
const workerPath = route.worker;
|
|
57
419
|
if (!workerPath) {
|
|
58
420
|
return new Response('Worker bundle not configured for SSR route', {
|
|
@@ -81,6 +443,30 @@ async function dispatchRouteWorker(route, request, env, ctx) {
|
|
|
81
443
|
return fetchHandler(request, env, ctx);
|
|
82
444
|
}
|
|
83
445
|
|
|
446
|
+
const runtime = getRuntimeModule(workerModule);
|
|
447
|
+
// Match the native server dispatcher: actions precede Flight trees, and
|
|
448
|
+
// neither path may fall back to HTML rendering or rewrite the Request body.
|
|
449
|
+
if (request.headers.get('x-rsc-action')) {
|
|
450
|
+
const handleAction = await runtime.handleAction;
|
|
451
|
+
if (typeof handleAction !== 'function') {
|
|
452
|
+
return new Response('Cannot find server action handler', { status: 500 });
|
|
453
|
+
}
|
|
454
|
+
return handleAction(request);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (request.headers.get('x-rsc-tree')) {
|
|
458
|
+
const rscPayloadHandler = await runtime.rscPayloadHandler;
|
|
459
|
+
if (typeof rscPayloadHandler !== 'function') {
|
|
460
|
+
return new Response('Cannot find request handler for RSC', {
|
|
461
|
+
status: 500,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
return rscPayloadHandler(
|
|
465
|
+
request,
|
|
466
|
+
await getRequestHandlerOptions(route, request, env, ctx, false),
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
|
|
84
470
|
const requestHandler = await getRequestHandler(workerModule);
|
|
85
471
|
|
|
86
472
|
if (typeof requestHandler === 'function') {
|
|
@@ -88,6 +474,7 @@ async function dispatchRouteWorker(route, request, env, ctx) {
|
|
|
88
474
|
route,
|
|
89
475
|
request,
|
|
90
476
|
env,
|
|
477
|
+
ctx,
|
|
91
478
|
);
|
|
92
479
|
|
|
93
480
|
return withRouteCssLinks(
|
|
@@ -96,9 +483,8 @@ async function dispatchRouteWorker(route, request, env, ctx) {
|
|
|
96
483
|
requestHandlerOptions.resource.routeManifest,
|
|
97
484
|
request,
|
|
98
485
|
env,
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
]?.getStylesheetHrefs(),
|
|
486
|
+
undefined,
|
|
487
|
+
requestHandlerOptions.locals[DISTRIBUTED_SSR_FRAGMENTS_LOCALS_KEY],
|
|
102
488
|
);
|
|
103
489
|
}
|
|
104
490
|
|
|
@@ -114,14 +500,46 @@ async function dispatchRouteWorker(route, request, env, ctx) {
|
|
|
114
500
|
);
|
|
115
501
|
}
|
|
116
502
|
|
|
117
|
-
|
|
503
|
+
// Worker Static Assets decode percent-encoded paths, so an encoded spelling
|
|
504
|
+
// of a mounted prefix (`/%70refix/...`, `/prefix%2F...`) would otherwise skip
|
|
505
|
+
// the BFF or service binding that owns the prefix and reach ASSETS directly.
|
|
506
|
+
// Resolve such spellings to the canonical pathname the owner sees; a
|
|
507
|
+
// backslash or undecodable spelling becomes a path the owner rejects.
|
|
508
|
+
function resolvePrefixPathname(pathname, prefix) {
|
|
118
509
|
if (!prefix || prefix === '/') {
|
|
119
|
-
return
|
|
510
|
+
return pathname;
|
|
120
511
|
}
|
|
121
512
|
|
|
122
513
|
const normalized = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;
|
|
514
|
+
let candidate = pathname;
|
|
515
|
+
|
|
516
|
+
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
517
|
+
if (candidate === normalized || candidate.startsWith(`${normalized}/`)) {
|
|
518
|
+
return candidate;
|
|
519
|
+
}
|
|
520
|
+
if (candidate.startsWith(`${normalized}\\`)) {
|
|
521
|
+
return `${normalized}/__invalid_encoded_path__`;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
let decoded;
|
|
525
|
+
try {
|
|
526
|
+
decoded = decodeURIComponent(candidate);
|
|
527
|
+
} catch {
|
|
528
|
+
return candidate.toLowerCase().startsWith(normalized.toLowerCase())
|
|
529
|
+
? `${normalized}/__invalid_encoded_path__`
|
|
530
|
+
: null;
|
|
531
|
+
}
|
|
532
|
+
if (decoded === candidate) {
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
candidate = decoded;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
123
540
|
|
|
124
|
-
|
|
541
|
+
function matchesPrefix(pathname, prefix) {
|
|
542
|
+
return resolvePrefixPathname(pathname, prefix) !== null;
|
|
125
543
|
}
|
|
126
544
|
|
|
127
545
|
function createRequestForMountedPrefix(request, prefix) {
|
|
@@ -131,12 +549,13 @@ function createRequestForMountedPrefix(request, prefix) {
|
|
|
131
549
|
|
|
132
550
|
const url = new URL(request.url);
|
|
133
551
|
const normalized = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;
|
|
552
|
+
const matchedPathname = resolvePrefixPathname(url.pathname, normalized);
|
|
134
553
|
|
|
135
|
-
if (
|
|
554
|
+
if (matchedPathname === null) {
|
|
136
555
|
return request;
|
|
137
556
|
}
|
|
138
557
|
|
|
139
|
-
const nextPath =
|
|
558
|
+
const nextPath = matchedPathname.slice(normalized.length) || '/';
|
|
140
559
|
url.pathname = nextPath.startsWith('/') ? nextPath : `/${nextPath}`;
|
|
141
560
|
|
|
142
561
|
return new Request(url, request);
|
|
@@ -158,117 +577,131 @@ function createEffectBffDispatcherErrorResponse(bff, error) {
|
|
|
158
577
|
);
|
|
159
578
|
}
|
|
160
579
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
580
|
+
// workerd binds every I/O object (sockets, timers, pending promises) to the request that
|
|
581
|
+
// created it, so an Effect runtime built while serving one request cannot serve the next: a
|
|
582
|
+
// pooled PostgreSQL connection or a cached in-flight effect from an earlier request never
|
|
583
|
+
// completes. Each BFF request therefore builds its own dispatcher and disposes it once the
|
|
584
|
+
// response body has been delivered; platform pools such as Hyperdrive keep connections warm.
|
|
585
|
+
async function createEffectBffDispatcher(bff, runtime) {
|
|
586
|
+
if (
|
|
587
|
+
typeof bff.dispatcherExport !== 'string' ||
|
|
588
|
+
bff.dispatcherExport.length === 0
|
|
589
|
+
) {
|
|
590
|
+
throw new Error('manifest does not declare dispatcherExport');
|
|
166
591
|
}
|
|
167
592
|
|
|
168
|
-
|
|
169
|
-
if (
|
|
170
|
-
typeof bff.dispatcherExport !== 'string' ||
|
|
171
|
-
bff.dispatcherExport.length === 0
|
|
172
|
-
) {
|
|
173
|
-
throw new Error('manifest does not declare dispatcherExport');
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const effectDispatcherFactory = runtime[bff.dispatcherExport];
|
|
593
|
+
const effectDispatcherFactory = runtime[bff.dispatcherExport];
|
|
177
594
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
595
|
+
if (typeof effectDispatcherFactory !== 'function') {
|
|
596
|
+
throw new Error(`worker bundle does not export ${bff.dispatcherExport}`);
|
|
597
|
+
}
|
|
181
598
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
throw new Error(
|
|
211
|
-
`manifest Effect BFF cross-project policy requires boolean ${field}`,
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
if (
|
|
216
|
-
!crossProjectPolicy.expectedOperationContracts ||
|
|
217
|
-
typeof crossProjectPolicy.expectedOperationContracts !== 'object' ||
|
|
218
|
-
Array.isArray(crossProjectPolicy.expectedOperationContracts)
|
|
219
|
-
) {
|
|
599
|
+
const effectConfig = bff.effect;
|
|
600
|
+
if (
|
|
601
|
+
!effectConfig ||
|
|
602
|
+
typeof effectConfig !== 'object' ||
|
|
603
|
+
Array.isArray(effectConfig)
|
|
604
|
+
) {
|
|
605
|
+
throw new Error('manifest declares invalid Effect BFF runtime config');
|
|
606
|
+
}
|
|
607
|
+
const crossProjectPolicy = effectConfig.crossProjectPolicy;
|
|
608
|
+
if (
|
|
609
|
+
!crossProjectPolicy ||
|
|
610
|
+
typeof crossProjectPolicy !== 'object' ||
|
|
611
|
+
Array.isArray(crossProjectPolicy)
|
|
612
|
+
) {
|
|
613
|
+
throw new Error(
|
|
614
|
+
'manifest declares invalid Effect BFF cross-project policy',
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
for (const field of [
|
|
618
|
+
'enabled',
|
|
619
|
+
'requireEnvelope',
|
|
620
|
+
'requireOperationContext',
|
|
621
|
+
'requireOperationContextDetails',
|
|
622
|
+
'requireOperationSchemaHash',
|
|
623
|
+
'requireOperationVersion',
|
|
624
|
+
'allowUnknownOperations',
|
|
625
|
+
]) {
|
|
626
|
+
if (typeof crossProjectPolicy[field] !== 'boolean') {
|
|
220
627
|
throw new Error(
|
|
221
|
-
|
|
628
|
+
`manifest Effect BFF cross-project policy requires boolean ${field}`,
|
|
222
629
|
);
|
|
223
630
|
}
|
|
631
|
+
}
|
|
632
|
+
if (
|
|
633
|
+
!crossProjectPolicy.expectedOperationContracts ||
|
|
634
|
+
typeof crossProjectPolicy.expectedOperationContracts !== 'object' ||
|
|
635
|
+
Array.isArray(crossProjectPolicy.expectedOperationContracts)
|
|
636
|
+
) {
|
|
637
|
+
throw new Error(
|
|
638
|
+
'manifest Effect BFF cross-project policy requires expectedOperationContracts object',
|
|
639
|
+
);
|
|
640
|
+
}
|
|
224
641
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
if (!effectDispatcher || typeof effectDispatcher.dispatch !== 'function') {
|
|
239
|
-
try {
|
|
240
|
-
await effectDispatcher?.dispose?.();
|
|
241
|
-
} catch {}
|
|
642
|
+
const effectDispatcher = await effectDispatcherFactory({
|
|
643
|
+
prefix: bff.prefix,
|
|
644
|
+
...(effectConfig?.openapi === undefined
|
|
645
|
+
? {}
|
|
646
|
+
: { openapi: effectConfig.openapi }),
|
|
647
|
+
...(effectConfig?.dataPlatform === undefined
|
|
648
|
+
? {}
|
|
649
|
+
: { dataPlatform: effectConfig.dataPlatform }),
|
|
650
|
+
...(effectConfig?.crossProjectPolicy === undefined
|
|
651
|
+
? {}
|
|
652
|
+
: { crossProjectPolicy }),
|
|
653
|
+
});
|
|
242
654
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
);
|
|
246
|
-
}
|
|
655
|
+
if (!effectDispatcher || typeof effectDispatcher.dispatch !== 'function') {
|
|
656
|
+
try {
|
|
657
|
+
await effectDispatcher?.dispose?.();
|
|
658
|
+
} catch {}
|
|
247
659
|
|
|
248
|
-
|
|
249
|
-
|
|
660
|
+
throw new Error(
|
|
661
|
+
`worker export ${bff.dispatcherExport} did not return a dispatcher with a dispatch function`,
|
|
662
|
+
);
|
|
663
|
+
}
|
|
250
664
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if (
|
|
254
|
-
effectBffDispatcherPromises.get(bff.worker) === effectDispatcherPromise
|
|
255
|
-
) {
|
|
256
|
-
effectBffDispatcherPromises.delete(bff.worker);
|
|
257
|
-
}
|
|
258
|
-
});
|
|
665
|
+
return effectDispatcher;
|
|
666
|
+
}
|
|
259
667
|
|
|
260
|
-
|
|
668
|
+
function disposeEffectBffDispatcherAfterResponse(response, dispatcher, ctx) {
|
|
669
|
+
if (response.body === null) {
|
|
670
|
+
ctx.waitUntil(dispatcher.dispose());
|
|
671
|
+
return response;
|
|
672
|
+
}
|
|
673
|
+
const { readable, writable } = new TransformStream();
|
|
674
|
+
// The body pipe settles when the client has the whole body or the stream failed; a failure
|
|
675
|
+
// already reached the client through `readable`, so it only has to release the runtime here.
|
|
676
|
+
ctx.waitUntil(
|
|
677
|
+
response.body
|
|
678
|
+
.pipeTo(writable)
|
|
679
|
+
.catch(() => undefined)
|
|
680
|
+
.then(() => dispatcher.dispose()),
|
|
681
|
+
);
|
|
682
|
+
return new Response(readable, response);
|
|
261
683
|
}
|
|
262
684
|
|
|
263
|
-
async function dispatchBffRequest(request, env) {
|
|
685
|
+
async function dispatchBffRequest(request, env, ctx) {
|
|
686
|
+
const rendererRejection = createWorkerRendererGuardResponse(request);
|
|
687
|
+
if (rendererRejection) return rendererRejection;
|
|
264
688
|
const bff = MODERN_WORKER_MANIFEST.bff;
|
|
265
689
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
690
|
+
const requestUrl = new URL(request.url);
|
|
691
|
+
const matchedPathname = resolvePrefixPathname(
|
|
692
|
+
requestUrl.pathname,
|
|
693
|
+
bff?.prefix,
|
|
694
|
+
);
|
|
695
|
+
if (!bff?.worker || matchedPathname === null) {
|
|
270
696
|
return null;
|
|
271
697
|
}
|
|
698
|
+
const canonicalRequest =
|
|
699
|
+
matchedPathname === requestUrl.pathname
|
|
700
|
+
? request
|
|
701
|
+
: new Request(
|
|
702
|
+
Object.assign(requestUrl, { pathname: matchedPathname }),
|
|
703
|
+
request,
|
|
704
|
+
);
|
|
272
705
|
if (bff.runtimeFramework !== 'effect') {
|
|
273
706
|
return createEffectBffDispatcherErrorResponse(
|
|
274
707
|
bff,
|
|
@@ -296,12 +729,23 @@ async function dispatchBffRequest(request, env) {
|
|
|
296
729
|
let effectDispatcher;
|
|
297
730
|
|
|
298
731
|
try {
|
|
299
|
-
effectDispatcher = await
|
|
732
|
+
effectDispatcher = await createEffectBffDispatcher(bff, runtime);
|
|
300
733
|
} catch (error) {
|
|
301
734
|
return createEffectBffDispatcherErrorResponse(bff, error);
|
|
302
735
|
}
|
|
303
736
|
|
|
304
|
-
|
|
737
|
+
let response;
|
|
738
|
+
try {
|
|
739
|
+
response = await effectDispatcher.dispatch(canonicalRequest, { env });
|
|
740
|
+
} catch (error) {
|
|
741
|
+
ctx.waitUntil(effectDispatcher.dispose());
|
|
742
|
+
throw error;
|
|
743
|
+
}
|
|
744
|
+
return disposeEffectBffDispatcherAfterResponse(
|
|
745
|
+
response,
|
|
746
|
+
effectDispatcher,
|
|
747
|
+
ctx,
|
|
748
|
+
);
|
|
305
749
|
}
|
|
306
750
|
|
|
307
751
|
const directHandler =
|