@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
@@ -3,10 +3,11 @@ function collectRemotes(options) {
3
3
  const remotes = [
4
4
  ...options.remotes ?? []
5
5
  ];
6
- if (options.remote) {
7
- const existingIndex = remotes.findIndex((remote)=>remote.name === options.remote?.name);
8
- if (existingIndex >= 0) remotes[existingIndex] = options.remote;
9
- else remotes.push(options.remote);
6
+ const configuredRemote = options.remote;
7
+ if (void 0 !== configuredRemote) {
8
+ const existingIndex = remotes.findIndex((remote)=>remote.name === configuredRemote.name);
9
+ if (existingIndex >= 0) remotes[existingIndex] = configuredRemote;
10
+ else remotes.push(configuredRemote);
10
11
  }
11
12
  return remotes;
12
13
  }
@@ -1,27 +1,91 @@
1
1
  import "node:module";
2
+ import { ModuleFederation } from "@module-federation/runtime";
2
3
  import { loadBackendFederationExpose } from "./entry.mjs";
4
+ import { createBackendFederationLoadEntryPlugin } from "./plugin.mjs";
3
5
  import { collectRemotes } from "./remotes.mjs";
4
- import { normalizeExpose } from "./utils.mjs";
5
- function exposeForRemoteRequest(expose) {
6
- return `./${normalizeExpose(expose)}`;
6
+ function _check_private_redeclaration(obj, privateCollection) {
7
+ if (privateCollection.has(obj)) throw new TypeError("Cannot initialize the same private elements twice on an object");
7
8
  }
9
+ function _class_apply_descriptor_get(receiver, descriptor) {
10
+ if (descriptor.get) return descriptor.get.call(receiver);
11
+ return descriptor.value;
12
+ }
13
+ function _class_apply_descriptor_set(receiver, descriptor, value) {
14
+ if (descriptor.set) descriptor.set.call(receiver, value);
15
+ else {
16
+ if (!descriptor.writable) throw new TypeError("attempted to set read only private field");
17
+ descriptor.value = value;
18
+ }
19
+ }
20
+ function _class_extract_field_descriptor(receiver, privateMap, action) {
21
+ if (!privateMap.has(receiver)) throw new TypeError("attempted to " + action + " private field on non-instance");
22
+ return privateMap.get(receiver);
23
+ }
24
+ function _class_private_field_get(receiver, privateMap) {
25
+ var descriptor = _class_extract_field_descriptor(receiver, privateMap, "get");
26
+ return _class_apply_descriptor_get(receiver, descriptor);
27
+ }
28
+ function _class_private_field_init(obj, privateMap, value) {
29
+ _check_private_redeclaration(obj, privateMap);
30
+ privateMap.set(obj, value);
31
+ }
32
+ function _class_private_field_set(receiver, privateMap, value) {
33
+ var descriptor = _class_extract_field_descriptor(receiver, privateMap, "set");
34
+ _class_apply_descriptor_set(receiver, descriptor, value);
35
+ return value;
36
+ }
37
+ let nextRuntimeId = 0;
8
38
  function parseRemoteRequest(request) {
9
39
  const [remoteName, ...exposeParts] = request.split('/');
10
- if (!remoteName || 0 === exposeParts.length) throw new Error(`[BFF][Effect] Invalid backend federation request ${request}.`);
11
- return {
12
- remoteName,
13
- expose: exposeForRemoteRequest(exposeParts.join('/'))
14
- };
40
+ if (void 0 === remoteName || 0 === remoteName.length || 0 === exposeParts.length) return;
41
+ return remoteName;
42
+ }
43
+ function createLocalEntryPlugin(options, remotes) {
44
+ return createBackendFederationLoadEntryPlugin({
45
+ resolveEntry (resolvedRemote) {
46
+ const remote = remotes.find((candidate)=>candidate.name === resolvedRemote.name);
47
+ if (void 0 === remote) return;
48
+ return {
49
+ get (expose) {
50
+ return ()=>loadBackendFederationExpose(remote, expose, options);
51
+ }
52
+ };
53
+ }
54
+ });
55
+ }
56
+ function isolateRegisteredRemoteEntries(remotes) {
57
+ const runtimeId = nextRuntimeId;
58
+ nextRuntimeId += 1;
59
+ return remotes.map((remote)=>({
60
+ ...remote,
61
+ entry: `${remote.entry}#modernjs-backend-runtime-${runtimeId}`
62
+ }));
63
+ }
64
+ var _remoteNames = /*#__PURE__*/ new WeakMap();
65
+ class BackendFederationRuntime extends ModuleFederation {
66
+ loadRemote(request, loadOptions) {
67
+ const remoteName = parseRemoteRequest(request);
68
+ if (void 0 === remoteName) return Promise.reject(new Error(`[BFF][Effect] Invalid backend federation request ${request}.`));
69
+ if (!_class_private_field_get(this, _remoteNames).has(remoteName)) return Promise.reject(new Error(`[BFF][Effect] Missing backend federation remote ${remoteName}.`));
70
+ return super.loadRemote(request, loadOptions);
71
+ }
72
+ constructor(options, remotes){
73
+ super({
74
+ name: options.hostName,
75
+ remotes: isolateRegisteredRemoteEntries(remotes),
76
+ shared: {},
77
+ plugins: [
78
+ createLocalEntryPlugin(options, remotes)
79
+ ]
80
+ }), _class_private_field_init(this, _remoteNames, {
81
+ writable: true,
82
+ value: void 0
83
+ });
84
+ _class_private_field_set(this, _remoteNames, new Set(remotes.map((remote)=>remote.name)));
85
+ }
15
86
  }
16
87
  function createBackendFederationRuntime(options) {
17
88
  const remotes = collectRemotes(options);
18
- return {
19
- async loadRemote (request) {
20
- const { remoteName, expose } = parseRemoteRequest(request);
21
- const remote = remotes.find((candidate)=>candidate.name === remoteName);
22
- if (!remote) throw new Error(`[BFF][Effect] Missing backend federation remote ${remoteName}.`);
23
- return loadBackendFederationExpose(remote, expose, options);
24
- }
25
- };
89
+ return new BackendFederationRuntime(options, remotes);
26
90
  }
27
91
  export { createBackendFederationRuntime };
@@ -0,0 +1,39 @@
1
+ import "node:module";
2
+ import { dispatchEffectBffRequestWithContext } from "./dispatch.mjs";
3
+ import { runWithEffectContext, useEffectContext, useOperationContext } from "./edge-context.mjs";
4
+ import { resolveEffectBffModuleHandler } from "./module.mjs";
5
+ export * from "./handler.mjs";
6
+ async function dispatchEffectBffRequest(handler, request, options = {}) {
7
+ return dispatchEffectBffRequestWithContext(handler, request, {
8
+ ...options,
9
+ runWithEffectContext: runWithEffectContext
10
+ });
11
+ }
12
+ async function createEffectBffEdgeDispatcher(options) {
13
+ const loaded = await resolveEffectBffModuleHandler(options.module, {
14
+ openapi: options.openapi,
15
+ dataPlatform: options.dataPlatform,
16
+ onWarning: options.onWarning
17
+ });
18
+ if (!loaded) throw new Error('[BFF][Effect] Invalid Effect edge module. Export defineEffectBff(...) or a { api, layer } HttpApi module.');
19
+ return {
20
+ dispatch: (request, dispatchOptions = {})=>dispatchEffectBffRequest(loaded.handler, request, {
21
+ ...dispatchOptions,
22
+ prefix: options.prefix,
23
+ onError: options.onError
24
+ }),
25
+ dispose: async ()=>{
26
+ await loaded.dispose?.();
27
+ }
28
+ };
29
+ }
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 { createEffectOperationContext } from "./operation-context.mjs";
39
+ export { createEffectBffEdgeDispatcher, createEffectBffEdgeHandler, createEffectBffTestHandler, dispatchEffectBffRequest, runWithEffectContext, useEffectContext, useOperationContext };
@@ -1,40 +1,3 @@
1
1
  import "node:module";
2
- import { dispatchEffectBffRequestWithContext } from "./dispatch.mjs";
3
- import { runWithEffectContext, useEffectContext, useOperationContext } from "./edge-context.mjs";
4
- import { resolveEffectBffModuleHandler } from "./module.mjs";
5
- export * from "./handler.mjs";
6
- async function dispatchEffectBffRequest(handler, request, options = {}) {
7
- return dispatchEffectBffRequestWithContext(handler, request, {
8
- ...options,
9
- runWithEffectContext: runWithEffectContext
10
- });
11
- }
12
- async function createEffectBffEdgeDispatcher(options) {
13
- const loaded = await resolveEffectBffModuleHandler(options.module, {
14
- openapi: options.openapi,
15
- dataPlatform: options.dataPlatform,
16
- onWarning: options.onWarning
17
- });
18
- if (!loaded) throw new Error('[BFF][Effect] Invalid Effect edge module. Export defineEffectBff(...) or a { api, layer } HttpApi module.');
19
- return {
20
- dispatch: (request, dispatchOptions = {})=>dispatchEffectBffRequest(loaded.handler, request, {
21
- ...dispatchOptions,
22
- prefix: options.prefix,
23
- onError: options.onError
24
- }),
25
- dispose: async ()=>{
26
- await loaded.dispose?.();
27
- }
28
- };
29
- }
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
- }
2
+ export * from "./edge-dispatcher.mjs";
38
3
  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";
39
- export { createEffectOperationContext } from "./operation-context.mjs";
40
- export { createEffectBffEdgeDispatcher, createEffectBffEdgeHandler, createEffectBffTestHandler, dispatchEffectBffRequest, runWithEffectContext, useEffectContext, useOperationContext };
@@ -1,6 +1,11 @@
1
1
  import "node:module";
2
2
  import { createOperationContractHash } from "@modern-js/bff-core";
3
3
  import { classifyEffectBffEntryModule, isValidatorAwareHandlerFactory } from "./entry-shape.mjs";
4
+ function resolveEffectEndpointName(endpoint) {
5
+ if ('string' == typeof endpoint.identifier && endpoint.identifier) return endpoint.identifier;
6
+ if ('string' == typeof endpoint.name && endpoint.name) return endpoint.name;
7
+ return '';
8
+ }
4
9
  function ensureLeadingSlash(pathname) {
5
10
  return pathname.startsWith('/') ? pathname : `/${pathname}`;
6
11
  }
@@ -29,7 +34,7 @@ function collectEffectEndpoints(reflect, api, prefix) {
29
34
  endpoints.push({
30
35
  apiId,
31
36
  groupName: String(group.identifier),
32
- endpointName: String(endpoint.name),
37
+ endpointName: resolveEffectEndpointName(endpoint),
33
38
  method: String(endpoint.method).toUpperCase(),
34
39
  routePath: getEffectRoutePath(prefix, String(endpoint.path))
35
40
  });
@@ -17,6 +17,7 @@ function defineEffectBff(definition) {
17
17
  openapi: options?.openapi,
18
18
  rpc: mergedRpcOptions,
19
19
  dataPlatform: mergeDataPlatformOptions(definition.dataPlatform, options?.dataPlatform),
20
+ interceptRequest: definition.interceptRequest,
20
21
  validateRequest: options?.validateRequest
21
22
  });
22
23
  };
@@ -13,23 +13,30 @@ function createHttpApiHandler(options) {
13
13
  const openApiLayer = createOpenApiLayer(options.api, options.openapi);
14
14
  const mergedLayer = openApiLayer ? __rspack_external_effect_Layer_69c46453.mergeAll(apiLayer, openApiLayer) : apiLayer;
15
15
  const httpApiHandler = HttpRouter.toWebHandler(mergedLayer);
16
- const withDataPlatformValidation = async (request, context)=>{
17
- const policyDenial = options.validateRequest?.(request);
18
- if (policyDenial) return policyDenial;
16
+ const dispatchHttpApiRequest = async (request, context)=>{
19
17
  const preparedRequest = await prepareJsonRequestBody(request);
20
18
  if (preparedRequest instanceof Response) return preparedRequest;
21
19
  const validationError = validateDataPlatformRequestEnvelope(preparedRequest, options.dataPlatform);
22
20
  if (validationError) return validationError;
23
21
  return httpApiHandler.handler(preparedRequest, toEffectServiceContext(context));
24
22
  };
23
+ const withRequestPolicy = async (request, context)=>{
24
+ const policyDenial = options.validateRequest?.(request);
25
+ if (policyDenial) return policyDenial;
26
+ const next = ()=>dispatchHttpApiRequest(request, context);
27
+ return options.interceptRequest ? options.interceptRequest({
28
+ request,
29
+ next
30
+ }) : next();
31
+ };
25
32
  const batchHandler = createDataPlatformBatchRequestHandler({
26
33
  dataPlatform: options.dataPlatform,
27
- handleItem: withDataPlatformValidation
34
+ handleItem: withRequestPolicy
28
35
  });
29
36
  const handleHttpApiRequest = async (request, context)=>{
30
37
  const pathname = getRequestPathname(request);
31
38
  if (batchHandler.enabled && pathname === batchHandler.path) return batchHandler.handle(request, context);
32
- return withDataPlatformValidation(request, context);
39
+ return withRequestPolicy(request, context);
33
40
  };
34
41
  if (!options.rpc) return {
35
42
  handler: handleHttpApiRequest,
@@ -44,7 +51,11 @@ function createHttpApiHandler(options) {
44
51
  if (isRpcRequest(request, rpcPath)) {
45
52
  const policyDenial = options.validateRequest?.(request);
46
53
  if (policyDenial) return policyDenial;
47
- return rpcHandler.handler(request, toEffectServiceContext(context));
54
+ const next = ()=>rpcHandler.handler(request, toEffectServiceContext(context));
55
+ return options.interceptRequest ? options.interceptRequest({
56
+ request,
57
+ next
58
+ }) : next();
48
59
  }
49
60
  return handleHttpApiRequest(request, context);
50
61
  },
@@ -1,6 +1,5 @@
1
1
  import "node:module";
2
- import { Hono, run } from "@modern-js/server-core";
3
- import { isProd, logger } from "@modern-js/utils";
2
+ import { logger } from "@modern-js/utils";
4
3
  import createHonoRoutes from "../../utils/createHonoRoutes.mjs";
5
4
  import { checkCrossProjectPolicyResponse, resolveAdapterCrossProjectPolicy } from "../../utils/crossProjectServerPolicy.mjs";
6
5
  import { createSafeFailureResponse } from "../safe-failure.mjs";
@@ -9,17 +8,9 @@ const before = [
9
8
  'custom-server-middleware',
10
9
  'render'
11
10
  ];
12
- const kParentHonoVars = Symbol.for('modernjs.hono.parentVars');
13
11
  class HonoAdapter {
14
- wrapInArray(handler) {
15
- if (Array.isArray(handler)) return handler;
16
- return [
17
- handler
18
- ];
19
- }
20
12
  constructor(api){
21
13
  this.apiMiddleware = [];
22
- this.apiServer = null;
23
14
  this.isHono = true;
24
15
  this.prefix = '/api';
25
16
  this.setHandlers = async ()=>{
@@ -30,7 +21,7 @@ class HonoAdapter {
30
21
  name: 'hono-bff-api',
31
22
  path,
32
23
  method,
33
- handler,
24
+ handler: this.withErrorBoundary(handler),
34
25
  order: 'post',
35
26
  before: before
36
27
  }));
@@ -51,85 +42,47 @@ class HonoAdapter {
51
42
  });
52
43
  }
53
44
  };
54
- this.registerApiRoutes = async ()=>{
55
- if (!this.isHono) return;
56
- this.apiServer = new Hono();
57
- this.apiServer.use('*', run);
58
- this.apiServer.use('*', async (c, next)=>{
59
- const nodeReq = c.env?.node?.req;
60
- const parentVars = nodeReq?.[kParentHonoVars];
61
- if (parentVars && 'object' == typeof parentVars) {
62
- delete nodeReq[kParentHonoVars];
63
- for (const [key, value] of Object.entries(parentVars))if (void 0 === c.get(key)) c.set(key, value);
64
- }
65
- await next();
66
- });
67
- this.apiMiddleware.forEach(({ path = '*', method = 'all', handler })=>{
68
- const handlers = this.wrapInArray(handler);
69
- if (0 === handlers.length) return;
70
- const firstHandler = handlers[0];
71
- const restHandlers = handlers.slice(1);
72
- const m = method;
73
- const server = this.apiServer;
74
- if (!server) return;
75
- const register = server[m];
76
- register.call(server, path, firstHandler, ...restHandlers);
77
- });
78
- this.apiServer.onError(async (err, c)=>{
79
- try {
80
- const serverConfig = this.api.getServerConfig();
81
- const onErrorHandler = serverConfig?.onError;
82
- if (onErrorHandler) {
83
- const result = await onErrorHandler(err, c);
84
- if (result instanceof Response) return result;
85
- } else logger.error(err);
86
- } catch (configError) {
87
- logger.error(`Error in serverConfig.onError handler: ${configError}`);
88
- }
89
- return createSafeFailureResponse(err);
90
- });
91
- };
92
- this.registerMiddleware = async (options)=>{
93
- const { prefix } = options;
45
+ this.registerMiddleware = async (options = {})=>{
94
46
  const { bffRuntimeFramework } = this.api.getServerContext();
95
47
  if ('hono' !== bffRuntimeFramework) {
96
48
  this.isHono = false;
97
49
  return;
98
50
  }
51
+ const { prefix } = options;
99
52
  this.prefix = prefix || this.prefix;
100
53
  const { middlewares: globalMiddlewares } = this.api.getServerContext();
101
54
  await this.setHandlers();
102
- if (isProd()) globalMiddlewares.push(...this.apiMiddleware);
103
- else {
104
- await this.registerApiRoutes();
105
- const dynamicApiMiddleware = {
106
- name: 'dynamic-bff-handler',
107
- path: `${prefix}/*`,
108
- method: 'all',
109
- order: 'post',
110
- before: before,
111
- handler: async (c, next)=>{
112
- if (this.apiServer) {
113
- const nodeReq = c.env?.node?.req;
114
- if (nodeReq) {
115
- const parentVars = c?.var;
116
- nodeReq[kParentHonoVars] = parentVars && 'object' == typeof parentVars ? {
117
- ...parentVars
118
- } : parentVars;
119
- }
120
- const response = await this.apiServer.fetch(c.req.raw, c.env);
121
- if (404 !== response.status) return new Response(response.body, response);
122
- }
123
- await next();
124
- }
125
- };
126
- globalMiddlewares.push(dynamicApiMiddleware);
55
+ globalMiddlewares.push(...this.apiMiddleware);
56
+ };
57
+ this.handleRouteError = async (error, c)=>{
58
+ try {
59
+ const onErrorHandler = this.api.getServerConfig()?.onError;
60
+ if (onErrorHandler) {
61
+ const result = await onErrorHandler(error, c);
62
+ if (result instanceof Response) return result;
63
+ } else logger.error(error);
64
+ } catch (configError) {
65
+ logger.error(`Error in serverConfig.onError handler: ${configError}`);
127
66
  }
67
+ return createSafeFailureResponse(error);
128
68
  };
129
- this.onApiHandlersUpdated = async ()=>{
130
- if (!this.isHono) return;
131
- await this.setHandlers();
132
- await this.registerApiRoutes();
69
+ this.withErrorBoundary = (handler)=>{
70
+ const handlers = Array.isArray(handler) ? handler : [
71
+ handler
72
+ ];
73
+ const [firstHandler, ...restHandlers] = handlers;
74
+ if (!firstHandler) return handler;
75
+ const boundary = async (c, next)=>{
76
+ try {
77
+ return await firstHandler(c, next);
78
+ } catch (error) {
79
+ return this.handleRouteError(error, c);
80
+ }
81
+ };
82
+ return Array.isArray(handler) ? [
83
+ boundary,
84
+ ...restHandlers
85
+ ] : boundary;
133
86
  };
134
87
  this.api = api;
135
88
  }
@@ -2,15 +2,17 @@ import "node:module";
2
2
  import { ApiRouter } from "@modern-js/bff-core";
3
3
  import { API_DIR, isFunction, isWebOnly } from "@modern-js/utils";
4
4
  import path from "path";
5
- import { EffectAdapter } from "./runtime/effect/adapter.mjs";
6
5
  import { HonoAdapter } from "./runtime/hono/adapter.mjs";
7
- const RUNTIME_ADAPTER_FACTORIES = {
8
- hono: [
9
- (api)=>new HonoAdapter(api)
10
- ],
11
- effect: [
12
- (api)=>new EffectAdapter(api)
13
- ]
6
+ const RUNTIME_ADAPTER_LOADERS = {
7
+ hono: async (api)=>[
8
+ new HonoAdapter(api)
9
+ ],
10
+ effect: async (api)=>{
11
+ const { EffectAdapter } = await import("./runtime/effect/adapter.mjs");
12
+ return [
13
+ new EffectAdapter(api)
14
+ ];
15
+ }
14
16
  };
15
17
  const normalizePrefixList = (prefix)=>{
16
18
  if (Array.isArray(prefix)) return prefix.filter(Boolean);
@@ -23,9 +25,6 @@ function resolveRuntimeFramework(runtimeFramework) {
23
25
  return 'hono' === runtimeFramework ? 'hono' : 'effect';
24
26
  }
25
27
  class Storage {
26
- reset() {
27
- this.middlewares = [];
28
- }
29
28
  constructor(){
30
29
  this.middlewares = [];
31
30
  }
@@ -37,7 +36,6 @@ const server = ()=>({
37
36
  let apiRouter = null;
38
37
  const appContext = api.getServerContext();
39
38
  const runtimeFramework = resolveRuntimeFramework(appContext.bffRuntimeFramework);
40
- const runtimeAdapters = RUNTIME_ADAPTER_FACTORIES[runtimeFramework].map((createAdapter)=>createAdapter(api));
41
39
  api.onPrepare(async ()=>{
42
40
  const appContext = api.getServerContext();
43
41
  const { render } = appContext;
@@ -83,29 +81,20 @@ const server = ()=>({
83
81
  ]
84
82
  });
85
83
  }
84
+ const runtimeAdapters = await RUNTIME_ADAPTER_LOADERS[runtimeFramework](api);
86
85
  await Promise.all(runtimeAdapters.map((adapter)=>adapter.registerMiddleware({
87
86
  prefix,
88
87
  enableHandleWeb
89
88
  })));
90
89
  });
91
90
  api.onReset(async ({ event })=>{
92
- storage.reset();
93
- const appContext = api.getServerContext();
94
- const { middlewares } = storage;
95
- api.updateServerContext({
96
- ...appContext,
97
- apiMiddlewares: middlewares
98
- });
99
- if ('file-change' === event.type) {
100
- if ('hono' === runtimeFramework && apiRouter) {
101
- const apiHandlerInfos = await apiRouter.getApiHandlers();
102
- const appContext = api.getServerContext();
103
- api.updateServerContext({
104
- ...appContext,
105
- apiHandlerInfos
106
- });
107
- }
108
- await Promise.all(runtimeAdapters.map((adapter)=>adapter.onApiHandlersUpdated?.()));
91
+ if ('file-change' === event.type && apiRouter) {
92
+ const appContext = api.getServerContext();
93
+ const apiHandlerInfos = await apiRouter.getApiHandlers();
94
+ api.updateServerContext({
95
+ ...appContext,
96
+ apiHandlerInfos
97
+ });
109
98
  }
110
99
  });
111
100
  const prepareApiServer = async (input, next)=>{
@@ -70,6 +70,6 @@ async function setPackage(files, appDirectory, relativeDistPath) {
70
70
  }
71
71
  });
72
72
  mergePackageJson(packageJson, addFiles, typesVersions, exports, relativeDistPath);
73
- await fs.promises.writeFile(packagePath, JSON.stringify(packageJson, null, 2));
73
+ await fs.promises.writeFile(packagePath, `${JSON.stringify(packageJson, null, 2)}\n`);
74
74
  }
75
75
  export { setPackage, writeClientModuleBoundary };
@@ -6,6 +6,8 @@ const API_DIR = '{apiDirectory}';
6
6
  const LAMBDA_DIR = '{lambdaDirectory}';
7
7
  const DIST_DIR = '{distDirectory}';
8
8
  const RUNTIME_FRAMEWORK = '{runtimeFramework}';
9
+ const EFFECT_ENTRY = '{effectEntry}';
10
+ const OPERATION_CONTRACTS_JSON = '{operationContracts}';
9
11
  const NODE_MODULES = 'node_modules';
10
12
  const crossProjectApiPlugin = ()=>({
11
13
  name: '@modern-js/plugin-independent-bff',
@@ -17,7 +19,8 @@ const crossProjectApiPlugin = ()=>({
17
19
  const { appDirectory: originAppDirectory } = api.getAppContext();
18
20
  const sdkPath = path.join(originAppDirectory, NODE_MODULES, PACKAGE_NAME);
19
21
  const sdkDistPath = path.join(sdkPath, DIST_DIR);
20
- const apiDirectory = path.join(sdkDistPath, API_DIR);
22
+ const effectEntry = EFFECT_ENTRY ? path.join(sdkDistPath, EFFECT_ENTRY) : void 0;
23
+ const apiDirectory = effectEntry ? path.dirname(effectEntry) : path.join(sdkDistPath, API_DIR);
21
24
  const lambdaDirectory = path.resolve(sdkDistPath, LAMBDA_DIR);
22
25
  api.updateAppContext({
23
26
  apiDirectory,
@@ -32,10 +35,16 @@ const crossProjectApiPlugin = ()=>({
32
35
  }
33
36
  const configuredRuntimeFramework = config?.bff?.runtimeFramework;
34
37
  if (configuredRuntimeFramework && configuredRuntimeFramework !== RUNTIME_FRAMEWORK) throw new Error(`[${PACKAGE_NAME}] Runtime framework mismatch for cross-project BFF. Detected "${configuredRuntimeFramework}", but producer SDK requires "${RUNTIME_FRAMEWORK}".`);
38
+ resolvedConfig.bff ??= {};
35
39
  resolvedConfig.bff.prefix = PREFIX;
36
40
  resolvedConfig.bff.runtimeFramework = RUNTIME_FRAMEWORK;
41
+ if (EFFECT_ENTRY) resolvedConfig.bff.effect = {
42
+ ...resolvedConfig.bff.effect,
43
+ entry: effectEntry
44
+ };
37
45
  resolvedConfig.bff.isCrossProjectServer = true;
38
46
  resolvedConfig.bff.requestId = resolvedConfig.bff.requestId || config?.bff?.requestId || PACKAGE_NAME || 'default';
47
+ const generatedOperationContracts = "{operationContracts}" === OPERATION_CONTRACTS_JSON ? {} : JSON.parse(OPERATION_CONTRACTS_JSON);
39
48
  resolvedConfig.bff.crossProjectPolicy = {
40
49
  ...resolvedConfig.bff.crossProjectPolicy || {},
41
50
  enabled: resolvedConfig.bff.crossProjectPolicy?.enabled ?? true,
@@ -44,7 +53,11 @@ const crossProjectApiPlugin = ()=>({
44
53
  requireOperationContextDetails: resolvedConfig.bff.crossProjectPolicy?.requireOperationContextDetails ?? true,
45
54
  requireOperationSchemaHash: resolvedConfig.bff.crossProjectPolicy?.requireOperationSchemaHash ?? true,
46
55
  requireOperationVersion: resolvedConfig.bff.crossProjectPolicy?.requireOperationVersion ?? true,
47
- allowUnknownOperations: resolvedConfig.bff.crossProjectPolicy?.allowUnknownOperations ?? false
56
+ allowUnknownOperations: resolvedConfig.bff.crossProjectPolicy?.allowUnknownOperations ?? false,
57
+ expectedOperationContracts: {
58
+ ...resolvedConfig.bff.crossProjectPolicy?.expectedOperationContracts || {},
59
+ ...generatedOperationContracts
60
+ }
48
61
  };
49
62
  return resolvedConfig;
50
63
  });
@@ -52,4 +65,4 @@ const crossProjectApiPlugin = ()=>({
52
65
  });
53
66
  const utils_crossProjectApiPlugin = crossProjectApiPlugin;
54
67
  export default utils_crossProjectApiPlugin;
55
- export { API_DIR, DIST_DIR, LAMBDA_DIR, PACKAGE_NAME, PREFIX, RUNTIME_FRAMEWORK, crossProjectApiPlugin };
68
+ export { API_DIR, DIST_DIR, EFFECT_ENTRY, LAMBDA_DIR, OPERATION_CONTRACTS_JSON, PACKAGE_NAME, PREFIX, RUNTIME_FRAMEWORK, crossProjectApiPlugin };
@@ -3,26 +3,27 @@ import { logger } from "@modern-js/utils";
3
3
  import { collectEffectEndpoints } from "../../runtime/effect/endpoint-contracts.mjs";
4
4
  import { renderEffectClientCode, renderEffectClientDeclaration } from "./rendering.mjs";
5
5
  import { getHttpApiRuntime, loadEffectApi } from "./runtime.mjs";
6
- async function generateEffectClient(options) {
7
- const api = await loadEffectApi({
6
+ function generateEffectClient(options) {
7
+ return loadEffectApi({
8
8
  appDir: options.appDir,
9
9
  resourcePath: options.resourcePath,
10
10
  onDependency: options.onDependency
11
+ }).then((api)=>{
12
+ if (null === api) {
13
+ logger.warn(`[BFF][Effect] Failed to generate client for ${options.resourcePath}: unable to resolve exported HttpApi.`);
14
+ return null;
15
+ }
16
+ return getHttpApiRuntime().then((httpApiRuntime)=>{
17
+ const endpoints = collectEffectEndpoints(httpApiRuntime.reflect, api, options.prefix);
18
+ return {
19
+ code: renderEffectClientCode(endpoints, options),
20
+ declaration: renderEffectClientDeclaration(endpoints),
21
+ endpoints
22
+ };
23
+ });
11
24
  });
12
- if (!api) {
13
- logger.warn(`[BFF][Effect] Failed to generate client for ${options.resourcePath}: unable to resolve exported HttpApi.`);
14
- return null;
15
- }
16
- const httpApiRuntime = await getHttpApiRuntime();
17
- const endpoints = collectEffectEndpoints(httpApiRuntime.reflect, api, options.prefix);
18
- return {
19
- code: renderEffectClientCode(endpoints, options),
20
- declaration: renderEffectClientDeclaration(endpoints),
21
- endpoints
22
- };
23
25
  }
24
- async function generateEffectClientCode(options) {
25
- const artifacts = await generateEffectClient(options);
26
- return artifacts ? artifacts.code : null;
26
+ function generateEffectClientCode(options) {
27
+ return generateEffectClient(options).then((artifacts)=>null === artifacts ? null : artifacts.code);
27
28
  }
28
29
  export { generateEffectClient, generateEffectClientCode };
@@ -1,6 +1,5 @@
1
1
  import "node:module";
2
- import { findExists, fs } from "@modern-js/utils";
3
- import path from "path";
2
+ import { findExists, fs, upath } from "@modern-js/utils";
4
3
  const JS_OR_TS_EXTS = [
5
4
  '.js',
6
5
  '.jsx',
@@ -14,13 +13,13 @@ const JS_OR_TS_EXTS = [
14
13
  function resolveEffectEntryFile(options) {
15
14
  const { appDir, apiDir, effectEntry } = options;
16
15
  const resolveEntry = (entryWithoutExt)=>{
17
- if (path.extname(entryWithoutExt)) return fs.existsSync(entryWithoutExt) ? entryWithoutExt : void 0;
16
+ if ('' !== upath.extname(entryWithoutExt)) return fs.existsSync(entryWithoutExt) ? entryWithoutExt : void 0;
18
17
  return findExists(JS_OR_TS_EXTS.map((ext)=>`${entryWithoutExt}${ext}`));
19
18
  };
20
- if (effectEntry) {
21
- const entryWithoutExt = path.isAbsolute(effectEntry) ? effectEntry : path.resolve(appDir, effectEntry);
19
+ if (void 0 !== effectEntry && '' !== effectEntry) {
20
+ const entryWithoutExt = upath.isAbsolute(effectEntry) ? effectEntry : upath.resolve(appDir, effectEntry);
22
21
  return resolveEntry(entryWithoutExt);
23
22
  }
24
- return resolveEntry(path.resolve(apiDir, 'index'));
23
+ return resolveEntry(upath.resolve(apiDir, 'index'));
25
24
  }
26
25
  export { resolveEffectEntryFile };