@bleedingdev/modern-js-plugin-bff-extensions 3.9.0-ultramodern.2 → 3.9.0-ultramodern.21

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.
Files changed (216) hide show
  1. package/README.md +25 -5
  2. package/dist/cjs/backend-federation/edge-runtime.js +3 -15
  3. package/dist/cjs/backend-federation/edge.js +9 -10
  4. package/dist/cjs/backend-federation/identity.js +26 -31
  5. package/dist/cjs/backend-federation/index.js +5 -5
  6. package/dist/cjs/backend-federation/integrity-plugin.js +112 -0
  7. package/dist/cjs/backend-federation/load.js +3 -13
  8. package/dist/cjs/backend-federation/node-shared.js +85 -0
  9. package/dist/cjs/backend-federation/node.js +6 -1
  10. package/dist/cjs/backend-federation/plugin.js +40 -21
  11. package/dist/cjs/backend-federation/runtime.js +16 -84
  12. package/dist/cjs/backend-federation/validate-loaded.js +2 -4
  13. package/dist/cjs/backend-federation-manifest/load.js +40 -12
  14. package/dist/cjs/backend-federation-manifest/metadata.js +3 -3
  15. package/dist/cjs/backend-federation-manifest/node.js +3 -1
  16. package/dist/cjs/backend-federation-manifest/remote.js +3 -9
  17. package/dist/cjs/backend-federation-manifest/validation.js +16 -12
  18. package/dist/cjs/cross-project-policy/evaluation.js +3 -3
  19. package/dist/cjs/cross-project-policy/node.js +11 -30
  20. package/dist/cjs/{backend-federation/legacy-warning.js → cross-project-policy/producer-runtime.js} +23 -5
  21. package/dist/cjs/{effect-adapter → effect-server}/cross-project-policy.js +5 -5
  22. package/dist/cjs/effect-server/entry.js +63 -0
  23. package/dist/cjs/{effect-adapter → effect-server}/error-response.js +4 -4
  24. package/dist/cjs/effect-server/index.js +199 -0
  25. package/dist/cjs/{client-generator → effect-source-loader}/built-entry.js +5 -6
  26. package/dist/cjs/{client-generator/runtime.js → effect-source-loader/http-api-runtime.js} +12 -20
  27. package/dist/cjs/effect-source-loader/index.js +18 -6
  28. package/dist/cjs/effect-source-loader/loader.js +17 -30
  29. package/dist/cjs/effect-source-loader/operation-contracts.js +60 -0
  30. package/dist/cjs/effect-source-loader/paths.js +90 -0
  31. package/dist/cjs/effect-source-loader/rspack-loader.js +73 -0
  32. package/dist/cjs/effect-source-loader/worker-runtime-source.js +71 -0
  33. package/dist/cjs/effect-source-loader/worker-runtime-wrapper.js +7 -50
  34. package/dist/cjs/hono/cross-project-policy.js +2 -1
  35. package/dist/cjs/{client-generator/paths.js → hono/node.js} +14 -25
  36. package/dist/esm/backend-federation/edge-runtime.mjs +2 -11
  37. package/dist/esm/backend-federation/edge.mjs +5 -6
  38. package/dist/esm/backend-federation/identity.mjs +21 -32
  39. package/dist/esm/backend-federation/index.mjs +1 -1
  40. package/dist/esm/backend-federation/integrity-plugin.mjs +68 -0
  41. package/dist/esm/backend-federation/load.mjs +2 -12
  42. package/dist/esm/backend-federation/node-shared.mjs +41 -0
  43. package/dist/esm/backend-federation/node.mjs +4 -2
  44. package/dist/esm/backend-federation/plugin.mjs +37 -18
  45. package/dist/esm/backend-federation/runtime.mjs +16 -84
  46. package/dist/esm/backend-federation/validate-loaded.mjs +2 -4
  47. package/dist/esm/backend-federation-manifest/load.mjs +40 -12
  48. package/dist/esm/backend-federation-manifest/metadata.mjs +1 -1
  49. package/dist/esm/backend-federation-manifest/node.mjs +3 -1
  50. package/dist/esm/backend-federation-manifest/remote.mjs +2 -8
  51. package/dist/esm/backend-federation-manifest/validation.mjs +15 -11
  52. package/dist/esm/cross-project-policy/evaluation.mjs +1 -1
  53. package/dist/esm/cross-project-policy/node.mjs +10 -19
  54. package/dist/esm/cross-project-policy/producer-runtime.mjs +20 -0
  55. package/dist/esm/{effect-adapter → effect-server}/cross-project-policy.mjs +3 -3
  56. package/dist/esm/effect-server/entry.mjs +15 -0
  57. package/dist/esm/{effect-adapter → effect-server}/error-response.mjs +2 -2
  58. package/dist/esm/effect-server/index.mjs +151 -0
  59. package/dist/esm/{client-generator → effect-source-loader}/built-entry.mjs +6 -7
  60. package/dist/esm/effect-source-loader/http-api-runtime.mjs +19 -0
  61. package/dist/esm/effect-source-loader/index.mjs +5 -2
  62. package/dist/esm/effect-source-loader/loader.mjs +17 -30
  63. package/dist/esm/effect-source-loader/operation-contracts.mjs +22 -0
  64. package/dist/esm/effect-source-loader/paths.mjs +43 -0
  65. package/dist/esm/effect-source-loader/rspack-loader.mjs +35 -0
  66. package/dist/esm/effect-source-loader/worker-runtime-source.mjs +23 -0
  67. package/dist/esm/effect-source-loader/worker-runtime-wrapper.mjs +5 -45
  68. package/dist/esm/hono/cross-project-policy.mjs +2 -1
  69. package/dist/esm-node/backend-federation/edge-runtime.mjs +2 -11
  70. package/dist/esm-node/backend-federation/edge.mjs +5 -6
  71. package/dist/esm-node/backend-federation/identity.mjs +21 -32
  72. package/dist/esm-node/backend-federation/index.mjs +1 -1
  73. package/dist/esm-node/backend-federation/integrity-plugin.mjs +69 -0
  74. package/dist/esm-node/backend-federation/load.mjs +2 -12
  75. package/dist/esm-node/backend-federation/node-shared.mjs +42 -0
  76. package/dist/esm-node/backend-federation/node.mjs +4 -2
  77. package/dist/esm-node/backend-federation/plugin.mjs +37 -18
  78. package/dist/esm-node/backend-federation/runtime.mjs +16 -84
  79. package/dist/esm-node/backend-federation/validate-loaded.mjs +2 -4
  80. package/dist/esm-node/backend-federation-manifest/load.mjs +40 -12
  81. package/dist/esm-node/backend-federation-manifest/metadata.mjs +1 -1
  82. package/dist/esm-node/backend-federation-manifest/node.mjs +3 -1
  83. package/dist/esm-node/backend-federation-manifest/remote.mjs +2 -8
  84. package/dist/esm-node/backend-federation-manifest/validation.mjs +15 -11
  85. package/dist/esm-node/cross-project-policy/evaluation.mjs +1 -1
  86. package/dist/esm-node/cross-project-policy/node.mjs +10 -19
  87. package/dist/esm-node/cross-project-policy/producer-runtime.mjs +21 -0
  88. package/dist/esm-node/{effect-adapter → effect-server}/cross-project-policy.mjs +3 -3
  89. package/dist/esm-node/effect-server/entry.mjs +16 -0
  90. package/dist/esm-node/{effect-adapter → effect-server}/error-response.mjs +2 -2
  91. package/dist/esm-node/effect-server/index.mjs +152 -0
  92. package/dist/esm-node/{client-generator → effect-source-loader}/built-entry.mjs +6 -7
  93. package/dist/esm-node/effect-source-loader/http-api-runtime.mjs +20 -0
  94. package/dist/esm-node/effect-source-loader/index.mjs +5 -2
  95. package/dist/esm-node/effect-source-loader/loader.mjs +17 -30
  96. package/dist/esm-node/effect-source-loader/operation-contracts.mjs +23 -0
  97. package/dist/esm-node/effect-source-loader/paths.mjs +44 -0
  98. package/dist/esm-node/effect-source-loader/rspack-loader.mjs +37 -0
  99. package/dist/esm-node/effect-source-loader/worker-runtime-source.mjs +24 -0
  100. package/dist/esm-node/effect-source-loader/worker-runtime-wrapper.mjs +5 -45
  101. package/dist/esm-node/hono/cross-project-policy.mjs +2 -1
  102. package/dist/esm-node/hono/node.mjs +14 -0
  103. package/dist/types/backend-federation/edge-runtime.d.ts +0 -5
  104. package/dist/types/backend-federation/edge.d.ts +5 -5
  105. package/dist/types/backend-federation/identity.d.ts +4 -16
  106. package/dist/types/backend-federation/index.d.ts +1 -1
  107. package/dist/types/backend-federation/integrity-plugin.d.ts +13 -0
  108. package/dist/types/backend-federation/load.d.ts +1 -7
  109. package/dist/types/backend-federation/node-shared.d.ts +20 -0
  110. package/dist/types/backend-federation/node.d.ts +4 -3
  111. package/dist/types/backend-federation/plugin.d.ts +4 -0
  112. package/dist/types/backend-federation/types.d.ts +3 -14
  113. package/dist/types/backend-federation/validate-loaded.d.ts +1 -2
  114. package/dist/types/backend-federation-manifest/metadata.d.ts +0 -6
  115. package/dist/types/backend-federation-manifest/types.d.ts +2 -1
  116. package/dist/types/cross-project-policy/evaluation.d.ts +1 -1
  117. package/dist/types/cross-project-policy/node.d.ts +3 -2
  118. package/dist/types/cross-project-policy/producer-runtime.d.ts +20 -0
  119. package/dist/types/effect-server/cross-project-policy.d.ts +5 -0
  120. package/dist/types/effect-server/entry.d.ts +3 -0
  121. package/dist/types/effect-server/error-response.d.ts +2 -0
  122. package/dist/types/effect-server/index.d.ts +4 -0
  123. package/dist/types/effect-source-loader/index.d.ts +5 -2
  124. package/dist/types/effect-source-loader/operation-contracts.d.ts +8 -0
  125. package/dist/types/effect-source-loader/paths.d.ts +23 -0
  126. package/dist/types/effect-source-loader/rspack-loader.d.ts +9 -0
  127. package/dist/types/effect-source-loader/worker-runtime-source.d.ts +4 -0
  128. package/dist/types/effect-source-loader/worker-runtime-wrapper.d.ts +4 -22
  129. package/dist/types/hono/node.d.ts +4 -0
  130. package/package.json +64 -36
  131. package/src/backend-federation/edge-runtime.ts +2 -13
  132. package/src/backend-federation/edge.ts +16 -21
  133. package/src/backend-federation/identity.ts +39 -65
  134. package/src/backend-federation/index.ts +1 -1
  135. package/src/backend-federation/integrity-plugin.ts +157 -0
  136. package/src/backend-federation/load.ts +7 -35
  137. package/src/backend-federation/node-shared.ts +76 -0
  138. package/src/backend-federation/node.ts +7 -14
  139. package/src/backend-federation/plugin.ts +73 -25
  140. package/src/backend-federation/runtime.ts +16 -90
  141. package/src/backend-federation/types.ts +3 -18
  142. package/src/backend-federation/validate-loaded.ts +2 -7
  143. package/src/backend-federation-manifest/load.ts +93 -38
  144. package/src/backend-federation-manifest/metadata.ts +1 -7
  145. package/src/backend-federation-manifest/node.ts +3 -2
  146. package/src/backend-federation-manifest/remote.ts +1 -7
  147. package/src/backend-federation-manifest/types.ts +2 -0
  148. package/src/backend-federation-manifest/validation.ts +29 -39
  149. package/src/cross-project-policy/evaluation.ts +1 -1
  150. package/src/cross-project-policy/node.ts +10 -41
  151. package/src/cross-project-policy/producer-runtime.ts +43 -0
  152. package/src/{effect-adapter → effect-server}/cross-project-policy.ts +8 -2
  153. package/src/effect-server/entry.ts +18 -0
  154. package/src/{effect-adapter → effect-server}/error-response.ts +1 -1
  155. package/src/effect-server/index.ts +242 -0
  156. package/src/{client-generator → effect-source-loader}/built-entry.ts +12 -12
  157. package/src/effect-source-loader/http-api-runtime.ts +39 -0
  158. package/src/effect-source-loader/index.ts +8 -5
  159. package/src/effect-source-loader/loader.ts +47 -52
  160. package/src/effect-source-loader/operation-contracts.ts +38 -0
  161. package/src/effect-source-loader/paths.ts +74 -0
  162. package/src/effect-source-loader/rspack-loader.ts +71 -0
  163. package/src/effect-source-loader/worker-runtime-source.ts +34 -0
  164. package/src/effect-source-loader/worker-runtime-wrapper.ts +5 -75
  165. package/src/hono/cross-project-policy.ts +2 -1
  166. package/src/hono/node.ts +24 -0
  167. package/dist/cjs/backend-federation/entry.js +0 -94
  168. package/dist/cjs/client-generator/generator.js +0 -73
  169. package/dist/cjs/client-generator/index.js +0 -54
  170. package/dist/cjs/client-generator/rendering.js +0 -253
  171. package/dist/cjs/client-generator/types.js +0 -18
  172. package/dist/cjs/effect-adapter/entry.js +0 -98
  173. package/dist/cjs/effect-adapter/index.js +0 -187
  174. package/dist/esm/backend-federation/entry.mjs +0 -56
  175. package/dist/esm/backend-federation/legacy-warning.mjs +0 -2
  176. package/dist/esm/client-generator/generator.mjs +0 -32
  177. package/dist/esm/client-generator/index.mjs +0 -3
  178. package/dist/esm/client-generator/paths.mjs +0 -24
  179. package/dist/esm/client-generator/rendering.mjs +0 -209
  180. package/dist/esm/client-generator/runtime.mjs +0 -27
  181. package/dist/esm/client-generator/types.mjs +0 -0
  182. package/dist/esm/effect-adapter/entry.mjs +0 -50
  183. package/dist/esm/effect-adapter/index.mjs +0 -139
  184. package/dist/esm-node/backend-federation/entry.mjs +0 -57
  185. package/dist/esm-node/backend-federation/legacy-warning.mjs +0 -3
  186. package/dist/esm-node/client-generator/generator.mjs +0 -33
  187. package/dist/esm-node/client-generator/index.mjs +0 -4
  188. package/dist/esm-node/client-generator/paths.mjs +0 -25
  189. package/dist/esm-node/client-generator/rendering.mjs +0 -210
  190. package/dist/esm-node/client-generator/runtime.mjs +0 -29
  191. package/dist/esm-node/client-generator/types.mjs +0 -1
  192. package/dist/esm-node/effect-adapter/entry.mjs +0 -51
  193. package/dist/esm-node/effect-adapter/index.mjs +0 -140
  194. package/dist/types/backend-federation/entry.d.ts +0 -2
  195. package/dist/types/backend-federation/legacy-warning.d.ts +0 -1
  196. package/dist/types/client-generator/generator.d.ts +0 -3
  197. package/dist/types/client-generator/index.d.ts +0 -4
  198. package/dist/types/client-generator/paths.d.ts +0 -5
  199. package/dist/types/client-generator/rendering.d.ts +0 -24
  200. package/dist/types/client-generator/types.d.ts +0 -33
  201. package/dist/types/effect-adapter/cross-project-policy.d.ts +0 -4
  202. package/dist/types/effect-adapter/entry.d.ts +0 -2
  203. package/dist/types/effect-adapter/error-response.d.ts +0 -2
  204. package/dist/types/effect-adapter/index.d.ts +0 -19
  205. package/src/backend-federation/entry.ts +0 -125
  206. package/src/backend-federation/legacy-warning.ts +0 -2
  207. package/src/client-generator/generator.ts +0 -52
  208. package/src/client-generator/index.ts +0 -10
  209. package/src/client-generator/paths.ts +0 -40
  210. package/src/client-generator/rendering.ts +0 -307
  211. package/src/client-generator/runtime.ts +0 -69
  212. package/src/client-generator/types.ts +0 -35
  213. package/src/effect-adapter/entry.ts +0 -95
  214. package/src/effect-adapter/index.ts +0 -246
  215. /package/dist/types/{client-generator → effect-source-loader}/built-entry.d.ts +0 -0
  216. /package/dist/types/{client-generator/runtime.d.ts → effect-source-loader/http-api-runtime.d.ts} +0 -0
@@ -1,99 +1,25 @@
1
1
  import { ModuleFederation } from '@module-federation/runtime';
2
2
 
3
- import { loadBackendFederationExpose } from './entry';
4
- import { createBackendFederationLoadEntryPlugin } from './plugin';
3
+ import { createBackendFederationIntegrityPlugin } from './integrity-plugin';
5
4
  import { collectRemotes } from './remotes';
6
- import type {
7
- BackendFederationRemote,
8
- BackendFederationRuntimeOptions,
9
- } from './types';
10
-
11
- let nextRuntimeId = 0;
12
-
13
- function parseRemoteRequest(request: string) {
14
- const [remoteName, ...exposeParts] = request.split('/');
15
- if (
16
- remoteName === undefined ||
17
- remoteName.length === 0 ||
18
- exposeParts.length === 0
19
- ) {
20
- return;
21
- }
22
- return remoteName;
23
- }
24
-
25
- function createLocalEntryPlugin(
26
- options: BackendFederationRuntimeOptions,
27
- remotes: BackendFederationRemote[],
28
- ) {
29
- return createBackendFederationLoadEntryPlugin({
30
- resolveEntry(resolvedRemote) {
31
- const remote = remotes.find(
32
- candidate => candidate.name === resolvedRemote.name,
33
- );
34
- if (remote === undefined) {
35
- return;
36
- }
37
- return {
38
- get(expose: string) {
39
- return () => loadBackendFederationExpose(remote, expose, options);
40
- },
41
- };
42
- },
43
- });
44
- }
45
-
46
- function isolateRegisteredRemoteEntries(remotes: BackendFederationRemote[]) {
47
- const runtimeId = nextRuntimeId;
48
- nextRuntimeId += 1;
49
- return remotes.map(remote => ({
50
- ...remote,
51
- entry: `${remote.entry}#modernjs-backend-runtime-${runtimeId}`,
52
- }));
53
- }
54
-
55
- class BackendFederationRuntime extends ModuleFederation {
56
- readonly #remoteNames: ReadonlySet<string>;
57
-
58
- constructor(
59
- options: BackendFederationRuntimeOptions,
60
- remotes: BackendFederationRemote[],
61
- ) {
62
- super({
63
- name: options.hostName,
64
- remotes: isolateRegisteredRemoteEntries(remotes),
65
- shared: {},
66
- plugins: [createLocalEntryPlugin(options, remotes)],
67
- });
68
- this.#remoteNames = new Set(remotes.map(remote => remote.name));
69
- }
70
-
71
- override loadRemote<T>(
72
- request: string,
73
- loadOptions?: NonNullable<Parameters<ModuleFederation['loadRemote']>[1]>,
74
- ): Promise<T | null> {
75
- const remoteName = parseRemoteRequest(request);
76
- if (remoteName === undefined) {
77
- return Promise.reject(
78
- new Error(
79
- `[BFF][Effect] Invalid backend federation request ${request}.`,
80
- ),
81
- );
82
- }
83
- if (!this.#remoteNames.has(remoteName)) {
84
- return Promise.reject(
85
- new Error(
86
- `[BFF][Effect] Missing backend federation remote ${remoteName}.`,
87
- ),
88
- );
89
- }
90
- return super.loadRemote<T>(request, loadOptions);
91
- }
92
- }
5
+ import type { BackendFederationRuntimeOptions } from './types';
93
6
 
94
7
  export function createBackendFederationRuntime(
95
8
  options: BackendFederationRuntimeOptions,
96
9
  ): ModuleFederation {
97
10
  const remotes = collectRemotes(options);
98
- return new BackendFederationRuntime(options, remotes);
11
+ return new ModuleFederation({
12
+ name: options.hostName,
13
+ // Remotes use the default share scope, where host shares are registered.
14
+ remotes: remotes.map(({ name, entry, type }) => ({
15
+ name,
16
+ entry,
17
+ ...(type ? { type } : {}),
18
+ })),
19
+ shared: options.shared ?? {},
20
+ plugins: [
21
+ ...(options.plugins ?? []),
22
+ createBackendFederationIntegrityPlugin(options, remotes),
23
+ ],
24
+ });
99
25
  }
@@ -1,9 +1,9 @@
1
+ import type { BACKEND_FEDERATION_EFFECT_EXPOSE } from '@modern-js/backend-federation-contracts';
1
2
  import type { EffectApiModule } from '@modern-js/bff-effect/effect';
2
3
  import type {
3
4
  BackendFederationRemoteEntryPolicy,
4
5
  BackendFederationRemoteEntryVerification,
5
6
  } from '@modern-js/server-runtime-extensions/backend-federation-security';
6
- import type { BACKEND_FEDERATION_EFFECT_EXPOSE } from '@modern-js/utils/universal';
7
7
  import type {
8
8
  ModuleFederation,
9
9
  ModuleFederationRuntimePlugin,
@@ -14,8 +14,6 @@ export type BackendFederationRemote = {
14
14
  name: string;
15
15
  entry: string;
16
16
  type?: 'commonjs-module' | 'module' | string;
17
- entryGlobalName?: string;
18
- shareScope?: string | string[];
19
17
  expose?: typeof BACKEND_FEDERATION_EFFECT_EXPOSE;
20
18
  verification?: BackendFederationRemoteEntryVerification;
21
19
  };
@@ -25,6 +23,8 @@ export type BackendFederationRuntimeOptions = {
25
23
  remote?: BackendFederationRemote;
26
24
  remotes?: BackendFederationRemote[];
27
25
  plugins?: ModuleFederationRuntimePlugin[];
26
+ /** Host shares handed to every remote container's `init()`. */
27
+ shared?: ConstructorParameters<typeof ModuleFederation>[0]['shared'];
28
28
  entryPolicy?: BackendFederationRemoteEntryPolicy;
29
29
  };
30
30
 
@@ -85,19 +85,4 @@ export type BackendFederationLoadOptions = BackendFederationRuntimeOptions & {
85
85
  export type BackendFederationIdentityLoadOptions =
86
86
  BackendFederationLoadOptions & {
87
87
  expected: BackendFederationExpectedIdentity;
88
- /**
89
- * Tolerate legacy exposes without identity metadata (mismatching declared
90
- * values still fail). Prefer leaving this unset: identity-less exposes
91
- * cannot be validated against a resolved delivery-unit record.
92
- */
93
- allowMissingIdentityMetadata?: boolean;
94
88
  };
95
-
96
- export type BackendFederationLoadEntryPlugin = ModuleFederationRuntimePlugin & {
97
- loadEntry?: (args: {
98
- remoteInfo: BackendFederationRemote;
99
- }) =>
100
- | BackendFederationEntryExports
101
- | undefined
102
- | Promise<BackendFederationEntryExports | undefined>;
103
- };
@@ -9,8 +9,7 @@ import { isRecord } from './utils';
9
9
  export function validateLoadedBackendFederatedEffectApi(
10
10
  loaded: unknown,
11
11
  options: {
12
- allowMissingIdentityMetadata?: boolean;
13
- expected?: BackendFederationExpectedIdentity;
12
+ expected: BackendFederationExpectedIdentity;
14
13
  remoteName: string;
15
14
  remoteRequest: string;
16
15
  },
@@ -52,14 +51,10 @@ export function validateLoadedBackendFederatedEffectApi(
52
51
  );
53
52
  }
54
53
 
55
- if (options.expected !== undefined) {
54
+ {
56
55
  const issues = validateExpectedBackendFederationIdentity(
57
56
  loaded,
58
57
  options.expected,
59
- {
60
- allowMissingIdentityMetadata:
61
- options.allowMissingIdentityMetadata === true,
62
- },
63
58
  );
64
59
  if (issues.length > 0) {
65
60
  throw new Error(
@@ -7,6 +7,12 @@ import {
7
7
  } from '../backend-federation';
8
8
  import { loadBackendFederatedEffectApi } from '../backend-federation/load';
9
9
  import { BackendFederationManifestAdapterError } from './errors';
10
+ import {
11
+ backendFederationMetadata,
12
+ manifestDeliveryUnit,
13
+ stringValue,
14
+ versionBoundaryMetadata,
15
+ } from './metadata';
10
16
  import { loadBackendFederationManifest } from './reference';
11
17
  import { resolveBackendFederationRemoteFromManifest } from './remote';
12
18
  import type {
@@ -19,6 +25,10 @@ import {
19
25
  validateLoadedBackendFederationContract,
20
26
  } from './validation';
21
27
 
28
+ // One manifest+entry budget. Explicit outer 0 opts out; nested 0 cannot
29
+ // disable a positive nested deadline.
30
+ const DEFAULT_SHARED_MANIFEST_LOAD_TIMEOUT_MS = 10_000;
31
+
22
32
  function createManifestLoadScope(
23
33
  options: BackendFederationManifestAdapterOptions,
24
34
  ) {
@@ -38,9 +48,14 @@ function createManifestLoadScope(
38
48
  );
39
49
  }
40
50
  }
51
+ const positivePolicyTimeouts = policyTimeouts.filter(timeout => timeout > 0);
41
52
  const timeoutMs =
42
53
  options.timeoutMs ??
43
- (policyTimeouts.length > 0 ? Math.min(...policyTimeouts) : 0);
54
+ (policyTimeouts.length === 0
55
+ ? DEFAULT_SHARED_MANIFEST_LOAD_TIMEOUT_MS
56
+ : positivePolicyTimeouts.length > 0
57
+ ? Math.min(...positivePolicyTimeouts)
58
+ : 0);
44
59
  const inputSignals = [
45
60
  options.signal,
46
61
  options.manifestPolicy?.signal,
@@ -134,6 +149,39 @@ function throwIfManifestLoadAborted(signal: AbortSignal) {
134
149
  }
135
150
  }
136
151
 
152
+ // Stop waiting even when trusted plugin work ignores cancellation. The race
153
+ // observes late rejections; it cannot stop that work from running.
154
+ function settleOnSharedAbort<T>(
155
+ promise: Promise<T>,
156
+ signal: AbortSignal,
157
+ ): Promise<T> {
158
+ if (signal.aborted) {
159
+ // Observe work even when the pre-aborted path never reaches Promise.race.
160
+ promise.catch(() => {});
161
+ throwIfManifestLoadAborted(signal);
162
+ }
163
+
164
+ let onAbort: (() => void) | undefined;
165
+ const aborted = new Promise<never>((_resolve, reject) => {
166
+ onAbort = () => {
167
+ reject(
168
+ new BackendFederationManifestAdapterError(
169
+ 'remote_unavailable',
170
+ '[BFF][Effect] Backend federation load was aborted before remote execution.',
171
+ signal.reason,
172
+ ),
173
+ );
174
+ };
175
+ signal.addEventListener('abort', onAbort, { once: true });
176
+ });
177
+
178
+ return Promise.race([promise, aborted]).finally(() => {
179
+ if (onAbort) {
180
+ signal.removeEventListener('abort', onAbort);
181
+ }
182
+ });
183
+ }
184
+
137
185
  export async function loadBackendFederatedEffectApiFromManifest(
138
186
  options: BackendFederationManifestAdapterOptions,
139
187
  ): Promise<BackendFederatedEffectApiModule> {
@@ -144,16 +192,19 @@ export async function loadBackendFederatedEffectApiFromManifest(
144
192
  try {
145
193
  scope = createManifestLoadScope(options);
146
194
  throwIfManifestLoadAborted(scope.signal);
147
- manifest = await loadBackendFederationManifest({
148
- ...options,
149
- manifestPolicy: {
150
- ...options.manifestPolicy,
195
+ manifest = await settleOnSharedAbort(
196
+ loadBackendFederationManifest({
197
+ ...options,
198
+ manifestPolicy: {
199
+ ...options.manifestPolicy,
200
+ signal: scope.signal,
201
+ timeoutMs: 0,
202
+ },
151
203
  signal: scope.signal,
152
204
  timeoutMs: 0,
153
- },
154
- signal: scope.signal,
155
- timeoutMs: 0,
156
- });
205
+ }),
206
+ scope.signal,
207
+ );
157
208
  validateBackendFederationManifest(manifest, options.expected);
158
209
  remote = resolveBackendFederationRemoteFromManifest(
159
210
  manifest,
@@ -204,37 +255,41 @@ export async function loadBackendFederatedEffectApiFromManifest(
204
255
  );
205
256
  }
206
257
 
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) => {
258
+ const deliveryUnit = manifestDeliveryUnit(
259
+ backendFederationMetadata(manifest),
260
+ versionBoundaryMetadata(manifest),
261
+ );
262
+ const unitId =
263
+ stringValue(deliveryUnit.boundary?.unitId) ??
264
+ stringValue(deliveryUnit.top?.unitId);
265
+ const buildMarker = stringValue(deliveryUnit.boundary?.buildMarker);
266
+ if (!unitId || !buildMarker)
232
267
  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,
268
+ 'manifest_invalid',
269
+ 'Backend federation manifest requires delivery-unit identity.',
236
270
  );
237
- });
271
+ const loaded = await settleOnSharedAbort(
272
+ loadBackendFederatedEffectApi({
273
+ hostName: options.hostName,
274
+ remote,
275
+ ...(options.plugins ? { plugins: options.plugins } : {}),
276
+ ...(options.shared ? { shared: options.shared } : {}),
277
+ entryPolicy: {
278
+ ...options.entryPolicy,
279
+ signal: scope.signal,
280
+ timeoutMs: 0,
281
+ },
282
+ ...(options.runtime ? { runtime: options.runtime } : {}),
283
+ expected: { unitId, buildMarker },
284
+ }).catch((error: unknown) => {
285
+ throw new BackendFederationManifestAdapterError(
286
+ scope?.timedOut() === true ? 'timeout' : classifyLoadError(error),
287
+ `[BFF][Effect] Backend federation remote ${remote?.name ?? 'unknown'} could not load ${remote?.expose ?? BACKEND_FEDERATION_EFFECT_EXPOSE}.`,
288
+ error,
289
+ );
290
+ }),
291
+ scope.signal,
292
+ );
238
293
 
239
294
  validateLoadedBackendFederationContract(loaded, manifest, remote);
240
295
 
@@ -3,7 +3,7 @@
3
3
  import {
4
4
  backendFederationMetadata as sharedBackendFederationMetadata,
5
5
  backendFederationVersionBoundary as sharedBackendFederationVersionBoundary,
6
- } from '@modern-js/utils/universal';
6
+ } from '@modern-js/backend-federation-contracts';
7
7
  import type { BackendFederationManifest } from './types';
8
8
 
9
9
  export function stringValue(value: unknown) {
@@ -56,12 +56,6 @@ export function deliveryUnitMetadata(
56
56
  return recordField(record, 'deliveryUnit');
57
57
  }
58
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
59
  export function manifestDeliveryUnit(
66
60
  backendFederation: Record<string, unknown> | undefined,
67
61
  boundary: Record<string, unknown> | undefined,
@@ -1,14 +1,15 @@
1
1
  import { evaluateNodeBackendFederationCommonJs } from '@modern-js/server-runtime-extensions/backend-federation-security/node';
2
-
3
2
  import type { BackendFederatedEffectApiModule } from '../backend-federation';
3
+ import { withEffectBffHostShared } from '../backend-federation/node-shared';
4
4
  import { loadBackendFederatedEffectApiFromManifest as loadUniversalBackendFederatedEffectApiFromManifest } from './load';
5
5
  import type { BackendFederationManifestAdapterOptions } from './types';
6
6
 
7
- export function loadBackendFederatedEffectApiFromManifest(
7
+ export async function loadBackendFederatedEffectApiFromManifest(
8
8
  options: BackendFederationManifestAdapterOptions,
9
9
  ): Promise<BackendFederatedEffectApiModule> {
10
10
  return loadUniversalBackendFederatedEffectApiFromManifest({
11
11
  ...options,
12
+ shared: withEffectBffHostShared(options.shared),
12
13
  entryPolicy: {
13
14
  ...options.entryPolicy,
14
15
  evaluateCommonJs:
@@ -1,10 +1,10 @@
1
1
  // @effect-diagnostics asyncFunction:off extendsNativeError:off globalTimers:off newPromise:off strictBooleanExpressions:off
2
2
 
3
+ import { backendFederationExposeNames } from '@modern-js/backend-federation-contracts';
3
4
  import {
4
5
  BackendFederationRemoteEntryError,
5
6
  resolveBackendFederationRemoteEntryVerification,
6
7
  } from '@modern-js/server-runtime-extensions/backend-federation-security';
7
- import { backendFederationExposeNames } from '@modern-js/utils/universal';
8
8
  import {
9
9
  BACKEND_FEDERATION_EFFECT_EXPOSE,
10
10
  type BackendFederationRemote,
@@ -91,12 +91,6 @@ export function resolveBackendFederationRemoteFromManifest(
91
91
  entry: remoteEntryUrl,
92
92
  type: remoteType,
93
93
  ...(verification ? { verification } : {}),
94
- ...(remoteOverride.entryGlobalName
95
- ? { entryGlobalName: remoteOverride.entryGlobalName }
96
- : {}),
97
94
  expose,
98
- ...(remoteOverride.shareScope
99
- ? { shareScope: remoteOverride.shareScope }
100
- : {}),
101
95
  };
102
96
  }
@@ -12,6 +12,7 @@ import type {
12
12
  import type {
13
13
  BackendFederatedEffectApiModule,
14
14
  BackendFederationRemote,
15
+ BackendFederationRuntimeOptions,
15
16
  } from '../backend-federation';
16
17
  import type { BackendFederationManifestAdapterError } from './errors';
17
18
 
@@ -68,6 +69,7 @@ export type BackendFederationManifestAdapterOptions = {
68
69
  plugins?: ModuleFederationRuntimePlugin[];
69
70
  remote?: Partial<BackendFederationRemote>;
70
71
  runtime?: ModuleFederation;
72
+ shared?: BackendFederationRuntimeOptions['shared'];
71
73
  signal?: AbortSignal;
72
74
  timeoutMs?: number;
73
75
  };
@@ -3,13 +3,18 @@
3
3
  import {
4
4
  formatBackendFederationValidationErrors,
5
5
  validateBackendFederationManifest as validateBackendFederationManifestContract,
6
- } from '@modern-js/utils/universal';
6
+ } from '@modern-js/backend-federation-contracts';
7
7
  import {
8
8
  BACKEND_FEDERATION_CONTRACT_VERSION,
9
9
  BACKEND_FEDERATION_NODE_ADAPTER_VERSION,
10
10
  type BackendFederatedEffectApiModule,
11
11
  type BackendFederationRemote,
12
12
  } from '../backend-federation';
13
+ import {
14
+ backendFederationCompatibility,
15
+ formatBackendFederationIdentityIssues,
16
+ validateBackendFederationCompatibility,
17
+ } from '../backend-federation/identity';
13
18
  import {
14
19
  assertConsistentValue,
15
20
  assertManifestAdapter,
@@ -19,7 +24,6 @@ import {
19
24
  import {
20
25
  backendFederationMetadata,
21
26
  manifestDeliveryUnit,
22
- recordField,
23
27
  stringValue,
24
28
  versionBoundaryMetadata,
25
29
  } from './metadata';
@@ -48,11 +52,6 @@ function resolveExpectedBuildMarker(
48
52
  );
49
53
  }
50
54
 
51
- // Intentionally no fallback to `buildVersion` here: this is only used to
52
- // compare against manifest `deliveryUnit.buildMarker`, which is a
53
- // separate, additive field. Callers that only pass `buildVersion` (the
54
- // pre-ADR-0019 expectation shape) must not be forced to also match a
55
- // delivery-unit build marker on the manifest.
56
55
  return expected.buildMarker;
57
56
  }
58
57
 
@@ -70,7 +69,6 @@ export function validateBackendFederationManifest(
70
69
  const manifestShape = validateBackendFederationManifestContract(manifest, {
71
70
  expectedContractVersion: false,
72
71
  expectedNodeAdapterVersion: false,
73
- validateDeliveryUnit: false,
74
72
  });
75
73
  assertManifestAdapter(
76
74
  manifestShape.ok,
@@ -151,6 +149,11 @@ export function validateBackendFederationManifest(
151
149
  const manifestUnitId =
152
150
  stringValue(deliveryUnit.boundary?.unitId) ??
153
151
  stringValue(deliveryUnit.top?.unitId);
152
+ assertManifestAdapter(
153
+ manifestUnitId && stringValue(deliveryUnit.boundary?.buildMarker),
154
+ 'manifest_invalid',
155
+ 'Backend federation manifest requires delivery-unit unitId and buildMarker.',
156
+ );
154
157
  assertVersionValue(manifestUnitId, expected.unitId, 'deliveryUnit.unitId');
155
158
  assertVersionValue(
156
159
  stringValue(deliveryUnit.boundary?.buildMarker),
@@ -167,7 +170,7 @@ export function validateLoadedBackendFederationContract(
167
170
  const loadedContract = loaded.backendFederationContract;
168
171
  const backendFederation = backendFederationMetadata(manifest);
169
172
  const boundary = versionBoundaryMetadata(manifest);
170
- const compatibility = recordField(loadedContract, 'compatibility');
173
+ const compatibility = backendFederationCompatibility(loaded);
171
174
 
172
175
  assertManifestAdapter(
173
176
  compatibility,
@@ -187,26 +190,6 @@ export function validateLoadedBackendFederationContract(
187
190
  remote.name,
188
191
  'expose name',
189
192
  );
190
- assertVersionValue(
191
- stringValue(compatibility.contractVersion),
192
- stringValue(backendFederation?.contractVersion),
193
- 'expose contractVersion',
194
- );
195
- assertVersionValue(
196
- stringValue(compatibility.nodeAdapterVersion),
197
- stringValue(backendFederation?.nodeAdapterVersion),
198
- 'expose nodeAdapterVersion',
199
- );
200
- assertVersionValue(
201
- stringValue(compatibility.packageName),
202
- stringValue(boundary?.packageName),
203
- 'expose packageName',
204
- );
205
- assertVersionValue(
206
- stringValue(compatibility.build),
207
- stringValue(boundary?.buildVersion),
208
- 'expose buildVersion',
209
- );
210
193
 
211
194
  const deliveryUnit = manifestDeliveryUnit(backendFederation, boundary);
212
195
  const manifestUnitId =
@@ -214,17 +197,24 @@ export function validateLoadedBackendFederationContract(
214
197
  stringValue(deliveryUnit.top?.unitId);
215
198
  const manifestBuildMarker = stringValue(deliveryUnit.boundary?.buildMarker);
216
199
 
217
- // Only enforced when both sides declare the field: absence on either side
218
- // is a legacy manifest/expose and must stay backward compatible.
219
- assertConsistentValue(
220
- manifestUnitId,
221
- stringValue(compatibility.unitId),
222
- 'deliveryUnit.unitId vs expose compatibility.unitId',
223
- );
224
- assertConsistentValue(
225
- manifestBuildMarker,
200
+ const issues = validateBackendFederationCompatibility(compatibility, {
201
+ contractVersion: stringValue(backendFederation?.contractVersion),
202
+ nodeAdapterVersion: stringValue(backendFederation?.nodeAdapterVersion),
203
+ packageName: stringValue(boundary?.packageName),
204
+ unitId: manifestUnitId,
205
+ build: manifestBuildMarker ?? stringValue(boundary?.buildVersion),
206
+ });
207
+ // Manifest validation establishes buildVersion/buildMarker consistency; the
208
+ // executed expose is still checked independently against that trusted record.
209
+ assertVersionValue(
226
210
  stringValue(compatibility.build),
227
- 'deliveryUnit.buildMarker vs expose compatibility.build',
211
+ stringValue(boundary?.buildVersion),
212
+ 'expose buildVersion',
213
+ );
214
+ assertManifestAdapter(
215
+ issues.length === 0,
216
+ 'version_mismatch',
217
+ `[BFF][Effect] ${formatBackendFederationIdentityIssues(issues)}.`,
228
218
  );
229
219
  }
230
220
 
@@ -3,7 +3,7 @@ import {
3
3
  evaluateCrossProjectPolicy,
4
4
  type NormalizedCrossProjectPolicy,
5
5
  resolveCrossProjectRequestObservation,
6
- } from '@modern-js/bff-core/security/cross-project-policy';
6
+ } from '@modern-js/server-runtime-extensions/bff-policy';
7
7
 
8
8
  export type ResolvedCrossProjectPolicy = NormalizedCrossProjectPolicy;
9
9
 
@@ -1,48 +1,17 @@
1
- import { readFileSync } from 'node:fs';
2
- import path from 'node:path';
3
-
1
+ import '@modern-js/server-runtime-extensions/server-config';
2
+ import type { ServerPluginAPI } from '@modern-js/server-core';
4
3
  import {
5
- deriveOperationVersion,
6
4
  type OperationContractSource,
7
5
  resolveCrossProjectPolicy,
8
- } from '@modern-js/bff-core';
9
- import type { ServerPluginAPI } from '@modern-js/server-core';
6
+ resolveOperationProducer,
7
+ } from '@modern-js/server-runtime-extensions/bff-policy/node';
10
8
 
11
9
  import type { ResolvedCrossProjectPolicy } from './evaluation';
12
10
 
13
- const readNearestPackageVersion = (
14
- startDir: string | undefined,
15
- ): string | undefined => {
16
- if (!startDir) {
17
- return undefined;
18
- }
19
-
20
- let current = path.resolve(startDir);
21
- for (let depth = 0; depth < 10; depth += 1) {
22
- try {
23
- const packageJson = JSON.parse(
24
- readFileSync(path.join(current, 'package.json'), 'utf8'),
25
- ) as { version?: unknown };
26
- if (typeof packageJson.version === 'string') {
27
- return packageJson.version;
28
- }
29
- } catch {
30
- // The producer package may be nested below its package.json.
31
- }
32
-
33
- const parent = path.dirname(current);
34
- if (parent === current) {
35
- return undefined;
36
- }
37
- current = parent;
38
- }
39
-
40
- return undefined;
41
- };
42
-
43
11
  export const resolveAdapterCrossProjectPolicy = (
44
12
  api: ServerPluginAPI,
45
13
  handlers: OperationContractSource[],
14
+ producer?: ReturnType<typeof resolveOperationProducer>,
46
15
  ): ResolvedCrossProjectPolicy | undefined => {
47
16
  const bff = api.getServerConfig()?.bff;
48
17
  const { apiDirectory, appDirectory } = api.getServerContext() as {
@@ -53,11 +22,11 @@ export const resolveAdapterCrossProjectPolicy = (
53
22
  return resolveCrossProjectPolicy({
54
23
  crossProjectPolicy: bff?.crossProjectPolicy,
55
24
  handlers,
56
- requestId: bff?.requestId,
57
25
  isCrossProjectServer: bff?.isCrossProjectServer,
58
- operationVersion: deriveOperationVersion(
59
- readNearestPackageVersion(apiDirectory) ??
60
- readNearestPackageVersion(appDirectory),
61
- ),
26
+ ...(producer ??
27
+ resolveOperationProducer({
28
+ directories: [apiDirectory, appDirectory],
29
+ requestId: bff?.requestId || 'default',
30
+ })),
62
31
  });
63
32
  };