@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,18 @@
|
|
|
1
|
+
import { formatBackendFederationIdentityIssues, validateExpectedBackendFederationIdentity } from "./identity.mjs";
|
|
2
|
+
import { isRecord } from "./utils.mjs";
|
|
3
|
+
function validateLoadedBackendFederatedEffectApi(loaded, options) {
|
|
4
|
+
if (!isRecord(loaded)) throw new Error(`[BFF][Effect] Backend federation expose ${options.remoteRequest} must load an object module.`);
|
|
5
|
+
const backendContract = loaded.backendFederationContract;
|
|
6
|
+
if (!isRecord(backendContract) || true !== backendContract.strictEffectApproach) throw new Error(`[BFF][Effect] Backend federation expose ${options.remoteRequest} must expose strict Effect metadata (strictEffectApproach: true).`);
|
|
7
|
+
if ('effect' !== backendContract.runtimeFramework) throw new Error(`[BFF][Effect] Backend federation expose ${options.remoteRequest} must expose strict Effect metadata (runtimeFramework: "effect").`);
|
|
8
|
+
if ('string' == typeof backendContract.name && backendContract.name !== options.remoteName) throw new Error(`[BFF][Effect] Backend federation expose ${options.remoteRequest} metadata name mismatch: expected ${options.remoteName}, received ${backendContract.name}.`);
|
|
9
|
+
if (!('runtime' in loaded)) throw new Error(`[BFF][Effect] Backend federation expose ${options.remoteRequest} must expose runtime.`);
|
|
10
|
+
if (void 0 !== options.expected) {
|
|
11
|
+
const issues = validateExpectedBackendFederationIdentity(loaded, options.expected, {
|
|
12
|
+
allowMissingIdentityMetadata: true === options.allowMissingIdentityMetadata
|
|
13
|
+
});
|
|
14
|
+
if (issues.length > 0) throw new Error(`[BFF][Effect] Backend federation expose ${options.remoteRequest} delivery-unit identity mismatch: ${formatBackendFederationIdentityIssues(issues)}.`);
|
|
15
|
+
}
|
|
16
|
+
return loaded;
|
|
17
|
+
}
|
|
18
|
+
export { validateLoadedBackendFederatedEffectApi };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const BACKEND_FEDERATION_MANIFEST_ADAPTER_FAILURE_EVENT = 'modernjs:microvertical-server-fallback';
|
|
2
|
+
class BackendFederationManifestAdapterError extends Error {
|
|
3
|
+
constructor(code, message, cause, details){
|
|
4
|
+
super(message), this.failureEvent = BACKEND_FEDERATION_MANIFEST_ADAPTER_FAILURE_EVENT;
|
|
5
|
+
this.name = 'BackendFederationManifestAdapterError';
|
|
6
|
+
this.code = code;
|
|
7
|
+
if (details) this.details = details;
|
|
8
|
+
if (void 0 !== cause) this.cause = cause;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function assertManifestAdapter(condition, code, message) {
|
|
12
|
+
if (!condition) throw new BackendFederationManifestAdapterError(code, message);
|
|
13
|
+
}
|
|
14
|
+
function assertVersionValue(actual, expected, label, details) {
|
|
15
|
+
if (void 0 === expected) return;
|
|
16
|
+
if (actual !== expected) throw new BackendFederationManifestAdapterError('version_mismatch', `[BFF][Effect] Backend federation ${label} mismatch: expected ${expected}, received ${actual ?? 'undefined'}.`, void 0, {
|
|
17
|
+
label,
|
|
18
|
+
expected,
|
|
19
|
+
received: actual,
|
|
20
|
+
...details
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function assertConsistentValue(left, right, label, details) {
|
|
24
|
+
if (void 0 === left || void 0 === right || left === right) return;
|
|
25
|
+
throw new BackendFederationManifestAdapterError('version_mismatch', `[BFF][Effect] Backend federation ${label} mismatch: expected ${left}, received ${right}.`, void 0, {
|
|
26
|
+
label,
|
|
27
|
+
expected: left,
|
|
28
|
+
received: right,
|
|
29
|
+
...details
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export { BACKEND_FEDERATION_MANIFEST_ADAPTER_FAILURE_EVENT, BackendFederationManifestAdapterError, assertConsistentValue, assertManifestAdapter, assertVersionValue };
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { BACKEND_FEDERATION_EFFECT_EXPOSE } from "../backend-federation/index.mjs";
|
|
2
|
+
import { loadBackendFederatedEffectApi } from "../backend-federation/load.mjs";
|
|
3
|
+
import { BackendFederationManifestAdapterError } from "./errors.mjs";
|
|
4
|
+
import { loadBackendFederationManifest } from "./reference.mjs";
|
|
5
|
+
import { resolveBackendFederationRemoteFromManifest } from "./remote.mjs";
|
|
6
|
+
import { classifyLoadError, validateBackendFederationManifest, validateLoadedBackendFederationContract } from "./validation.mjs";
|
|
7
|
+
function createManifestLoadScope(options) {
|
|
8
|
+
const controller = new AbortController();
|
|
9
|
+
const policyTimeouts = [
|
|
10
|
+
options.manifestPolicy?.timeoutMs,
|
|
11
|
+
options.entryPolicy?.timeoutMs
|
|
12
|
+
].filter((timeout)=>void 0 !== timeout);
|
|
13
|
+
const configuredTimeouts = [
|
|
14
|
+
options.timeoutMs,
|
|
15
|
+
...policyTimeouts
|
|
16
|
+
].filter((timeout)=>void 0 !== timeout);
|
|
17
|
+
for (const timeout of configuredTimeouts)if (!Number.isSafeInteger(timeout) || timeout < 0) throw new BackendFederationManifestAdapterError('manifest_invalid', '[BFF][Effect] Backend federation timeoutMs must be a non-negative safe integer.');
|
|
18
|
+
const timeoutMs = options.timeoutMs ?? (policyTimeouts.length > 0 ? Math.min(...policyTimeouts) : 0);
|
|
19
|
+
const inputSignals = [
|
|
20
|
+
options.signal,
|
|
21
|
+
options.manifestPolicy?.signal,
|
|
22
|
+
options.entryPolicy?.signal
|
|
23
|
+
].filter((signal)=>void 0 !== signal);
|
|
24
|
+
const abortListeners = [];
|
|
25
|
+
let timedOut = false;
|
|
26
|
+
const forwardAbort = (signal)=>{
|
|
27
|
+
controller.abort(signal.reason);
|
|
28
|
+
};
|
|
29
|
+
for (const signal of inputSignals){
|
|
30
|
+
if (signal.aborted) {
|
|
31
|
+
forwardAbort(signal);
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
const listener = ()=>forwardAbort(signal);
|
|
35
|
+
abortListeners.push([
|
|
36
|
+
signal,
|
|
37
|
+
listener
|
|
38
|
+
]);
|
|
39
|
+
signal.addEventListener('abort', listener, {
|
|
40
|
+
once: true
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const timeout = timeoutMs > 0 ? setTimeout(()=>{
|
|
44
|
+
if (controller.signal.aborted) return;
|
|
45
|
+
timedOut = true;
|
|
46
|
+
controller.abort(new BackendFederationManifestAdapterError('timeout', `[BFF][Effect] Backend federation load timed out after ${timeoutMs}ms.`));
|
|
47
|
+
}, timeoutMs) : void 0;
|
|
48
|
+
return {
|
|
49
|
+
cleanup () {
|
|
50
|
+
if (timeout) clearTimeout(timeout);
|
|
51
|
+
for (const [signal, listener] of abortListeners)signal.removeEventListener('abort', listener);
|
|
52
|
+
},
|
|
53
|
+
signal: controller.signal,
|
|
54
|
+
timedOut: ()=>timedOut,
|
|
55
|
+
timeoutMs
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function isRemoteManifestReference(options) {
|
|
59
|
+
if (void 0 !== options.manifest || options.manifestPath) return false;
|
|
60
|
+
return Boolean(options.manifestUrl || options.manifestEnv);
|
|
61
|
+
}
|
|
62
|
+
function hasPinnedRemoteEntryExpectation(options) {
|
|
63
|
+
const expected = options.entryPolicy?.expected;
|
|
64
|
+
return 'number' == typeof expected?.byteLength && 'string' == typeof expected.entryUrl && 'string' == typeof expected.remoteName && 'string' == typeof expected.sha256;
|
|
65
|
+
}
|
|
66
|
+
function isCallerPinnedStaticRemote(options, remote) {
|
|
67
|
+
return options.remote?.entry === remote.entry && options.remote?.name === remote.name && /^(?:binding|service|static):/u.test(remote.entry);
|
|
68
|
+
}
|
|
69
|
+
function throwIfManifestLoadAborted(signal) {
|
|
70
|
+
if (signal.aborted) throw new BackendFederationManifestAdapterError('remote_unavailable', '[BFF][Effect] Backend federation load was aborted before remote execution.', signal.reason);
|
|
71
|
+
}
|
|
72
|
+
async function loadBackendFederatedEffectApiFromManifest(options) {
|
|
73
|
+
let manifest;
|
|
74
|
+
let remote;
|
|
75
|
+
let scope;
|
|
76
|
+
try {
|
|
77
|
+
scope = createManifestLoadScope(options);
|
|
78
|
+
throwIfManifestLoadAborted(scope.signal);
|
|
79
|
+
manifest = await loadBackendFederationManifest({
|
|
80
|
+
...options,
|
|
81
|
+
manifestPolicy: {
|
|
82
|
+
...options.manifestPolicy,
|
|
83
|
+
signal: scope.signal,
|
|
84
|
+
timeoutMs: 0
|
|
85
|
+
},
|
|
86
|
+
signal: scope.signal,
|
|
87
|
+
timeoutMs: 0
|
|
88
|
+
});
|
|
89
|
+
validateBackendFederationManifest(manifest, options.expected);
|
|
90
|
+
remote = resolveBackendFederationRemoteFromManifest(manifest, options.remote);
|
|
91
|
+
throwIfManifestLoadAborted(scope.signal);
|
|
92
|
+
const remoteManifest = isRemoteManifestReference(options);
|
|
93
|
+
if (remoteManifest && void 0 !== options.runtime) throw new BackendFederationManifestAdapterError('manifest_invalid', `[BFF][Effect] Backend federation remote ${remote.name} cannot use a custom runtime selected by a network manifest.`);
|
|
94
|
+
if (remoteManifest && !/^https?:\/\//iu.test(remote.entry) && !isCallerPinnedStaticRemote(options, remote)) throw new BackendFederationManifestAdapterError('manifest_invalid', `[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.`);
|
|
95
|
+
if (remoteManifest && /^https?:\/\//iu.test(remote.entry) && !hasPinnedRemoteEntryExpectation(options)) throw new BackendFederationManifestAdapterError('manifest_invalid', `[BFF][Effect] Backend federation remote ${remote.name} from a network manifest requires caller-pinned entryUrl, remoteName, sha256, and byteLength values.`);
|
|
96
|
+
if (/^https?:\/\//iu.test(remote.entry) && void 0 === remote.verification && options.entryPolicy?.expected === void 0) throw new BackendFederationManifestAdapterError('manifest_invalid', `[BFF][Effect] Backend federation remote ${remote.name} requires entry.sha256 and entry.byteLength before network execution.`);
|
|
97
|
+
if (/^https?:\/\//iu.test(remote.entry) && void 0 !== options.runtime) throw new BackendFederationManifestAdapterError('manifest_invalid', `[BFF][Effect] Backend federation remote ${remote.name} cannot use a custom runtime for network entry execution.`);
|
|
98
|
+
const loaded = await loadBackendFederatedEffectApi({
|
|
99
|
+
hostName: options.hostName,
|
|
100
|
+
remote,
|
|
101
|
+
...options.plugins ? {
|
|
102
|
+
plugins: options.plugins
|
|
103
|
+
} : {},
|
|
104
|
+
entryPolicy: {
|
|
105
|
+
...options.entryPolicy,
|
|
106
|
+
signal: scope.signal,
|
|
107
|
+
timeoutMs: 0
|
|
108
|
+
},
|
|
109
|
+
...options.runtime ? {
|
|
110
|
+
runtime: options.runtime
|
|
111
|
+
} : {},
|
|
112
|
+
...options.expected?.unitId && options.expected.buildMarker ? {
|
|
113
|
+
expected: {
|
|
114
|
+
unitId: options.expected.unitId,
|
|
115
|
+
buildMarker: options.expected.buildMarker
|
|
116
|
+
},
|
|
117
|
+
allowMissingIdentityMetadata: true
|
|
118
|
+
} : {}
|
|
119
|
+
}).catch((error)=>{
|
|
120
|
+
throw new BackendFederationManifestAdapterError(scope?.timedOut() === true ? 'timeout' : classifyLoadError(error), `[BFF][Effect] Backend federation remote ${remote?.name ?? 'unknown'} could not load ${remote?.expose ?? BACKEND_FEDERATION_EFFECT_EXPOSE}.`, error);
|
|
121
|
+
});
|
|
122
|
+
validateLoadedBackendFederationContract(loaded, manifest, remote);
|
|
123
|
+
return loaded;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
const adapterError = scope?.timedOut() === true ? new BackendFederationManifestAdapterError('timeout', `[BFF][Effect] Backend federation load timed out after ${scope.timeoutMs}ms.`, error) : error instanceof BackendFederationManifestAdapterError ? error : new BackendFederationManifestAdapterError('remote_unavailable', '[BFF][Effect] Backend federation manifest adapter failed.', error);
|
|
126
|
+
if (options.fallback) return options.fallback(adapterError, {
|
|
127
|
+
manifest,
|
|
128
|
+
remote
|
|
129
|
+
});
|
|
130
|
+
throw adapterError;
|
|
131
|
+
} finally{
|
|
132
|
+
scope?.cleanup();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
export { loadBackendFederatedEffectApiFromManifest };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { backendFederationMetadata, backendFederationVersionBoundary } from "@modern-js/utils/universal";
|
|
2
|
+
function stringValue(value) {
|
|
3
|
+
return 'string' == typeof value && value.length > 0 ? value : void 0;
|
|
4
|
+
}
|
|
5
|
+
function isRecord(value) {
|
|
6
|
+
return 'object' == typeof value && null !== value;
|
|
7
|
+
}
|
|
8
|
+
function recordField(value, field) {
|
|
9
|
+
const fieldValue = value?.[field];
|
|
10
|
+
return isRecord(fieldValue) ? fieldValue : void 0;
|
|
11
|
+
}
|
|
12
|
+
function resolveRemoteEntryFromMetadata(manifest) {
|
|
13
|
+
const metaData = recordField(manifest, 'metaData');
|
|
14
|
+
const remoteEntry = recordField(metaData, 'remoteEntry');
|
|
15
|
+
const publicPath = stringValue(metaData?.publicPath) ?? stringValue(metaData?.ssrPublicPath);
|
|
16
|
+
const entryName = stringValue(remoteEntry?.name);
|
|
17
|
+
if (!publicPath || !entryName) return;
|
|
18
|
+
const entryPath = stringValue(remoteEntry?.path) ?? '';
|
|
19
|
+
const normalizedBase = publicPath.replace(/\/+$/u, '');
|
|
20
|
+
const normalizedPath = entryPath.replace(/^\/+|\/+$/gu, '');
|
|
21
|
+
return [
|
|
22
|
+
normalizedBase,
|
|
23
|
+
normalizedPath,
|
|
24
|
+
entryName
|
|
25
|
+
].filter(Boolean).join('/');
|
|
26
|
+
}
|
|
27
|
+
function metadata_backendFederationMetadata(manifest) {
|
|
28
|
+
return backendFederationMetadata(manifest);
|
|
29
|
+
}
|
|
30
|
+
function versionBoundaryMetadata(manifest) {
|
|
31
|
+
return backendFederationVersionBoundary(manifest);
|
|
32
|
+
}
|
|
33
|
+
function deliveryUnitMetadata(record) {
|
|
34
|
+
return recordField(record, 'deliveryUnit');
|
|
35
|
+
}
|
|
36
|
+
function manifestDeliveryUnit(backendFederation, boundary) {
|
|
37
|
+
return {
|
|
38
|
+
boundary: deliveryUnitMetadata(boundary),
|
|
39
|
+
top: deliveryUnitMetadata(backendFederation)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export { deliveryUnitMetadata, isRecord, manifestDeliveryUnit, metadata_backendFederationMetadata as backendFederationMetadata, recordField, resolveRemoteEntryFromMetadata, stringValue, versionBoundaryMetadata };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { evaluateNodeBackendFederationCommonJs } from "@modern-js/server-runtime-extensions/backend-federation-security/node";
|
|
2
|
+
import { loadBackendFederatedEffectApiFromManifest } from "./load.mjs";
|
|
3
|
+
function node_loadBackendFederatedEffectApiFromManifest(options) {
|
|
4
|
+
return loadBackendFederatedEffectApiFromManifest({
|
|
5
|
+
...options,
|
|
6
|
+
entryPolicy: {
|
|
7
|
+
...options.entryPolicy,
|
|
8
|
+
evaluateCommonJs: options.entryPolicy?.evaluateCommonJs ?? evaluateNodeBackendFederationCommonJs
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export { node_loadBackendFederatedEffectApiFromManifest as loadBackendFederatedEffectApiFromManifest };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { BackendFederationRemoteEntryError, loadBoundedBackendFederationResource, redactBackendFederationUrl } from "@modern-js/server-runtime-extensions/backend-federation-security";
|
|
2
|
+
import { BACKEND_FEDERATION_MANIFEST_FILE } from "../backend-federation/index.mjs";
|
|
3
|
+
import { BackendFederationManifestAdapterError, assertManifestAdapter } from "./errors.mjs";
|
|
4
|
+
import { isRecord } from "./metadata.mjs";
|
|
5
|
+
function getProcessEnv() {
|
|
6
|
+
return "u" > typeof process ? process.env : void 0;
|
|
7
|
+
}
|
|
8
|
+
function resolveManifestReferenceSource(options) {
|
|
9
|
+
if (options.manifestPath) return 'path';
|
|
10
|
+
if (options.manifestUrl) return 'url';
|
|
11
|
+
if (options.manifestEnv) return 'env';
|
|
12
|
+
}
|
|
13
|
+
function resolveBackendFederationManifestReference(options) {
|
|
14
|
+
if (options.manifestPath) return options.manifestPath;
|
|
15
|
+
if (options.manifestUrl) return options.manifestUrl;
|
|
16
|
+
if (!options.manifestEnv) return;
|
|
17
|
+
return options.env?.[options.manifestEnv] ?? getProcessEnv()?.[options.manifestEnv];
|
|
18
|
+
}
|
|
19
|
+
function assertExpectedIdentityForReference(options, source) {
|
|
20
|
+
if (void 0 === source || 'path' === source) return;
|
|
21
|
+
if (options.expected?.unitId && options.expected.buildMarker) return;
|
|
22
|
+
throw new BackendFederationManifestAdapterError('version_mismatch', `[BFF][Effect] Backend federation ${source} manifest references require expected.unitId and expected.buildMarker.`, void 0, {
|
|
23
|
+
label: 'expected.deliveryUnit',
|
|
24
|
+
expected: 'unitId + buildMarker',
|
|
25
|
+
received: options.expected,
|
|
26
|
+
referenceSource: source
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function isHttpUrl(value) {
|
|
30
|
+
return /^https?:\/\//iu.test(value);
|
|
31
|
+
}
|
|
32
|
+
function referenceForDiagnostics(reference) {
|
|
33
|
+
return isHttpUrl(reference) ? redactBackendFederationUrl(reference) : '[local manifest]';
|
|
34
|
+
}
|
|
35
|
+
async function readFileReference(reference) {
|
|
36
|
+
const [{ fileURLToPath }, fs] = await Promise.all([
|
|
37
|
+
import("node:url"),
|
|
38
|
+
import("node:fs/promises")
|
|
39
|
+
]);
|
|
40
|
+
const filePath = reference.startsWith('file:') ? fileURLToPath(reference) : reference;
|
|
41
|
+
return fs.readFile(filePath, 'utf8');
|
|
42
|
+
}
|
|
43
|
+
async function withTimeout(promise, timeoutMs, label) {
|
|
44
|
+
if (!timeoutMs || timeoutMs <= 0) return promise;
|
|
45
|
+
let timeout;
|
|
46
|
+
try {
|
|
47
|
+
return await Promise.race([
|
|
48
|
+
promise,
|
|
49
|
+
new Promise((_, reject)=>{
|
|
50
|
+
timeout = setTimeout(()=>{
|
|
51
|
+
reject(new BackendFederationManifestAdapterError('timeout', `[BFF][Effect] ${label} timed out after ${timeoutMs}ms.`));
|
|
52
|
+
}, timeoutMs);
|
|
53
|
+
})
|
|
54
|
+
]);
|
|
55
|
+
} finally{
|
|
56
|
+
if (timeout) clearTimeout(timeout);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function loadBackendFederationManifest(options) {
|
|
60
|
+
if (options.manifest) return options.manifest;
|
|
61
|
+
const reference = resolveBackendFederationManifestReference(options);
|
|
62
|
+
if (!reference) throw new BackendFederationManifestAdapterError('manifest_unavailable', `[BFF][Effect] Backend federation manifest reference is missing. Pass manifest, manifestPath, manifestUrl, or manifestEnv for ${BACKEND_FEDERATION_MANIFEST_FILE}.`);
|
|
63
|
+
assertExpectedIdentityForReference(options, resolveManifestReferenceSource(options));
|
|
64
|
+
const referenceSource = resolveManifestReferenceSource(options);
|
|
65
|
+
if ('path' === referenceSource && isHttpUrl(reference)) throw new BackendFederationManifestAdapterError('manifest_unavailable', '[BFF][Effect] Backend federation manifestPath must identify an explicit local file. Use manifestUrl for HTTP(S) manifests.');
|
|
66
|
+
if (('url' === referenceSource || 'env' === referenceSource) && !isHttpUrl(reference)) throw new BackendFederationManifestAdapterError('manifest_unavailable', `[BFF][Effect] Backend federation ${referenceSource} manifest references must use HTTP(S). Use manifestPath for an explicit local file.`);
|
|
67
|
+
try {
|
|
68
|
+
const diagnosticReference = referenceForDiagnostics(reference);
|
|
69
|
+
const source = isHttpUrl(reference) ? new TextDecoder('utf-8', {
|
|
70
|
+
fatal: true
|
|
71
|
+
}).decode(await loadBoundedBackendFederationResource(reference, {
|
|
72
|
+
allowInsecureHttp: options.manifestPolicy?.allowInsecureHttp === true,
|
|
73
|
+
fetch: options.fetch,
|
|
74
|
+
kind: 'manifest',
|
|
75
|
+
maxBytes: options.manifestPolicy?.maxBytes,
|
|
76
|
+
signal: options.manifestPolicy?.signal ?? options.signal,
|
|
77
|
+
timeoutMs: options.manifestPolicy?.timeoutMs ?? options.timeoutMs
|
|
78
|
+
})) : await withTimeout(readFileReference(reference), options.manifestPolicy?.timeoutMs ?? options.timeoutMs, 'Backend federation local manifest');
|
|
79
|
+
const manifest = JSON.parse(source);
|
|
80
|
+
assertManifestAdapter(isRecord(manifest), 'manifest_invalid', `[BFF][Effect] Backend federation manifest ${diagnosticReference} must be a JSON object.`);
|
|
81
|
+
return manifest;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
if (error instanceof BackendFederationManifestAdapterError) throw error;
|
|
84
|
+
if (error instanceof BackendFederationRemoteEntryError) throw new BackendFederationManifestAdapterError('timeout' === error.code ? 'timeout' : 'manifest_unavailable', `[BFF][Effect] ${error.message}`, error, error.details);
|
|
85
|
+
throw new BackendFederationManifestAdapterError('manifest_unavailable', `[BFF][Effect] Backend federation manifest ${referenceForDiagnostics(reference)} could not be loaded.`, error);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export { loadBackendFederationManifest, resolveBackendFederationManifestReference, withTimeout };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { BackendFederationRemoteEntryError, resolveBackendFederationRemoteEntryVerification } from "@modern-js/server-runtime-extensions/backend-federation-security";
|
|
2
|
+
import { backendFederationExposeNames } from "@modern-js/utils/universal";
|
|
3
|
+
import { BACKEND_FEDERATION_EFFECT_EXPOSE } from "../backend-federation/index.mjs";
|
|
4
|
+
import { BackendFederationManifestAdapterError, assertManifestAdapter } from "./errors.mjs";
|
|
5
|
+
import { backendFederationMetadata, recordField, resolveRemoteEntryFromMetadata, stringValue } from "./metadata.mjs";
|
|
6
|
+
function resolveBackendFederationRemoteFromManifest(manifest, remoteOverride = {}) {
|
|
7
|
+
const backendFederation = backendFederationMetadata(manifest);
|
|
8
|
+
const entry = recordField(manifest, 'entry');
|
|
9
|
+
const metaData = recordField(manifest, 'metaData');
|
|
10
|
+
const remoteEntry = recordField(metaData, 'remoteEntry');
|
|
11
|
+
const remoteName = remoteOverride.name ?? stringValue(backendFederation?.name) ?? stringValue(manifest.name) ?? stringValue(manifest.id);
|
|
12
|
+
const manifestEntryUrl = stringValue(backendFederation?.containerEntry) ?? stringValue(entry?.url) ?? resolveRemoteEntryFromMetadata(manifest);
|
|
13
|
+
const remoteEntryUrl = remoteOverride.entry ?? manifestEntryUrl;
|
|
14
|
+
const remoteType = remoteOverride.type ?? stringValue(backendFederation?.remoteType) ?? stringValue(entry?.type) ?? stringValue(remoteEntry?.type) ?? 'module';
|
|
15
|
+
const expose = remoteOverride.expose ?? backendFederationExposeNames(backendFederation)[0] ?? BACKEND_FEDERATION_EFFECT_EXPOSE;
|
|
16
|
+
let verification;
|
|
17
|
+
try {
|
|
18
|
+
const manifestVerification = resolveBackendFederationRemoteEntryVerification(manifest);
|
|
19
|
+
verification = remoteOverride.verification ?? (remoteEntryUrl === manifestEntryUrl ? manifestVerification : void 0);
|
|
20
|
+
} catch (cause) {
|
|
21
|
+
if (cause instanceof BackendFederationRemoteEntryError) throw new BackendFederationManifestAdapterError('identity_mismatch' === cause.code ? 'version_mismatch' : 'manifest_invalid', `[BFF][Effect] ${cause.message}`, cause, cause.details);
|
|
22
|
+
throw cause;
|
|
23
|
+
}
|
|
24
|
+
assertManifestAdapter(remoteName, 'manifest_invalid', '[BFF][Effect] Backend federation manifest must declare a remote name.');
|
|
25
|
+
assertManifestAdapter(remoteEntryUrl, 'manifest_invalid', '[BFF][Effect] Backend federation manifest must declare a backend remote entry URL.');
|
|
26
|
+
assertManifestAdapter(expose === BACKEND_FEDERATION_EFFECT_EXPOSE, 'manifest_invalid', `[BFF][Effect] Backend federation manifest expose must be ${BACKEND_FEDERATION_EFFECT_EXPOSE}.`);
|
|
27
|
+
return {
|
|
28
|
+
name: remoteName,
|
|
29
|
+
entry: remoteEntryUrl,
|
|
30
|
+
type: remoteType,
|
|
31
|
+
...verification ? {
|
|
32
|
+
verification
|
|
33
|
+
} : {},
|
|
34
|
+
...remoteOverride.entryGlobalName ? {
|
|
35
|
+
entryGlobalName: remoteOverride.entryGlobalName
|
|
36
|
+
} : {},
|
|
37
|
+
expose,
|
|
38
|
+
...remoteOverride.shareScope ? {
|
|
39
|
+
shareScope: remoteOverride.shareScope
|
|
40
|
+
} : {}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export { resolveBackendFederationRemoteFromManifest };
|
|
File without changes
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { formatBackendFederationValidationErrors, validateBackendFederationManifest } from "@modern-js/utils/universal";
|
|
2
|
+
import { BACKEND_FEDERATION_CONTRACT_VERSION, BACKEND_FEDERATION_NODE_ADAPTER_VERSION } from "../backend-federation/index.mjs";
|
|
3
|
+
import { BackendFederationManifestAdapterError, assertConsistentValue, assertManifestAdapter, assertVersionValue } from "./errors.mjs";
|
|
4
|
+
import { backendFederationMetadata, manifestDeliveryUnit, recordField, stringValue, versionBoundaryMetadata } from "./metadata.mjs";
|
|
5
|
+
function resolveExpectedBuildMarker(expected) {
|
|
6
|
+
if (void 0 !== expected.buildVersion && void 0 !== expected.buildMarker && expected.buildVersion !== expected.buildMarker) throw new BackendFederationManifestAdapterError('version_mismatch', `[BFF][Effect] Backend federation expected buildVersion/buildMarker mismatch: buildVersion ${expected.buildVersion}, buildMarker ${expected.buildMarker}.`, void 0, {
|
|
7
|
+
label: 'expected.buildVersion/buildMarker',
|
|
8
|
+
expected: expected.buildVersion,
|
|
9
|
+
received: expected.buildMarker
|
|
10
|
+
});
|
|
11
|
+
return expected.buildMarker;
|
|
12
|
+
}
|
|
13
|
+
function validation_validateBackendFederationManifest(manifest, expected = {}) {
|
|
14
|
+
const expectedBuildMarker = resolveExpectedBuildMarker(expected);
|
|
15
|
+
const backendFederation = backendFederationMetadata(manifest);
|
|
16
|
+
assertManifestAdapter(backendFederation, 'manifest_invalid', '[BFF][Effect] Backend federation manifest must declare backendFederation metadata.');
|
|
17
|
+
const manifestShape = validateBackendFederationManifest(manifest, {
|
|
18
|
+
expectedContractVersion: false,
|
|
19
|
+
expectedNodeAdapterVersion: false,
|
|
20
|
+
validateDeliveryUnit: false
|
|
21
|
+
});
|
|
22
|
+
assertManifestAdapter(manifestShape.ok, 'manifest_invalid', `[BFF][Effect] Backend federation manifest schema invalid: ${formatBackendFederationValidationErrors(manifestShape.errors)}.`);
|
|
23
|
+
assertManifestAdapter('effect' === backendFederation.runtimeFramework, 'strict_effect_required', '[BFF][Effect] Backend federation manifest must declare runtimeFramework: "effect".');
|
|
24
|
+
assertManifestAdapter(true === backendFederation.strictEffectApproach, 'strict_effect_required', '[BFF][Effect] Backend federation manifest must declare strictEffectApproach: true.');
|
|
25
|
+
assertVersionValue(stringValue(backendFederation.contractVersion), expected.contractVersion ?? BACKEND_FEDERATION_CONTRACT_VERSION, 'manifest contractVersion');
|
|
26
|
+
assertVersionValue(stringValue(backendFederation.nodeAdapterVersion), expected.nodeAdapterVersion ?? BACKEND_FEDERATION_NODE_ADAPTER_VERSION, 'manifest nodeAdapterVersion');
|
|
27
|
+
const boundary = versionBoundaryMetadata(manifest);
|
|
28
|
+
assertVersionValue(stringValue(backendFederation.name) ?? stringValue(manifest.name) ?? stringValue(manifest.id), expected.remoteName, 'manifest remoteName');
|
|
29
|
+
assertVersionValue(stringValue(boundary?.packageName), expected.packageName, 'versionBoundary.packageName');
|
|
30
|
+
assertVersionValue(stringValue(boundary?.version), expected.version, 'versionBoundary.version');
|
|
31
|
+
assertVersionValue(stringValue(boundary?.buildVersion), expected.buildVersion, 'versionBoundary.buildVersion');
|
|
32
|
+
assertConsistentValue(stringValue(manifest.version), stringValue(boundary?.version), 'manifest versionBoundary.version');
|
|
33
|
+
assertConsistentValue(stringValue(manifest.buildVersion), stringValue(boundary?.buildVersion), 'manifest versionBoundary.buildVersion');
|
|
34
|
+
const deliveryUnit = manifestDeliveryUnit(backendFederation, boundary);
|
|
35
|
+
assertConsistentValue(stringValue(deliveryUnit.boundary?.buildMarker), stringValue(boundary?.buildVersion), 'versionBoundary.deliveryUnit.buildMarker vs versionBoundary.buildVersion');
|
|
36
|
+
assertConsistentValue(stringValue(deliveryUnit.top?.unitId), stringValue(deliveryUnit.boundary?.unitId), 'backendFederation.deliveryUnit.unitId vs versionBoundary.deliveryUnit.unitId');
|
|
37
|
+
const manifestUnitId = stringValue(deliveryUnit.boundary?.unitId) ?? stringValue(deliveryUnit.top?.unitId);
|
|
38
|
+
assertVersionValue(manifestUnitId, expected.unitId, 'deliveryUnit.unitId');
|
|
39
|
+
assertVersionValue(stringValue(deliveryUnit.boundary?.buildMarker), expectedBuildMarker, 'deliveryUnit.buildMarker');
|
|
40
|
+
}
|
|
41
|
+
function validateLoadedBackendFederationContract(loaded, manifest, remote) {
|
|
42
|
+
const loadedContract = loaded.backendFederationContract;
|
|
43
|
+
const backendFederation = backendFederationMetadata(manifest);
|
|
44
|
+
const boundary = versionBoundaryMetadata(manifest);
|
|
45
|
+
const compatibility = recordField(loadedContract, 'compatibility');
|
|
46
|
+
assertManifestAdapter(compatibility, 'version_mismatch', `[BFF][Effect] Backend federation expose ${remote.name}/${remote.expose?.replace(/^\.\//u, '')} must declare compatibility metadata.`);
|
|
47
|
+
assertVersionValue(stringValue(loadedContract?.role), 'microvertical-server', 'expose role');
|
|
48
|
+
assertVersionValue(stringValue(loadedContract?.name), remote.name, 'expose name');
|
|
49
|
+
assertVersionValue(stringValue(compatibility.contractVersion), stringValue(backendFederation?.contractVersion), 'expose contractVersion');
|
|
50
|
+
assertVersionValue(stringValue(compatibility.nodeAdapterVersion), stringValue(backendFederation?.nodeAdapterVersion), 'expose nodeAdapterVersion');
|
|
51
|
+
assertVersionValue(stringValue(compatibility.packageName), stringValue(boundary?.packageName), 'expose packageName');
|
|
52
|
+
assertVersionValue(stringValue(compatibility.build), stringValue(boundary?.buildVersion), 'expose buildVersion');
|
|
53
|
+
const deliveryUnit = manifestDeliveryUnit(backendFederation, boundary);
|
|
54
|
+
const manifestUnitId = stringValue(deliveryUnit.boundary?.unitId) ?? stringValue(deliveryUnit.top?.unitId);
|
|
55
|
+
const manifestBuildMarker = stringValue(deliveryUnit.boundary?.buildMarker);
|
|
56
|
+
assertConsistentValue(manifestUnitId, stringValue(compatibility.unitId), 'deliveryUnit.unitId vs expose compatibility.unitId');
|
|
57
|
+
assertConsistentValue(manifestBuildMarker, stringValue(compatibility.build), 'deliveryUnit.buildMarker vs expose compatibility.build');
|
|
58
|
+
}
|
|
59
|
+
function classifyLoadError(error) {
|
|
60
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
61
|
+
if ('object' == typeof error && null !== error && 'code' in error && 'timeout' === error.code || /timed out/iu.test(message)) return 'timeout';
|
|
62
|
+
if (message.includes('delivery-unit identity mismatch')) return 'version_mismatch';
|
|
63
|
+
if (message.includes('strictEffectApproach') || message.includes('runtimeFramework')) return 'strict_effect_required';
|
|
64
|
+
return 'remote_unavailable';
|
|
65
|
+
}
|
|
66
|
+
export { classifyLoadError, validateLoadedBackendFederationContract, validation_validateBackendFederationManifest as validateBackendFederationManifest };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { fs, upath } from "@modern-js/utils";
|
|
2
|
+
import { resolveEffectEntryFile } from "./paths.mjs";
|
|
3
|
+
const BUILT_ENTRY_EXT = /\.(?:[cm]?ts|tsx|jsx)$/u;
|
|
4
|
+
function resolveBuiltEffectEntry(appDirectory, distDirectory, sourceEntry) {
|
|
5
|
+
if (void 0 === sourceEntry || 0 === sourceEntry.length) return;
|
|
6
|
+
const relativeEntry = upath.relative(appDirectory, sourceEntry);
|
|
7
|
+
if ('..' === relativeEntry || relativeEntry.startsWith(`..${upath.sep}`)) throw new Error(`Effect BFF entry must be inside the application directory: ${sourceEntry}`);
|
|
8
|
+
const builtEntry = upath.resolve(distDirectory, relativeEntry).replace(BUILT_ENTRY_EXT, '.js');
|
|
9
|
+
return fs.existsSync(builtEntry) ? builtEntry : void 0;
|
|
10
|
+
}
|
|
11
|
+
async function bundleBuiltEffectEntryForNode(options) {
|
|
12
|
+
const { appDir, apiDir, distDir, effectEntry, format } = options;
|
|
13
|
+
const sourceEntry = resolveEffectEntryFile({
|
|
14
|
+
appDir,
|
|
15
|
+
apiDir,
|
|
16
|
+
effectEntry
|
|
17
|
+
});
|
|
18
|
+
const builtEntry = resolveBuiltEffectEntry(appDir, distDir, sourceEntry);
|
|
19
|
+
if (void 0 === builtEntry || 0 === builtEntry.length) throw new Error(`Effect BFF entry was not emitted into ${distDir}: ${sourceEntry ?? upath.resolve(apiDir, 'index')}`);
|
|
20
|
+
const { bundleEffectEntryForNode } = await import("../effect-source-loader/loader.mjs");
|
|
21
|
+
return bundleEffectEntryForNode({
|
|
22
|
+
appDir,
|
|
23
|
+
entryPath: builtEntry,
|
|
24
|
+
format
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function resolveEffectEntryPaths(options) {
|
|
28
|
+
const { appDir, apiDir, effectEntry } = options;
|
|
29
|
+
const sourceEffectEntry = resolveEffectEntryFile({
|
|
30
|
+
appDir,
|
|
31
|
+
apiDir,
|
|
32
|
+
effectEntry
|
|
33
|
+
});
|
|
34
|
+
const relativeEffectEntry = void 0 !== sourceEffectEntry && sourceEffectEntry.length > 0 ? upath.relative(appDir, sourceEffectEntry).replace(BUILT_ENTRY_EXT, '.js') : '';
|
|
35
|
+
return {
|
|
36
|
+
sourceEffectEntry,
|
|
37
|
+
relativeEffectEntry
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export { bundleBuiltEffectEntryForNode, resolveBuiltEffectEntry, resolveEffectEntryPaths };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { collectEffectEndpoints } from "@modern-js/bff-effect/effect";
|
|
2
|
+
import { logger } from "@modern-js/utils";
|
|
3
|
+
import { createEffectClientGeneration, renderEffectClientCode, renderEffectClientDeclaration } from "./rendering.mjs";
|
|
4
|
+
import { getHttpApiRuntime, loadEffectApi } from "./runtime.mjs";
|
|
5
|
+
function generateEffectClient(options) {
|
|
6
|
+
return loadEffectApi({
|
|
7
|
+
appDir: options.appDir,
|
|
8
|
+
resourcePath: options.resourcePath,
|
|
9
|
+
onDependency: options.onDependency
|
|
10
|
+
}).then((api)=>{
|
|
11
|
+
if (null === api) {
|
|
12
|
+
logger.warn(`[BFF][Effect] Failed to generate client for ${options.resourcePath}: unable to resolve exported HttpApi.`);
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
return getHttpApiRuntime().then((httpApiRuntime)=>{
|
|
16
|
+
const endpoints = collectEffectEndpoints(httpApiRuntime.reflect, api, options.prefix);
|
|
17
|
+
const generation = createEffectClientGeneration(endpoints, options);
|
|
18
|
+
return {
|
|
19
|
+
code: renderEffectClientCode(generation),
|
|
20
|
+
declaration: renderEffectClientDeclaration(endpoints),
|
|
21
|
+
endpoints,
|
|
22
|
+
operationContracts: generation.operationContracts,
|
|
23
|
+
operationVersion: generation.operationVersion,
|
|
24
|
+
requestId: generation.requestId
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function generateEffectClientCode(options) {
|
|
30
|
+
return generateEffectClient(options).then((artifacts)=>null === artifacts ? null : artifacts.code);
|
|
31
|
+
}
|
|
32
|
+
export { generateEffectClient, generateEffectClientCode };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { findExists, fs, upath } from "@modern-js/utils";
|
|
2
|
+
const JS_OR_TS_EXTS = [
|
|
3
|
+
'.js',
|
|
4
|
+
'.jsx',
|
|
5
|
+
'.ts',
|
|
6
|
+
'.tsx',
|
|
7
|
+
'.mjs',
|
|
8
|
+
'.mts',
|
|
9
|
+
'.cjs',
|
|
10
|
+
'.cts'
|
|
11
|
+
];
|
|
12
|
+
function resolveEffectEntryFile(options) {
|
|
13
|
+
const { appDir, apiDir, effectEntry } = options;
|
|
14
|
+
const resolveEntry = (entryWithoutExt)=>{
|
|
15
|
+
if ('' !== upath.extname(entryWithoutExt)) return fs.existsSync(entryWithoutExt) ? entryWithoutExt : void 0;
|
|
16
|
+
return findExists(JS_OR_TS_EXTS.map((ext)=>`${entryWithoutExt}${ext}`)) || void 0;
|
|
17
|
+
};
|
|
18
|
+
if (void 0 !== effectEntry && '' !== effectEntry) {
|
|
19
|
+
const entryWithoutExt = upath.isAbsolute(effectEntry) ? effectEntry : upath.resolve(appDir, effectEntry);
|
|
20
|
+
return resolveEntry(entryWithoutExt);
|
|
21
|
+
}
|
|
22
|
+
return resolveEntry(upath.resolve(apiDir, 'index'));
|
|
23
|
+
}
|
|
24
|
+
export { resolveEffectEntryFile };
|