@bleedingdev/modern-js-plugin-bff-extensions 3.8.3-ultramodern.2
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 +22 -0
- package/README.md +29 -0
- package/dist/cjs/backend-federation/edge-runtime.js +230 -0
- package/dist/cjs/backend-federation/edge.js +80 -0
- package/dist/cjs/backend-federation/entry.js +94 -0
- package/dist/cjs/backend-federation/identity.js +82 -0
- package/dist/cjs/backend-federation/index.js +61 -0
- package/dist/cjs/backend-federation/legacy-warning.js +40 -0
- package/dist/cjs/backend-federation/load.js +72 -0
- package/dist/cjs/backend-federation/node.js +50 -0
- package/dist/cjs/backend-federation/plugin.js +63 -0
- package/dist/cjs/backend-federation/remotes.js +51 -0
- package/dist/cjs/backend-federation/runtime.js +128 -0
- package/dist/cjs/backend-federation/types.js +18 -0
- package/dist/cjs/backend-federation/utils.js +48 -0
- package/dist/cjs/backend-federation/validate-loaded.js +56 -0
- package/dist/cjs/backend-federation-manifest/errors.js +83 -0
- package/dist/cjs/backend-federation-manifest/index.js +48 -0
- package/dist/cjs/backend-federation-manifest/load.js +173 -0
- package/dist/cjs/backend-federation-manifest/metadata.js +101 -0
- package/dist/cjs/backend-federation-manifest/node.js +50 -0
- package/dist/cjs/backend-federation-manifest/reference.js +132 -0
- package/dist/cjs/backend-federation-manifest/remote.js +81 -0
- package/dist/cjs/backend-federation-manifest/types.js +18 -0
- package/dist/cjs/backend-federation-manifest/validation.js +110 -0
- package/dist/cjs/client-generator/built-entry.js +84 -0
- package/dist/cjs/client-generator/generator.js +73 -0
- package/dist/cjs/client-generator/index.js +54 -0
- package/dist/cjs/client-generator/paths.js +62 -0
- package/dist/cjs/client-generator/rendering.js +253 -0
- package/dist/cjs/client-generator/runtime.js +68 -0
- package/dist/cjs/client-generator/types.js +18 -0
- package/dist/cjs/cross-project-generation/index.js +40 -0
- package/dist/cjs/cross-project-generation/render.js +53 -0
- package/dist/cjs/cross-project-policy/evaluation.js +75 -0
- package/dist/cjs/cross-project-policy/index.js +40 -0
- package/dist/cjs/cross-project-policy/node.js +76 -0
- package/dist/cjs/effect-adapter/cross-project-policy.js +64 -0
- package/dist/cjs/effect-adapter/entry.js +98 -0
- package/dist/cjs/effect-adapter/error-response.js +75 -0
- package/dist/cjs/effect-adapter/index.js +187 -0
- package/dist/cjs/effect-source-loader/index.js +51 -0
- package/dist/cjs/effect-source-loader/loader.js +341 -0
- package/dist/cjs/effect-source-loader/worker-runtime-wrapper.js +97 -0
- package/dist/cjs/hono/bind-route-handlers.js +76 -0
- package/dist/cjs/hono/cross-project-policy.js +48 -0
- package/dist/cjs/hono/index.js +40 -0
- package/dist/esm/backend-federation/edge-runtime.mjs +186 -0
- package/dist/esm/backend-federation/edge.mjs +21 -0
- package/dist/esm/backend-federation/entry.mjs +56 -0
- package/dist/esm/backend-federation/identity.mjs +41 -0
- package/dist/esm/backend-federation/index.mjs +4 -0
- package/dist/esm/backend-federation/legacy-warning.mjs +2 -0
- package/dist/esm/backend-federation/load.mjs +34 -0
- package/dist/esm/backend-federation/node.mjs +12 -0
- package/dist/esm/backend-federation/plugin.mjs +25 -0
- package/dist/esm/backend-federation/remotes.mjs +13 -0
- package/dist/esm/backend-federation/runtime.mjs +90 -0
- package/dist/esm/backend-federation/types.mjs +0 -0
- package/dist/esm/backend-federation/utils.mjs +7 -0
- package/dist/esm/backend-federation/validate-loaded.mjs +18 -0
- package/dist/esm/backend-federation-manifest/errors.mjs +32 -0
- package/dist/esm/backend-federation-manifest/index.mjs +3 -0
- package/dist/esm/backend-federation-manifest/load.mjs +135 -0
- package/dist/esm/backend-federation-manifest/metadata.mjs +42 -0
- package/dist/esm/backend-federation-manifest/node.mjs +12 -0
- package/dist/esm/backend-federation-manifest/reference.mjs +88 -0
- package/dist/esm/backend-federation-manifest/remote.mjs +43 -0
- package/dist/esm/backend-federation-manifest/types.mjs +0 -0
- package/dist/esm/backend-federation-manifest/validation.mjs +66 -0
- package/dist/esm/client-generator/built-entry.mjs +40 -0
- package/dist/esm/client-generator/generator.mjs +32 -0
- package/dist/esm/client-generator/index.mjs +3 -0
- package/dist/esm/client-generator/paths.mjs +24 -0
- package/dist/esm/client-generator/rendering.mjs +209 -0
- package/dist/esm/client-generator/runtime.mjs +27 -0
- package/dist/esm/client-generator/types.mjs +0 -0
- package/dist/esm/cross-project-generation/index.mjs +1 -0
- package/dist/esm/cross-project-generation/render.mjs +15 -0
- package/dist/esm/cross-project-policy/evaluation.mjs +34 -0
- package/dist/esm/cross-project-policy/index.mjs +1 -0
- package/dist/esm/cross-project-policy/node.mjs +28 -0
- package/dist/esm/effect-adapter/cross-project-policy.mjs +26 -0
- package/dist/esm/effect-adapter/entry.mjs +50 -0
- package/dist/esm/effect-adapter/error-response.mjs +37 -0
- package/dist/esm/effect-adapter/index.mjs +139 -0
- package/dist/esm/effect-source-loader/index.mjs +3 -0
- package/dist/esm/effect-source-loader/loader.mjs +285 -0
- package/dist/esm/effect-source-loader/worker-runtime-wrapper.mjs +56 -0
- package/dist/esm/hono/bind-route-handlers.mjs +38 -0
- package/dist/esm/hono/cross-project-policy.mjs +10 -0
- package/dist/esm/hono/index.mjs +1 -0
- package/dist/esm-node/backend-federation/edge-runtime.mjs +187 -0
- package/dist/esm-node/backend-federation/edge.mjs +22 -0
- package/dist/esm-node/backend-federation/entry.mjs +57 -0
- package/dist/esm-node/backend-federation/identity.mjs +42 -0
- package/dist/esm-node/backend-federation/index.mjs +5 -0
- package/dist/esm-node/backend-federation/legacy-warning.mjs +3 -0
- package/dist/esm-node/backend-federation/load.mjs +35 -0
- package/dist/esm-node/backend-federation/node.mjs +13 -0
- package/dist/esm-node/backend-federation/plugin.mjs +26 -0
- package/dist/esm-node/backend-federation/remotes.mjs +14 -0
- package/dist/esm-node/backend-federation/runtime.mjs +91 -0
- package/dist/esm-node/backend-federation/types.mjs +1 -0
- package/dist/esm-node/backend-federation/utils.mjs +8 -0
- package/dist/esm-node/backend-federation/validate-loaded.mjs +19 -0
- package/dist/esm-node/backend-federation-manifest/errors.mjs +33 -0
- package/dist/esm-node/backend-federation-manifest/index.mjs +4 -0
- package/dist/esm-node/backend-federation-manifest/load.mjs +136 -0
- package/dist/esm-node/backend-federation-manifest/metadata.mjs +43 -0
- package/dist/esm-node/backend-federation-manifest/node.mjs +13 -0
- package/dist/esm-node/backend-federation-manifest/reference.mjs +89 -0
- package/dist/esm-node/backend-federation-manifest/remote.mjs +44 -0
- package/dist/esm-node/backend-federation-manifest/types.mjs +1 -0
- package/dist/esm-node/backend-federation-manifest/validation.mjs +67 -0
- package/dist/esm-node/client-generator/built-entry.mjs +41 -0
- package/dist/esm-node/client-generator/generator.mjs +33 -0
- package/dist/esm-node/client-generator/index.mjs +4 -0
- package/dist/esm-node/client-generator/paths.mjs +25 -0
- package/dist/esm-node/client-generator/rendering.mjs +210 -0
- package/dist/esm-node/client-generator/runtime.mjs +29 -0
- package/dist/esm-node/client-generator/types.mjs +1 -0
- package/dist/esm-node/cross-project-generation/index.mjs +2 -0
- package/dist/esm-node/cross-project-generation/render.mjs +16 -0
- package/dist/esm-node/cross-project-policy/evaluation.mjs +35 -0
- package/dist/esm-node/cross-project-policy/index.mjs +2 -0
- package/dist/esm-node/cross-project-policy/node.mjs +29 -0
- package/dist/esm-node/effect-adapter/cross-project-policy.mjs +27 -0
- package/dist/esm-node/effect-adapter/entry.mjs +51 -0
- package/dist/esm-node/effect-adapter/error-response.mjs +38 -0
- package/dist/esm-node/effect-adapter/index.mjs +140 -0
- package/dist/esm-node/effect-source-loader/index.mjs +4 -0
- package/dist/esm-node/effect-source-loader/loader.mjs +286 -0
- package/dist/esm-node/effect-source-loader/worker-runtime-wrapper.mjs +57 -0
- package/dist/esm-node/hono/bind-route-handlers.mjs +39 -0
- package/dist/esm-node/hono/cross-project-policy.mjs +11 -0
- package/dist/esm-node/hono/index.mjs +2 -0
- package/dist/types/backend-federation/edge-runtime.d.ts +46 -0
- package/dist/types/backend-federation/edge.d.ts +28 -0
- package/dist/types/backend-federation/entry.d.ts +2 -0
- package/dist/types/backend-federation/identity.d.ts +30 -0
- package/dist/types/backend-federation/index.d.ts +6 -0
- package/dist/types/backend-federation/legacy-warning.d.ts +1 -0
- package/dist/types/backend-federation/load.d.ts +13 -0
- package/dist/types/backend-federation/node.d.ts +4 -0
- package/dist/types/backend-federation/plugin.d.ts +3 -0
- package/dist/types/backend-federation/remotes.d.ts +2 -0
- package/dist/types/backend-federation/runtime.d.ts +3 -0
- package/dist/types/backend-federation/types.d.ts +76 -0
- package/dist/types/backend-federation/utils.d.ts +2 -0
- package/dist/types/backend-federation/validate-loaded.d.ts +8 -0
- package/dist/types/backend-federation-manifest/errors.d.ts +11 -0
- package/dist/types/backend-federation-manifest/index.d.ts +5 -0
- package/dist/types/backend-federation-manifest/load.d.ts +3 -0
- package/dist/types/backend-federation-manifest/metadata.d.ts +18 -0
- package/dist/types/backend-federation-manifest/node.d.ts +3 -0
- package/dist/types/backend-federation-manifest/reference.d.ts +4 -0
- package/dist/types/backend-federation-manifest/remote.d.ts +3 -0
- package/dist/types/backend-federation-manifest/types.d.ts +49 -0
- package/dist/types/backend-federation-manifest/validation.d.ts +5 -0
- package/dist/types/client-generator/built-entry.d.ts +16 -0
- package/dist/types/client-generator/generator.d.ts +3 -0
- package/dist/types/client-generator/index.d.ts +4 -0
- package/dist/types/client-generator/paths.d.ts +5 -0
- package/dist/types/client-generator/rendering.d.ts +24 -0
- package/dist/types/client-generator/runtime.d.ts +12 -0
- package/dist/types/client-generator/types.d.ts +33 -0
- package/dist/types/cross-project-generation/index.d.ts +1 -0
- package/dist/types/cross-project-generation/render.d.ts +1 -0
- package/dist/types/cross-project-policy/evaluation.d.ts +4 -0
- package/dist/types/cross-project-policy/index.d.ts +1 -0
- package/dist/types/cross-project-policy/node.d.ts +4 -0
- package/dist/types/effect-adapter/cross-project-policy.d.ts +4 -0
- package/dist/types/effect-adapter/entry.d.ts +2 -0
- package/dist/types/effect-adapter/error-response.d.ts +2 -0
- package/dist/types/effect-adapter/index.d.ts +19 -0
- package/dist/types/effect-source-loader/index.d.ts +3 -0
- package/dist/types/effect-source-loader/loader.d.ts +17 -0
- package/dist/types/effect-source-loader/worker-runtime-wrapper.d.ts +25 -0
- package/dist/types/hono/bind-route-handlers.d.ts +10 -0
- package/dist/types/hono/cross-project-policy.d.ts +3 -0
- package/dist/types/hono/index.d.ts +1 -0
- package/package.json +213 -0
- package/src/backend-federation/edge-runtime.ts +258 -0
- package/src/backend-federation/edge.ts +128 -0
- package/src/backend-federation/entry.ts +125 -0
- package/src/backend-federation/identity.ts +97 -0
- package/src/backend-federation/index.ts +30 -0
- package/src/backend-federation/legacy-warning.ts +2 -0
- package/src/backend-federation/load.ts +102 -0
- package/src/backend-federation/node.ts +33 -0
- package/src/backend-federation/plugin.ts +35 -0
- package/src/backend-federation/remotes.ts +17 -0
- package/src/backend-federation/runtime.ts +99 -0
- package/src/backend-federation/types.ts +103 -0
- package/src/backend-federation/utils.ts +7 -0
- package/src/backend-federation/validate-loaded.ts +72 -0
- package/src/backend-federation-manifest/errors.ts +85 -0
- package/src/backend-federation-manifest/index.ts +11 -0
- package/src/backend-federation-manifest/load.ts +266 -0
- package/src/backend-federation-manifest/metadata.ts +73 -0
- package/src/backend-federation-manifest/node.ts +19 -0
- package/src/backend-federation-manifest/reference.ts +240 -0
- package/src/backend-federation-manifest/remote.ts +102 -0
- package/src/backend-federation-manifest/types.ts +73 -0
- package/src/backend-federation-manifest/validation.ts +255 -0
- package/src/client-generator/built-entry.ts +70 -0
- package/src/client-generator/generator.ts +52 -0
- package/src/client-generator/index.ts +10 -0
- package/src/client-generator/paths.ts +40 -0
- package/src/client-generator/rendering.ts +307 -0
- package/src/client-generator/runtime.ts +69 -0
- package/src/client-generator/types.ts +35 -0
- package/src/cross-project-generation/index.ts +1 -0
- package/src/cross-project-generation/render.ts +14 -0
- package/src/cross-project-policy/evaluation.ts +70 -0
- package/src/cross-project-policy/index.ts +1 -0
- package/src/cross-project-policy/node.ts +63 -0
- package/src/effect-adapter/cross-project-policy.ts +63 -0
- package/src/effect-adapter/entry.ts +95 -0
- package/src/effect-adapter/error-response.ts +68 -0
- package/src/effect-adapter/index.ts +246 -0
- package/src/effect-source-loader/index.ts +6 -0
- package/src/effect-source-loader/loader.ts +397 -0
- package/src/effect-source-loader/worker-runtime-wrapper.ts +90 -0
- package/src/hono/bind-route-handlers.ts +62 -0
- package/src/hono/cross-project-policy.ts +26 -0
- package/src/hono/index.ts +4 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { BackendFederationExpectedIdentity } from './identity';
|
|
2
|
+
import {
|
|
3
|
+
formatBackendFederationIdentityIssues,
|
|
4
|
+
validateExpectedBackendFederationIdentity,
|
|
5
|
+
} from './identity';
|
|
6
|
+
import type { BackendFederatedEffectApiModule } from './types';
|
|
7
|
+
import { isRecord } from './utils';
|
|
8
|
+
|
|
9
|
+
export function validateLoadedBackendFederatedEffectApi(
|
|
10
|
+
loaded: unknown,
|
|
11
|
+
options: {
|
|
12
|
+
allowMissingIdentityMetadata?: boolean;
|
|
13
|
+
expected?: BackendFederationExpectedIdentity;
|
|
14
|
+
remoteName: string;
|
|
15
|
+
remoteRequest: string;
|
|
16
|
+
},
|
|
17
|
+
): BackendFederatedEffectApiModule {
|
|
18
|
+
if (!isRecord(loaded)) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
`[BFF][Effect] Backend federation expose ${options.remoteRequest} must load an object module.`,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const backendContract = loaded.backendFederationContract;
|
|
25
|
+
if (
|
|
26
|
+
!isRecord(backendContract) ||
|
|
27
|
+
backendContract.strictEffectApproach !== true
|
|
28
|
+
) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`[BFF][Effect] Backend federation expose ${options.remoteRequest} must expose strict Effect metadata (strictEffectApproach: true).`,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (backendContract.runtimeFramework !== 'effect') {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`[BFF][Effect] Backend federation expose ${options.remoteRequest} must expose strict Effect metadata (runtimeFramework: "effect").`,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (
|
|
41
|
+
typeof backendContract.name === 'string' &&
|
|
42
|
+
backendContract.name !== options.remoteName
|
|
43
|
+
) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`[BFF][Effect] Backend federation expose ${options.remoteRequest} metadata name mismatch: expected ${options.remoteName}, received ${backendContract.name}.`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!('runtime' in loaded)) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`[BFF][Effect] Backend federation expose ${options.remoteRequest} must expose runtime.`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (options.expected !== undefined) {
|
|
56
|
+
const issues = validateExpectedBackendFederationIdentity(
|
|
57
|
+
loaded,
|
|
58
|
+
options.expected,
|
|
59
|
+
{
|
|
60
|
+
allowMissingIdentityMetadata:
|
|
61
|
+
options.allowMissingIdentityMetadata === true,
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
if (issues.length > 0) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`[BFF][Effect] Backend federation expose ${options.remoteRequest} delivery-unit identity mismatch: ${formatBackendFederationIdentityIssues(issues)}.`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return loaded as BackendFederatedEffectApiModule;
|
|
72
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// @effect-diagnostics asyncFunction:off extendsNativeError:off globalTimers:off newPromise:off strictBooleanExpressions:off
|
|
2
|
+
|
|
3
|
+
export type BackendFederationManifestAdapterErrorCode =
|
|
4
|
+
| 'manifest_unavailable'
|
|
5
|
+
| 'manifest_invalid'
|
|
6
|
+
| 'remote_unavailable'
|
|
7
|
+
| 'strict_effect_required'
|
|
8
|
+
| 'timeout'
|
|
9
|
+
| 'version_mismatch';
|
|
10
|
+
|
|
11
|
+
export const BACKEND_FEDERATION_MANIFEST_ADAPTER_FAILURE_EVENT =
|
|
12
|
+
'modernjs:microvertical-server-fallback' as const;
|
|
13
|
+
|
|
14
|
+
export class BackendFederationManifestAdapterError extends Error {
|
|
15
|
+
code: BackendFederationManifestAdapterErrorCode;
|
|
16
|
+
readonly failureEvent = BACKEND_FEDERATION_MANIFEST_ADAPTER_FAILURE_EVENT;
|
|
17
|
+
details?: Record<string, unknown>;
|
|
18
|
+
|
|
19
|
+
constructor(
|
|
20
|
+
code: BackendFederationManifestAdapterErrorCode,
|
|
21
|
+
message: string,
|
|
22
|
+
cause?: unknown,
|
|
23
|
+
details?: Record<string, unknown>,
|
|
24
|
+
) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = 'BackendFederationManifestAdapterError';
|
|
27
|
+
this.code = code;
|
|
28
|
+
|
|
29
|
+
if (details) {
|
|
30
|
+
this.details = details;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (cause !== undefined) {
|
|
34
|
+
(this as { cause?: unknown }).cause = cause;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function assertManifestAdapter(
|
|
40
|
+
condition: unknown,
|
|
41
|
+
code: BackendFederationManifestAdapterErrorCode,
|
|
42
|
+
message: string,
|
|
43
|
+
): asserts condition {
|
|
44
|
+
if (!condition) {
|
|
45
|
+
throw new BackendFederationManifestAdapterError(code, message);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function assertVersionValue(
|
|
50
|
+
actual: string | undefined,
|
|
51
|
+
expected: string | undefined,
|
|
52
|
+
label: string,
|
|
53
|
+
details?: Record<string, unknown>,
|
|
54
|
+
) {
|
|
55
|
+
if (expected === undefined) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (actual !== expected) {
|
|
60
|
+
throw new BackendFederationManifestAdapterError(
|
|
61
|
+
'version_mismatch',
|
|
62
|
+
`[BFF][Effect] Backend federation ${label} mismatch: expected ${expected}, received ${actual ?? 'undefined'}.`,
|
|
63
|
+
undefined,
|
|
64
|
+
{ label, expected, received: actual, ...details },
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function assertConsistentValue(
|
|
70
|
+
left: string | undefined,
|
|
71
|
+
right: string | undefined,
|
|
72
|
+
label: string,
|
|
73
|
+
details?: Record<string, unknown>,
|
|
74
|
+
) {
|
|
75
|
+
if (left === undefined || right === undefined || left === right) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
throw new BackendFederationManifestAdapterError(
|
|
80
|
+
'version_mismatch',
|
|
81
|
+
`[BFF][Effect] Backend federation ${label} mismatch: expected ${left}, received ${right}.`,
|
|
82
|
+
undefined,
|
|
83
|
+
{ label, expected: left, received: right, ...details },
|
|
84
|
+
);
|
|
85
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type { BackendFederationManifestAdapterErrorCode } from './errors';
|
|
2
|
+
export { BackendFederationManifestAdapterError } from './errors';
|
|
3
|
+
export { loadBackendFederationManifest } from './reference';
|
|
4
|
+
export { resolveBackendFederationRemoteFromManifest } from './remote';
|
|
5
|
+
export type {
|
|
6
|
+
BackendFederationManifest,
|
|
7
|
+
BackendFederationManifestAdapterFallback,
|
|
8
|
+
BackendFederationManifestAdapterOptions,
|
|
9
|
+
BackendFederationManifestFetchResponse,
|
|
10
|
+
BackendFederationVersionBoundaryExpectation,
|
|
11
|
+
} from './types';
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
// @effect-diagnostics asyncFunction:off extendsNativeError:off globalTimers:off newPromise:off strictBooleanExpressions:off
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
BACKEND_FEDERATION_EFFECT_EXPOSE,
|
|
5
|
+
type BackendFederatedEffectApiModule,
|
|
6
|
+
type BackendFederationRemote,
|
|
7
|
+
} from '../backend-federation';
|
|
8
|
+
import { loadBackendFederatedEffectApi } from '../backend-federation/load';
|
|
9
|
+
import { BackendFederationManifestAdapterError } from './errors';
|
|
10
|
+
import { loadBackendFederationManifest } from './reference';
|
|
11
|
+
import { resolveBackendFederationRemoteFromManifest } from './remote';
|
|
12
|
+
import type {
|
|
13
|
+
BackendFederationManifest,
|
|
14
|
+
BackendFederationManifestAdapterOptions,
|
|
15
|
+
} from './types';
|
|
16
|
+
import {
|
|
17
|
+
classifyLoadError,
|
|
18
|
+
validateBackendFederationManifest,
|
|
19
|
+
validateLoadedBackendFederationContract,
|
|
20
|
+
} from './validation';
|
|
21
|
+
|
|
22
|
+
function createManifestLoadScope(
|
|
23
|
+
options: BackendFederationManifestAdapterOptions,
|
|
24
|
+
) {
|
|
25
|
+
const controller = new AbortController();
|
|
26
|
+
const policyTimeouts = [
|
|
27
|
+
options.manifestPolicy?.timeoutMs,
|
|
28
|
+
options.entryPolicy?.timeoutMs,
|
|
29
|
+
].filter((timeout): timeout is number => timeout !== undefined);
|
|
30
|
+
const configuredTimeouts = [options.timeoutMs, ...policyTimeouts].filter(
|
|
31
|
+
(timeout): timeout is number => timeout !== undefined,
|
|
32
|
+
);
|
|
33
|
+
for (const timeout of configuredTimeouts) {
|
|
34
|
+
if (!Number.isSafeInteger(timeout) || timeout < 0) {
|
|
35
|
+
throw new BackendFederationManifestAdapterError(
|
|
36
|
+
'manifest_invalid',
|
|
37
|
+
'[BFF][Effect] Backend federation timeoutMs must be a non-negative safe integer.',
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const timeoutMs =
|
|
42
|
+
options.timeoutMs ??
|
|
43
|
+
(policyTimeouts.length > 0 ? Math.min(...policyTimeouts) : 0);
|
|
44
|
+
const inputSignals = [
|
|
45
|
+
options.signal,
|
|
46
|
+
options.manifestPolicy?.signal,
|
|
47
|
+
options.entryPolicy?.signal,
|
|
48
|
+
].filter((signal): signal is AbortSignal => signal !== undefined);
|
|
49
|
+
const abortListeners: Array<[AbortSignal, () => void]> = [];
|
|
50
|
+
let timedOut = false;
|
|
51
|
+
const forwardAbort = (signal: AbortSignal) => {
|
|
52
|
+
controller.abort(signal.reason);
|
|
53
|
+
};
|
|
54
|
+
for (const signal of inputSignals) {
|
|
55
|
+
if (signal.aborted) {
|
|
56
|
+
forwardAbort(signal);
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
const listener = () => forwardAbort(signal);
|
|
60
|
+
abortListeners.push([signal, listener]);
|
|
61
|
+
signal.addEventListener('abort', listener, { once: true });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const timeout =
|
|
65
|
+
timeoutMs > 0
|
|
66
|
+
? setTimeout(() => {
|
|
67
|
+
if (controller.signal.aborted) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
timedOut = true;
|
|
71
|
+
controller.abort(
|
|
72
|
+
new BackendFederationManifestAdapterError(
|
|
73
|
+
'timeout',
|
|
74
|
+
`[BFF][Effect] Backend federation load timed out after ${timeoutMs}ms.`,
|
|
75
|
+
),
|
|
76
|
+
);
|
|
77
|
+
}, timeoutMs)
|
|
78
|
+
: undefined;
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
cleanup() {
|
|
82
|
+
if (timeout) {
|
|
83
|
+
clearTimeout(timeout);
|
|
84
|
+
}
|
|
85
|
+
for (const [signal, listener] of abortListeners) {
|
|
86
|
+
signal.removeEventListener('abort', listener);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
signal: controller.signal,
|
|
90
|
+
timedOut: () => timedOut,
|
|
91
|
+
timeoutMs,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isRemoteManifestReference(
|
|
96
|
+
options: BackendFederationManifestAdapterOptions,
|
|
97
|
+
) {
|
|
98
|
+
if (options.manifest !== undefined || options.manifestPath) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
return Boolean(options.manifestUrl || options.manifestEnv);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function hasPinnedRemoteEntryExpectation(
|
|
105
|
+
options: BackendFederationManifestAdapterOptions,
|
|
106
|
+
) {
|
|
107
|
+
const expected = options.entryPolicy?.expected;
|
|
108
|
+
return (
|
|
109
|
+
typeof expected?.byteLength === 'number' &&
|
|
110
|
+
typeof expected.entryUrl === 'string' &&
|
|
111
|
+
typeof expected.remoteName === 'string' &&
|
|
112
|
+
typeof expected.sha256 === 'string'
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function isCallerPinnedStaticRemote(
|
|
117
|
+
options: BackendFederationManifestAdapterOptions,
|
|
118
|
+
remote: BackendFederationRemote,
|
|
119
|
+
) {
|
|
120
|
+
return (
|
|
121
|
+
options.remote?.entry === remote.entry &&
|
|
122
|
+
options.remote?.name === remote.name &&
|
|
123
|
+
/^(?:binding|service|static):/u.test(remote.entry)
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function throwIfManifestLoadAborted(signal: AbortSignal) {
|
|
128
|
+
if (signal.aborted) {
|
|
129
|
+
throw new BackendFederationManifestAdapterError(
|
|
130
|
+
'remote_unavailable',
|
|
131
|
+
'[BFF][Effect] Backend federation load was aborted before remote execution.',
|
|
132
|
+
signal.reason,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function loadBackendFederatedEffectApiFromManifest(
|
|
138
|
+
options: BackendFederationManifestAdapterOptions,
|
|
139
|
+
): Promise<BackendFederatedEffectApiModule> {
|
|
140
|
+
let manifest: BackendFederationManifest | undefined;
|
|
141
|
+
let remote: BackendFederationRemote | undefined;
|
|
142
|
+
let scope: ReturnType<typeof createManifestLoadScope> | undefined;
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
scope = createManifestLoadScope(options);
|
|
146
|
+
throwIfManifestLoadAborted(scope.signal);
|
|
147
|
+
manifest = await loadBackendFederationManifest({
|
|
148
|
+
...options,
|
|
149
|
+
manifestPolicy: {
|
|
150
|
+
...options.manifestPolicy,
|
|
151
|
+
signal: scope.signal,
|
|
152
|
+
timeoutMs: 0,
|
|
153
|
+
},
|
|
154
|
+
signal: scope.signal,
|
|
155
|
+
timeoutMs: 0,
|
|
156
|
+
});
|
|
157
|
+
validateBackendFederationManifest(manifest, options.expected);
|
|
158
|
+
remote = resolveBackendFederationRemoteFromManifest(
|
|
159
|
+
manifest,
|
|
160
|
+
options.remote,
|
|
161
|
+
);
|
|
162
|
+
throwIfManifestLoadAborted(scope.signal);
|
|
163
|
+
const remoteManifest = isRemoteManifestReference(options);
|
|
164
|
+
if (remoteManifest && options.runtime !== undefined) {
|
|
165
|
+
throw new BackendFederationManifestAdapterError(
|
|
166
|
+
'manifest_invalid',
|
|
167
|
+
`[BFF][Effect] Backend federation remote ${remote.name} cannot use a custom runtime selected by a network manifest.`,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
if (
|
|
171
|
+
remoteManifest &&
|
|
172
|
+
!/^https?:\/\//iu.test(remote.entry) &&
|
|
173
|
+
!isCallerPinnedStaticRemote(options, remote)
|
|
174
|
+
) {
|
|
175
|
+
throw new BackendFederationManifestAdapterError(
|
|
176
|
+
'manifest_invalid',
|
|
177
|
+
`[BFF][Effect] Backend federation network manifests cannot select local, global, or plugin entry ${remote.entry}. Pin a static/service binding in caller configuration or use a verified HTTP(S) entry.`,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
if (
|
|
181
|
+
remoteManifest &&
|
|
182
|
+
/^https?:\/\//iu.test(remote.entry) &&
|
|
183
|
+
!hasPinnedRemoteEntryExpectation(options)
|
|
184
|
+
) {
|
|
185
|
+
throw new BackendFederationManifestAdapterError(
|
|
186
|
+
'manifest_invalid',
|
|
187
|
+
`[BFF][Effect] Backend federation remote ${remote.name} from a network manifest requires caller-pinned entryUrl, remoteName, sha256, and byteLength values.`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
if (
|
|
191
|
+
/^https?:\/\//iu.test(remote.entry) &&
|
|
192
|
+
remote.verification === undefined &&
|
|
193
|
+
options.entryPolicy?.expected === undefined
|
|
194
|
+
) {
|
|
195
|
+
throw new BackendFederationManifestAdapterError(
|
|
196
|
+
'manifest_invalid',
|
|
197
|
+
`[BFF][Effect] Backend federation remote ${remote.name} requires entry.sha256 and entry.byteLength before network execution.`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
if (/^https?:\/\//iu.test(remote.entry) && options.runtime !== undefined) {
|
|
201
|
+
throw new BackendFederationManifestAdapterError(
|
|
202
|
+
'manifest_invalid',
|
|
203
|
+
`[BFF][Effect] Backend federation remote ${remote.name} cannot use a custom runtime for network entry execution.`,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const loaded = await loadBackendFederatedEffectApi({
|
|
208
|
+
hostName: options.hostName,
|
|
209
|
+
remote,
|
|
210
|
+
...(options.plugins ? { plugins: options.plugins } : {}),
|
|
211
|
+
entryPolicy: {
|
|
212
|
+
...options.entryPolicy,
|
|
213
|
+
signal: scope.signal,
|
|
214
|
+
timeoutMs: 0,
|
|
215
|
+
},
|
|
216
|
+
...(options.runtime ? { runtime: options.runtime } : {}),
|
|
217
|
+
// MV-G23: route the manifest adapter through the raw loader's shared
|
|
218
|
+
// delivery-unit identity validation when an identity is expected.
|
|
219
|
+
...(options.expected?.unitId && options.expected.buildMarker
|
|
220
|
+
? {
|
|
221
|
+
expected: {
|
|
222
|
+
unitId: options.expected.unitId,
|
|
223
|
+
buildMarker: options.expected.buildMarker,
|
|
224
|
+
},
|
|
225
|
+
// The manifest's identity is already validated against
|
|
226
|
+
// `expected`; legacy exposes without identity metadata stay
|
|
227
|
+
// loadable (mismatching declared values still fail).
|
|
228
|
+
allowMissingIdentityMetadata: true,
|
|
229
|
+
}
|
|
230
|
+
: {}),
|
|
231
|
+
}).catch((error: unknown) => {
|
|
232
|
+
throw new BackendFederationManifestAdapterError(
|
|
233
|
+
scope?.timedOut() === true ? 'timeout' : classifyLoadError(error),
|
|
234
|
+
`[BFF][Effect] Backend federation remote ${remote?.name ?? 'unknown'} could not load ${remote?.expose ?? BACKEND_FEDERATION_EFFECT_EXPOSE}.`,
|
|
235
|
+
error,
|
|
236
|
+
);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
validateLoadedBackendFederationContract(loaded, manifest, remote);
|
|
240
|
+
|
|
241
|
+
return loaded;
|
|
242
|
+
} catch (error) {
|
|
243
|
+
const adapterError =
|
|
244
|
+
scope?.timedOut() === true
|
|
245
|
+
? new BackendFederationManifestAdapterError(
|
|
246
|
+
'timeout',
|
|
247
|
+
`[BFF][Effect] Backend federation load timed out after ${scope.timeoutMs}ms.`,
|
|
248
|
+
error,
|
|
249
|
+
)
|
|
250
|
+
: error instanceof BackendFederationManifestAdapterError
|
|
251
|
+
? error
|
|
252
|
+
: new BackendFederationManifestAdapterError(
|
|
253
|
+
'remote_unavailable',
|
|
254
|
+
'[BFF][Effect] Backend federation manifest adapter failed.',
|
|
255
|
+
error,
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
if (options.fallback) {
|
|
259
|
+
return options.fallback(adapterError, { manifest, remote });
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
throw adapterError;
|
|
263
|
+
} finally {
|
|
264
|
+
scope?.cleanup();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// @effect-diagnostics asyncFunction:off extendsNativeError:off globalTimers:off newPromise:off strictBooleanExpressions:off
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
backendFederationMetadata as sharedBackendFederationMetadata,
|
|
5
|
+
backendFederationVersionBoundary as sharedBackendFederationVersionBoundary,
|
|
6
|
+
} from '@modern-js/utils/universal';
|
|
7
|
+
import type { BackendFederationManifest } from './types';
|
|
8
|
+
|
|
9
|
+
export function stringValue(value: unknown) {
|
|
10
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
14
|
+
return typeof value === 'object' && value !== null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function recordField(
|
|
18
|
+
value: Record<string, unknown> | undefined,
|
|
19
|
+
field: string,
|
|
20
|
+
) {
|
|
21
|
+
const fieldValue = value?.[field];
|
|
22
|
+
return isRecord(fieldValue) ? fieldValue : undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function resolveRemoteEntryFromMetadata(
|
|
26
|
+
manifest: BackendFederationManifest,
|
|
27
|
+
) {
|
|
28
|
+
const metaData = recordField(manifest, 'metaData');
|
|
29
|
+
const remoteEntry = recordField(metaData, 'remoteEntry');
|
|
30
|
+
const publicPath =
|
|
31
|
+
stringValue(metaData?.publicPath) ?? stringValue(metaData?.ssrPublicPath);
|
|
32
|
+
const entryName = stringValue(remoteEntry?.name);
|
|
33
|
+
|
|
34
|
+
if (!publicPath || !entryName) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const entryPath = stringValue(remoteEntry?.path) ?? '';
|
|
39
|
+
const normalizedBase = publicPath.replace(/\/+$/u, '');
|
|
40
|
+
const normalizedPath = entryPath.replace(/^\/+|\/+$/gu, '');
|
|
41
|
+
|
|
42
|
+
return [normalizedBase, normalizedPath, entryName].filter(Boolean).join('/');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function backendFederationMetadata(manifest: BackendFederationManifest) {
|
|
46
|
+
return sharedBackendFederationMetadata(manifest);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function versionBoundaryMetadata(manifest: BackendFederationManifest) {
|
|
50
|
+
return sharedBackendFederationVersionBoundary(manifest);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function deliveryUnitMetadata(
|
|
54
|
+
record: Record<string, unknown> | undefined,
|
|
55
|
+
) {
|
|
56
|
+
return recordField(record, 'deliveryUnit');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Resolves the delivery-unit identity root for a manifest (ADR-0019 §3):
|
|
61
|
+
* prefers `versionBoundary.deliveryUnit`, falls back to the top-level
|
|
62
|
+
* `backendFederation.deliveryUnit` record. Both are additive/optional so
|
|
63
|
+
* legacy manifests without delivery-unit metadata continue to validate.
|
|
64
|
+
*/
|
|
65
|
+
export function manifestDeliveryUnit(
|
|
66
|
+
backendFederation: Record<string, unknown> | undefined,
|
|
67
|
+
boundary: Record<string, unknown> | undefined,
|
|
68
|
+
) {
|
|
69
|
+
return {
|
|
70
|
+
boundary: deliveryUnitMetadata(boundary),
|
|
71
|
+
top: deliveryUnitMetadata(backendFederation),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { evaluateNodeBackendFederationCommonJs } from '@modern-js/server-runtime-extensions/backend-federation-security/node';
|
|
2
|
+
|
|
3
|
+
import type { BackendFederatedEffectApiModule } from '../backend-federation';
|
|
4
|
+
import { loadBackendFederatedEffectApiFromManifest as loadUniversalBackendFederatedEffectApiFromManifest } from './load';
|
|
5
|
+
import type { BackendFederationManifestAdapterOptions } from './types';
|
|
6
|
+
|
|
7
|
+
export function loadBackendFederatedEffectApiFromManifest(
|
|
8
|
+
options: BackendFederationManifestAdapterOptions,
|
|
9
|
+
): Promise<BackendFederatedEffectApiModule> {
|
|
10
|
+
return loadUniversalBackendFederatedEffectApiFromManifest({
|
|
11
|
+
...options,
|
|
12
|
+
entryPolicy: {
|
|
13
|
+
...options.entryPolicy,
|
|
14
|
+
evaluateCommonJs:
|
|
15
|
+
options.entryPolicy?.evaluateCommonJs ??
|
|
16
|
+
evaluateNodeBackendFederationCommonJs,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
}
|