@bleedingdev/modern-js-plugin-bff 3.5.0-ultramodern.99 → 3.8.1-ultramodern.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) 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/handler/definition.js +1 -0
  17. package/dist/cjs/runtime/effect/handler/http.js +17 -6
  18. package/dist/cjs/utils/crossProjectApiPlugin.js +21 -2
  19. package/dist/cjs/utils/effect-client-generator/generator.js +17 -16
  20. package/dist/cjs/utils/effect-client-generator/paths.js +4 -15
  21. package/dist/cjs/utils/effect-client-generator/rendering.js +8 -19
  22. package/dist/cjs/utils/effect-client-generator/runtime.js +15 -32
  23. package/dist/cjs/utils/effectSourceLoader.js +70 -10
  24. package/dist/cjs/utils/pluginGenerator.js +4 -4
  25. package/dist/esm/cli/compress.mjs +1 -1
  26. package/dist/esm/cli/generator.mjs +108 -98
  27. package/dist/esm/cli/serverGlobalVars.mjs +44 -48
  28. package/dist/esm/cli.mjs +2 -0
  29. package/dist/esm/loader.mjs +6 -5
  30. package/dist/esm/runtime/effect/adapter/cross-project-policy.mjs +17 -12
  31. package/dist/esm/runtime/effect/adapter/entry.mjs +13 -5
  32. package/dist/esm/runtime/effect/adapter/error-response.mjs +11 -9
  33. package/dist/esm/runtime/effect/adapter.mjs +3 -3
  34. package/dist/esm/runtime/effect/backend-federation/load.mjs +26 -17
  35. package/dist/esm/runtime/effect/backend-federation/plugin.mjs +12 -2
  36. package/dist/esm/runtime/effect/backend-federation/remotes.mjs +5 -4
  37. package/dist/esm/runtime/effect/backend-federation/runtime.mjs +80 -16
  38. package/dist/esm/runtime/effect/edge-dispatcher.mjs +38 -0
  39. package/dist/esm/runtime/effect/edge.mjs +1 -38
  40. package/dist/esm/runtime/effect/handler/definition.mjs +1 -0
  41. package/dist/esm/runtime/effect/handler/http.mjs +17 -6
  42. package/dist/esm/utils/crossProjectApiPlugin.mjs +16 -3
  43. package/dist/esm/utils/effect-client-generator/generator.mjs +17 -16
  44. package/dist/esm/utils/effect-client-generator/paths.mjs +5 -6
  45. package/dist/esm/utils/effect-client-generator/rendering.mjs +9 -10
  46. package/dist/esm/utils/effect-client-generator/runtime.mjs +16 -23
  47. package/dist/esm/utils/effectSourceLoader.mjs +69 -12
  48. package/dist/esm/utils/pluginGenerator.mjs +5 -5
  49. package/dist/esm-node/cli/compress.mjs +1 -1
  50. package/dist/esm-node/cli/generator.mjs +108 -98
  51. package/dist/esm-node/cli/serverGlobalVars.mjs +44 -48
  52. package/dist/esm-node/cli.mjs +2 -0
  53. package/dist/esm-node/loader.mjs +6 -5
  54. package/dist/esm-node/runtime/effect/adapter/cross-project-policy.mjs +17 -12
  55. package/dist/esm-node/runtime/effect/adapter/entry.mjs +13 -5
  56. package/dist/esm-node/runtime/effect/adapter/error-response.mjs +11 -9
  57. package/dist/esm-node/runtime/effect/adapter.mjs +3 -3
  58. package/dist/esm-node/runtime/effect/backend-federation/load.mjs +26 -17
  59. package/dist/esm-node/runtime/effect/backend-federation/plugin.mjs +12 -2
  60. package/dist/esm-node/runtime/effect/backend-federation/remotes.mjs +5 -4
  61. package/dist/esm-node/runtime/effect/backend-federation/runtime.mjs +80 -16
  62. package/dist/esm-node/runtime/effect/edge-dispatcher.mjs +39 -0
  63. package/dist/esm-node/runtime/effect/edge.mjs +1 -38
  64. package/dist/esm-node/runtime/effect/handler/definition.mjs +1 -0
  65. package/dist/esm-node/runtime/effect/handler/http.mjs +17 -6
  66. package/dist/esm-node/utils/crossProjectApiPlugin.mjs +16 -3
  67. package/dist/esm-node/utils/effect-client-generator/generator.mjs +17 -16
  68. package/dist/esm-node/utils/effect-client-generator/paths.mjs +5 -6
  69. package/dist/esm-node/utils/effect-client-generator/rendering.mjs +9 -10
  70. package/dist/esm-node/utils/effect-client-generator/runtime.mjs +16 -23
  71. package/dist/esm-node/utils/effectSourceLoader.mjs +69 -12
  72. package/dist/esm-node/utils/pluginGenerator.mjs +5 -5
  73. package/dist/types/cli/generator.d.ts +2 -2
  74. package/dist/types/cli/serverGlobalVars.d.ts +4 -2
  75. package/dist/types/cli.d.ts +1 -0
  76. package/dist/types/runtime/effect/adapter/cross-project-policy.d.ts +1 -1
  77. package/dist/types/runtime/effect/backend-federation/runtime.d.ts +1 -1
  78. package/dist/types/runtime/effect/edge-dispatcher.d.ts +26 -0
  79. package/dist/types/runtime/effect/edge.d.ts +1 -26
  80. package/dist/types/runtime/effect/handler/definition.d.ts +2 -12
  81. package/dist/types/runtime/effect/handler/http.d.ts +2 -1
  82. package/dist/types/runtime/effect/handler/types.d.ts +5 -0
  83. package/dist/types/utils/crossProjectApiPlugin.d.ts +2 -0
  84. package/dist/types/utils/effectSourceLoader.d.ts +10 -0
  85. package/dist/types/utils/pluginGenerator.d.ts +3 -1
  86. package/package.json +34 -18
@@ -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 };
@@ -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
  },
@@ -5,6 +5,8 @@ const API_DIR = '{apiDirectory}';
5
5
  const LAMBDA_DIR = '{lambdaDirectory}';
6
6
  const DIST_DIR = '{distDirectory}';
7
7
  const RUNTIME_FRAMEWORK = '{runtimeFramework}';
8
+ const EFFECT_ENTRY = '{effectEntry}';
9
+ const OPERATION_CONTRACTS_JSON = '{operationContracts}';
8
10
  const NODE_MODULES = 'node_modules';
9
11
  const crossProjectApiPlugin = ()=>({
10
12
  name: '@modern-js/plugin-independent-bff',
@@ -16,7 +18,8 @@ const crossProjectApiPlugin = ()=>({
16
18
  const { appDirectory: originAppDirectory } = api.getAppContext();
17
19
  const sdkPath = path.join(originAppDirectory, NODE_MODULES, PACKAGE_NAME);
18
20
  const sdkDistPath = path.join(sdkPath, DIST_DIR);
19
- const apiDirectory = path.join(sdkDistPath, API_DIR);
21
+ const effectEntry = EFFECT_ENTRY ? path.join(sdkDistPath, EFFECT_ENTRY) : void 0;
22
+ const apiDirectory = effectEntry ? path.dirname(effectEntry) : path.join(sdkDistPath, API_DIR);
20
23
  const lambdaDirectory = path.resolve(sdkDistPath, LAMBDA_DIR);
21
24
  api.updateAppContext({
22
25
  apiDirectory,
@@ -31,10 +34,16 @@ const crossProjectApiPlugin = ()=>({
31
34
  }
32
35
  const configuredRuntimeFramework = config?.bff?.runtimeFramework;
33
36
  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}".`);
37
+ resolvedConfig.bff ??= {};
34
38
  resolvedConfig.bff.prefix = PREFIX;
35
39
  resolvedConfig.bff.runtimeFramework = RUNTIME_FRAMEWORK;
40
+ if (EFFECT_ENTRY) resolvedConfig.bff.effect = {
41
+ ...resolvedConfig.bff.effect,
42
+ entry: effectEntry
43
+ };
36
44
  resolvedConfig.bff.isCrossProjectServer = true;
37
45
  resolvedConfig.bff.requestId = resolvedConfig.bff.requestId || config?.bff?.requestId || PACKAGE_NAME || 'default';
46
+ const generatedOperationContracts = "{operationContracts}" === OPERATION_CONTRACTS_JSON ? {} : JSON.parse(OPERATION_CONTRACTS_JSON);
38
47
  resolvedConfig.bff.crossProjectPolicy = {
39
48
  ...resolvedConfig.bff.crossProjectPolicy || {},
40
49
  enabled: resolvedConfig.bff.crossProjectPolicy?.enabled ?? true,
@@ -43,7 +52,11 @@ const crossProjectApiPlugin = ()=>({
43
52
  requireOperationContextDetails: resolvedConfig.bff.crossProjectPolicy?.requireOperationContextDetails ?? true,
44
53
  requireOperationSchemaHash: resolvedConfig.bff.crossProjectPolicy?.requireOperationSchemaHash ?? true,
45
54
  requireOperationVersion: resolvedConfig.bff.crossProjectPolicy?.requireOperationVersion ?? true,
46
- allowUnknownOperations: resolvedConfig.bff.crossProjectPolicy?.allowUnknownOperations ?? false
55
+ allowUnknownOperations: resolvedConfig.bff.crossProjectPolicy?.allowUnknownOperations ?? false,
56
+ expectedOperationContracts: {
57
+ ...resolvedConfig.bff.crossProjectPolicy?.expectedOperationContracts || {},
58
+ ...generatedOperationContracts
59
+ }
47
60
  };
48
61
  return resolvedConfig;
49
62
  });
@@ -51,4 +64,4 @@ const crossProjectApiPlugin = ()=>({
51
64
  });
52
65
  const utils_crossProjectApiPlugin = crossProjectApiPlugin;
53
66
  export default utils_crossProjectApiPlugin;
54
- export { API_DIR, DIST_DIR, LAMBDA_DIR, PACKAGE_NAME, PREFIX, RUNTIME_FRAMEWORK, crossProjectApiPlugin };
67
+ export { API_DIR, DIST_DIR, EFFECT_ENTRY, LAMBDA_DIR, OPERATION_CONTRACTS_JSON, PACKAGE_NAME, PREFIX, RUNTIME_FRAMEWORK, crossProjectApiPlugin };
@@ -2,26 +2,27 @@ import { logger } from "@modern-js/utils";
2
2
  import { collectEffectEndpoints } from "../../runtime/effect/endpoint-contracts.mjs";
3
3
  import { renderEffectClientCode, renderEffectClientDeclaration } from "./rendering.mjs";
4
4
  import { getHttpApiRuntime, loadEffectApi } from "./runtime.mjs";
5
- async function generateEffectClient(options) {
6
- const api = await loadEffectApi({
5
+ function generateEffectClient(options) {
6
+ return loadEffectApi({
7
7
  appDir: options.appDir,
8
8
  resourcePath: options.resourcePath,
9
9
  onDependency: options.onDependency
10
+ }).then((api)=>{
11
+ if (null === api) {
12
+ logger.warn(`[BFF][Effect] Failed to generate client for ${options.resourcePath}: unable to resolve exported HttpApi.`);
13
+ return null;
14
+ }
15
+ return getHttpApiRuntime().then((httpApiRuntime)=>{
16
+ const endpoints = collectEffectEndpoints(httpApiRuntime.reflect, api, options.prefix);
17
+ return {
18
+ code: renderEffectClientCode(endpoints, options),
19
+ declaration: renderEffectClientDeclaration(endpoints),
20
+ endpoints
21
+ };
22
+ });
10
23
  });
11
- if (!api) {
12
- logger.warn(`[BFF][Effect] Failed to generate client for ${options.resourcePath}: unable to resolve exported HttpApi.`);
13
- return null;
14
- }
15
- const httpApiRuntime = await getHttpApiRuntime();
16
- const endpoints = collectEffectEndpoints(httpApiRuntime.reflect, api, options.prefix);
17
- return {
18
- code: renderEffectClientCode(endpoints, options),
19
- declaration: renderEffectClientDeclaration(endpoints),
20
- endpoints
21
- };
22
24
  }
23
- async function generateEffectClientCode(options) {
24
- const artifacts = await generateEffectClient(options);
25
- return artifacts ? artifacts.code : null;
25
+ function generateEffectClientCode(options) {
26
+ return generateEffectClient(options).then((artifacts)=>null === artifacts ? null : artifacts.code);
26
27
  }
27
28
  export { generateEffectClient, generateEffectClientCode };
@@ -1,5 +1,4 @@
1
- import { findExists, fs } from "@modern-js/utils";
2
- import path from "path";
1
+ import { findExists, fs, upath } from "@modern-js/utils";
3
2
  const JS_OR_TS_EXTS = [
4
3
  '.js',
5
4
  '.jsx',
@@ -13,13 +12,13 @@ const JS_OR_TS_EXTS = [
13
12
  function resolveEffectEntryFile(options) {
14
13
  const { appDir, apiDir, effectEntry } = options;
15
14
  const resolveEntry = (entryWithoutExt)=>{
16
- if (path.extname(entryWithoutExt)) return fs.existsSync(entryWithoutExt) ? entryWithoutExt : void 0;
15
+ if ('' !== upath.extname(entryWithoutExt)) return fs.existsSync(entryWithoutExt) ? entryWithoutExt : void 0;
17
16
  return findExists(JS_OR_TS_EXTS.map((ext)=>`${entryWithoutExt}${ext}`));
18
17
  };
19
- if (effectEntry) {
20
- const entryWithoutExt = path.isAbsolute(effectEntry) ? effectEntry : path.resolve(appDir, effectEntry);
18
+ if (void 0 !== effectEntry && '' !== effectEntry) {
19
+ const entryWithoutExt = upath.isAbsolute(effectEntry) ? effectEntry : upath.resolve(appDir, effectEntry);
21
20
  return resolveEntry(entryWithoutExt);
22
21
  }
23
- return resolveEntry(path.resolve(apiDir, 'index'));
22
+ return resolveEntry(upath.resolve(apiDir, 'index'));
24
23
  }
25
24
  export { resolveEffectEntryFile };
@@ -1,6 +1,5 @@
1
1
  import { deriveOperationVersion } from "@modern-js/bff-core";
2
- import { fs } from "@modern-js/utils";
3
- import path from "path";
2
+ import { fs, upath } from "@modern-js/utils";
4
3
  import { createEffectEndpointContractHash, ensureLeadingSlash, normalizeEffectPrefix } from "../../runtime/effect/endpoint-contracts.mjs";
5
4
  const DEFAULT_REQUEST_CREATOR = '@modern-js/plugin-bff/client';
6
5
  const EFFECT_CLIENT_RUNTIME_IMPORT = '@modern-js/plugin-bff/effect-client-runtime';
@@ -13,17 +12,17 @@ function isAbsoluteUrl(value) {
13
12
  }
14
13
  }
15
14
  function resolveBatchEndpoint(prefix, endpoint) {
16
- const value = endpoint || '/_data/batch';
15
+ const value = void 0 === endpoint || '' === endpoint ? '/_data/batch' : endpoint;
17
16
  if (isAbsoluteUrl(value)) return value;
18
17
  const normalizedPrefix = normalizeEffectPrefix(prefix);
19
18
  const normalizedEndpoint = ensureLeadingSlash(value);
20
- if (!normalizedPrefix) return normalizedEndpoint;
19
+ if ('' === normalizedPrefix) return normalizedEndpoint;
21
20
  if (normalizedEndpoint === normalizedPrefix || normalizedEndpoint.startsWith(`${normalizedPrefix}/`)) return normalizedEndpoint;
22
21
  return `${normalizedPrefix}${'/' === normalizedEndpoint ? '' : normalizedEndpoint}`;
23
22
  }
24
23
  function getPackageInfo(appDir) {
25
24
  try {
26
- const packageJsonPath = path.resolve(appDir, './package.json');
25
+ const packageJsonPath = upath.resolve(appDir, './package.json');
27
26
  const packageJson = fs.readJSONSync(packageJsonPath);
28
27
  return {
29
28
  name: packageJson.name,
@@ -38,9 +37,9 @@ function renderEffectClientCode(endpoints, options) {
38
37
  const httpMethodDecider = options.httpMethodDecider || 'functionName';
39
38
  const packageInfo = getPackageInfo(options.appDir);
40
39
  const packageName = packageInfo.name;
41
- const dataPlatformAppNamespace = packageName || 'unknown-app';
42
- const requestId = 'bundle' === options.target ? packageName || process.env.npm_package_name : void 0;
43
- const normalizedRequestId = requestId || 'default';
40
+ const dataPlatformAppNamespace = void 0 === packageName || '' === packageName ? 'unknown-app' : packageName;
41
+ const requestId = 'bundle' === options.target ? void 0 !== packageName && '' !== packageName ? packageName : void 0 : void 0;
42
+ const normalizedRequestId = void 0 === requestId || '' === requestId ? 'default' : requestId;
44
43
  const operationVersion = deriveOperationVersion(packageInfo.version);
45
44
  const batchConfig = options.dataPlatformBatch;
46
45
  const batchEndpoint = resolveBatchEndpoint(options.prefix, batchConfig?.endpoint);
@@ -57,7 +56,7 @@ function renderEffectClientCode(endpoints, options) {
57
56
  };
58
57
  const config = {
59
58
  appNamespace: dataPlatformAppNamespace,
60
- ...requestId ? {
59
+ ...void 0 !== requestId && '' !== requestId ? {
61
60
  requestId
62
61
  } : {},
63
62
  port: options.port,
@@ -71,7 +70,7 @@ function renderEffectClientCode(endpoints, options) {
71
70
  maxBatchSize: batchConfig?.maxBatchSize ?? 16,
72
71
  maxBatchBytes: batchConfig?.maxBatchBytes ?? 65536,
73
72
  requestTimeoutMs: batchConfig?.requestTimeoutMs ?? 10000,
74
- allowedMethods: batchConfig?.allowedMethods && batchConfig.allowedMethods.length > 0 ? batchConfig.allowedMethods : [
73
+ allowedMethods: batchConfig?.allowedMethods !== void 0 && batchConfig.allowedMethods.length > 0 ? batchConfig.allowedMethods : [
75
74
  'GET'
76
75
  ]
77
76
  }
@@ -1,34 +1,27 @@
1
- import { compatibleRequire } from "@modern-js/utils";
2
- import path from "path";
1
+ import { compatibleRequire, upath } from "@modern-js/utils";
3
2
  import { extractHttpApiFromModule } from "../../runtime/effect/endpoint-contracts.mjs";
4
3
  import { loadEffectSourceModule } from "../effectSourceLoader.mjs";
5
4
  let httpApiRuntimePromise;
6
5
  function isRecord(value) {
7
6
  return 'object' == typeof value && null !== value;
8
7
  }
9
- async function getHttpApiRuntime() {
10
- if (!httpApiRuntimePromise) httpApiRuntimePromise = (async ()=>{
11
- let mod;
12
- try {
13
- mod = await compatibleRequire('effect/unstable/httpapi', false);
14
- } catch (error) {
15
- const message = error instanceof Error ? error.message : String(error);
16
- if (!message.includes("Cannot find module 'effect/unstable/httpapi'")) throw error;
17
- const effectPackageJson = require.resolve('effect/package.json');
18
- const effectHttpApiRuntimePath = path.join(path.dirname(effectPackageJson), 'dist', 'unstable', 'httpapi', 'index.js');
19
- mod = await compatibleRequire(effectHttpApiRuntimePath, false);
20
- }
21
- if (isRecord(mod) && isRecord(mod.HttpApi)) {
22
- const maybeHttpApi = mod.HttpApi;
23
- if ('function' == typeof maybeHttpApi.isHttpApi && 'function' == typeof maybeHttpApi.reflect) return maybeHttpApi;
24
- }
8
+ function isHttpApiRuntime(value) {
9
+ return isRecord(value) && 'function' == typeof value.isHttpApi && 'function' == typeof value.reflect;
10
+ }
11
+ function getHttpApiRuntime() {
12
+ if (void 0 === httpApiRuntimePromise) httpApiRuntimePromise = compatibleRequire('effect/unstable/httpapi', false).catch((error)=>{
13
+ const message = error instanceof Error ? error.message : String(error);
14
+ if (!message.includes("Cannot find module 'effect/unstable/httpapi'")) throw error;
15
+ const effectPackageJson = require.resolve('effect/package.json');
16
+ const effectHttpApiRuntimePath = upath.join(upath.dirname(effectPackageJson), 'dist', 'unstable', 'httpapi', 'index.js');
17
+ return compatibleRequire(effectHttpApiRuntimePath, false);
18
+ }).then((mod)=>{
19
+ if (isRecord(mod) && isHttpApiRuntime(mod.HttpApi)) return mod.HttpApi;
25
20
  throw new Error('[BFF][Effect] Unable to resolve HttpApi runtime from effect/unstable/httpapi.');
26
- })();
21
+ });
27
22
  return httpApiRuntimePromise;
28
23
  }
29
- async function loadEffectApi(options) {
30
- const httpApiRuntime = await getHttpApiRuntime();
31
- const mod = await loadEffectSourceModule(options);
32
- return extractHttpApiFromModule(mod, httpApiRuntime.isHttpApi);
24
+ function loadEffectApi(options) {
25
+ return getHttpApiRuntime().then((httpApiRuntime)=>loadEffectSourceModule(options).then((mod)=>extractHttpApiFromModule(mod, httpApiRuntime.isHttpApi)));
33
26
  }
34
27
  export { getHttpApiRuntime, loadEffectApi };
@@ -1,6 +1,6 @@
1
1
  import node_crypto from "node:crypto";
2
2
  import node_fs from "node:fs";
3
- import { builtinModules } from "node:module";
3
+ import { builtinModules, createRequire } from "node:module";
4
4
  import node_path from "node:path";
5
5
  import { pathToFileURL } from "node:url";
6
6
  import { build, transform } from "esbuild";
@@ -75,10 +75,29 @@ const nodeBuiltins = new Set([
75
75
  ...builtinModules.map((name)=>`node:${name}`)
76
76
  ]);
77
77
  const dependencyResolutionMarker = 'modern-js-effect-dependency-resolution';
78
+ async function isEsmOnlyFile(filename) {
79
+ const extension = node_path.extname(filename).toLowerCase();
80
+ if ('.mjs' === extension || '.mts' === extension) return true;
81
+ if ('.cjs' === extension || '.cts' === extension) return false;
82
+ let directory = node_path.dirname(filename);
83
+ while(true){
84
+ const packagePath = node_path.join(directory, 'package.json');
85
+ try {
86
+ const packageJson = JSON.parse(await node_fs.promises.readFile(packagePath, 'utf8'));
87
+ return 'module' === packageJson.type;
88
+ } catch (error) {
89
+ if (!error || 'object' != typeof error || !('code' in error) || 'ENOENT' !== error.code) throw error;
90
+ }
91
+ const parent = node_path.dirname(directory);
92
+ if (parent === directory) return false;
93
+ directory = parent;
94
+ }
95
+ }
78
96
  function isBareSpecifier(specifier) {
79
97
  return !specifier.startsWith('.') && !specifier.startsWith('/') && !node_path.isAbsolute(specifier);
80
98
  }
81
- function externalizeInstalledDependencies() {
99
+ function externalizeInstalledDependencies(options) {
100
+ const runtimeRequire = createRequire(node_path.join(options.runtimeResolveDir, '__modern_effect_entry.cjs'));
82
101
  return {
83
102
  name: 'modern-js-effect-installed-dependencies',
84
103
  setup (buildApi) {
@@ -108,19 +127,46 @@ function externalizeInstalledDependencies() {
108
127
  path: args.path
109
128
  };
110
129
  const realPath = await node_fs.promises.realpath(resolution.path).catch(()=>resolution.path);
111
- if (realPath.includes(`${node_path.sep}node_modules${node_path.sep}`)) return {
112
- external: true,
113
- path: args.path
114
- };
130
+ if (realPath.includes(`${node_path.sep}node_modules${node_path.sep}`)) {
131
+ const runtimeResolution = await buildApi.resolve(args.path, {
132
+ importer: '',
133
+ kind: args.kind,
134
+ namespace: 'file',
135
+ pluginData: {
136
+ [dependencyResolutionMarker]: true
137
+ },
138
+ resolveDir: options.runtimeResolveDir
139
+ });
140
+ if (0 === runtimeResolution.errors.length && runtimeResolution.path) {
141
+ if ('cjs' === options.format) {
142
+ let requiredPath;
143
+ try {
144
+ requiredPath = runtimeRequire.resolve(args.path);
145
+ } catch {
146
+ requiredPath = runtimeResolution.path;
147
+ }
148
+ if (await isEsmOnlyFile(requiredPath)) return {
149
+ namespace: resolution.namespace,
150
+ path: resolution.path,
151
+ pluginData: resolution.pluginData,
152
+ sideEffects: resolution.sideEffects,
153
+ suffix: resolution.suffix,
154
+ warnings: resolution.warnings
155
+ };
156
+ }
157
+ return {
158
+ external: true,
159
+ path: args.path
160
+ };
161
+ }
162
+ }
115
163
  return {
116
164
  namespace: resolution.namespace,
117
165
  path: resolution.path,
118
166
  pluginData: resolution.pluginData,
119
167
  sideEffects: resolution.sideEffects,
120
168
  suffix: resolution.suffix,
121
- warnings: resolution.warnings,
122
- watchDirs: resolution.watchDirs,
123
- watchFiles: resolution.watchFiles
169
+ warnings: resolution.warnings
124
170
  };
125
171
  });
126
172
  }
@@ -139,7 +185,10 @@ async function bundleEffectEntryForNode(options) {
139
185
  outfile: options.entryPath,
140
186
  platform: 'node',
141
187
  plugins: [
142
- externalizeInstalledDependencies(),
188
+ externalizeInstalledDependencies({
189
+ format: options.format,
190
+ runtimeResolveDir: options.appDir
191
+ }),
143
192
  preserveSourceModuleSemantics()
144
193
  ],
145
194
  target: 'node20'
@@ -155,6 +204,11 @@ function normalizeImportedModule(module) {
155
204
  if (1 === Object.keys(namespace).length && null !== commonJsModule && 'object' == typeof commonJsModule && true === commonJsModule.__esModule) return commonJsModule;
156
205
  return module;
157
206
  }
207
+ async function loadEffectBuiltModule(resourcePath) {
208
+ const canonicalPath = await node_fs.promises.realpath(node_path.resolve(resourcePath));
209
+ if (!await isEsmOnlyFile(canonicalPath)) return createRequire(canonicalPath)(canonicalPath);
210
+ return normalizeImportedModule(await dynamicImport(pathToFileURL(canonicalPath).href));
211
+ }
158
212
  async function loadEffectSourceModule(options) {
159
213
  const resourcePath = node_path.resolve(options.resourcePath);
160
214
  const appDir = node_path.resolve(options.appDir ?? node_path.dirname(resourcePath));
@@ -181,7 +235,10 @@ async function loadEffectSourceModule(options) {
181
235
  outfile: outputPath,
182
236
  platform: 'node',
183
237
  plugins: [
184
- externalizeInstalledDependencies(),
238
+ externalizeInstalledDependencies({
239
+ format: 'esm',
240
+ runtimeResolveDir: appDir
241
+ }),
185
242
  preserveSourceModuleSemantics()
186
243
  ],
187
244
  sourcemap: 'inline',
@@ -196,4 +253,4 @@ async function loadEffectSourceModule(options) {
196
253
  const revision = node_crypto.createHash('sha256').update(output).digest('hex');
197
254
  return normalizeImportedModule(await dynamicImport(`${pathToFileURL(outputPath).href}?v=${revision}`));
198
255
  }
199
- export { bundleEffectEntryForNode, loadEffectSourceModule };
256
+ export { bundleEffectEntryForNode, loadEffectBuiltModule, loadEffectSourceModule };