@bleedingdev/modern-js-plugin-bff 3.5.0-ultramodern.3 → 3.5.0-ultramodern.30

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 (40) hide show
  1. package/dist/cjs/runtime/effect/adapter.js +9 -40
  2. package/dist/cjs/runtime/effect/backend-federation-manifest.js +287 -0
  3. package/dist/cjs/runtime/effect/backend-federation.js +189 -0
  4. package/dist/cjs/runtime/effect/dispatch.js +116 -0
  5. package/dist/cjs/runtime/effect/edge.js +58 -69
  6. package/dist/cjs/runtime/effect/endpoint-contracts.js +7 -12
  7. package/dist/cjs/runtime/effect/entry-shape.js +87 -0
  8. package/dist/cjs/runtime/effect/handler.js +15 -16
  9. package/dist/cjs/runtime/effect/index.js +91 -15
  10. package/dist/cjs/runtime/effect/module.js +23 -80
  11. package/dist/esm/runtime/effect/adapter.mjs +10 -41
  12. package/dist/esm/runtime/effect/backend-federation-manifest.mjs +237 -0
  13. package/dist/esm/runtime/effect/backend-federation.mjs +132 -0
  14. package/dist/esm/runtime/effect/dispatch.mjs +63 -0
  15. package/dist/esm/runtime/effect/edge.mjs +17 -63
  16. package/dist/esm/runtime/effect/endpoint-contracts.mjs +7 -12
  17. package/dist/esm/runtime/effect/entry-shape.mjs +36 -0
  18. package/dist/esm/runtime/effect/handler.mjs +1 -4
  19. package/dist/esm/runtime/effect/index.mjs +3 -0
  20. package/dist/esm/runtime/effect/module.mjs +23 -80
  21. package/dist/esm-node/runtime/effect/adapter.mjs +10 -41
  22. package/dist/esm-node/runtime/effect/backend-federation-manifest.mjs +238 -0
  23. package/dist/esm-node/runtime/effect/backend-federation.mjs +133 -0
  24. package/dist/esm-node/runtime/effect/dispatch.mjs +64 -0
  25. package/dist/esm-node/runtime/effect/edge.mjs +17 -63
  26. package/dist/esm-node/runtime/effect/endpoint-contracts.mjs +7 -12
  27. package/dist/esm-node/runtime/effect/entry-shape.mjs +37 -0
  28. package/dist/esm-node/runtime/effect/handler.mjs +1 -4
  29. package/dist/esm-node/runtime/effect/index.mjs +3 -0
  30. package/dist/esm-node/runtime/effect/module.mjs +23 -80
  31. package/dist/types/runtime/effect/backend-federation-manifest.d.ts +60 -0
  32. package/dist/types/runtime/effect/backend-federation.d.ts +60 -0
  33. package/dist/types/runtime/effect/dispatch.d.ts +17 -0
  34. package/dist/types/runtime/effect/edge.d.ts +9 -8
  35. package/dist/types/runtime/effect/endpoint-contracts.d.ts +2 -2
  36. package/dist/types/runtime/effect/entry-shape.d.ts +31 -0
  37. package/dist/types/runtime/effect/handler.d.ts +4 -20
  38. package/dist/types/runtime/effect/index.d.ts +3 -0
  39. package/dist/types/runtime/effect-client/index.d.ts +1 -1
  40. package/package.json +26 -14
@@ -0,0 +1,133 @@
1
+ import "node:module";
2
+ const BACKEND_FEDERATION_EFFECT_EXPOSE = './effect-api';
3
+ const BACKEND_FEDERATION_MANIFEST_FILE = 'backend-mf-manifest.json';
4
+ const BACKEND_FEDERATION_CONTRACT_VERSION = 'microvertical-server-effect-v1';
5
+ const BACKEND_FEDERATION_NODE_ADAPTER_VERSION = 'backend-mf-effect-v1';
6
+ function normalizeExpose(expose) {
7
+ return expose.replace(/^\.\//u, '');
8
+ }
9
+ function exposeForRemoteRequest(expose) {
10
+ return `./${normalizeExpose(expose)}`;
11
+ }
12
+ function isRecord(value) {
13
+ return 'object' == typeof value && null !== value;
14
+ }
15
+ function collectRemotes(options) {
16
+ const remotes = [
17
+ ...options.remotes ?? []
18
+ ];
19
+ if (options.remote) {
20
+ const existingIndex = remotes.findIndex((remote)=>remote.name === options.remote?.name);
21
+ if (existingIndex >= 0) remotes[existingIndex] = options.remote;
22
+ else remotes.push(options.remote);
23
+ }
24
+ return remotes;
25
+ }
26
+ function parseRemoteRequest(request) {
27
+ const [remoteName, ...exposeParts] = request.split('/');
28
+ if (!remoteName || 0 === exposeParts.length) throw new Error(`[BFF][Effect] Invalid backend federation request ${request}.`);
29
+ return {
30
+ remoteName,
31
+ expose: exposeForRemoteRequest(exposeParts.join('/'))
32
+ };
33
+ }
34
+ function decodeDataUrl(url) {
35
+ const commaIndex = url.indexOf(',');
36
+ if (commaIndex < 0) throw new Error('[BFF][Effect] Invalid backend federation data URL entry.');
37
+ return decodeURIComponent(url.slice(commaIndex + 1));
38
+ }
39
+ async function readCommonJsEntrySource(remote) {
40
+ if (remote.entry.startsWith('data:')) return decodeDataUrl(remote.entry);
41
+ if (remote.entry.startsWith('http://') || remote.entry.startsWith('https://')) {
42
+ const response = await fetch(remote.entry);
43
+ if (!response.ok) throw new Error(`[BFF][Effect] Failed to load backend federation remote ${remote.name}: ${response.status}`);
44
+ return response.text();
45
+ }
46
+ throw new Error(`[BFF][Effect] Backend federation remote ${remote.name} cannot load CommonJS entry ${remote.entry}.`);
47
+ }
48
+ function evaluateCommonJsEntry(remote, source) {
49
+ const module = {
50
+ exports: {}
51
+ };
52
+ const exports = module.exports;
53
+ const evaluate = new Function('module', 'exports', 'globalThis', source);
54
+ evaluate(module, exports, globalThis);
55
+ const entry = module.exports.default ?? module.exports;
56
+ if (!isRecord(entry)) throw new Error(`[BFF][Effect] Backend federation remote ${remote.name} entry must load object module.`);
57
+ return entry;
58
+ }
59
+ async function importModuleEntry(remote) {
60
+ const entry = await import(remote.entry);
61
+ return entry.default ?? entry;
62
+ }
63
+ async function resolvePluginEntry(remote, plugins) {
64
+ for (const plugin of plugins ?? []){
65
+ const loadEntry = plugin.loadEntry;
66
+ if ('function' != typeof loadEntry) continue;
67
+ const entry = await loadEntry({
68
+ remoteInfo: remote
69
+ });
70
+ if (entry) return entry;
71
+ }
72
+ }
73
+ async function loadBackendFederationEntry(remote, plugins) {
74
+ const pluginEntry = await resolvePluginEntry(remote, plugins);
75
+ if (pluginEntry) return pluginEntry;
76
+ if (remote.entryGlobalName) {
77
+ const globalEntry = globalThis[remote.entryGlobalName];
78
+ if (isRecord(globalEntry)) return globalEntry;
79
+ }
80
+ if ('module' === remote.type || remote.entry.startsWith('file:')) return importModuleEntry(remote);
81
+ return evaluateCommonJsEntry(remote, await readCommonJsEntrySource(remote));
82
+ }
83
+ async function loadBackendFederationExpose(remote, expose, options) {
84
+ const entry = await loadBackendFederationEntry(remote, options.plugins);
85
+ if ('function' == typeof entry.init) await entry.init({
86
+ hostName: options.hostName
87
+ });
88
+ if ('function' != typeof entry.get) throw new Error(`[BFF][Effect] Backend federation remote ${remote.name} entry must expose get().`);
89
+ const factory = await entry.get(expose);
90
+ if ('function' != typeof factory) throw new Error(`[BFF][Effect] Backend federation expose ${remote.name}/${normalizeExpose(expose)} must load factory function.`);
91
+ return factory();
92
+ }
93
+ function createBackendFederationRuntime(options) {
94
+ const remotes = collectRemotes(options);
95
+ return {
96
+ async loadRemote (request) {
97
+ const { remoteName, expose } = parseRemoteRequest(request);
98
+ const remote = remotes.find((candidate)=>candidate.name === remoteName);
99
+ if (!remote) throw new Error(`[BFF][Effect] Missing backend federation remote ${remoteName}.`);
100
+ return loadBackendFederationExpose(remote, expose, options);
101
+ }
102
+ };
103
+ }
104
+ function createBackendFederationLoadEntryPlugin(options) {
105
+ return {
106
+ name: 'modernjs-backend-federation-load-entry',
107
+ async loadEntry ({ remoteInfo }) {
108
+ return options.resolveEntry({
109
+ name: remoteInfo.name,
110
+ entry: remoteInfo.entry,
111
+ type: remoteInfo.type,
112
+ entryGlobalName: remoteInfo.entryGlobalName,
113
+ shareScope: remoteInfo.shareScope
114
+ });
115
+ }
116
+ };
117
+ }
118
+ async function loadBackendFederatedEffectApi(options) {
119
+ const remoteName = options.remote?.name ?? options.remoteName;
120
+ if (!remoteName) throw new Error('[BFF][Effect] Missing backend federation remote name.');
121
+ const runtime = options.runtime ?? createBackendFederationRuntime(options);
122
+ const expose = options.expose ?? options.remote?.expose ?? BACKEND_FEDERATION_EFFECT_EXPOSE;
123
+ const remoteRequest = `${remoteName}/${normalizeExpose(expose)}`;
124
+ const loaded = await runtime.loadRemote(remoteRequest);
125
+ if (!isRecord(loaded)) throw new Error(`[BFF][Effect] Backend federation expose ${remoteRequest} must load an object module.`);
126
+ const backendContract = loaded.backendFederationContract;
127
+ if (!isRecord(backendContract) || true !== backendContract.strictEffectApproach) throw new Error(`[BFF][Effect] Backend federation expose ${remoteRequest} must expose strict Effect metadata (strictEffectApproach: true).`);
128
+ if ('effect' !== backendContract.runtimeFramework) throw new Error(`[BFF][Effect] Backend federation expose ${remoteRequest} must expose strict Effect metadata (runtimeFramework: "effect").`);
129
+ if ('string' == typeof backendContract.name && backendContract.name !== remoteName) throw new Error(`[BFF][Effect] Backend federation expose ${remoteRequest} metadata name mismatch: expected ${remoteName}, received ${backendContract.name}.`);
130
+ if (!('runtime' in loaded)) throw new Error(`[BFF][Effect] Backend federation expose ${remoteRequest} must expose api and runtime.`);
131
+ return loaded;
132
+ }
133
+ export { BACKEND_FEDERATION_CONTRACT_VERSION, BACKEND_FEDERATION_EFFECT_EXPOSE, BACKEND_FEDERATION_MANIFEST_FILE, BACKEND_FEDERATION_NODE_ADAPTER_VERSION, createBackendFederationLoadEntryPlugin, createBackendFederationRuntime, loadBackendFederatedEffectApi };
@@ -0,0 +1,64 @@
1
+ import "node:module";
2
+ import { createSafeFailureResponse } from "../safe-failure.mjs";
3
+ import { createEffectOperationContext } from "./operation-context.mjs";
4
+ function normalizeEffectBffPrefix(prefix) {
5
+ if (!prefix || '/' === prefix) return '';
6
+ return prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;
7
+ }
8
+ function removeEffectBffPrefixFromPath(pathname, prefix) {
9
+ const normalized = normalizeEffectBffPrefix(prefix);
10
+ if (!normalized || pathname !== normalized && !pathname.startsWith(`${normalized}/`)) return pathname;
11
+ const sliced = pathname.slice(normalized.length);
12
+ return sliced.startsWith('/') ? sliced : `/${sliced}`;
13
+ }
14
+ function matchesEffectBffPrefix(pathname, prefix) {
15
+ const normalized = normalizeEffectBffPrefix(prefix);
16
+ return !normalized || pathname === normalized || pathname.startsWith(`${normalized}/`);
17
+ }
18
+ function createEffectBffMountedRequest(req, prefix) {
19
+ const url = new URL(req.url);
20
+ const nextPath = removeEffectBffPrefixFromPath(url.pathname, prefix);
21
+ if (nextPath !== url.pathname) {
22
+ url.pathname = nextPath;
23
+ return new Request(url, req);
24
+ }
25
+ return req;
26
+ }
27
+ function createEffectBffDispatchContext(originalRequest, mountedRequest, options) {
28
+ const path = options.path ?? new URL(originalRequest.url).pathname;
29
+ const method = options.method ?? originalRequest.method;
30
+ const env = options.env ?? {};
31
+ return {
32
+ request: mountedRequest,
33
+ env,
34
+ path,
35
+ method,
36
+ operationContext: createEffectOperationContext({
37
+ request: mountedRequest,
38
+ env,
39
+ path,
40
+ method
41
+ })
42
+ };
43
+ }
44
+ async function dispatchEffectBffRequestWithContext(handler, request, options) {
45
+ const requestPath = new URL(request.url).pathname;
46
+ if (!matchesEffectBffPrefix(requestPath, options.prefix)) return new Response('', {
47
+ status: 404
48
+ });
49
+ const mountedRequest = createEffectBffMountedRequest(request, options.prefix);
50
+ const effectContext = createEffectBffDispatchContext(request, mountedRequest, options);
51
+ try {
52
+ const response = await options.runWithEffectContext(effectContext, ()=>handler(mountedRequest, effectContext));
53
+ if (!(response instanceof Response)) throw new Error('[BFF][Effect] Effect handler must return Response instance.');
54
+ return new Response(response.body, response);
55
+ } catch (error) {
56
+ if (error instanceof Response) return new Response(error.body, error);
57
+ if (options.onError) {
58
+ const errorResponse = await options.onError(error, effectContext);
59
+ if (errorResponse instanceof Response) return errorResponse;
60
+ }
61
+ return createSafeFailureResponse(error);
62
+ }
63
+ }
64
+ export { createEffectBffDispatchContext, createEffectBffMountedRequest, dispatchEffectBffRequestWithContext, matchesEffectBffPrefix, normalizeEffectBffPrefix, removeEffectBffPrefixFromPath };
@@ -1,71 +1,15 @@
1
1
  import "node:module";
2
- import { createSafeFailureResponse } from "../safe-failure.mjs";
2
+ import { dispatchEffectBffRequestWithContext } from "./dispatch.mjs";
3
3
  import { runWithEffectContext, useEffectContext, useOperationContext } from "./edge-context.mjs";
4
4
  import { resolveEffectBffModuleHandler } from "./module.mjs";
5
- import { createEffectOperationContext } from "./operation-context.mjs";
6
5
  export * from "./handler.mjs";
7
- function normalizePrefix(prefix) {
8
- if (!prefix || '/' === prefix) return '';
9
- return prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;
10
- }
11
- function removePrefixFromPath(pathname, prefix) {
12
- const normalized = normalizePrefix(prefix);
13
- if (!normalized || pathname !== normalized && !pathname.startsWith(`${normalized}/`)) return pathname;
14
- const sliced = pathname.slice(normalized.length);
15
- return sliced.startsWith('/') ? sliced : `/${sliced}`;
16
- }
17
- function matchesPrefix(pathname, prefix) {
18
- const normalized = normalizePrefix(prefix);
19
- return !normalized || pathname === normalized || pathname.startsWith(`${normalized}/`);
20
- }
21
- function createRequestForMountedPrefix(req, prefix) {
22
- const url = new URL(req.url);
23
- const nextPath = removePrefixFromPath(url.pathname, prefix);
24
- if (nextPath === url.pathname) return req;
25
- url.pathname = nextPath;
26
- return new Request(url, req);
27
- }
28
- function createEdgeEffectContext(originalRequest, effectRequest, options) {
29
- const originalPath = options.path || new URL(originalRequest.url).pathname;
30
- const method = options.method || originalRequest.method;
31
- return {
32
- request: effectRequest,
33
- env: options.env || {},
34
- path: originalPath,
35
- method,
36
- operationContext: createEffectOperationContext({
37
- request: effectRequest,
38
- env: options.env || {},
39
- path: originalPath,
40
- method
41
- })
42
- };
43
- }
44
- function createRuntimeErrorResponse(error) {
45
- return createSafeFailureResponse(error);
46
- }
47
6
  async function dispatchEffectBffRequest(handler, request, options = {}) {
48
- const requestPathname = new URL(request.url).pathname;
49
- if (!matchesPrefix(requestPathname, options.prefix)) return new Response(null, {
50
- status: 404
7
+ return dispatchEffectBffRequestWithContext(handler, request, {
8
+ ...options,
9
+ runWithEffectContext: runWithEffectContext
51
10
  });
52
- const effectRequest = createRequestForMountedPrefix(request, options.prefix);
53
- const effectContext = createEdgeEffectContext(request, effectRequest, options);
54
- try {
55
- const response = await runWithEffectContext(effectContext, ()=>handler.length > 1 ? handler(effectRequest, effectContext) : handler(effectRequest));
56
- if (!(response instanceof Response)) throw new Error('[BFF][Effect] Effect handler must return a Response instance.');
57
- return new Response(response.body, response);
58
- } catch (error) {
59
- if (error instanceof Response) return new Response(error.body, error);
60
- if (options.onError) {
61
- const errorResponse = await options.onError(error, effectContext);
62
- if (errorResponse instanceof Response) return errorResponse;
63
- }
64
- return createRuntimeErrorResponse(error);
65
- }
66
11
  }
67
- const createEffectBffTestHandler = createEffectBffEdgeHandler;
68
- async function createEffectBffEdgeHandler(options) {
12
+ async function createEffectBffEdgeDispatcher(options) {
69
13
  const loaded = await resolveEffectBffModuleHandler(options.module, {
70
14
  openapi: options.openapi,
71
15
  dataPlatform: options.dataPlatform,
@@ -73,7 +17,7 @@ async function createEffectBffEdgeHandler(options) {
73
17
  });
74
18
  if (!loaded) throw new Error('[BFF][Effect] Invalid Effect edge module. Export defineEffectBff(...) or a { api, layer } HttpApi module.');
75
19
  return {
76
- handler: (request, dispatchOptions = {})=>dispatchEffectBffRequest(loaded.handler, request, {
20
+ dispatch: (request, dispatchOptions = {})=>dispatchEffectBffRequest(loaded.handler, request, {
77
21
  ...dispatchOptions,
78
22
  prefix: options.prefix,
79
23
  onError: options.onError
@@ -83,4 +27,14 @@ async function createEffectBffEdgeHandler(options) {
83
27
  }
84
28
  };
85
29
  }
86
- export { createEffectBffEdgeHandler, createEffectBffTestHandler, createEffectOperationContext, dispatchEffectBffRequest, runWithEffectContext, useEffectContext, useOperationContext };
30
+ const createEffectBffTestHandler = createEffectBffEdgeHandler;
31
+ async function createEffectBffEdgeHandler(options) {
32
+ const dispatcher = await createEffectBffEdgeDispatcher(options);
33
+ return {
34
+ handler: dispatcher.dispatch,
35
+ dispose: dispatcher.dispose
36
+ };
37
+ }
38
+ export { BACKEND_FEDERATION_CONTRACT_VERSION, BACKEND_FEDERATION_EFFECT_EXPOSE, BACKEND_FEDERATION_MANIFEST_FILE, BACKEND_FEDERATION_NODE_ADAPTER_VERSION, createBackendFederationLoadEntryPlugin, createBackendFederationRuntime, loadBackendFederatedEffectApi } from "./backend-federation.mjs";
39
+ export { createEffectOperationContext } from "./operation-context.mjs";
40
+ export { createEffectBffEdgeDispatcher, createEffectBffEdgeHandler, createEffectBffTestHandler, dispatchEffectBffRequest, runWithEffectContext, useEffectContext, useOperationContext };
@@ -1,5 +1,6 @@
1
1
  import "node:module";
2
2
  import { createOperationContractHash } from "@modern-js/bff-core";
3
+ import { classifyEffectBffEntryModule, isValidatorAwareHandlerFactory } from "./entry-shape.mjs";
3
4
  function ensureLeadingSlash(pathname) {
4
5
  return pathname.startsWith('/') ? pathname : `/${pathname}`;
5
6
  }
@@ -49,19 +50,13 @@ function createEffectOperationContractSource(endpoint) {
49
50
  routePath: endpoint.routePath
50
51
  };
51
52
  }
52
- function isRecord(value) {
53
- return 'object' == typeof value && null !== value;
54
- }
55
53
  async function extractHttpApiFromModule(mod, isHttpApi) {
56
- if (!isRecord(mod)) return null;
57
- if (isHttpApi(mod.api)) return mod.api;
58
- const entry = mod.default;
59
- if (isRecord(entry) && isHttpApi(entry.api)) return entry.api;
60
- if ('function' == typeof entry && 0 === entry.length) {
61
- const output = await entry();
62
- if (isRecord(output) && isHttpApi(output.api)) return output.api;
63
- }
64
- return null;
54
+ const facts = classifyEffectBffEntryModule(mod, {
55
+ isRequestHandler: (value)=>'function' == typeof value,
56
+ isValidatorAwareHandlerFactory: isValidatorAwareHandlerFactory,
57
+ isHttpApi
58
+ });
59
+ return !facts?.legacyShape && facts?.api !== void 0 && facts.hasRuntimeLayer && (void 0 === facts.createHandler || facts.createHandlerValidatorAware) ? facts.api : null;
65
60
  }
66
61
  function createEffectEndpointContractHash(endpoint, requestId) {
67
62
  return createOperationContractHash(createEffectOperationContractSource(endpoint), requestId);
@@ -0,0 +1,37 @@
1
+ import "node:module";
2
+ const EFFECT_VALIDATOR_AWARE_FACTORY = Symbol.for('modernjs.effect.validatorAware');
3
+ function isValidatorAwareHandlerFactory(factory) {
4
+ return Boolean('function' == typeof factory && factory[EFFECT_VALIDATOR_AWARE_FACTORY]);
5
+ }
6
+ const strictEffectApproachMessage = '[BFF][Effect] strictEffectApproach is enforced: Effect API entries export defineEffectBff(...) or { api, layer } HttpApi module. Raw handler exports, default request handlers, unbranded custom createHandler factories not valid Effect API entries.';
7
+ function isEffectEntryRecord(value) {
8
+ return 'object' == typeof value && null !== value;
9
+ }
10
+ function classifyEffectBffEntryModule(value, predicates) {
11
+ if (!isEffectEntryRecord(value)) return null;
12
+ const rootModule = value;
13
+ if (predicates.isRequestHandler(rootModule.handler)) return createEntryShapeFacts(rootModule, predicates, '`handler` export');
14
+ const defaultEntry = rootModule.default;
15
+ if (predicates.isRequestHandler(defaultEntry)) return createEntryShapeFacts(rootModule, predicates, 'default request handler');
16
+ const module = isEffectEntryRecord(defaultEntry) ? {
17
+ ...rootModule,
18
+ ...defaultEntry
19
+ } : rootModule;
20
+ if (predicates.isRequestHandler(module.handler)) return createEntryShapeFacts(module, predicates, '`handler` export');
21
+ return createEntryShapeFacts(module, predicates);
22
+ }
23
+ function createEntryShapeFacts(module, predicates, legacyShape) {
24
+ const createHandler = 'function' == typeof module.createHandler ? module.createHandler : void 0;
25
+ const api = predicates.isHttpApi(module.api) ? module.api : void 0;
26
+ const hasRuntimeLayer = void 0 !== module.layer;
27
+ return {
28
+ module,
29
+ legacyShape,
30
+ createHandler,
31
+ createHandlerValidatorAware: void 0 !== createHandler && predicates.isValidatorAwareHandlerFactory(createHandler),
32
+ api,
33
+ layer: module.layer,
34
+ hasRuntimeLayer
35
+ };
36
+ }
37
+ export { EFFECT_VALIDATOR_AWARE_FACTORY, classifyEffectBffEntryModule, isEffectEntryRecord, isValidatorAwareHandlerFactory, strictEffectApproachMessage };
@@ -4,6 +4,7 @@ import { HttpRouter, HttpServer, HttpServerResponse, HttpTraceContext } from "ef
4
4
  import { HttpApiBuilder, OpenApi } from "effect/unstable/httpapi";
5
5
  import { RpcSerialization, RpcServer } from "effect/unstable/rpc";
6
6
  import { DEFAULT_DATA_BATCH_ENDPOINT, DEFAULT_DATA_BATCH_HEADER, DEFAULT_DATA_ENVELOPE_HEADER, decodeRequestEnvelopeHeader, validateRequestEnvelope, validateSelectionPlan } from "../data-platform/index.mjs";
7
+ import { EFFECT_VALIDATOR_AWARE_FACTORY, isValidatorAwareHandlerFactory } from "./entry-shape.mjs";
7
8
  import * as __rspack_external_effect_Config_29be8a92 from "effect/Config";
8
9
  import * as __rspack_external_effect_Effect_194ac36c from "effect/Effect";
9
10
  import * as __rspack_external_effect_Option_4d691636 from "effect/Option";
@@ -15,10 +16,6 @@ import * as __rspack_external_effect_Context_f1289ca3 from "effect/Context";
15
16
  const emptyEffectServiceContext = __rspack_external_effect_Context_f1289ca3.empty();
16
17
  const isEffectServiceContext = (context)=>'object' == typeof context && null !== context && 'mapUnsafe' in context;
17
18
  const toEffectServiceContext = (context)=>isEffectServiceContext(context) ? context : emptyEffectServiceContext;
18
- const EFFECT_VALIDATOR_AWARE_FACTORY = Symbol.for('modernjs.effect.validatorAware');
19
- function isValidatorAwareHandlerFactory(factory) {
20
- return 'function' == typeof factory && true === factory[EFFECT_VALIDATOR_AWARE_FACTORY];
21
- }
22
19
  function normalizeOpenApiPath(pathname) {
23
20
  if (!pathname.startsWith('/')) return `/${pathname}`;
24
21
  return pathname;
@@ -1,6 +1,9 @@
1
1
  import "node:module";
2
2
  import * as __rspack_external__effect_opentelemetry_8bbbb5af from "@effect/opentelemetry";
3
+ export * from "./backend-federation.mjs";
3
4
  export * from "./handler.mjs";
5
+ export { BACKEND_FEDERATION_CONTRACT_VERSION, BACKEND_FEDERATION_EFFECT_EXPOSE, BACKEND_FEDERATION_MANIFEST_FILE, BACKEND_FEDERATION_NODE_ADAPTER_VERSION, createBackendFederationLoadEntryPlugin, createBackendFederationRuntime, loadBackendFederatedEffectApi } from "./backend-federation.mjs";
6
+ export { BackendFederationManifestAdapterError, loadBackendFederatedEffectApiFromManifest, loadBackendFederationManifest, resolveBackendFederationRemoteFromManifest } from "./backend-federation-manifest.mjs";
4
7
  export { createEffectOperationContext, runWithEffectContext, useEffectContext, useOperationContext } from "./context.mjs";
5
8
  export { createEffectBffEdgeHandler, createEffectBffTestHandler, dispatchEffectBffRequest } from "./edge.mjs";
6
9
  export { __rspack_external__effect_opentelemetry_8bbbb5af as OpenTelemetry };
@@ -1,24 +1,15 @@
1
1
  import "node:module";
2
2
  import { HttpApi } from "effect/unstable/httpapi";
3
- import { createHttpApiHandler, isValidatorAwareHandlerFactory } from "./handler.mjs";
3
+ import { classifyEffectBffEntryModule, isValidatorAwareHandlerFactory, strictEffectApproachMessage } from "./entry-shape.mjs";
4
+ import { createHttpApiHandler } from "./handler.mjs";
4
5
  import * as __rspack_external_effect_Context_f1289ca3 from "effect/Context";
5
- function isRecord(value) {
6
- return 'object' == typeof value && null !== value;
7
- }
8
- function includesRuntimeExports(value) {
9
- return 'api' in value || 'layer' in value || 'createHandler' in value || 'handler' in value;
10
- }
11
6
  function isRequestHandler(value) {
12
7
  return 'function' == typeof value;
13
8
  }
14
- function isEffectApiDefinition(module) {
15
- return HttpApi.isHttpApi(module.api) && void 0 !== module.layer;
16
- }
17
9
  function isEffectServiceContext(context) {
18
10
  return 'object' == typeof context && null !== context && 'mapUnsafe' in context;
19
11
  }
20
12
  const emptyEffectServiceContext = __rspack_external_effect_Context_f1289ca3.empty();
21
- const strictEffectApproachMessage = '[BFF][Effect] strictEffectApproach is enforced: Effect API entries must export defineEffectBff(...) or a { api, layer } HttpApi module. Raw handler exports, default request handlers, and unbranded custom createHandler factories are not valid Effect API entries.';
22
13
  function rejectLegacyEffectModuleShape(options, shape) {
23
14
  options.onWarning?.(`${strictEffectApproachMessage} Rejected ${shape}.`);
24
15
  return true;
@@ -41,41 +32,21 @@ function createLoadedHttpApiHandler(webHandler) {
41
32
  dispose: webHandler.dispose
42
33
  };
43
34
  }
44
- function resolveNormalizedEffectBffModuleHandler(normalizedModule, options = {}) {
45
- if (isRequestHandler(normalizedModule.handler)) {
46
- if (rejectLegacyEffectModuleShape(options, '`handler` export')) return null;
47
- return {
48
- handler: normalizedModule.handler
49
- };
50
- }
51
- const entry = normalizedModule.default;
52
- if (isRequestHandler(entry)) {
53
- if (rejectLegacyEffectModuleShape(options, 'default request handler')) return null;
54
- return {
55
- handler: entry
56
- };
57
- }
58
- if (isRecord(entry)) normalizedModule = {
59
- ...normalizedModule,
60
- ...entry
61
- };
62
- if (isRecord(entry) && 'handler' in entry) {
63
- const maybeHandler = entry.handler;
64
- if (isRequestHandler(maybeHandler)) normalizedModule = {
65
- ...normalizedModule,
66
- handler: maybeHandler
67
- };
68
- }
69
- if (isRequestHandler(normalizedModule.handler)) {
70
- if (rejectLegacyEffectModuleShape(options, '`handler` export')) return null;
71
- return {
72
- handler: normalizedModule.handler
73
- };
35
+ function classifyEffectBffRuntimeEntryShape(module) {
36
+ return classifyEffectBffEntryModule(module, {
37
+ isRequestHandler: isRequestHandler,
38
+ isValidatorAwareHandlerFactory: isValidatorAwareHandlerFactory,
39
+ isHttpApi: HttpApi.isHttpApi
40
+ });
41
+ }
42
+ function resolveClassifiedEffectBffModuleHandler(facts, options = {}) {
43
+ if (!facts) return null;
44
+ if (facts.legacyShape) {
45
+ if (rejectLegacyEffectModuleShape(options, facts.legacyShape)) return null;
74
46
  }
75
- if ('function' == typeof normalizedModule.createHandler) {
76
- const factory = normalizedModule.createHandler;
77
- const validatorAware = isValidatorAwareHandlerFactory(factory);
78
- if (!validatorAware && rejectLegacyEffectModuleShape(options, 'unbranded `createHandler` export')) return null;
47
+ if ('function' == typeof facts.createHandler) {
48
+ if (!facts.createHandlerValidatorAware && rejectLegacyEffectModuleShape(options, 'unbranded `createHandler` export')) return null;
49
+ const factory = facts.createHandler;
79
50
  const webHandler = factory({
80
51
  openapi: options.openapi,
81
52
  dataPlatform: options.dataPlatform,
@@ -83,11 +54,11 @@ function resolveNormalizedEffectBffModuleHandler(normalizedModule, options = {})
83
54
  });
84
55
  return createLoadedHandler(webHandler);
85
56
  }
86
- if (isEffectApiDefinition(normalizedModule)) {
57
+ if (void 0 !== facts.api && facts.hasRuntimeLayer) {
87
58
  options.onWarning?.('[BFF][Effect] Detected { api, layer } export without createHandler. Prefer `defineEffectBff(...)` from @modern-js/plugin-bff/server to avoid module instance mismatch.');
88
59
  const webHandler = createHttpApiHandler({
89
- api: normalizedModule.api,
90
- layer: normalizedModule.layer,
60
+ api: facts.api,
61
+ layer: facts.layer,
91
62
  openapi: options.openapi,
92
63
  dataPlatform: options.dataPlatform,
93
64
  validateRequest: options.validateRequest
@@ -96,38 +67,10 @@ function resolveNormalizedEffectBffModuleHandler(normalizedModule, options = {})
96
67
  }
97
68
  return null;
98
69
  }
70
+ function resolveNormalizedEffectBffModuleHandler(normalizedModule, options = {}) {
71
+ return resolveClassifiedEffectBffModuleHandler(classifyEffectBffRuntimeEntryShape(normalizedModule), options);
72
+ }
99
73
  function resolveEffectBffModuleHandler(mod, options = {}) {
100
- let normalizedModule = mod;
101
- const mergeRuntimeExports = (value)=>{
102
- if (!isRecord(value) || !includesRuntimeExports(value)) return;
103
- normalizedModule = {
104
- ...normalizedModule,
105
- ...value
106
- };
107
- };
108
- if (isRequestHandler(normalizedModule.handler)) {
109
- if (rejectLegacyEffectModuleShape(options, '`handler` export')) return Promise.resolve(null);
110
- return Promise.resolve({
111
- handler: normalizedModule.handler
112
- });
113
- }
114
- const entry = normalizedModule.default;
115
- if (isRequestHandler(entry)) {
116
- if (rejectLegacyEffectModuleShape(options, 'default request handler')) return Promise.resolve(null);
117
- return Promise.resolve({
118
- handler: entry
119
- });
120
- }
121
- if ('function' == typeof entry && 0 === entry.length) return Promise.resolve(entry()).then((out)=>{
122
- if (isRequestHandler(out)) {
123
- if (rejectLegacyEffectModuleShape(options, 'default factory returning a request handler')) return null;
124
- return {
125
- handler: out
126
- };
127
- }
128
- mergeRuntimeExports(out);
129
- return resolveNormalizedEffectBffModuleHandler(normalizedModule, options);
130
- });
131
- return Promise.resolve(resolveNormalizedEffectBffModuleHandler(normalizedModule, options));
74
+ return Promise.resolve(resolveNormalizedEffectBffModuleHandler(mod, options));
132
75
  }
133
76
  export { resolveEffectBffModuleHandler };
@@ -0,0 +1,60 @@
1
+ import type { ModuleFederation, ModuleFederationRuntimePlugin } from '@module-federation/runtime';
2
+ import { type BackendFederatedEffectApiModule, type BackendFederationRemote } from './backend-federation';
3
+ export type BackendFederationManifestAdapterErrorCode = 'manifest_unavailable' | 'manifest_invalid' | 'remote_unavailable' | 'strict_effect_required' | 'timeout' | 'version_mismatch';
4
+ export declare const BACKEND_FEDERATION_MANIFEST_ADAPTER_FAILURE_EVENT: 'modernjs:microvertical-server-fallback';
5
+ export declare class BackendFederationManifestAdapterError extends Error {
6
+ code: BackendFederationManifestAdapterErrorCode;
7
+ readonly failureEvent: "modernjs:microvertical-server-fallback";
8
+ details?: Record<string, unknown>;
9
+ constructor(code: BackendFederationManifestAdapterErrorCode, message: string, cause?: unknown, details?: Record<string, unknown>);
10
+ }
11
+ export type BackendFederationManifest = Record<string, unknown>;
12
+ export type BackendFederationVersionBoundaryExpectation = {
13
+ buildVersion?: string;
14
+ /**
15
+ * Delivery-unit build marker (ADR-0019). Semantically the same value as
16
+ * `buildVersion`; treated as an alias. If both are set and disagree the
17
+ * expectation itself is invalid and fails closed before any manifest
18
+ * comparison runs.
19
+ */
20
+ buildMarker?: string;
21
+ contractVersion?: string;
22
+ nodeAdapterVersion?: string;
23
+ packageName?: string;
24
+ remoteName?: string;
25
+ /**
26
+ * Delivery-unit identity root (ADR-0019 §3). When set, the manifest's
27
+ * `backendFederation.versionBoundary.deliveryUnit.unitId` (or the
28
+ * top-level `backendFederation.deliveryUnit.unitId` fallback) must match.
29
+ */
30
+ unitId?: string;
31
+ version?: string;
32
+ };
33
+ export type BackendFederationManifestFetchResponse = {
34
+ ok: boolean;
35
+ status: number;
36
+ statusText?: string;
37
+ text: () => Promise<string>;
38
+ };
39
+ export type BackendFederationManifestAdapterFallback = (error: BackendFederationManifestAdapterError, context: {
40
+ manifest?: BackendFederationManifest;
41
+ remote?: BackendFederationRemote;
42
+ }) => BackendFederatedEffectApiModule | Promise<BackendFederatedEffectApiModule>;
43
+ export type BackendFederationManifestAdapterOptions = {
44
+ env?: Record<string, string | undefined>;
45
+ expected?: BackendFederationVersionBoundaryExpectation;
46
+ fallback?: BackendFederationManifestAdapterFallback;
47
+ fetch?: (url: string) => Promise<BackendFederationManifestFetchResponse>;
48
+ hostName: string;
49
+ manifest?: BackendFederationManifest;
50
+ manifestEnv?: string;
51
+ manifestPath?: string;
52
+ manifestUrl?: string;
53
+ plugins?: ModuleFederationRuntimePlugin[];
54
+ remote?: Partial<BackendFederationRemote>;
55
+ runtime?: ModuleFederation;
56
+ timeoutMs?: number;
57
+ };
58
+ export declare function loadBackendFederationManifest(options: Pick<BackendFederationManifestAdapterOptions, 'env' | 'fetch' | 'manifest' | 'manifestEnv' | 'manifestPath' | 'manifestUrl' | 'timeoutMs'>): Promise<BackendFederationManifest>;
59
+ export declare function resolveBackendFederationRemoteFromManifest(manifest: BackendFederationManifest, remoteOverride?: Partial<BackendFederationRemote>): BackendFederationRemote;
60
+ export declare function loadBackendFederatedEffectApiFromManifest(options: BackendFederationManifestAdapterOptions): Promise<BackendFederatedEffectApiModule>;