@bleedingdev/modern-js-plugin-bff-extensions 3.9.0-ultramodern.2 → 3.9.0-ultramodern.20
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 +25 -5
- package/dist/cjs/backend-federation/edge-runtime.js +3 -15
- package/dist/cjs/backend-federation/edge.js +9 -10
- package/dist/cjs/backend-federation/identity.js +26 -31
- package/dist/cjs/backend-federation/index.js +5 -5
- package/dist/cjs/backend-federation/integrity-plugin.js +112 -0
- package/dist/cjs/backend-federation/load.js +3 -13
- package/dist/cjs/backend-federation/node-shared.js +85 -0
- package/dist/cjs/backend-federation/node.js +6 -1
- package/dist/cjs/backend-federation/plugin.js +40 -21
- package/dist/cjs/backend-federation/runtime.js +16 -84
- package/dist/cjs/backend-federation/validate-loaded.js +2 -4
- package/dist/cjs/backend-federation-manifest/load.js +40 -12
- package/dist/cjs/backend-federation-manifest/metadata.js +3 -3
- package/dist/cjs/backend-federation-manifest/node.js +3 -1
- package/dist/cjs/backend-federation-manifest/remote.js +3 -9
- package/dist/cjs/backend-federation-manifest/validation.js +16 -12
- package/dist/cjs/cross-project-policy/evaluation.js +3 -3
- package/dist/cjs/cross-project-policy/node.js +11 -30
- package/dist/cjs/{backend-federation/legacy-warning.js → cross-project-policy/producer-runtime.js} +23 -5
- package/dist/cjs/{effect-adapter → effect-server}/cross-project-policy.js +5 -5
- package/dist/cjs/effect-server/entry.js +63 -0
- package/dist/cjs/{effect-adapter → effect-server}/error-response.js +4 -4
- package/dist/cjs/effect-server/index.js +199 -0
- package/dist/cjs/{client-generator → effect-source-loader}/built-entry.js +5 -6
- package/dist/cjs/{client-generator/runtime.js → effect-source-loader/http-api-runtime.js} +12 -20
- package/dist/cjs/effect-source-loader/index.js +18 -6
- package/dist/cjs/effect-source-loader/loader.js +17 -30
- package/dist/cjs/effect-source-loader/operation-contracts.js +60 -0
- package/dist/cjs/effect-source-loader/paths.js +90 -0
- package/dist/cjs/effect-source-loader/rspack-loader.js +73 -0
- package/dist/cjs/effect-source-loader/worker-runtime-source.js +71 -0
- package/dist/cjs/effect-source-loader/worker-runtime-wrapper.js +7 -50
- package/dist/cjs/hono/cross-project-policy.js +2 -1
- package/dist/cjs/{client-generator/paths.js → hono/node.js} +14 -25
- package/dist/esm/backend-federation/edge-runtime.mjs +2 -11
- package/dist/esm/backend-federation/edge.mjs +5 -6
- package/dist/esm/backend-federation/identity.mjs +21 -32
- package/dist/esm/backend-federation/index.mjs +1 -1
- package/dist/esm/backend-federation/integrity-plugin.mjs +68 -0
- package/dist/esm/backend-federation/load.mjs +2 -12
- package/dist/esm/backend-federation/node-shared.mjs +41 -0
- package/dist/esm/backend-federation/node.mjs +4 -2
- package/dist/esm/backend-federation/plugin.mjs +37 -18
- package/dist/esm/backend-federation/runtime.mjs +16 -84
- package/dist/esm/backend-federation/validate-loaded.mjs +2 -4
- package/dist/esm/backend-federation-manifest/load.mjs +40 -12
- package/dist/esm/backend-federation-manifest/metadata.mjs +1 -1
- package/dist/esm/backend-federation-manifest/node.mjs +3 -1
- package/dist/esm/backend-federation-manifest/remote.mjs +2 -8
- package/dist/esm/backend-federation-manifest/validation.mjs +15 -11
- package/dist/esm/cross-project-policy/evaluation.mjs +1 -1
- package/dist/esm/cross-project-policy/node.mjs +10 -19
- package/dist/esm/cross-project-policy/producer-runtime.mjs +20 -0
- package/dist/esm/{effect-adapter → effect-server}/cross-project-policy.mjs +3 -3
- package/dist/esm/effect-server/entry.mjs +15 -0
- package/dist/esm/{effect-adapter → effect-server}/error-response.mjs +2 -2
- package/dist/esm/effect-server/index.mjs +151 -0
- package/dist/esm/{client-generator → effect-source-loader}/built-entry.mjs +6 -7
- package/dist/esm/effect-source-loader/http-api-runtime.mjs +19 -0
- package/dist/esm/effect-source-loader/index.mjs +5 -2
- package/dist/esm/effect-source-loader/loader.mjs +17 -30
- package/dist/esm/effect-source-loader/operation-contracts.mjs +22 -0
- package/dist/esm/effect-source-loader/paths.mjs +43 -0
- package/dist/esm/effect-source-loader/rspack-loader.mjs +35 -0
- package/dist/esm/effect-source-loader/worker-runtime-source.mjs +23 -0
- package/dist/esm/effect-source-loader/worker-runtime-wrapper.mjs +5 -45
- package/dist/esm/hono/cross-project-policy.mjs +2 -1
- package/dist/esm-node/backend-federation/edge-runtime.mjs +2 -11
- package/dist/esm-node/backend-federation/edge.mjs +5 -6
- package/dist/esm-node/backend-federation/identity.mjs +21 -32
- package/dist/esm-node/backend-federation/index.mjs +1 -1
- package/dist/esm-node/backend-federation/integrity-plugin.mjs +69 -0
- package/dist/esm-node/backend-federation/load.mjs +2 -12
- package/dist/esm-node/backend-federation/node-shared.mjs +42 -0
- package/dist/esm-node/backend-federation/node.mjs +4 -2
- package/dist/esm-node/backend-federation/plugin.mjs +37 -18
- package/dist/esm-node/backend-federation/runtime.mjs +16 -84
- package/dist/esm-node/backend-federation/validate-loaded.mjs +2 -4
- package/dist/esm-node/backend-federation-manifest/load.mjs +40 -12
- package/dist/esm-node/backend-federation-manifest/metadata.mjs +1 -1
- package/dist/esm-node/backend-federation-manifest/node.mjs +3 -1
- package/dist/esm-node/backend-federation-manifest/remote.mjs +2 -8
- package/dist/esm-node/backend-federation-manifest/validation.mjs +15 -11
- package/dist/esm-node/cross-project-policy/evaluation.mjs +1 -1
- package/dist/esm-node/cross-project-policy/node.mjs +10 -19
- package/dist/esm-node/cross-project-policy/producer-runtime.mjs +21 -0
- package/dist/esm-node/{effect-adapter → effect-server}/cross-project-policy.mjs +3 -3
- package/dist/esm-node/effect-server/entry.mjs +16 -0
- package/dist/esm-node/{effect-adapter → effect-server}/error-response.mjs +2 -2
- package/dist/esm-node/effect-server/index.mjs +152 -0
- package/dist/esm-node/{client-generator → effect-source-loader}/built-entry.mjs +6 -7
- package/dist/esm-node/effect-source-loader/http-api-runtime.mjs +20 -0
- package/dist/esm-node/effect-source-loader/index.mjs +5 -2
- package/dist/esm-node/effect-source-loader/loader.mjs +17 -30
- package/dist/esm-node/effect-source-loader/operation-contracts.mjs +23 -0
- package/dist/esm-node/effect-source-loader/paths.mjs +44 -0
- package/dist/esm-node/effect-source-loader/rspack-loader.mjs +37 -0
- package/dist/esm-node/effect-source-loader/worker-runtime-source.mjs +24 -0
- package/dist/esm-node/effect-source-loader/worker-runtime-wrapper.mjs +5 -45
- package/dist/esm-node/hono/cross-project-policy.mjs +2 -1
- package/dist/esm-node/hono/node.mjs +14 -0
- package/dist/types/backend-federation/edge-runtime.d.ts +0 -5
- package/dist/types/backend-federation/edge.d.ts +5 -5
- package/dist/types/backend-federation/identity.d.ts +4 -16
- package/dist/types/backend-federation/index.d.ts +1 -1
- package/dist/types/backend-federation/integrity-plugin.d.ts +13 -0
- package/dist/types/backend-federation/load.d.ts +1 -7
- package/dist/types/backend-federation/node-shared.d.ts +20 -0
- package/dist/types/backend-federation/node.d.ts +4 -3
- package/dist/types/backend-federation/plugin.d.ts +4 -0
- package/dist/types/backend-federation/types.d.ts +3 -14
- package/dist/types/backend-federation/validate-loaded.d.ts +1 -2
- package/dist/types/backend-federation-manifest/metadata.d.ts +0 -6
- package/dist/types/backend-federation-manifest/types.d.ts +2 -1
- package/dist/types/cross-project-policy/evaluation.d.ts +1 -1
- package/dist/types/cross-project-policy/node.d.ts +3 -2
- package/dist/types/cross-project-policy/producer-runtime.d.ts +20 -0
- package/dist/types/effect-server/cross-project-policy.d.ts +5 -0
- package/dist/types/effect-server/entry.d.ts +3 -0
- package/dist/types/effect-server/error-response.d.ts +2 -0
- package/dist/types/effect-server/index.d.ts +4 -0
- package/dist/types/effect-source-loader/index.d.ts +5 -2
- package/dist/types/effect-source-loader/operation-contracts.d.ts +8 -0
- package/dist/types/effect-source-loader/paths.d.ts +23 -0
- package/dist/types/effect-source-loader/rspack-loader.d.ts +9 -0
- package/dist/types/effect-source-loader/worker-runtime-source.d.ts +4 -0
- package/dist/types/effect-source-loader/worker-runtime-wrapper.d.ts +4 -22
- package/dist/types/hono/node.d.ts +4 -0
- package/package.json +64 -36
- package/src/backend-federation/edge-runtime.ts +2 -13
- package/src/backend-federation/edge.ts +16 -21
- package/src/backend-federation/identity.ts +39 -65
- package/src/backend-federation/index.ts +1 -1
- package/src/backend-federation/integrity-plugin.ts +157 -0
- package/src/backend-federation/load.ts +7 -35
- package/src/backend-federation/node-shared.ts +76 -0
- package/src/backend-federation/node.ts +7 -14
- package/src/backend-federation/plugin.ts +73 -25
- package/src/backend-federation/runtime.ts +16 -90
- package/src/backend-federation/types.ts +3 -18
- package/src/backend-federation/validate-loaded.ts +2 -7
- package/src/backend-federation-manifest/load.ts +93 -38
- package/src/backend-federation-manifest/metadata.ts +1 -7
- package/src/backend-federation-manifest/node.ts +3 -2
- package/src/backend-federation-manifest/remote.ts +1 -7
- package/src/backend-federation-manifest/types.ts +2 -0
- package/src/backend-federation-manifest/validation.ts +29 -39
- package/src/cross-project-policy/evaluation.ts +1 -1
- package/src/cross-project-policy/node.ts +10 -41
- package/src/cross-project-policy/producer-runtime.ts +43 -0
- package/src/{effect-adapter → effect-server}/cross-project-policy.ts +8 -2
- package/src/effect-server/entry.ts +18 -0
- package/src/{effect-adapter → effect-server}/error-response.ts +1 -1
- package/src/effect-server/index.ts +242 -0
- package/src/{client-generator → effect-source-loader}/built-entry.ts +12 -12
- package/src/effect-source-loader/http-api-runtime.ts +39 -0
- package/src/effect-source-loader/index.ts +8 -5
- package/src/effect-source-loader/loader.ts +47 -52
- package/src/effect-source-loader/operation-contracts.ts +38 -0
- package/src/effect-source-loader/paths.ts +74 -0
- package/src/effect-source-loader/rspack-loader.ts +71 -0
- package/src/effect-source-loader/worker-runtime-source.ts +34 -0
- package/src/effect-source-loader/worker-runtime-wrapper.ts +5 -75
- package/src/hono/cross-project-policy.ts +2 -1
- package/src/hono/node.ts +24 -0
- package/dist/cjs/backend-federation/entry.js +0 -94
- package/dist/cjs/client-generator/generator.js +0 -73
- package/dist/cjs/client-generator/index.js +0 -54
- package/dist/cjs/client-generator/rendering.js +0 -253
- package/dist/cjs/client-generator/types.js +0 -18
- package/dist/cjs/effect-adapter/entry.js +0 -98
- package/dist/cjs/effect-adapter/index.js +0 -187
- package/dist/esm/backend-federation/entry.mjs +0 -56
- package/dist/esm/backend-federation/legacy-warning.mjs +0 -2
- package/dist/esm/client-generator/generator.mjs +0 -32
- package/dist/esm/client-generator/index.mjs +0 -3
- package/dist/esm/client-generator/paths.mjs +0 -24
- package/dist/esm/client-generator/rendering.mjs +0 -209
- package/dist/esm/client-generator/runtime.mjs +0 -27
- package/dist/esm/client-generator/types.mjs +0 -0
- package/dist/esm/effect-adapter/entry.mjs +0 -50
- package/dist/esm/effect-adapter/index.mjs +0 -139
- package/dist/esm-node/backend-federation/entry.mjs +0 -57
- package/dist/esm-node/backend-federation/legacy-warning.mjs +0 -3
- package/dist/esm-node/client-generator/generator.mjs +0 -33
- package/dist/esm-node/client-generator/index.mjs +0 -4
- package/dist/esm-node/client-generator/paths.mjs +0 -25
- package/dist/esm-node/client-generator/rendering.mjs +0 -210
- package/dist/esm-node/client-generator/runtime.mjs +0 -29
- package/dist/esm-node/client-generator/types.mjs +0 -1
- package/dist/esm-node/effect-adapter/entry.mjs +0 -51
- package/dist/esm-node/effect-adapter/index.mjs +0 -140
- package/dist/types/backend-federation/entry.d.ts +0 -2
- package/dist/types/backend-federation/legacy-warning.d.ts +0 -1
- package/dist/types/client-generator/generator.d.ts +0 -3
- package/dist/types/client-generator/index.d.ts +0 -4
- package/dist/types/client-generator/paths.d.ts +0 -5
- package/dist/types/client-generator/rendering.d.ts +0 -24
- package/dist/types/client-generator/types.d.ts +0 -33
- package/dist/types/effect-adapter/cross-project-policy.d.ts +0 -4
- package/dist/types/effect-adapter/entry.d.ts +0 -2
- package/dist/types/effect-adapter/error-response.d.ts +0 -2
- package/dist/types/effect-adapter/index.d.ts +0 -19
- package/src/backend-federation/entry.ts +0 -125
- package/src/backend-federation/legacy-warning.ts +0 -2
- package/src/client-generator/generator.ts +0 -52
- package/src/client-generator/index.ts +0 -10
- package/src/client-generator/paths.ts +0 -40
- package/src/client-generator/rendering.ts +0 -307
- package/src/client-generator/runtime.ts +0 -69
- package/src/client-generator/types.ts +0 -35
- package/src/effect-adapter/entry.ts +0 -95
- package/src/effect-adapter/index.ts +0 -246
- /package/dist/types/{client-generator → effect-source-loader}/built-entry.d.ts +0 -0
- /package/dist/types/{client-generator/runtime.d.ts → effect-source-loader/http-api-runtime.d.ts} +0 -0
|
@@ -17,77 +17,51 @@ export type BackendFederationIdentityIssue = {
|
|
|
17
17
|
message: string;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
* Compares the expose's `backendFederationContract.compatibility` identity
|
|
23
|
-
* (`unitId`, `build`) against the consumer's expected delivery-unit identity.
|
|
24
|
-
* Missing identity metadata is an error: with an expectation present there is
|
|
25
|
-
* no legacy escape hatch.
|
|
26
|
-
*/
|
|
27
|
-
export function validateExpectedBackendFederationIdentity(
|
|
28
|
-
loaded: unknown,
|
|
29
|
-
expected: BackendFederationExpectedIdentity,
|
|
30
|
-
options: {
|
|
31
|
-
/**
|
|
32
|
-
* Tolerate exposes that declare no identity metadata (legacy modules).
|
|
33
|
-
* Mismatching declared values still fail. Used by the manifest adapter,
|
|
34
|
-
* whose manifest-side identity is already validated against `expected`;
|
|
35
|
-
* the raw identity-aware loader is strict.
|
|
36
|
-
*/
|
|
37
|
-
allowMissingIdentityMetadata?: boolean;
|
|
38
|
-
} = {},
|
|
39
|
-
): BackendFederationIdentityIssue[] {
|
|
40
|
-
const allowMissing = options.allowMissingIdentityMetadata === true;
|
|
41
|
-
const issues: BackendFederationIdentityIssue[] = [];
|
|
20
|
+
/** Decode independently at each received-manifest / executed-expose boundary. */
|
|
21
|
+
export function backendFederationCompatibility(loaded: unknown) {
|
|
42
22
|
const contract = isRecord(loaded)
|
|
43
23
|
? loaded.backendFederationContract
|
|
44
24
|
: undefined;
|
|
45
25
|
const compatibility = isRecord(contract) ? contract.compatibility : undefined;
|
|
26
|
+
return isRecord(compatibility) ? compatibility : undefined;
|
|
27
|
+
}
|
|
46
28
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
message: `missing delivery-unit id; expected ${expected.unitId}`,
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
} else if (unitId !== expected.unitId) {
|
|
69
|
-
issues.push({
|
|
70
|
-
path: 'backendFederationContract.compatibility.unitId',
|
|
71
|
-
message: `expected ${expected.unitId}, received ${unitId}`,
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const build = compatibility.build;
|
|
76
|
-
if (typeof build !== 'string' || build.length === 0) {
|
|
77
|
-
if (!allowMissing) {
|
|
78
|
-
issues.push({
|
|
79
|
-
path: 'backendFederationContract.compatibility.build',
|
|
80
|
-
message: `missing build marker; expected ${expected.buildMarker}`,
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
} else if (build !== expected.buildMarker) {
|
|
84
|
-
issues.push({
|
|
85
|
-
path: 'backendFederationContract.compatibility.build',
|
|
86
|
-
message: `expected ${expected.buildMarker}, received ${build}`,
|
|
87
|
-
});
|
|
88
|
-
}
|
|
29
|
+
export function validateBackendFederationCompatibility(
|
|
30
|
+
compatibility: Record<string, unknown>,
|
|
31
|
+
expected: Record<string, string | undefined>,
|
|
32
|
+
): BackendFederationIdentityIssue[] {
|
|
33
|
+
return Object.entries(expected).flatMap(([field, value]) =>
|
|
34
|
+
value === undefined ||
|
|
35
|
+
(typeof compatibility[field] === 'string' &&
|
|
36
|
+
compatibility[field].length > 0 &&
|
|
37
|
+
compatibility[field] === value)
|
|
38
|
+
? []
|
|
39
|
+
: [
|
|
40
|
+
{
|
|
41
|
+
path: `backendFederationContract.compatibility.${field}`,
|
|
42
|
+
message: `expected ${value}, received ${typeof compatibility[field] === 'string' ? compatibility[field] : typeof compatibility[field]}`,
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
);
|
|
46
|
+
}
|
|
89
47
|
|
|
90
|
-
|
|
48
|
+
export function validateExpectedBackendFederationIdentity(
|
|
49
|
+
loaded: unknown,
|
|
50
|
+
expected: BackendFederationExpectedIdentity,
|
|
51
|
+
): BackendFederationIdentityIssue[] {
|
|
52
|
+
const compatibility = backendFederationCompatibility(loaded);
|
|
53
|
+
return compatibility
|
|
54
|
+
? validateBackendFederationCompatibility(compatibility, {
|
|
55
|
+
unitId: expected.unitId,
|
|
56
|
+
build: expected.buildMarker,
|
|
57
|
+
})
|
|
58
|
+
: [
|
|
59
|
+
{
|
|
60
|
+
path: 'backendFederationContract.compatibility',
|
|
61
|
+
message:
|
|
62
|
+
'loaded module declares no compatibility metadata; delivery-unit identity cannot be validated',
|
|
63
|
+
},
|
|
64
|
+
];
|
|
91
65
|
}
|
|
92
66
|
|
|
93
67
|
export function formatBackendFederationIdentityIssues(
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
BACKEND_FEDERATION_EFFECT_EXPOSE,
|
|
6
6
|
BACKEND_FEDERATION_MANIFEST_FILE,
|
|
7
7
|
BACKEND_FEDERATION_NODE_ADAPTER_VERSION,
|
|
8
|
-
} from '@modern-js/
|
|
8
|
+
} from '@modern-js/backend-federation-contracts';
|
|
9
9
|
|
|
10
10
|
export {
|
|
11
11
|
type BackendFederationExpectedIdentity,
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// @effect-diagnostics asyncFunction:off globalFetch:off strictBooleanExpressions:off
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
evaluateBackendFederationCommonJsEntry,
|
|
5
|
+
loadVerifiedBackendFederationEntry,
|
|
6
|
+
} from '@modern-js/server-runtime-extensions/backend-federation-security';
|
|
7
|
+
import {
|
|
8
|
+
Module,
|
|
9
|
+
type ModuleFederation,
|
|
10
|
+
type ModuleFederationRuntimePlugin,
|
|
11
|
+
} from '@module-federation/runtime';
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
BackendFederationEntryExports,
|
|
15
|
+
BackendFederationRemote,
|
|
16
|
+
BackendFederationRuntimeOptions,
|
|
17
|
+
} from './types';
|
|
18
|
+
|
|
19
|
+
type RemoteInfo = ConstructorParameters<typeof Module>[0]['remoteInfo'];
|
|
20
|
+
|
|
21
|
+
export const PROVIDED_ENTRY_SCHEME = /^(?:binding|service|static):/u;
|
|
22
|
+
|
|
23
|
+
function decodeDataUrl(remote: BackendFederationRemote) {
|
|
24
|
+
const commaIndex = remote.entry.indexOf(',');
|
|
25
|
+
if (commaIndex < 0) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
`[BFF][Effect] Backend federation remote ${remote.name} has an invalid data URL entry.`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return new TextEncoder().encode(
|
|
31
|
+
decodeURIComponent(remote.entry.slice(commaIndex + 1)),
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Node caches ES modules by URL, so every runtime importing the same file: or
|
|
36
|
+
// data: module shares one container. Only evaluated CommonJS containers (the
|
|
37
|
+
// verified network path) are isolated per runtime.
|
|
38
|
+
async function importContainer(remote: BackendFederationRemote) {
|
|
39
|
+
const namespace = await import(/* webpackIgnore: true */ remote.entry);
|
|
40
|
+
return (namespace.default ?? namespace) as BackendFederationEntryExports;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** One load path per entry scheme; anything else names the remote and fails. */
|
|
44
|
+
function loadContainer(
|
|
45
|
+
remote: BackendFederationRemote,
|
|
46
|
+
options: BackendFederationRuntimeOptions,
|
|
47
|
+
): Promise<BackendFederationEntryExports> {
|
|
48
|
+
const verified = Boolean(
|
|
49
|
+
remote.verification || options.entryPolicy?.expected,
|
|
50
|
+
);
|
|
51
|
+
const scheme = /^[a-z][a-z0-9+.-]*:/iu.exec(remote.entry)?.[0].toLowerCase();
|
|
52
|
+
if (scheme === 'https:' || scheme === 'http:') {
|
|
53
|
+
if (!verified) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`[BFF][Effect] Backend federation remote ${remote.name} requires verified entry bytes before network execution.`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return loadVerifiedBackendFederationEntry({
|
|
59
|
+
...options.entryPolicy,
|
|
60
|
+
remote,
|
|
61
|
+
...(remote.verification ? { verification: remote.verification } : {}),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (verified) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`[BFF][Effect] Backend federation remote ${remote.name} declares entry verification, but only http(s) entries are fetched and verified.`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
if (scheme === 'data:' && remote.type !== 'module') {
|
|
70
|
+
return Promise.resolve(
|
|
71
|
+
evaluateBackendFederationCommonJsEntry(
|
|
72
|
+
remote,
|
|
73
|
+
decodeDataUrl(remote),
|
|
74
|
+
options.entryPolicy?.evaluateCommonJs,
|
|
75
|
+
),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
if (scheme === 'data:' || scheme === 'file:') {
|
|
79
|
+
return importContainer(remote);
|
|
80
|
+
}
|
|
81
|
+
throw new Error(
|
|
82
|
+
`[BFF][Effect] Backend federation remote ${remote.name} uses unsupported entry ${remote.entry}. Use a verified http(s) entry, a data: or file: entry, or a binding:, service: or static: entry served by createBackendFederationLoadEntryPlugin().`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function registerRemoteContainer(
|
|
87
|
+
origin: ModuleFederation,
|
|
88
|
+
remoteInfo: RemoteInfo,
|
|
89
|
+
container: BackendFederationEntryExports,
|
|
90
|
+
) {
|
|
91
|
+
const module = new Module({ host: origin, remoteInfo });
|
|
92
|
+
module.remoteEntryExports = container as InstanceType<
|
|
93
|
+
typeof Module
|
|
94
|
+
>['remoteEntryExports'];
|
|
95
|
+
origin.moduleCache.set(remoteInfo.name, module);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Loads each configured remote once per runtime and registers the resulting
|
|
100
|
+
* container with that runtime only, so Module Federation initializes it with
|
|
101
|
+
* the runtime's own share scope. Registered last, it replaces any container a
|
|
102
|
+
* caller plugin supplied for a scheme it owns.
|
|
103
|
+
*/
|
|
104
|
+
export function createBackendFederationIntegrityPlugin(
|
|
105
|
+
options: BackendFederationRuntimeOptions,
|
|
106
|
+
remotes: readonly BackendFederationRemote[],
|
|
107
|
+
): ModuleFederationRuntimePlugin {
|
|
108
|
+
const loading = new Map<string, Promise<void>>();
|
|
109
|
+
|
|
110
|
+
const register = async (origin: ModuleFederation, remoteInfo: RemoteInfo) => {
|
|
111
|
+
const remote = remotes.find(
|
|
112
|
+
candidate => candidate.name === remoteInfo.name,
|
|
113
|
+
);
|
|
114
|
+
if (remote === undefined) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`[BFF][Effect] Missing backend federation remote ${remoteInfo.name}.`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
if (PROVIDED_ENTRY_SCHEME.test(remote.entry)) {
|
|
120
|
+
if (
|
|
121
|
+
origin.moduleCache.get(remote.name)?.remoteEntryExports === undefined
|
|
122
|
+
) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
`[BFF][Effect] Backend federation remote ${remote.name} has no entry provider for ${remote.entry}. Pass createBackendFederationLoadEntryPlugin() in plugins.`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const container = await loadContainer(remote, options);
|
|
130
|
+
if (typeof container?.get !== 'function') {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`[BFF][Effect] Backend federation remote ${remote.name} entry must expose get().`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
registerRemoteContainer(origin, remoteInfo, container);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
name: 'modernjs-backend-federation-integrity',
|
|
140
|
+
afterMatchRemote({ origin, remoteInfo }) {
|
|
141
|
+
if (remoteInfo === undefined) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
let pending = loading.get(remoteInfo.name);
|
|
145
|
+
if (pending === undefined) {
|
|
146
|
+
pending = register(origin, remoteInfo);
|
|
147
|
+
loading.set(remoteInfo.name, pending);
|
|
148
|
+
pending.catch(() => {
|
|
149
|
+
if (loading.get(remoteInfo.name) === pending) {
|
|
150
|
+
loading.delete(remoteInfo.name);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return pending;
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
@@ -1,31 +1,14 @@
|
|
|
1
|
-
import { BACKEND_FEDERATION_EFFECT_EXPOSE } from '@modern-js/
|
|
2
|
-
import * as Effect from 'effect/Effect';
|
|
3
|
-
import * as Logger from 'effect/Logger';
|
|
1
|
+
import { BACKEND_FEDERATION_EFFECT_EXPOSE } from '@modern-js/backend-federation-contracts';
|
|
4
2
|
|
|
5
|
-
import { type BackendFederationExpectedIdentity } from './identity';
|
|
6
|
-
import { LEGACY_LOAD_WARNING } from './legacy-warning';
|
|
7
3
|
import { collectRemotes } from './remotes';
|
|
8
4
|
import { createBackendFederationRuntime } from './runtime';
|
|
9
5
|
import type {
|
|
10
6
|
BackendFederatedEffectApiModule,
|
|
11
7
|
BackendFederationIdentityLoadOptions,
|
|
12
|
-
BackendFederationLoadOptions,
|
|
13
8
|
} from './types';
|
|
14
9
|
import { normalizeExpose } from './utils';
|
|
15
10
|
import { validateLoadedBackendFederatedEffectApi } from './validate-loaded';
|
|
16
11
|
|
|
17
|
-
const legacyWarningLogger = Logger.withLeveledConsole(Logger.formatLogFmt);
|
|
18
|
-
|
|
19
|
-
function warnLegacyLoad() {
|
|
20
|
-
Effect.runSync(
|
|
21
|
-
Effect.provideService(
|
|
22
|
-
Effect.logWarning(LEGACY_LOAD_WARNING),
|
|
23
|
-
Logger.CurrentLoggers,
|
|
24
|
-
new Set([legacyWarningLogger]),
|
|
25
|
-
),
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
12
|
/**
|
|
30
13
|
* Load a federated Effect API with mandatory delivery-unit identity
|
|
31
14
|
* validation (MV-G23): the loaded expose's compatibility metadata must match
|
|
@@ -33,20 +16,6 @@ function warnLegacyLoad() {
|
|
|
33
16
|
*/
|
|
34
17
|
export function loadBackendFederatedEffectApi(
|
|
35
18
|
options: BackendFederationIdentityLoadOptions,
|
|
36
|
-
): Promise<BackendFederatedEffectApiModule>;
|
|
37
|
-
/**
|
|
38
|
-
* @deprecated Pass `expected` (delivery-unit `unitId` + `buildMarker`).
|
|
39
|
-
* Loads without an expected identity cannot be validated against a resolved
|
|
40
|
-
* delivery-unit record and emit a runtime warning.
|
|
41
|
-
*/
|
|
42
|
-
export function loadBackendFederatedEffectApi(
|
|
43
|
-
options: BackendFederationLoadOptions,
|
|
44
|
-
): Promise<BackendFederatedEffectApiModule>;
|
|
45
|
-
export function loadBackendFederatedEffectApi(
|
|
46
|
-
options: BackendFederationLoadOptions & {
|
|
47
|
-
expected?: BackendFederationExpectedIdentity;
|
|
48
|
-
allowMissingIdentityMetadata?: boolean;
|
|
49
|
-
},
|
|
50
19
|
): Promise<BackendFederatedEffectApiModule> {
|
|
51
20
|
const remoteName = options.remote?.name ?? options.remoteName;
|
|
52
21
|
if (remoteName === undefined || remoteName.length === 0) {
|
|
@@ -55,8 +24,12 @@ export function loadBackendFederatedEffectApi(
|
|
|
55
24
|
);
|
|
56
25
|
}
|
|
57
26
|
|
|
58
|
-
if (options.expected
|
|
59
|
-
|
|
27
|
+
if (!options.expected?.unitId || !options.expected.buildMarker) {
|
|
28
|
+
return Promise.reject(
|
|
29
|
+
new Error(
|
|
30
|
+
'[BFF][Effect] Backend federation requires expected.unitId and expected.buildMarker.',
|
|
31
|
+
),
|
|
32
|
+
);
|
|
60
33
|
}
|
|
61
34
|
|
|
62
35
|
if (options.runtime !== undefined) {
|
|
@@ -93,7 +66,6 @@ export function loadBackendFederatedEffectApi(
|
|
|
93
66
|
const remoteRequest = `${remoteName}/${normalizeExpose(expose)}`;
|
|
94
67
|
return runtime.loadRemote(remoteRequest).then(loaded =>
|
|
95
68
|
validateLoadedBackendFederatedEffectApi(loaded, {
|
|
96
|
-
allowMissingIdentityMetadata: options.allowMissingIdentityMetadata,
|
|
97
69
|
expected: options.expected,
|
|
98
70
|
remoteName,
|
|
99
71
|
remoteRequest,
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import type * as EffectContextModule from '@modern-js/bff-effect/context';
|
|
3
|
+
import type * as EffectEdgeModule from '@modern-js/bff-effect/effect-edge';
|
|
4
|
+
|
|
5
|
+
import type { BackendFederationRuntimeOptions } from './types';
|
|
6
|
+
|
|
7
|
+
type HostShared = NonNullable<BackendFederationRuntimeOptions['shared']>;
|
|
8
|
+
|
|
9
|
+
const fromAdapter = createRequire(import.meta.url);
|
|
10
|
+
const effectPackageJson = fromAdapter.resolve(
|
|
11
|
+
'@modern-js/bff-effect/package.json',
|
|
12
|
+
);
|
|
13
|
+
const fromEffectPackage = createRequire(effectPackageJson);
|
|
14
|
+
|
|
15
|
+
let hostShared: HostShared | undefined;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Shares the package-private Effect handler factory registry and the Effect
|
|
19
|
+
* request storage with every backend container, so factories a remote creates
|
|
20
|
+
* with defineEffectBff are recognized by this host's module resolver and its
|
|
21
|
+
* endpoints read the context this host's dispatcher enters.
|
|
22
|
+
*
|
|
23
|
+
* Loaded on first call, not when this module loads: a CommonJS require() of
|
|
24
|
+
* the Effect ES modules throws while a concurrent import() is still loading
|
|
25
|
+
* them.
|
|
26
|
+
*/
|
|
27
|
+
export function effectBffHostShared(): HostShared {
|
|
28
|
+
if (hostShared !== undefined) {
|
|
29
|
+
return hostShared;
|
|
30
|
+
}
|
|
31
|
+
const {
|
|
32
|
+
EFFECT_BFF_CONTEXT_STORAGE_SHARE,
|
|
33
|
+
EFFECT_BFF_HANDLER_FACTORY_REGISTRY_SHARE,
|
|
34
|
+
} = fromEffectPackage(
|
|
35
|
+
'@modern-js/bff-effect/effect-edge',
|
|
36
|
+
) as typeof EffectEdgeModule;
|
|
37
|
+
const { getEffectContextStorage } = fromEffectPackage(
|
|
38
|
+
'@modern-js/bff-effect/context',
|
|
39
|
+
) as typeof EffectContextModule;
|
|
40
|
+
const handlerFactoryRegistry: unknown = fromEffectPackage(
|
|
41
|
+
`#effect-entry-shape-${'registry'}`,
|
|
42
|
+
);
|
|
43
|
+
const version = (fromAdapter(effectPackageJson) as { version: string })
|
|
44
|
+
.version;
|
|
45
|
+
hostShared = {
|
|
46
|
+
[EFFECT_BFF_HANDLER_FACTORY_REGISTRY_SHARE]: {
|
|
47
|
+
version,
|
|
48
|
+
lib: () => handlerFactoryRegistry,
|
|
49
|
+
shareConfig: { singleton: true, requiredVersion: false },
|
|
50
|
+
},
|
|
51
|
+
[EFFECT_BFF_CONTEXT_STORAGE_SHARE]: {
|
|
52
|
+
version,
|
|
53
|
+
lib: getEffectContextStorage,
|
|
54
|
+
shareConfig: { singleton: true, requiredVersion: false },
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
return hostShared;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Adds the host shares to caller shares. Their names are reserved: a caller
|
|
62
|
+
* replacement would make containers use a registry or a request storage this
|
|
63
|
+
* host never consults.
|
|
64
|
+
*/
|
|
65
|
+
export function withEffectBffHostShared(
|
|
66
|
+
shared: BackendFederationRuntimeOptions['shared'],
|
|
67
|
+
): HostShared {
|
|
68
|
+
const host = effectBffHostShared();
|
|
69
|
+
const reserved = Object.keys(host).find(name => shared && name in shared);
|
|
70
|
+
if (reserved !== undefined) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`[BFF][Effect] Share ${reserved} is reserved for the Node host. Remove it from options.shared.`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
return { ...shared, ...host };
|
|
76
|
+
}
|
|
@@ -1,28 +1,21 @@
|
|
|
1
1
|
import { evaluateNodeBackendFederationCommonJs } from '@modern-js/server-runtime-extensions/backend-federation-security/node';
|
|
2
|
-
|
|
3
|
-
import type { BackendFederationExpectedIdentity } from './identity';
|
|
4
2
|
import { loadBackendFederatedEffectApi as loadUniversalBackendFederatedEffectApi } from './load';
|
|
3
|
+
import { effectBffHostShared, withEffectBffHostShared } from './node-shared';
|
|
4
|
+
|
|
5
|
+
/** Call for the `shared` option when building a custom runtime for Node hosts. */
|
|
6
|
+
export { effectBffHostShared };
|
|
7
|
+
|
|
5
8
|
import type {
|
|
6
9
|
BackendFederatedEffectApiModule,
|
|
7
10
|
BackendFederationIdentityLoadOptions,
|
|
8
|
-
BackendFederationLoadOptions,
|
|
9
11
|
} from './types';
|
|
10
12
|
|
|
11
|
-
export function loadBackendFederatedEffectApi(
|
|
13
|
+
export async function loadBackendFederatedEffectApi(
|
|
12
14
|
options: BackendFederationIdentityLoadOptions,
|
|
13
|
-
): Promise<BackendFederatedEffectApiModule>;
|
|
14
|
-
/** @deprecated Pass `expected` delivery-unit identity. */
|
|
15
|
-
export function loadBackendFederatedEffectApi(
|
|
16
|
-
options: BackendFederationLoadOptions,
|
|
17
|
-
): Promise<BackendFederatedEffectApiModule>;
|
|
18
|
-
export function loadBackendFederatedEffectApi(
|
|
19
|
-
options: BackendFederationLoadOptions & {
|
|
20
|
-
expected?: BackendFederationExpectedIdentity;
|
|
21
|
-
allowMissingIdentityMetadata?: boolean;
|
|
22
|
-
},
|
|
23
15
|
): Promise<BackendFederatedEffectApiModule> {
|
|
24
16
|
return loadUniversalBackendFederatedEffectApi({
|
|
25
17
|
...options,
|
|
18
|
+
shared: withEffectBffHostShared(options.shared),
|
|
26
19
|
entryPolicy: {
|
|
27
20
|
...options.entryPolicy,
|
|
28
21
|
evaluateCommonJs:
|
|
@@ -1,35 +1,83 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
ModuleFederation,
|
|
3
|
+
ModuleFederationRuntimePlugin,
|
|
4
|
+
} from '@module-federation/runtime';
|
|
2
5
|
|
|
3
|
-
import
|
|
6
|
+
import {
|
|
7
|
+
PROVIDED_ENTRY_SCHEME,
|
|
8
|
+
registerRemoteContainer,
|
|
9
|
+
} from './integrity-plugin';
|
|
10
|
+
import type {
|
|
11
|
+
BackendFederationEntryExports,
|
|
12
|
+
BackendFederationLoadEntryPluginOptions,
|
|
13
|
+
} from './types';
|
|
4
14
|
|
|
15
|
+
function toContainer(entry: BackendFederationEntryExports) {
|
|
16
|
+
return {
|
|
17
|
+
get(id: string) {
|
|
18
|
+
return () => Promise.resolve(entry.get(id)).then(factory => factory());
|
|
19
|
+
},
|
|
20
|
+
init(...args: unknown[]) {
|
|
21
|
+
return entry.init?.(...args);
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Serves binding:, service: and static: entries. The provider resolves each
|
|
28
|
+
* remote once per runtime and registers the container with that runtime only.
|
|
29
|
+
*/
|
|
5
30
|
export function createBackendFederationLoadEntryPlugin(
|
|
6
31
|
options: BackendFederationLoadEntryPluginOptions,
|
|
7
32
|
): ModuleFederationRuntimePlugin {
|
|
33
|
+
const provided = new WeakMap<ModuleFederation, Map<string, Promise<void>>>();
|
|
34
|
+
|
|
8
35
|
return {
|
|
9
36
|
name: 'modernjs-backend-federation-load-entry',
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
afterMatchRemote({ origin, remoteInfo }) {
|
|
38
|
+
if (
|
|
39
|
+
remoteInfo === undefined ||
|
|
40
|
+
!PROVIDED_ENTRY_SCHEME.test(remoteInfo.entry)
|
|
41
|
+
) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
let entries = provided.get(origin);
|
|
45
|
+
// The first provider that registers a container wins; later ones are
|
|
46
|
+
// not consulted.
|
|
47
|
+
if (
|
|
48
|
+
entries?.has(remoteInfo.name) !== true &&
|
|
49
|
+
origin.moduleCache.get(remoteInfo.name)?.remoteEntryExports !==
|
|
50
|
+
undefined
|
|
51
|
+
) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (entries === undefined) {
|
|
55
|
+
entries = new Map();
|
|
56
|
+
provided.set(origin, entries);
|
|
57
|
+
}
|
|
58
|
+
const runtimeEntries = entries;
|
|
59
|
+
let pending = runtimeEntries.get(remoteInfo.name);
|
|
60
|
+
if (pending === undefined) {
|
|
61
|
+
pending = Promise.resolve(
|
|
62
|
+
options.resolveEntry({
|
|
63
|
+
name: remoteInfo.name,
|
|
64
|
+
entry: remoteInfo.entry,
|
|
65
|
+
type: remoteInfo.type,
|
|
66
|
+
}),
|
|
67
|
+
).then(entry => {
|
|
68
|
+
if (entry !== undefined) {
|
|
69
|
+
registerRemoteContainer(origin, remoteInfo, toContainer(entry));
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
const settled = pending;
|
|
73
|
+
runtimeEntries.set(remoteInfo.name, settled);
|
|
74
|
+
settled.catch(() => {
|
|
75
|
+
if (runtimeEntries.get(remoteInfo.name) === settled) {
|
|
76
|
+
runtimeEntries.delete(remoteInfo.name);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return pending;
|
|
33
81
|
},
|
|
34
82
|
};
|
|
35
83
|
}
|