@bleedingdev/modern-js-plugin-bff 3.5.0-ultramodern.98 → 3.8.1-ultramodern.1

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 (102) hide show
  1. package/dist/cjs/cli/compress.js +1 -1
  2. package/dist/cjs/cli/generator.js +106 -97
  3. package/dist/cjs/cli/serverGlobalVars.js +43 -57
  4. package/dist/cjs/cli.js +6 -2
  5. package/dist/cjs/loader.js +6 -5
  6. package/dist/cjs/runtime/effect/adapter/cross-project-policy.js +17 -12
  7. package/dist/cjs/runtime/effect/adapter/entry.js +13 -5
  8. package/dist/cjs/runtime/effect/adapter/error-response.js +11 -9
  9. package/dist/cjs/runtime/effect/adapter.js +2 -2
  10. package/dist/cjs/runtime/effect/backend-federation/load.js +26 -17
  11. package/dist/cjs/runtime/effect/backend-federation/plugin.js +12 -2
  12. package/dist/cjs/runtime/effect/backend-federation/remotes.js +5 -4
  13. package/dist/cjs/runtime/effect/backend-federation/runtime.js +80 -16
  14. package/dist/cjs/runtime/effect/edge-dispatcher.js +148 -0
  15. package/dist/cjs/runtime/effect/edge.js +19 -100
  16. package/dist/cjs/runtime/effect/endpoint-contracts.js +6 -1
  17. package/dist/cjs/runtime/effect/handler/definition.js +1 -0
  18. package/dist/cjs/runtime/effect/handler/http.js +17 -6
  19. package/dist/cjs/runtime/hono/adapter.js +32 -79
  20. package/dist/cjs/server.js +19 -30
  21. package/dist/cjs/utils/client-generator/write-package.js +1 -1
  22. package/dist/cjs/utils/crossProjectApiPlugin.js +21 -2
  23. package/dist/cjs/utils/effect-client-generator/generator.js +17 -16
  24. package/dist/cjs/utils/effect-client-generator/paths.js +4 -15
  25. package/dist/cjs/utils/effect-client-generator/rendering.js +8 -19
  26. package/dist/cjs/utils/effect-client-generator/runtime.js +15 -32
  27. package/dist/cjs/utils/effectSourceLoader.js +70 -10
  28. package/dist/cjs/utils/pluginGenerator.js +4 -4
  29. package/dist/esm/cli/compress.mjs +1 -1
  30. package/dist/esm/cli/generator.mjs +108 -98
  31. package/dist/esm/cli/serverGlobalVars.mjs +44 -48
  32. package/dist/esm/cli.mjs +2 -0
  33. package/dist/esm/loader.mjs +6 -5
  34. package/dist/esm/runtime/effect/adapter/cross-project-policy.mjs +17 -12
  35. package/dist/esm/runtime/effect/adapter/entry.mjs +13 -5
  36. package/dist/esm/runtime/effect/adapter/error-response.mjs +11 -9
  37. package/dist/esm/runtime/effect/adapter.mjs +3 -3
  38. package/dist/esm/runtime/effect/backend-federation/load.mjs +26 -17
  39. package/dist/esm/runtime/effect/backend-federation/plugin.mjs +12 -2
  40. package/dist/esm/runtime/effect/backend-federation/remotes.mjs +5 -4
  41. package/dist/esm/runtime/effect/backend-federation/runtime.mjs +80 -16
  42. package/dist/esm/runtime/effect/edge-dispatcher.mjs +38 -0
  43. package/dist/esm/runtime/effect/edge.mjs +1 -38
  44. package/dist/esm/runtime/effect/endpoint-contracts.mjs +6 -1
  45. package/dist/esm/runtime/effect/handler/definition.mjs +1 -0
  46. package/dist/esm/runtime/effect/handler/http.mjs +17 -6
  47. package/dist/esm/runtime/hono/adapter.mjs +33 -80
  48. package/dist/esm/server.mjs +18 -29
  49. package/dist/esm/utils/client-generator/write-package.mjs +1 -1
  50. package/dist/esm/utils/crossProjectApiPlugin.mjs +16 -3
  51. package/dist/esm/utils/effect-client-generator/generator.mjs +17 -16
  52. package/dist/esm/utils/effect-client-generator/paths.mjs +5 -6
  53. package/dist/esm/utils/effect-client-generator/rendering.mjs +9 -10
  54. package/dist/esm/utils/effect-client-generator/runtime.mjs +16 -23
  55. package/dist/esm/utils/effectSourceLoader.mjs +69 -12
  56. package/dist/esm/utils/pluginGenerator.mjs +5 -5
  57. package/dist/esm-node/cli/compress.mjs +1 -1
  58. package/dist/esm-node/cli/generator.mjs +108 -98
  59. package/dist/esm-node/cli/serverGlobalVars.mjs +44 -48
  60. package/dist/esm-node/cli.mjs +2 -0
  61. package/dist/esm-node/loader.mjs +6 -5
  62. package/dist/esm-node/runtime/effect/adapter/cross-project-policy.mjs +17 -12
  63. package/dist/esm-node/runtime/effect/adapter/entry.mjs +13 -5
  64. package/dist/esm-node/runtime/effect/adapter/error-response.mjs +11 -9
  65. package/dist/esm-node/runtime/effect/adapter.mjs +3 -3
  66. package/dist/esm-node/runtime/effect/backend-federation/load.mjs +26 -17
  67. package/dist/esm-node/runtime/effect/backend-federation/plugin.mjs +12 -2
  68. package/dist/esm-node/runtime/effect/backend-federation/remotes.mjs +5 -4
  69. package/dist/esm-node/runtime/effect/backend-federation/runtime.mjs +80 -16
  70. package/dist/esm-node/runtime/effect/edge-dispatcher.mjs +39 -0
  71. package/dist/esm-node/runtime/effect/edge.mjs +1 -38
  72. package/dist/esm-node/runtime/effect/endpoint-contracts.mjs +6 -1
  73. package/dist/esm-node/runtime/effect/handler/definition.mjs +1 -0
  74. package/dist/esm-node/runtime/effect/handler/http.mjs +17 -6
  75. package/dist/esm-node/runtime/hono/adapter.mjs +33 -80
  76. package/dist/esm-node/server.mjs +18 -29
  77. package/dist/esm-node/utils/client-generator/write-package.mjs +1 -1
  78. package/dist/esm-node/utils/crossProjectApiPlugin.mjs +16 -3
  79. package/dist/esm-node/utils/effect-client-generator/generator.mjs +17 -16
  80. package/dist/esm-node/utils/effect-client-generator/paths.mjs +5 -6
  81. package/dist/esm-node/utils/effect-client-generator/rendering.mjs +9 -10
  82. package/dist/esm-node/utils/effect-client-generator/runtime.mjs +16 -23
  83. package/dist/esm-node/utils/effectSourceLoader.mjs +69 -12
  84. package/dist/esm-node/utils/pluginGenerator.mjs +5 -5
  85. package/dist/types/cli/generator.d.ts +2 -2
  86. package/dist/types/cli/serverGlobalVars.d.ts +4 -2
  87. package/dist/types/cli.d.ts +1 -0
  88. package/dist/types/runtime/effect/adapter/cross-project-policy.d.ts +1 -1
  89. package/dist/types/runtime/effect/backend-federation/runtime.d.ts +1 -1
  90. package/dist/types/runtime/effect/edge-dispatcher.d.ts +26 -0
  91. package/dist/types/runtime/effect/edge.d.ts +1 -26
  92. package/dist/types/runtime/effect/endpoint-contracts.d.ts +6 -0
  93. package/dist/types/runtime/effect/handler/definition.d.ts +2 -12
  94. package/dist/types/runtime/effect/handler/http.d.ts +3 -2
  95. package/dist/types/runtime/effect/handler/openapi.d.ts +1 -1
  96. package/dist/types/runtime/effect/handler/types.d.ts +10 -5
  97. package/dist/types/runtime/effect-client/index.d.ts +1 -1
  98. package/dist/types/runtime/hono/adapter.d.ts +14 -8
  99. package/dist/types/utils/crossProjectApiPlugin.d.ts +2 -0
  100. package/dist/types/utils/effectSourceLoader.d.ts +10 -0
  101. package/dist/types/utils/pluginGenerator.d.ts +3 -1
  102. package/package.json +44 -16
@@ -22,23 +22,31 @@ function relativeAppPath(appDirectory, entryPath) {
22
22
  const relativePath = path.relative(appDirectory, entryPath);
23
23
  return '..' === relativePath || relativePath.startsWith(`..${path.sep}`) ? void 0 : relativePath;
24
24
  }
25
+ function isDependencyPath(relativePath) {
26
+ return relativePath?.split(/[\\/]/u).includes('node_modules') ?? false;
27
+ }
25
28
  function resolveEffectAdapterEntryFile(api) {
26
29
  const { appDirectory, apiDirectory, distDirectory } = api.getServerContext();
27
30
  const appRoot = path.resolve(appDirectory || process.cwd());
28
- const productionRoot = isProd() && distDirectory ? distDirectory : void 0;
31
+ const productionRoot = isProd() && distDirectory ? path.resolve(distDirectory) : void 0;
29
32
  const bffConfig = api.getServerConfig()?.bff;
30
33
  const configuredEntry = bffConfig?.effect?.entry;
31
34
  if (configuredEntry) {
32
35
  const sourceEntry = path.isAbsolute(configuredEntry) ? configuredEntry : path.resolve(appRoot, configuredEntry);
33
36
  if (productionRoot) {
34
37
  const relativeEntry = relativeAppPath(appRoot, sourceEntry);
38
+ if (void 0 === relativeEntry || isDependencyPath(relativeEntry)) return resolveJsOrTsEntry(sourceEntry);
35
39
  return relativeEntry ? resolveJsOrTsEntry(builtEntryPath(path.resolve(productionRoot, relativeEntry))) : void 0;
36
40
  }
37
41
  return resolveJsOrTsEntry(sourceEntry);
38
42
  }
39
- const relativeApiDirectory = apiDirectory ? path.isAbsolute(apiDirectory) ? relativeAppPath(appRoot, apiDirectory) : apiDirectory : API_DIR;
40
- if (!relativeApiDirectory) return;
41
- const apiRoot = path.resolve(productionRoot || appRoot, relativeApiDirectory);
42
- return resolveJsOrTsEntry(path.resolve(apiRoot, 'index'));
43
+ const resolvedApiDirectory = path.resolve(appRoot, apiDirectory || API_DIR);
44
+ const productionApiDirectory = productionRoot ? void 0 !== relativeAppPath(productionRoot, resolvedApiDirectory) ? resolvedApiDirectory : (()=>{
45
+ const relativeApiDirectory = relativeAppPath(appRoot, resolvedApiDirectory);
46
+ if (isDependencyPath(relativeApiDirectory)) return resolvedApiDirectory;
47
+ return relativeApiDirectory ? path.resolve(productionRoot, relativeApiDirectory) : void 0;
48
+ })() : resolvedApiDirectory;
49
+ if (!productionApiDirectory) return;
50
+ return resolveJsOrTsEntry(path.resolve(productionApiDirectory, 'index'));
43
51
  }
44
52
  export { resolveEffectAdapterEntryFile };
@@ -1,22 +1,24 @@
1
1
  import { logger } from "@modern-js/utils";
2
2
  import { createSafeFailureResponse } from "../../safe-failure.mjs";
3
- async function createEffectAdapterRuntimeErrorResponse(api, error, c) {
3
+ function createEffectAdapterRuntimeErrorResponse(api, error, c) {
4
4
  try {
5
5
  const serverConfig = api.getServerConfig();
6
6
  const onErrorHandler = serverConfig?.onError;
7
- if (onErrorHandler) {
7
+ if (void 0 !== onErrorHandler) {
8
8
  const onErrorContext = ensureJsonContext(c);
9
- const result = await onErrorHandler(error instanceof Error ? error : new Error(String(error)), onErrorContext);
10
- if (result instanceof Response) return result;
11
- } else logger.error(error);
9
+ return Promise.resolve(onErrorHandler(error instanceof Error ? error : new Error(String(error)), onErrorContext)).then((result)=>result instanceof Response ? result : createSafeFailureResponse(error)).catch((configError)=>{
10
+ logger.error(`Error in serverConfig.onError handler: ${String(configError)}`);
11
+ return createSafeFailureResponse(error);
12
+ });
13
+ }
14
+ logger.error(error instanceof Error ? error : new Error(String(error)));
12
15
  } catch (configError) {
13
16
  logger.error(`Error in serverConfig.onError handler: ${configError}`);
14
17
  }
15
- return createSafeFailureResponse(error);
18
+ return Promise.resolve(createSafeFailureResponse(error));
16
19
  }
17
20
  function ensureJsonContext(c) {
18
- const maybeJsonContext = c;
19
- if ('function' == typeof maybeJsonContext.json) return c;
21
+ if ('json' in c && 'function' == typeof c.json) return c;
20
22
  const headers = {
21
23
  'content-type': 'application/json; charset=utf-8'
22
24
  };
@@ -27,7 +29,7 @@ function ensureJsonContext(c) {
27
29
  headers: extraHeaders
28
30
  } : statusOrInit;
29
31
  const responseHeaders = new Headers(headers);
30
- if (responseInit.headers) new Headers(responseInit.headers).forEach((value, key)=>{
32
+ if (void 0 !== responseInit.headers) new Headers(responseInit.headers).forEach((value, key)=>{
31
33
  responseHeaders.set(key, value);
32
34
  });
33
35
  return new Response(JSON.stringify(data), {
@@ -1,6 +1,6 @@
1
1
  import { fs, isProd, logger } from "@modern-js/utils";
2
2
  import path from "path";
3
- import { loadEffectSourceModule } from "../../utils/effectSourceLoader.mjs";
3
+ import { loadEffectBuiltModule, loadEffectSourceModule } from "../../utils/effectSourceLoader.mjs";
4
4
  import { before } from "./adapter/constants.mjs";
5
5
  import { resolveEffectAdapterCrossProjectPolicy } from "./adapter/cross-project-policy.mjs";
6
6
  import { resolveEffectAdapterEntryFile } from "./adapter/entry.mjs";
@@ -29,10 +29,10 @@ class EffectAdapter {
29
29
  let mod;
30
30
  try {
31
31
  const { appDirectory } = this.api.getServerContext();
32
- mod = await loadEffectSourceModule({
32
+ mod = await (isProd() ? loadEffectBuiltModule(entryFile) : loadEffectSourceModule({
33
33
  resourcePath: entryFile,
34
34
  appDir: appDirectory || path.dirname(entryFile)
35
- });
35
+ }));
36
36
  } catch (error) {
37
37
  logger.error(`[BFF][Effect] Failed to load Effect entry: ${entryFile}\n${String(error)}`);
38
38
  this.handler = null;
@@ -2,27 +2,36 @@ import { BACKEND_FEDERATION_EFFECT_EXPOSE } from "@modern-js/utils/universal";
2
2
  import { formatBackendFederationIdentityIssues, validateExpectedBackendFederationIdentity } from "./identity.mjs";
3
3
  import { createBackendFederationRuntime } from "./runtime.mjs";
4
4
  import { isRecord, normalizeExpose } from "./utils.mjs";
5
+ import * as __rspack_external_effect_Effect_41060786 from "effect/Effect";
6
+ import * as __rspack_external_effect_Logger_1691a01b from "effect/Logger";
5
7
  const LEGACY_LOAD_WARNING = '[BFF][Effect] loadBackendFederatedEffectApi was called without an expected delivery-unit identity (expected.unitId + expected.buildMarker). Identity-less public backend loads are deprecated (ADR-0019/MV-G23) and will be rejected in a future major.';
6
- async function loadBackendFederatedEffectApi(options) {
8
+ const legacyWarningLogger = __rspack_external_effect_Logger_1691a01b.withLeveledConsole(__rspack_external_effect_Logger_1691a01b.formatLogFmt);
9
+ function warnLegacyLoad() {
10
+ __rspack_external_effect_Effect_41060786.runSync(__rspack_external_effect_Effect_41060786.provideService(__rspack_external_effect_Effect_41060786.logWarning(LEGACY_LOAD_WARNING), __rspack_external_effect_Logger_1691a01b.CurrentLoggers, new Set([
11
+ legacyWarningLogger
12
+ ])));
13
+ }
14
+ function loadBackendFederatedEffectApi(options) {
7
15
  const remoteName = options.remote?.name ?? options.remoteName;
8
- if (!remoteName) throw new Error('[BFF][Effect] Missing backend federation remote name.');
9
- if (void 0 === options.expected) console.warn(LEGACY_LOAD_WARNING);
16
+ if (void 0 === remoteName || 0 === remoteName.length) return Promise.reject(new Error('[BFF][Effect] Missing backend federation remote name.'));
17
+ if (void 0 === options.expected) warnLegacyLoad();
10
18
  const runtime = options.runtime ?? createBackendFederationRuntime(options);
11
19
  const expose = options.expose ?? options.remote?.expose ?? BACKEND_FEDERATION_EFFECT_EXPOSE;
12
20
  const remoteRequest = `${remoteName}/${normalizeExpose(expose)}`;
13
- const loaded = await runtime.loadRemote(remoteRequest);
14
- if (!isRecord(loaded)) throw new Error(`[BFF][Effect] Backend federation expose ${remoteRequest} must load an object module.`);
15
- const backendContract = loaded.backendFederationContract;
16
- if (!isRecord(backendContract) || true !== backendContract.strictEffectApproach) throw new Error(`[BFF][Effect] Backend federation expose ${remoteRequest} must expose strict Effect metadata (strictEffectApproach: true).`);
17
- if ('effect' !== backendContract.runtimeFramework) throw new Error(`[BFF][Effect] Backend federation expose ${remoteRequest} must expose strict Effect metadata (runtimeFramework: "effect").`);
18
- 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}.`);
19
- if (!('runtime' in loaded)) throw new Error(`[BFF][Effect] Backend federation expose ${remoteRequest} must expose api and runtime.`);
20
- if (void 0 !== options.expected) {
21
- const issues = validateExpectedBackendFederationIdentity(loaded, options.expected, {
22
- allowMissingIdentityMetadata: true === options.allowMissingIdentityMetadata
23
- });
24
- if (issues.length > 0) throw new Error(`[BFF][Effect] Backend federation expose ${remoteRequest} delivery-unit identity mismatch: ${formatBackendFederationIdentityIssues(issues)}.`);
25
- }
26
- return loaded;
21
+ return runtime.loadRemote(remoteRequest).then((loaded)=>{
22
+ if (!isRecord(loaded)) throw new Error(`[BFF][Effect] Backend federation expose ${remoteRequest} must load an object module.`);
23
+ const backendContract = loaded.backendFederationContract;
24
+ if (!isRecord(backendContract) || true !== backendContract.strictEffectApproach) throw new Error(`[BFF][Effect] Backend federation expose ${remoteRequest} must expose strict Effect metadata (strictEffectApproach: true).`);
25
+ if ('effect' !== backendContract.runtimeFramework) throw new Error(`[BFF][Effect] Backend federation expose ${remoteRequest} must expose strict Effect metadata (runtimeFramework: "effect").`);
26
+ 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}.`);
27
+ if (!('runtime' in loaded)) throw new Error(`[BFF][Effect] Backend federation expose ${remoteRequest} must expose api and runtime.`);
28
+ if (void 0 !== options.expected) {
29
+ const issues = validateExpectedBackendFederationIdentity(loaded, options.expected, {
30
+ allowMissingIdentityMetadata: true === options.allowMissingIdentityMetadata
31
+ });
32
+ if (issues.length > 0) throw new Error(`[BFF][Effect] Backend federation expose ${remoteRequest} delivery-unit identity mismatch: ${formatBackendFederationIdentityIssues(issues)}.`);
33
+ }
34
+ return loaded;
35
+ });
27
36
  }
28
37
  export { loadBackendFederatedEffectApi };
@@ -1,13 +1,23 @@
1
1
  function createBackendFederationLoadEntryPlugin(options) {
2
2
  return {
3
3
  name: 'modernjs-backend-federation-load-entry',
4
- async loadEntry ({ remoteInfo }) {
5
- return options.resolveEntry({
4
+ loadEntry ({ remoteInfo }) {
5
+ return Promise.resolve(options.resolveEntry({
6
6
  name: remoteInfo.name,
7
7
  entry: remoteInfo.entry,
8
8
  type: remoteInfo.type,
9
9
  entryGlobalName: remoteInfo.entryGlobalName,
10
10
  shareScope: remoteInfo.shareScope
11
+ })).then((entry)=>{
12
+ if (void 0 === entry) return;
13
+ return {
14
+ get (id) {
15
+ return ()=>Promise.resolve(entry.get(id)).then((factory)=>factory());
16
+ },
17
+ init (...args) {
18
+ return entry.init?.(...args);
19
+ }
20
+ };
11
21
  });
12
22
  }
13
23
  };
@@ -2,10 +2,11 @@ function collectRemotes(options) {
2
2
  const remotes = [
3
3
  ...options.remotes ?? []
4
4
  ];
5
- if (options.remote) {
6
- const existingIndex = remotes.findIndex((remote)=>remote.name === options.remote?.name);
7
- if (existingIndex >= 0) remotes[existingIndex] = options.remote;
8
- else remotes.push(options.remote);
5
+ const configuredRemote = options.remote;
6
+ if (void 0 !== configuredRemote) {
7
+ const existingIndex = remotes.findIndex((remote)=>remote.name === configuredRemote.name);
8
+ if (existingIndex >= 0) remotes[existingIndex] = configuredRemote;
9
+ else remotes.push(configuredRemote);
9
10
  }
10
11
  return remotes;
11
12
  }
@@ -1,26 +1,90 @@
1
+ import { ModuleFederation } from "@module-federation/runtime";
1
2
  import { loadBackendFederationExpose } from "./entry.mjs";
3
+ import { createBackendFederationLoadEntryPlugin } from "./plugin.mjs";
2
4
  import { collectRemotes } from "./remotes.mjs";
3
- import { normalizeExpose } from "./utils.mjs";
4
- function exposeForRemoteRequest(expose) {
5
- return `./${normalizeExpose(expose)}`;
5
+ function _check_private_redeclaration(obj, privateCollection) {
6
+ if (privateCollection.has(obj)) throw new TypeError("Cannot initialize the same private elements twice on an object");
6
7
  }
8
+ function _class_apply_descriptor_get(receiver, descriptor) {
9
+ if (descriptor.get) return descriptor.get.call(receiver);
10
+ return descriptor.value;
11
+ }
12
+ function _class_apply_descriptor_set(receiver, descriptor, value) {
13
+ if (descriptor.set) descriptor.set.call(receiver, value);
14
+ else {
15
+ if (!descriptor.writable) throw new TypeError("attempted to set read only private field");
16
+ descriptor.value = value;
17
+ }
18
+ }
19
+ function _class_extract_field_descriptor(receiver, privateMap, action) {
20
+ if (!privateMap.has(receiver)) throw new TypeError("attempted to " + action + " private field on non-instance");
21
+ return privateMap.get(receiver);
22
+ }
23
+ function _class_private_field_get(receiver, privateMap) {
24
+ var descriptor = _class_extract_field_descriptor(receiver, privateMap, "get");
25
+ return _class_apply_descriptor_get(receiver, descriptor);
26
+ }
27
+ function _class_private_field_init(obj, privateMap, value) {
28
+ _check_private_redeclaration(obj, privateMap);
29
+ privateMap.set(obj, value);
30
+ }
31
+ function _class_private_field_set(receiver, privateMap, value) {
32
+ var descriptor = _class_extract_field_descriptor(receiver, privateMap, "set");
33
+ _class_apply_descriptor_set(receiver, descriptor, value);
34
+ return value;
35
+ }
36
+ let nextRuntimeId = 0;
7
37
  function parseRemoteRequest(request) {
8
38
  const [remoteName, ...exposeParts] = request.split('/');
9
- if (!remoteName || 0 === exposeParts.length) throw new Error(`[BFF][Effect] Invalid backend federation request ${request}.`);
10
- return {
11
- remoteName,
12
- expose: exposeForRemoteRequest(exposeParts.join('/'))
13
- };
39
+ if (void 0 === remoteName || 0 === remoteName.length || 0 === exposeParts.length) return;
40
+ return remoteName;
41
+ }
42
+ function createLocalEntryPlugin(options, remotes) {
43
+ return createBackendFederationLoadEntryPlugin({
44
+ resolveEntry (resolvedRemote) {
45
+ const remote = remotes.find((candidate)=>candidate.name === resolvedRemote.name);
46
+ if (void 0 === remote) return;
47
+ return {
48
+ get (expose) {
49
+ return ()=>loadBackendFederationExpose(remote, expose, options);
50
+ }
51
+ };
52
+ }
53
+ });
54
+ }
55
+ function isolateRegisteredRemoteEntries(remotes) {
56
+ const runtimeId = nextRuntimeId;
57
+ nextRuntimeId += 1;
58
+ return remotes.map((remote)=>({
59
+ ...remote,
60
+ entry: `${remote.entry}#modernjs-backend-runtime-${runtimeId}`
61
+ }));
62
+ }
63
+ var _remoteNames = /*#__PURE__*/ new WeakMap();
64
+ class BackendFederationRuntime extends ModuleFederation {
65
+ loadRemote(request, loadOptions) {
66
+ const remoteName = parseRemoteRequest(request);
67
+ if (void 0 === remoteName) return Promise.reject(new Error(`[BFF][Effect] Invalid backend federation request ${request}.`));
68
+ if (!_class_private_field_get(this, _remoteNames).has(remoteName)) return Promise.reject(new Error(`[BFF][Effect] Missing backend federation remote ${remoteName}.`));
69
+ return super.loadRemote(request, loadOptions);
70
+ }
71
+ constructor(options, remotes){
72
+ super({
73
+ name: options.hostName,
74
+ remotes: isolateRegisteredRemoteEntries(remotes),
75
+ shared: {},
76
+ plugins: [
77
+ createLocalEntryPlugin(options, remotes)
78
+ ]
79
+ }), _class_private_field_init(this, _remoteNames, {
80
+ writable: true,
81
+ value: void 0
82
+ });
83
+ _class_private_field_set(this, _remoteNames, new Set(remotes.map((remote)=>remote.name)));
84
+ }
14
85
  }
15
86
  function createBackendFederationRuntime(options) {
16
87
  const remotes = collectRemotes(options);
17
- return {
18
- async loadRemote (request) {
19
- const { remoteName, expose } = parseRemoteRequest(request);
20
- const remote = remotes.find((candidate)=>candidate.name === remoteName);
21
- if (!remote) throw new Error(`[BFF][Effect] Missing backend federation remote ${remoteName}.`);
22
- return loadBackendFederationExpose(remote, expose, options);
23
- }
24
- };
88
+ return new BackendFederationRuntime(options, remotes);
25
89
  }
26
90
  export { createBackendFederationRuntime };
@@ -0,0 +1,38 @@
1
+ import { dispatchEffectBffRequestWithContext } from "./dispatch.mjs";
2
+ import { runWithEffectContext, useEffectContext, useOperationContext } from "./edge-context.mjs";
3
+ import { resolveEffectBffModuleHandler } from "./module.mjs";
4
+ export * from "./handler.mjs";
5
+ async function dispatchEffectBffRequest(handler, request, options = {}) {
6
+ return dispatchEffectBffRequestWithContext(handler, request, {
7
+ ...options,
8
+ runWithEffectContext: runWithEffectContext
9
+ });
10
+ }
11
+ async function createEffectBffEdgeDispatcher(options) {
12
+ const loaded = await resolveEffectBffModuleHandler(options.module, {
13
+ openapi: options.openapi,
14
+ dataPlatform: options.dataPlatform,
15
+ onWarning: options.onWarning
16
+ });
17
+ if (!loaded) throw new Error('[BFF][Effect] Invalid Effect edge module. Export defineEffectBff(...) or a { api, layer } HttpApi module.');
18
+ return {
19
+ dispatch: (request, dispatchOptions = {})=>dispatchEffectBffRequest(loaded.handler, request, {
20
+ ...dispatchOptions,
21
+ prefix: options.prefix,
22
+ onError: options.onError
23
+ }),
24
+ dispose: async ()=>{
25
+ await loaded.dispose?.();
26
+ }
27
+ };
28
+ }
29
+ const createEffectBffTestHandler = createEffectBffEdgeHandler;
30
+ async function createEffectBffEdgeHandler(options) {
31
+ const dispatcher = await createEffectBffEdgeDispatcher(options);
32
+ return {
33
+ handler: dispatcher.dispatch,
34
+ dispose: dispatcher.dispose
35
+ };
36
+ }
37
+ export { createEffectOperationContext } from "./operation-context.mjs";
38
+ export { createEffectBffEdgeDispatcher, createEffectBffEdgeHandler, createEffectBffTestHandler, dispatchEffectBffRequest, runWithEffectContext, useEffectContext, useOperationContext };
@@ -1,39 +1,2 @@
1
- import { dispatchEffectBffRequestWithContext } from "./dispatch.mjs";
2
- import { runWithEffectContext, useEffectContext, useOperationContext } from "./edge-context.mjs";
3
- import { resolveEffectBffModuleHandler } from "./module.mjs";
4
- export * from "./handler.mjs";
5
- async function dispatchEffectBffRequest(handler, request, options = {}) {
6
- return dispatchEffectBffRequestWithContext(handler, request, {
7
- ...options,
8
- runWithEffectContext: runWithEffectContext
9
- });
10
- }
11
- async function createEffectBffEdgeDispatcher(options) {
12
- const loaded = await resolveEffectBffModuleHandler(options.module, {
13
- openapi: options.openapi,
14
- dataPlatform: options.dataPlatform,
15
- onWarning: options.onWarning
16
- });
17
- if (!loaded) throw new Error('[BFF][Effect] Invalid Effect edge module. Export defineEffectBff(...) or a { api, layer } HttpApi module.');
18
- return {
19
- dispatch: (request, dispatchOptions = {})=>dispatchEffectBffRequest(loaded.handler, request, {
20
- ...dispatchOptions,
21
- prefix: options.prefix,
22
- onError: options.onError
23
- }),
24
- dispose: async ()=>{
25
- await loaded.dispose?.();
26
- }
27
- };
28
- }
29
- const createEffectBffTestHandler = createEffectBffEdgeHandler;
30
- async function createEffectBffEdgeHandler(options) {
31
- const dispatcher = await createEffectBffEdgeDispatcher(options);
32
- return {
33
- handler: dispatcher.dispatch,
34
- dispose: dispatcher.dispose
35
- };
36
- }
1
+ export * from "./edge-dispatcher.mjs";
37
2
  export { BACKEND_FEDERATION_CONTRACT_VERSION, BACKEND_FEDERATION_EFFECT_EXPOSE, BACKEND_FEDERATION_MANIFEST_FILE, BACKEND_FEDERATION_NODE_ADAPTER_VERSION, createBackendFederationLoadEntryPlugin, createBackendFederationRuntime, loadBackendFederatedEffectApi, validateExpectedBackendFederationIdentity } from "./backend-federation.mjs";
38
- export { createEffectOperationContext } from "./operation-context.mjs";
39
- export { createEffectBffEdgeDispatcher, createEffectBffEdgeHandler, createEffectBffTestHandler, dispatchEffectBffRequest, runWithEffectContext, useEffectContext, useOperationContext };
@@ -1,5 +1,10 @@
1
1
  import { createOperationContractHash } from "@modern-js/bff-core";
2
2
  import { classifyEffectBffEntryModule, isValidatorAwareHandlerFactory } from "./entry-shape.mjs";
3
+ function resolveEffectEndpointName(endpoint) {
4
+ if ('string' == typeof endpoint.identifier && endpoint.identifier) return endpoint.identifier;
5
+ if ('string' == typeof endpoint.name && endpoint.name) return endpoint.name;
6
+ return '';
7
+ }
3
8
  function ensureLeadingSlash(pathname) {
4
9
  return pathname.startsWith('/') ? pathname : `/${pathname}`;
5
10
  }
@@ -28,7 +33,7 @@ function collectEffectEndpoints(reflect, api, prefix) {
28
33
  endpoints.push({
29
34
  apiId,
30
35
  groupName: String(group.identifier),
31
- endpointName: String(endpoint.name),
36
+ endpointName: resolveEffectEndpointName(endpoint),
32
37
  method: String(endpoint.method).toUpperCase(),
33
38
  routePath: getEffectRoutePath(prefix, String(endpoint.path))
34
39
  });
@@ -16,6 +16,7 @@ function defineEffectBff(definition) {
16
16
  openapi: options?.openapi,
17
17
  rpc: mergedRpcOptions,
18
18
  dataPlatform: mergeDataPlatformOptions(definition.dataPlatform, options?.dataPlatform),
19
+ interceptRequest: definition.interceptRequest,
19
20
  validateRequest: options?.validateRequest
20
21
  });
21
22
  };
@@ -12,23 +12,30 @@ function createHttpApiHandler(options) {
12
12
  const openApiLayer = createOpenApiLayer(options.api, options.openapi);
13
13
  const mergedLayer = openApiLayer ? __rspack_external_effect_Layer_69c46453.mergeAll(apiLayer, openApiLayer) : apiLayer;
14
14
  const httpApiHandler = HttpRouter.toWebHandler(mergedLayer);
15
- const withDataPlatformValidation = async (request, context)=>{
16
- const policyDenial = options.validateRequest?.(request);
17
- if (policyDenial) return policyDenial;
15
+ const dispatchHttpApiRequest = async (request, context)=>{
18
16
  const preparedRequest = await prepareJsonRequestBody(request);
19
17
  if (preparedRequest instanceof Response) return preparedRequest;
20
18
  const validationError = validateDataPlatformRequestEnvelope(preparedRequest, options.dataPlatform);
21
19
  if (validationError) return validationError;
22
20
  return httpApiHandler.handler(preparedRequest, toEffectServiceContext(context));
23
21
  };
22
+ const withRequestPolicy = async (request, context)=>{
23
+ const policyDenial = options.validateRequest?.(request);
24
+ if (policyDenial) return policyDenial;
25
+ const next = ()=>dispatchHttpApiRequest(request, context);
26
+ return options.interceptRequest ? options.interceptRequest({
27
+ request,
28
+ next
29
+ }) : next();
30
+ };
24
31
  const batchHandler = createDataPlatformBatchRequestHandler({
25
32
  dataPlatform: options.dataPlatform,
26
- handleItem: withDataPlatformValidation
33
+ handleItem: withRequestPolicy
27
34
  });
28
35
  const handleHttpApiRequest = async (request, context)=>{
29
36
  const pathname = getRequestPathname(request);
30
37
  if (batchHandler.enabled && pathname === batchHandler.path) return batchHandler.handle(request, context);
31
- return withDataPlatformValidation(request, context);
38
+ return withRequestPolicy(request, context);
32
39
  };
33
40
  if (!options.rpc) return {
34
41
  handler: handleHttpApiRequest,
@@ -43,7 +50,11 @@ function createHttpApiHandler(options) {
43
50
  if (isRpcRequest(request, rpcPath)) {
44
51
  const policyDenial = options.validateRequest?.(request);
45
52
  if (policyDenial) return policyDenial;
46
- return rpcHandler.handler(request, toEffectServiceContext(context));
53
+ const next = ()=>rpcHandler.handler(request, toEffectServiceContext(context));
54
+ return options.interceptRequest ? options.interceptRequest({
55
+ request,
56
+ next
57
+ }) : next();
47
58
  }
48
59
  return handleHttpApiRequest(request, context);
49
60
  },
@@ -1,5 +1,4 @@
1
- import { Hono, run } from "@modern-js/server-core";
2
- import { isProd, logger } from "@modern-js/utils";
1
+ import { logger } from "@modern-js/utils";
3
2
  import createHonoRoutes from "../../utils/createHonoRoutes.mjs";
4
3
  import { checkCrossProjectPolicyResponse, resolveAdapterCrossProjectPolicy } from "../../utils/crossProjectServerPolicy.mjs";
5
4
  import { createSafeFailureResponse } from "../safe-failure.mjs";
@@ -8,17 +7,9 @@ const before = [
8
7
  'custom-server-middleware',
9
8
  'render'
10
9
  ];
11
- const kParentHonoVars = Symbol.for('modernjs.hono.parentVars');
12
10
  class HonoAdapter {
13
- wrapInArray(handler) {
14
- if (Array.isArray(handler)) return handler;
15
- return [
16
- handler
17
- ];
18
- }
19
11
  constructor(api){
20
12
  this.apiMiddleware = [];
21
- this.apiServer = null;
22
13
  this.isHono = true;
23
14
  this.prefix = '/api';
24
15
  this.setHandlers = async ()=>{
@@ -29,7 +20,7 @@ class HonoAdapter {
29
20
  name: 'hono-bff-api',
30
21
  path,
31
22
  method,
32
- handler,
23
+ handler: this.withErrorBoundary(handler),
33
24
  order: 'post',
34
25
  before: before
35
26
  }));
@@ -50,85 +41,47 @@ class HonoAdapter {
50
41
  });
51
42
  }
52
43
  };
53
- this.registerApiRoutes = async ()=>{
54
- if (!this.isHono) return;
55
- this.apiServer = new Hono();
56
- this.apiServer.use('*', run);
57
- this.apiServer.use('*', async (c, next)=>{
58
- const nodeReq = c.env?.node?.req;
59
- const parentVars = nodeReq?.[kParentHonoVars];
60
- if (parentVars && 'object' == typeof parentVars) {
61
- delete nodeReq[kParentHonoVars];
62
- for (const [key, value] of Object.entries(parentVars))if (void 0 === c.get(key)) c.set(key, value);
63
- }
64
- await next();
65
- });
66
- this.apiMiddleware.forEach(({ path = '*', method = 'all', handler })=>{
67
- const handlers = this.wrapInArray(handler);
68
- if (0 === handlers.length) return;
69
- const firstHandler = handlers[0];
70
- const restHandlers = handlers.slice(1);
71
- const m = method;
72
- const server = this.apiServer;
73
- if (!server) return;
74
- const register = server[m];
75
- register.call(server, path, firstHandler, ...restHandlers);
76
- });
77
- this.apiServer.onError(async (err, c)=>{
78
- try {
79
- const serverConfig = this.api.getServerConfig();
80
- const onErrorHandler = serverConfig?.onError;
81
- if (onErrorHandler) {
82
- const result = await onErrorHandler(err, c);
83
- if (result instanceof Response) return result;
84
- } else logger.error(err);
85
- } catch (configError) {
86
- logger.error(`Error in serverConfig.onError handler: ${configError}`);
87
- }
88
- return createSafeFailureResponse(err);
89
- });
90
- };
91
- this.registerMiddleware = async (options)=>{
92
- const { prefix } = options;
44
+ this.registerMiddleware = async (options = {})=>{
93
45
  const { bffRuntimeFramework } = this.api.getServerContext();
94
46
  if ('hono' !== bffRuntimeFramework) {
95
47
  this.isHono = false;
96
48
  return;
97
49
  }
50
+ const { prefix } = options;
98
51
  this.prefix = prefix || this.prefix;
99
52
  const { middlewares: globalMiddlewares } = this.api.getServerContext();
100
53
  await this.setHandlers();
101
- if (isProd()) globalMiddlewares.push(...this.apiMiddleware);
102
- else {
103
- await this.registerApiRoutes();
104
- const dynamicApiMiddleware = {
105
- name: 'dynamic-bff-handler',
106
- path: `${prefix}/*`,
107
- method: 'all',
108
- order: 'post',
109
- before: before,
110
- handler: async (c, next)=>{
111
- if (this.apiServer) {
112
- const nodeReq = c.env?.node?.req;
113
- if (nodeReq) {
114
- const parentVars = c?.var;
115
- nodeReq[kParentHonoVars] = parentVars && 'object' == typeof parentVars ? {
116
- ...parentVars
117
- } : parentVars;
118
- }
119
- const response = await this.apiServer.fetch(c.req.raw, c.env);
120
- if (404 !== response.status) return new Response(response.body, response);
121
- }
122
- await next();
123
- }
124
- };
125
- globalMiddlewares.push(dynamicApiMiddleware);
54
+ globalMiddlewares.push(...this.apiMiddleware);
55
+ };
56
+ this.handleRouteError = async (error, c)=>{
57
+ try {
58
+ const onErrorHandler = this.api.getServerConfig()?.onError;
59
+ if (onErrorHandler) {
60
+ const result = await onErrorHandler(error, c);
61
+ if (result instanceof Response) return result;
62
+ } else logger.error(error);
63
+ } catch (configError) {
64
+ logger.error(`Error in serverConfig.onError handler: ${configError}`);
126
65
  }
66
+ return createSafeFailureResponse(error);
127
67
  };
128
- this.onApiHandlersUpdated = async ()=>{
129
- if (!this.isHono) return;
130
- await this.setHandlers();
131
- await this.registerApiRoutes();
68
+ this.withErrorBoundary = (handler)=>{
69
+ const handlers = Array.isArray(handler) ? handler : [
70
+ handler
71
+ ];
72
+ const [firstHandler, ...restHandlers] = handlers;
73
+ if (!firstHandler) return handler;
74
+ const boundary = async (c, next)=>{
75
+ try {
76
+ return await firstHandler(c, next);
77
+ } catch (error) {
78
+ return this.handleRouteError(error, c);
79
+ }
80
+ };
81
+ return Array.isArray(handler) ? [
82
+ boundary,
83
+ ...restHandlers
84
+ ] : boundary;
132
85
  };
133
86
  this.api = api;
134
87
  }