@bleedingdev/modern-js-plugin-bff 3.5.0-ultramodern.101 → 3.5.0-ultramodern.102

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 (34) hide show
  1. package/dist/cjs/cli/generator.js +12 -1
  2. package/dist/cjs/cli.js +6 -2
  3. package/dist/cjs/loader.js +5 -4
  4. package/dist/cjs/runtime/effect/adapter/entry.js +13 -5
  5. package/dist/cjs/runtime/effect/handler/definition.js +1 -0
  6. package/dist/cjs/runtime/effect/handler/http.js +17 -6
  7. package/dist/cjs/utils/crossProjectApiPlugin.js +21 -2
  8. package/dist/cjs/utils/effectSourceLoader.js +63 -7
  9. package/dist/cjs/utils/pluginGenerator.js +4 -4
  10. package/dist/esm/cli/generator.mjs +13 -2
  11. package/dist/esm/cli.mjs +2 -0
  12. package/dist/esm/loader.mjs +5 -4
  13. package/dist/esm/runtime/effect/adapter/entry.mjs +13 -5
  14. package/dist/esm/runtime/effect/handler/definition.mjs +1 -0
  15. package/dist/esm/runtime/effect/handler/http.mjs +17 -6
  16. package/dist/esm/utils/crossProjectApiPlugin.mjs +16 -3
  17. package/dist/esm/utils/effectSourceLoader.mjs +64 -8
  18. package/dist/esm/utils/pluginGenerator.mjs +5 -5
  19. package/dist/esm-node/cli/generator.mjs +13 -2
  20. package/dist/esm-node/cli.mjs +2 -0
  21. package/dist/esm-node/loader.mjs +5 -4
  22. package/dist/esm-node/runtime/effect/adapter/entry.mjs +13 -5
  23. package/dist/esm-node/runtime/effect/handler/definition.mjs +1 -0
  24. package/dist/esm-node/runtime/effect/handler/http.mjs +17 -6
  25. package/dist/esm-node/utils/crossProjectApiPlugin.mjs +16 -3
  26. package/dist/esm-node/utils/effectSourceLoader.mjs +64 -8
  27. package/dist/esm-node/utils/pluginGenerator.mjs +5 -5
  28. package/dist/types/cli.d.ts +1 -0
  29. package/dist/types/runtime/effect/handler/definition.d.ts +2 -12
  30. package/dist/types/runtime/effect/handler/http.d.ts +2 -1
  31. package/dist/types/runtime/effect/handler/types.d.ts +5 -0
  32. package/dist/types/utils/crossProjectApiPlugin.d.ts +2 -0
  33. package/dist/types/utils/pluginGenerator.d.ts +3 -1
  34. package/package.json +12 -12
@@ -130,16 +130,27 @@ const createBffGenerator = (api)=>{
130
130
  });
131
131
  const lambdaDir = apiRouter.getLambdaDir();
132
132
  const existLambda = apiRouter.isExistLambda();
133
+ const packageJson = utils_namespaceObject.fs.readJSONSync(external_path_default().resolve(appDirectory, 'package.json'));
134
+ const requestId = packageJson.name || process.env.npm_package_name;
135
+ const operationContracts = existLambda ? (0, bff_core_namespaceObject.buildOperationContractMap)({
136
+ handlers: await apiRouter.getApiHandlers(),
137
+ requestId,
138
+ operationVersion: (0, bff_core_namespaceObject.deriveOperationVersion)(packageJson.version)
139
+ }) : {};
133
140
  const runtime = bff?.runtimeCreateRequest || RUNTIME_CREATE_REQUEST;
134
141
  const relativeApiPath = external_path_default().relative(appDirectory, apiDirectory);
135
142
  const relativeLambdaPath = external_path_default().relative(appDirectory, lambdaDir);
143
+ const sourceEffectEntry = 'effect' === bffRuntimeFramework ? resolveEffectSourceEntry(appDirectory, apiDirectory, bff?.effect?.entry) : void 0;
144
+ const relativeEffectEntry = sourceEffectEntry ? external_path_default().relative(appDirectory, sourceEffectEntry).replace(/\.(?:[cm]?ts|tsx|jsx)$/u, '.js') : '';
136
145
  await pluginGenerator_js_default()({
137
146
  prefix,
138
147
  appDirectory,
139
148
  relativeDistPath,
140
149
  relativeApiPath,
141
150
  relativeLambdaPath,
142
- runtimeFramework: 'hono' === bffRuntimeFramework ? 'hono' : 'effect'
151
+ runtimeFramework: 'hono' === bffRuntimeFramework ? 'hono' : 'effect',
152
+ relativeEffectEntry,
153
+ operationContracts
143
154
  });
144
155
  await clientGenerator_js_default()({
145
156
  prefix,
package/dist/cjs/cli.js CHANGED
@@ -37,7 +37,8 @@ var __webpack_require__ = {};
37
37
  var __webpack_exports__ = {};
38
38
  __webpack_require__.r(__webpack_exports__);
39
39
  __webpack_require__.d(__webpack_exports__, {
40
- bffPlugin: ()=>bffPlugin
40
+ bffPlugin: ()=>bffPlugin,
41
+ default: ()=>cli
41
42
  });
42
43
  const bff_core_namespaceObject = require("@modern-js/bff-core");
43
44
  const utils_namespaceObject = require("@modern-js/utils");
@@ -170,9 +171,12 @@ const bffPlugin = ()=>({
170
171
  });
171
172
  }
172
173
  });
174
+ const cli = bffPlugin;
173
175
  exports.bffPlugin = __webpack_exports__.bffPlugin;
176
+ exports["default"] = __webpack_exports__["default"];
174
177
  for(var __rspack_i in __webpack_exports__)if (-1 === [
175
- "bffPlugin"
178
+ "bffPlugin",
179
+ "default"
176
180
  ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
177
181
  Object.defineProperty(exports, '__esModule', {
178
182
  value: true
@@ -46,6 +46,7 @@ var external_path_default = /*#__PURE__*/ __webpack_require__.n(external_path_na
46
46
  const effectClientGenerator_js_namespaceObject = require("./utils/effectClientGenerator.js");
47
47
  const EFFECT_BFF_WORKER_RUNTIME_QUERY = 'modern-bff-runtime';
48
48
  const EFFECT_BFF_WORKER_RUNTIME_SOURCE_QUERY = 'modern-bff-runtime-source';
49
+ const createErrorModule = (message)=>`throw new Error(${JSON.stringify(message)});`;
49
50
  async function transformEffectRuntimeSource(source, filename) {
50
51
  const swc = await import("@swc/core");
51
52
  const result = await swc.transform(source, {
@@ -108,13 +109,13 @@ async function loader(source) {
108
109
  onDependency: (dependency)=>this.addDependency(dependency)
109
110
  });
110
111
  if (code) return void callback(void 0, code);
111
- callback(void 0, `throw new Error('Failed to generate Effect client for ${resourcePath}')`);
112
+ callback(void 0, createErrorModule(`Failed to generate Effect client for ${resourcePath}`));
112
113
  return;
113
114
  }
114
115
  const warning = `The file ${resourcePath} is not allowed to be imported in src directory, only API definition files are allowed.`;
115
116
  if (!draftOptions.existLambda) {
116
117
  utils_namespaceObject.logger.warn(warning);
117
- callback(null, `throw new Error('${warning}')`);
118
+ callback(null, createErrorModule(warning));
118
119
  return;
119
120
  }
120
121
  const options = {
@@ -131,14 +132,14 @@ async function loader(source) {
131
132
  const { lambdaDir } = draftOptions;
132
133
  if (!resourcePath.startsWith(lambdaDir)) {
133
134
  utils_namespaceObject.logger.warn(warning);
134
- callback(null, `throw new Error('${warning}')`);
135
+ callback(null, createErrorModule(warning));
135
136
  return;
136
137
  }
137
138
  if (draftOptions.fetcher) options.fetcher = draftOptions.fetcher;
138
139
  if (draftOptions.requestCreator) options.requestCreator = draftOptions.requestCreator;
139
140
  options.requireResolve = require.resolve;
140
141
  const result = await (0, bff_core_namespaceObject.generateClient)(options);
141
- callback(void 0, result.isOk ? result.value : `throw new Error('${result.value}')`);
142
+ callback(void 0, result.isOk ? result.value : createErrorModule(result.value));
142
143
  }
143
144
  const src_loader = loader;
144
145
  exports["default"] = __webpack_exports__["default"];
@@ -64,24 +64,32 @@ function relativeAppPath(appDirectory, entryPath) {
64
64
  const relativePath = external_path_default().relative(appDirectory, entryPath);
65
65
  return '..' === relativePath || relativePath.startsWith(`..${external_path_default().sep}`) ? void 0 : relativePath;
66
66
  }
67
+ function isDependencyPath(relativePath) {
68
+ return relativePath?.split(/[\\/]/u).includes('node_modules') ?? false;
69
+ }
67
70
  function resolveEffectAdapterEntryFile(api) {
68
71
  const { appDirectory, apiDirectory, distDirectory } = api.getServerContext();
69
72
  const appRoot = external_path_default().resolve(appDirectory || process.cwd());
70
- const productionRoot = (0, utils_namespaceObject.isProd)() && distDirectory ? distDirectory : void 0;
73
+ const productionRoot = (0, utils_namespaceObject.isProd)() && distDirectory ? external_path_default().resolve(distDirectory) : void 0;
71
74
  const bffConfig = api.getServerConfig()?.bff;
72
75
  const configuredEntry = bffConfig?.effect?.entry;
73
76
  if (configuredEntry) {
74
77
  const sourceEntry = external_path_default().isAbsolute(configuredEntry) ? configuredEntry : external_path_default().resolve(appRoot, configuredEntry);
75
78
  if (productionRoot) {
76
79
  const relativeEntry = relativeAppPath(appRoot, sourceEntry);
80
+ if (void 0 === relativeEntry || isDependencyPath(relativeEntry)) return resolveJsOrTsEntry(sourceEntry);
77
81
  return relativeEntry ? resolveJsOrTsEntry(builtEntryPath(external_path_default().resolve(productionRoot, relativeEntry))) : void 0;
78
82
  }
79
83
  return resolveJsOrTsEntry(sourceEntry);
80
84
  }
81
- const relativeApiDirectory = apiDirectory ? external_path_default().isAbsolute(apiDirectory) ? relativeAppPath(appRoot, apiDirectory) : apiDirectory : utils_namespaceObject.API_DIR;
82
- if (!relativeApiDirectory) return;
83
- const apiRoot = external_path_default().resolve(productionRoot || appRoot, relativeApiDirectory);
84
- return resolveJsOrTsEntry(external_path_default().resolve(apiRoot, 'index'));
85
+ const resolvedApiDirectory = external_path_default().resolve(appRoot, apiDirectory || utils_namespaceObject.API_DIR);
86
+ const productionApiDirectory = productionRoot ? void 0 !== relativeAppPath(productionRoot, resolvedApiDirectory) ? resolvedApiDirectory : (()=>{
87
+ const relativeApiDirectory = relativeAppPath(appRoot, resolvedApiDirectory);
88
+ if (isDependencyPath(relativeApiDirectory)) return resolvedApiDirectory;
89
+ return relativeApiDirectory ? external_path_default().resolve(productionRoot, relativeApiDirectory) : void 0;
90
+ })() : resolvedApiDirectory;
91
+ if (!productionApiDirectory) return;
92
+ return resolveJsOrTsEntry(external_path_default().resolve(productionApiDirectory, 'index'));
85
93
  }
86
94
  exports.resolveEffectAdapterEntryFile = __webpack_exports__.resolveEffectAdapterEntryFile;
87
95
  for(var __rspack_i in __webpack_exports__)if (-1 === [
@@ -49,6 +49,7 @@ function defineEffectBff(definition) {
49
49
  openapi: options?.openapi,
50
50
  rpc: mergedRpcOptions,
51
51
  dataPlatform: (0, external_envelope_js_namespaceObject.mergeDataPlatformOptions)(definition.dataPlatform, options?.dataPlatform),
52
+ interceptRequest: definition.interceptRequest,
52
53
  validateRequest: options?.validateRequest
53
54
  });
54
55
  };
@@ -44,23 +44,30 @@ function createHttpApiHandler(options) {
44
44
  const openApiLayer = (0, external_openapi_js_namespaceObject.createOpenApiLayer)(options.api, options.openapi);
45
45
  const mergedLayer = openApiLayer ? Layer_namespaceObject.mergeAll(apiLayer, openApiLayer) : apiLayer;
46
46
  const httpApiHandler = http_namespaceObject.HttpRouter.toWebHandler(mergedLayer);
47
- const withDataPlatformValidation = async (request, context)=>{
48
- const policyDenial = options.validateRequest?.(request);
49
- if (policyDenial) return policyDenial;
47
+ const dispatchHttpApiRequest = async (request, context)=>{
50
48
  const preparedRequest = await (0, external_batch_js_namespaceObject.prepareJsonRequestBody)(request);
51
49
  if (preparedRequest instanceof Response) return preparedRequest;
52
50
  const validationError = (0, external_envelope_js_namespaceObject.validateDataPlatformRequestEnvelope)(preparedRequest, options.dataPlatform);
53
51
  if (validationError) return validationError;
54
52
  return httpApiHandler.handler(preparedRequest, (0, external_types_js_namespaceObject.toEffectServiceContext)(context));
55
53
  };
54
+ const withRequestPolicy = async (request, context)=>{
55
+ const policyDenial = options.validateRequest?.(request);
56
+ if (policyDenial) return policyDenial;
57
+ const next = ()=>dispatchHttpApiRequest(request, context);
58
+ return options.interceptRequest ? options.interceptRequest({
59
+ request,
60
+ next
61
+ }) : next();
62
+ };
56
63
  const batchHandler = (0, external_batch_handler_js_namespaceObject.createDataPlatformBatchRequestHandler)({
57
64
  dataPlatform: options.dataPlatform,
58
- handleItem: withDataPlatformValidation
65
+ handleItem: withRequestPolicy
59
66
  });
60
67
  const handleHttpApiRequest = async (request, context)=>{
61
68
  const pathname = (0, external_routing_js_namespaceObject.getRequestPathname)(request);
62
69
  if (batchHandler.enabled && pathname === batchHandler.path) return batchHandler.handle(request, context);
63
- return withDataPlatformValidation(request, context);
70
+ return withRequestPolicy(request, context);
64
71
  };
65
72
  if (!options.rpc) return {
66
73
  handler: handleHttpApiRequest,
@@ -75,7 +82,11 @@ function createHttpApiHandler(options) {
75
82
  if ((0, external_routing_js_namespaceObject.isRpcRequest)(request, rpcPath)) {
76
83
  const policyDenial = options.validateRequest?.(request);
77
84
  if (policyDenial) return policyDenial;
78
- return rpcHandler.handler(request, (0, external_types_js_namespaceObject.toEffectServiceContext)(context));
85
+ const next = ()=>rpcHandler.handler(request, (0, external_types_js_namespaceObject.toEffectServiceContext)(context));
86
+ return options.interceptRequest ? options.interceptRequest({
87
+ request,
88
+ next
89
+ }) : next();
79
90
  }
80
91
  return handleHttpApiRequest(request, context);
81
92
  },
@@ -39,7 +39,9 @@ __webpack_require__.r(__webpack_exports__);
39
39
  __webpack_require__.d(__webpack_exports__, {
40
40
  API_DIR: ()=>API_DIR,
41
41
  DIST_DIR: ()=>DIST_DIR,
42
+ EFFECT_ENTRY: ()=>EFFECT_ENTRY,
42
43
  LAMBDA_DIR: ()=>LAMBDA_DIR,
44
+ OPERATION_CONTRACTS_JSON: ()=>OPERATION_CONTRACTS_JSON,
43
45
  PACKAGE_NAME: ()=>PACKAGE_NAME,
44
46
  PREFIX: ()=>PREFIX,
45
47
  RUNTIME_FRAMEWORK: ()=>RUNTIME_FRAMEWORK,
@@ -54,6 +56,8 @@ const API_DIR = '{apiDirectory}';
54
56
  const LAMBDA_DIR = '{lambdaDirectory}';
55
57
  const DIST_DIR = '{distDirectory}';
56
58
  const RUNTIME_FRAMEWORK = '{runtimeFramework}';
59
+ const EFFECT_ENTRY = '{effectEntry}';
60
+ const OPERATION_CONTRACTS_JSON = '{operationContracts}';
57
61
  const NODE_MODULES = 'node_modules';
58
62
  const crossProjectApiPlugin = ()=>({
59
63
  name: '@modern-js/plugin-independent-bff',
@@ -65,7 +69,8 @@ const crossProjectApiPlugin = ()=>({
65
69
  const { appDirectory: originAppDirectory } = api.getAppContext();
66
70
  const sdkPath = external_path_default().join(originAppDirectory, NODE_MODULES, PACKAGE_NAME);
67
71
  const sdkDistPath = external_path_default().join(sdkPath, DIST_DIR);
68
- const apiDirectory = external_path_default().join(sdkDistPath, API_DIR);
72
+ const effectEntry = EFFECT_ENTRY ? external_path_default().join(sdkDistPath, EFFECT_ENTRY) : void 0;
73
+ const apiDirectory = effectEntry ? external_path_default().dirname(effectEntry) : external_path_default().join(sdkDistPath, API_DIR);
69
74
  const lambdaDirectory = external_path_default().resolve(sdkDistPath, LAMBDA_DIR);
70
75
  api.updateAppContext({
71
76
  apiDirectory,
@@ -80,10 +85,16 @@ const crossProjectApiPlugin = ()=>({
80
85
  }
81
86
  const configuredRuntimeFramework = config?.bff?.runtimeFramework;
82
87
  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}".`);
88
+ resolvedConfig.bff ??= {};
83
89
  resolvedConfig.bff.prefix = PREFIX;
84
90
  resolvedConfig.bff.runtimeFramework = RUNTIME_FRAMEWORK;
91
+ if (EFFECT_ENTRY) resolvedConfig.bff.effect = {
92
+ ...resolvedConfig.bff.effect,
93
+ entry: effectEntry
94
+ };
85
95
  resolvedConfig.bff.isCrossProjectServer = true;
86
96
  resolvedConfig.bff.requestId = resolvedConfig.bff.requestId || config?.bff?.requestId || PACKAGE_NAME || 'default';
97
+ const generatedOperationContracts = "{operationContracts}" === OPERATION_CONTRACTS_JSON ? {} : JSON.parse(OPERATION_CONTRACTS_JSON);
87
98
  resolvedConfig.bff.crossProjectPolicy = {
88
99
  ...resolvedConfig.bff.crossProjectPolicy || {},
89
100
  enabled: resolvedConfig.bff.crossProjectPolicy?.enabled ?? true,
@@ -92,7 +103,11 @@ const crossProjectApiPlugin = ()=>({
92
103
  requireOperationContextDetails: resolvedConfig.bff.crossProjectPolicy?.requireOperationContextDetails ?? true,
93
104
  requireOperationSchemaHash: resolvedConfig.bff.crossProjectPolicy?.requireOperationSchemaHash ?? true,
94
105
  requireOperationVersion: resolvedConfig.bff.crossProjectPolicy?.requireOperationVersion ?? true,
95
- allowUnknownOperations: resolvedConfig.bff.crossProjectPolicy?.allowUnknownOperations ?? false
106
+ allowUnknownOperations: resolvedConfig.bff.crossProjectPolicy?.allowUnknownOperations ?? false,
107
+ expectedOperationContracts: {
108
+ ...resolvedConfig.bff.crossProjectPolicy?.expectedOperationContracts || {},
109
+ ...generatedOperationContracts
110
+ }
96
111
  };
97
112
  return resolvedConfig;
98
113
  });
@@ -101,7 +116,9 @@ const crossProjectApiPlugin = ()=>({
101
116
  const utils_crossProjectApiPlugin = crossProjectApiPlugin;
102
117
  exports.API_DIR = __webpack_exports__.API_DIR;
103
118
  exports.DIST_DIR = __webpack_exports__.DIST_DIR;
119
+ exports.EFFECT_ENTRY = __webpack_exports__.EFFECT_ENTRY;
104
120
  exports.LAMBDA_DIR = __webpack_exports__.LAMBDA_DIR;
121
+ exports.OPERATION_CONTRACTS_JSON = __webpack_exports__.OPERATION_CONTRACTS_JSON;
105
122
  exports.PACKAGE_NAME = __webpack_exports__.PACKAGE_NAME;
106
123
  exports.PREFIX = __webpack_exports__.PREFIX;
107
124
  exports.RUNTIME_FRAMEWORK = __webpack_exports__.RUNTIME_FRAMEWORK;
@@ -110,7 +127,9 @@ exports["default"] = __webpack_exports__["default"];
110
127
  for(var __rspack_i in __webpack_exports__)if (-1 === [
111
128
  "API_DIR",
112
129
  "DIST_DIR",
130
+ "EFFECT_ENTRY",
113
131
  "LAMBDA_DIR",
132
+ "OPERATION_CONTRACTS_JSON",
114
133
  "PACKAGE_NAME",
115
134
  "PREFIX",
116
135
  "RUNTIME_FRAMEWORK",
@@ -120,10 +120,29 @@ const nodeBuiltins = new Set([
120
120
  ...external_node_module_namespaceObject.builtinModules.map((name)=>`node:${name}`)
121
121
  ]);
122
122
  const dependencyResolutionMarker = 'modern-js-effect-dependency-resolution';
123
+ async function isEsmOnlyFile(filename) {
124
+ const extension = external_node_path_default().extname(filename).toLowerCase();
125
+ if ('.mjs' === extension || '.mts' === extension) return true;
126
+ if ('.cjs' === extension || '.cts' === extension) return false;
127
+ let directory = external_node_path_default().dirname(filename);
128
+ while(true){
129
+ const packagePath = external_node_path_default().join(directory, 'package.json');
130
+ try {
131
+ const packageJson = JSON.parse(await external_node_fs_default().promises.readFile(packagePath, 'utf8'));
132
+ return 'module' === packageJson.type;
133
+ } catch (error) {
134
+ if (!error || 'object' != typeof error || !('code' in error) || 'ENOENT' !== error.code) throw error;
135
+ }
136
+ const parent = external_node_path_default().dirname(directory);
137
+ if (parent === directory) return false;
138
+ directory = parent;
139
+ }
140
+ }
123
141
  function isBareSpecifier(specifier) {
124
142
  return !specifier.startsWith('.') && !specifier.startsWith('/') && !external_node_path_default().isAbsolute(specifier);
125
143
  }
126
- function externalizeInstalledDependencies() {
144
+ function externalizeInstalledDependencies(options) {
145
+ const runtimeRequire = (0, external_node_module_namespaceObject.createRequire)(external_node_path_default().join(options.runtimeResolveDir, '__modern_effect_entry.cjs'));
127
146
  return {
128
147
  name: 'modern-js-effect-installed-dependencies',
129
148
  setup (buildApi) {
@@ -153,10 +172,41 @@ function externalizeInstalledDependencies() {
153
172
  path: args.path
154
173
  };
155
174
  const realPath = await external_node_fs_default().promises.realpath(resolution.path).catch(()=>resolution.path);
156
- if (realPath.includes(`${external_node_path_default().sep}node_modules${external_node_path_default().sep}`)) return {
157
- external: true,
158
- path: args.path
159
- };
175
+ if (realPath.includes(`${external_node_path_default().sep}node_modules${external_node_path_default().sep}`)) {
176
+ const runtimeResolution = await buildApi.resolve(args.path, {
177
+ importer: '',
178
+ kind: args.kind,
179
+ namespace: 'file',
180
+ pluginData: {
181
+ [dependencyResolutionMarker]: true
182
+ },
183
+ resolveDir: options.runtimeResolveDir
184
+ });
185
+ if (0 === runtimeResolution.errors.length && runtimeResolution.path) {
186
+ if ('cjs' === options.format) {
187
+ let requiredPath;
188
+ try {
189
+ requiredPath = runtimeRequire.resolve(args.path);
190
+ } catch {
191
+ requiredPath = runtimeResolution.path;
192
+ }
193
+ if (await isEsmOnlyFile(requiredPath)) return {
194
+ namespace: resolution.namespace,
195
+ path: resolution.path,
196
+ pluginData: resolution.pluginData,
197
+ sideEffects: resolution.sideEffects,
198
+ suffix: resolution.suffix,
199
+ warnings: resolution.warnings,
200
+ watchDirs: resolution.watchDirs,
201
+ watchFiles: resolution.watchFiles
202
+ };
203
+ }
204
+ return {
205
+ external: true,
206
+ path: args.path
207
+ };
208
+ }
209
+ }
160
210
  return {
161
211
  namespace: resolution.namespace,
162
212
  path: resolution.path,
@@ -184,7 +234,10 @@ async function bundleEffectEntryForNode(options) {
184
234
  outfile: options.entryPath,
185
235
  platform: 'node',
186
236
  plugins: [
187
- externalizeInstalledDependencies(),
237
+ externalizeInstalledDependencies({
238
+ format: options.format,
239
+ runtimeResolveDir: options.appDir
240
+ }),
188
241
  preserveSourceModuleSemantics()
189
242
  ],
190
243
  target: 'node20'
@@ -226,7 +279,10 @@ async function loadEffectSourceModule(options) {
226
279
  outfile: outputPath,
227
280
  platform: 'node',
228
281
  plugins: [
229
- externalizeInstalledDependencies(),
282
+ externalizeInstalledDependencies({
283
+ format: 'esm',
284
+ runtimeResolveDir: appDir
285
+ }),
230
286
  preserveSourceModuleSemantics()
231
287
  ],
232
288
  sourcemap: 'inline',
@@ -43,18 +43,18 @@ const utils_namespaceObject = require("@modern-js/utils");
43
43
  const external_path_namespaceObject = require("path");
44
44
  var external_path_default = /*#__PURE__*/ __webpack_require__.n(external_path_namespaceObject);
45
45
  const external_crossProjectApiPlugin_js_namespaceObject = require("./crossProjectApiPlugin.js");
46
- function replaceContent(source, packageName, prefix, relativeDistPath, relativeApiPath, relativeLambdaPath, runtimeFramework) {
47
- const updatedSource = source.replace(new RegExp(external_crossProjectApiPlugin_js_namespaceObject.PACKAGE_NAME, 'g'), packageName).replace(new RegExp(external_crossProjectApiPlugin_js_namespaceObject.PREFIX, 'g'), prefix).replace(new RegExp(external_crossProjectApiPlugin_js_namespaceObject.DIST_DIR, 'g'), (0, utils_namespaceObject.normalizeToPosixPath)(relativeDistPath)).replace(new RegExp(external_crossProjectApiPlugin_js_namespaceObject.API_DIR, 'g'), (0, utils_namespaceObject.normalizeToPosixPath)(relativeApiPath)).replace(new RegExp(external_crossProjectApiPlugin_js_namespaceObject.LAMBDA_DIR, 'g'), (0, utils_namespaceObject.normalizeToPosixPath)(relativeLambdaPath)).replace(new RegExp(external_crossProjectApiPlugin_js_namespaceObject.RUNTIME_FRAMEWORK, 'g'), runtimeFramework);
46
+ function replaceContent(source, packageName, prefix, relativeDistPath, relativeApiPath, relativeLambdaPath, runtimeFramework, relativeEffectEntry, operationContracts) {
47
+ const updatedSource = source.replace(new RegExp(external_crossProjectApiPlugin_js_namespaceObject.PACKAGE_NAME, 'g'), packageName).replace(new RegExp(external_crossProjectApiPlugin_js_namespaceObject.PREFIX, 'g'), prefix).replace(new RegExp(external_crossProjectApiPlugin_js_namespaceObject.DIST_DIR, 'g'), (0, utils_namespaceObject.normalizeToPosixPath)(relativeDistPath)).replace(new RegExp(external_crossProjectApiPlugin_js_namespaceObject.API_DIR, 'g'), (0, utils_namespaceObject.normalizeToPosixPath)(relativeApiPath)).replace(new RegExp(external_crossProjectApiPlugin_js_namespaceObject.LAMBDA_DIR, 'g'), (0, utils_namespaceObject.normalizeToPosixPath)(relativeLambdaPath)).replace(new RegExp(external_crossProjectApiPlugin_js_namespaceObject.RUNTIME_FRAMEWORK, 'g'), runtimeFramework).replace(new RegExp(external_crossProjectApiPlugin_js_namespaceObject.EFFECT_ENTRY, 'g'), (0, utils_namespaceObject.normalizeToPosixPath)(relativeEffectEntry)).replace(`'${external_crossProjectApiPlugin_js_namespaceObject.OPERATION_CONTRACTS_JSON}'`, ()=>JSON.stringify(JSON.stringify(operationContracts)));
48
48
  return updatedSource;
49
49
  }
50
- async function pluginGenerator({ prefix, appDirectory, relativeDistPath, relativeApiPath, relativeLambdaPath, runtimeFramework }) {
50
+ async function pluginGenerator({ prefix, appDirectory, relativeDistPath, relativeApiPath, relativeLambdaPath, runtimeFramework, relativeEffectEntry, operationContracts }) {
51
51
  try {
52
52
  const packageContent = await utils_namespaceObject.fs.readFile(external_path_default().resolve(appDirectory, './package.json'), 'utf8');
53
53
  const packageJson = JSON.parse(packageContent);
54
54
  const pluginDir = external_path_default().resolve(appDirectory, `./${relativeDistPath}`, 'plugin');
55
55
  const pluginPath = external_path_default().join(pluginDir, 'index.js');
56
56
  const pluginTemplate = await utils_namespaceObject.fs.readFile(external_path_default().resolve(__dirname, 'crossProjectApiPlugin.js'), 'utf8');
57
- const updatedPlugin = replaceContent(pluginTemplate, packageJson.name, prefix, relativeDistPath, relativeApiPath, relativeLambdaPath, runtimeFramework);
57
+ const updatedPlugin = replaceContent(pluginTemplate, packageJson.name, prefix, relativeDistPath, relativeApiPath, relativeLambdaPath, runtimeFramework, relativeEffectEntry, operationContracts);
58
58
  await utils_namespaceObject.fs.ensureFile(pluginPath);
59
59
  await utils_namespaceObject.fs.writeFile(pluginPath, updatedPlugin);
60
60
  const typeContent = `import type { AppTools, CliPlugin } from '@modern-js/app-tools';
@@ -1,4 +1,4 @@
1
- import { ApiRouter } from "@modern-js/bff-core";
1
+ import { ApiRouter, buildOperationContractMap, deriveOperationVersion } from "@modern-js/bff-core";
2
2
  import { compile } from "@modern-js/server-utils";
3
3
  import { API_DIR, SHARED_DIR, fs, resolveServerTsconfig } from "@modern-js/utils";
4
4
  import path from "path";
@@ -85,16 +85,27 @@ const createBffGenerator = (api)=>{
85
85
  });
86
86
  const lambdaDir = apiRouter.getLambdaDir();
87
87
  const existLambda = apiRouter.isExistLambda();
88
+ const packageJson = fs.readJSONSync(path.resolve(appDirectory, 'package.json'));
89
+ const requestId = packageJson.name || process.env.npm_package_name;
90
+ const operationContracts = existLambda ? buildOperationContractMap({
91
+ handlers: await apiRouter.getApiHandlers(),
92
+ requestId,
93
+ operationVersion: deriveOperationVersion(packageJson.version)
94
+ }) : {};
88
95
  const runtime = bff?.runtimeCreateRequest || RUNTIME_CREATE_REQUEST;
89
96
  const relativeApiPath = path.relative(appDirectory, apiDirectory);
90
97
  const relativeLambdaPath = path.relative(appDirectory, lambdaDir);
98
+ const sourceEffectEntry = 'effect' === bffRuntimeFramework ? resolveEffectSourceEntry(appDirectory, apiDirectory, bff?.effect?.entry) : void 0;
99
+ const relativeEffectEntry = sourceEffectEntry ? path.relative(appDirectory, sourceEffectEntry).replace(/\.(?:[cm]?ts|tsx|jsx)$/u, '.js') : '';
91
100
  await pluginGenerator({
92
101
  prefix,
93
102
  appDirectory,
94
103
  relativeDistPath,
95
104
  relativeApiPath,
96
105
  relativeLambdaPath,
97
- runtimeFramework: 'hono' === bffRuntimeFramework ? 'hono' : 'effect'
106
+ runtimeFramework: 'hono' === bffRuntimeFramework ? 'hono' : 'effect',
107
+ relativeEffectEntry,
108
+ operationContracts
98
109
  });
99
110
  await clientGenerator({
100
111
  prefix,
package/dist/esm/cli.mjs CHANGED
@@ -128,4 +128,6 @@ const bffPlugin = ()=>({
128
128
  });
129
129
  }
130
130
  });
131
+ const cli = bffPlugin;
132
+ export default cli;
131
133
  export { bffPlugin };
@@ -4,6 +4,7 @@ import path from "path";
4
4
  import { generateEffectClientCode, resolveEffectEntryFile } from "./utils/effectClientGenerator.mjs";
5
5
  const EFFECT_BFF_WORKER_RUNTIME_QUERY = 'modern-bff-runtime';
6
6
  const EFFECT_BFF_WORKER_RUNTIME_SOURCE_QUERY = 'modern-bff-runtime-source';
7
+ const createErrorModule = (message)=>`throw new Error(${JSON.stringify(message)});`;
7
8
  async function transformEffectRuntimeSource(source, filename) {
8
9
  const swc = await import("@swc/core");
9
10
  const result = await swc.transform(source, {
@@ -66,13 +67,13 @@ async function loader(source) {
66
67
  onDependency: (dependency)=>this.addDependency(dependency)
67
68
  });
68
69
  if (code) return void callback(void 0, code);
69
- callback(void 0, `throw new Error('Failed to generate Effect client for ${resourcePath}')`);
70
+ callback(void 0, createErrorModule(`Failed to generate Effect client for ${resourcePath}`));
70
71
  return;
71
72
  }
72
73
  const warning = `The file ${resourcePath} is not allowed to be imported in src directory, only API definition files are allowed.`;
73
74
  if (!draftOptions.existLambda) {
74
75
  logger.warn(warning);
75
- callback(null, `throw new Error('${warning}')`);
76
+ callback(null, createErrorModule(warning));
76
77
  return;
77
78
  }
78
79
  const options = {
@@ -89,14 +90,14 @@ async function loader(source) {
89
90
  const { lambdaDir } = draftOptions;
90
91
  if (!resourcePath.startsWith(lambdaDir)) {
91
92
  logger.warn(warning);
92
- callback(null, `throw new Error('${warning}')`);
93
+ callback(null, createErrorModule(warning));
93
94
  return;
94
95
  }
95
96
  if (draftOptions.fetcher) options.fetcher = draftOptions.fetcher;
96
97
  if (draftOptions.requestCreator) options.requestCreator = draftOptions.requestCreator;
97
98
  options.requireResolve = require.resolve;
98
99
  const result = await generateClient(options);
99
- callback(void 0, result.isOk ? result.value : `throw new Error('${result.value}')`);
100
+ callback(void 0, result.isOk ? result.value : createErrorModule(result.value));
100
101
  }
101
102
  const src_loader = loader;
102
103
  export default src_loader;
@@ -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 };
@@ -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 };