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