@modern-js/plugin-bff 2.69.5 → 3.0.0-alpha.0

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 (76) hide show
  1. package/dist/cjs/cli.js +256 -272
  2. package/dist/cjs/constants.js +43 -34
  3. package/dist/cjs/index.js +55 -19
  4. package/dist/cjs/loader.js +69 -65
  5. package/dist/cjs/runtime/create-request/index.js +39 -29
  6. package/dist/cjs/runtime/hono/adapter.js +128 -126
  7. package/dist/cjs/runtime/hono/index.js +78 -30
  8. package/dist/cjs/runtime/hono/operators.js +64 -67
  9. package/dist/cjs/server.js +159 -165
  10. package/dist/cjs/utils/clientGenerator.js +204 -206
  11. package/dist/cjs/utils/createHonoRoutes.js +128 -144
  12. package/dist/cjs/utils/crossProjectApiPlugin.js +91 -81
  13. package/dist/cjs/utils/pluginGenerator.js +66 -54
  14. package/dist/cjs/utils/runtimeGenerator.js +67 -45
  15. package/dist/esm/cli.mjs +214 -0
  16. package/dist/esm/constants.mjs +11 -0
  17. package/dist/esm/loader.mjs +39 -0
  18. package/dist/esm/runtime/create-request/{index.js → index.mjs} +1 -5
  19. package/dist/esm/runtime/hono/adapter.mjs +95 -0
  20. package/dist/{esm-node/runtime/hono/index.js → esm/runtime/hono/index.mjs} +2 -4
  21. package/dist/esm/runtime/hono/operators.mjs +31 -0
  22. package/dist/esm/server.mjs +122 -0
  23. package/dist/esm/utils/clientGenerator.mjs +175 -0
  24. package/dist/esm/utils/createHonoRoutes.mjs +91 -0
  25. package/dist/esm/utils/crossProjectApiPlugin.mjs +34 -0
  26. package/dist/esm/utils/pluginGenerator.mjs +29 -0
  27. package/dist/esm/utils/runtimeGenerator.mjs +43 -0
  28. package/dist/esm-node/cli.mjs +214 -0
  29. package/dist/esm-node/constants.mjs +11 -0
  30. package/dist/esm-node/index.mjs +1 -0
  31. package/dist/esm-node/loader.mjs +39 -0
  32. package/dist/esm-node/runtime/create-request/{index.js → index.mjs} +1 -5
  33. package/dist/esm-node/runtime/hono/adapter.mjs +95 -0
  34. package/dist/{esm/runtime/hono/index.js → esm-node/runtime/hono/index.mjs} +3 -5
  35. package/dist/esm-node/runtime/hono/operators.mjs +31 -0
  36. package/dist/esm-node/server.mjs +122 -0
  37. package/dist/esm-node/utils/clientGenerator.mjs +175 -0
  38. package/dist/esm-node/utils/createHonoRoutes.mjs +91 -0
  39. package/dist/esm-node/utils/crossProjectApiPlugin.mjs +34 -0
  40. package/dist/esm-node/utils/pluginGenerator.mjs +29 -0
  41. package/dist/esm-node/utils/runtimeGenerator.mjs +43 -0
  42. package/dist/types/loader.d.ts +2 -2
  43. package/dist/types/runtime/hono/adapter.d.ts +3 -3
  44. package/dist/types/server.d.ts +2 -2
  45. package/dist/types/utils/runtimeGenerator.d.ts +2 -1
  46. package/package.json +53 -31
  47. package/rslib.config.mts +4 -0
  48. package/dist/cjs/helper.js +0 -48
  49. package/dist/esm/cli.js +0 -425
  50. package/dist/esm/constants.js +0 -14
  51. package/dist/esm/helper.js +0 -13
  52. package/dist/esm/index.js +0 -1
  53. package/dist/esm/loader.js +0 -75
  54. package/dist/esm/runtime/hono/adapter.js +0 -243
  55. package/dist/esm/runtime/hono/operators.js +0 -79
  56. package/dist/esm/server.js +0 -258
  57. package/dist/esm/utils/clientGenerator.js +0 -517
  58. package/dist/esm/utils/createHonoRoutes.js +0 -319
  59. package/dist/esm/utils/crossProjectApiPlugin.js +0 -49
  60. package/dist/esm/utils/pluginGenerator.js +0 -94
  61. package/dist/esm/utils/runtimeGenerator.js +0 -55
  62. package/dist/esm-node/cli.js +0 -246
  63. package/dist/esm-node/constants.js +0 -14
  64. package/dist/esm-node/helper.js +0 -14
  65. package/dist/esm-node/loader.js +0 -49
  66. package/dist/esm-node/runtime/hono/adapter.js +0 -103
  67. package/dist/esm-node/runtime/hono/operators.js +0 -46
  68. package/dist/esm-node/server.js +0 -142
  69. package/dist/esm-node/utils/clientGenerator.js +0 -192
  70. package/dist/esm-node/utils/createHonoRoutes.js +0 -120
  71. package/dist/esm-node/utils/crossProjectApiPlugin.js +0 -47
  72. package/dist/esm-node/utils/pluginGenerator.js +0 -31
  73. package/dist/esm-node/utils/runtimeGenerator.js +0 -35
  74. package/dist/types/helper.d.ts +0 -2
  75. package/types.d.ts +0 -3
  76. /package/dist/{esm-node/index.js → esm/index.mjs} +0 -0
@@ -0,0 +1,95 @@
1
+ import { Hono } from "@modern-js/server-core";
2
+ import { isProd, logger } from "@modern-js/utils";
3
+ import createHonoRoutes from "../../utils/createHonoRoutes.mjs";
4
+ const before = [
5
+ 'custom-server-hook',
6
+ 'custom-server-middleware',
7
+ 'render'
8
+ ];
9
+ class HonoAdapter {
10
+ wrapInArray(handler) {
11
+ if (Array.isArray(handler)) return handler;
12
+ return [
13
+ handler
14
+ ];
15
+ }
16
+ constructor(api){
17
+ this.apiMiddleware = [];
18
+ this.apiServer = null;
19
+ this.isHono = true;
20
+ this.setHandlers = async ()=>{
21
+ if (!this.isHono) return;
22
+ const { apiHandlerInfos } = this.api.getServerContext();
23
+ const honoHandlers = createHonoRoutes(apiHandlerInfos);
24
+ this.apiMiddleware = honoHandlers.map(({ path, method, handler })=>({
25
+ name: 'hono-bff-api',
26
+ path,
27
+ method,
28
+ handler,
29
+ order: 'post',
30
+ before
31
+ }));
32
+ };
33
+ this.registerApiRoutes = async ()=>{
34
+ if (!this.isHono) return;
35
+ this.apiServer = new Hono();
36
+ this.apiMiddleware.forEach(({ path = '*', method = 'all', handler })=>{
37
+ const handlers = this.wrapInArray(handler);
38
+ if (0 === handlers.length) return;
39
+ const firstHandler = handlers[0];
40
+ const restHandlers = handlers.slice(1);
41
+ const m = method;
42
+ const server = this.apiServer;
43
+ if (!server) return;
44
+ const register = server[m];
45
+ register.call(server, path, firstHandler, ...restHandlers);
46
+ });
47
+ this.apiServer.onError(async (err, c)=>{
48
+ try {
49
+ const serverConfig = this.api.getServerConfig();
50
+ const onErrorHandler = serverConfig?.onError;
51
+ if (onErrorHandler) {
52
+ const result = await onErrorHandler(err, c);
53
+ if (result instanceof Response) return result;
54
+ } else logger.error(err);
55
+ } catch (configError) {
56
+ logger.error(`Error in serverConfig.onError handler: ${configError}`);
57
+ }
58
+ return c.json({
59
+ message: err?.message || '[BFF] Internal Server Error'
60
+ }, err?.status || 500);
61
+ });
62
+ };
63
+ this.registerMiddleware = async (options)=>{
64
+ const { prefix } = options;
65
+ const { bffRuntimeFramework } = this.api.getServerContext();
66
+ if ('hono' !== bffRuntimeFramework) {
67
+ this.isHono = false;
68
+ return;
69
+ }
70
+ const { middlewares: globalMiddlewares } = this.api.getServerContext();
71
+ await this.setHandlers();
72
+ if (isProd()) globalMiddlewares.push(...this.apiMiddleware);
73
+ else {
74
+ await this.registerApiRoutes();
75
+ const dynamicApiMiddleware = {
76
+ name: 'dynamic-bff-handler',
77
+ path: `${prefix}/*`,
78
+ method: 'all',
79
+ order: 'post',
80
+ before,
81
+ handler: async (c, next)=>{
82
+ if (this.apiServer) {
83
+ const response = await this.apiServer.fetch(c.req.raw, c.env);
84
+ if (404 !== response.status) return new Response(response.body, response);
85
+ }
86
+ await next();
87
+ }
88
+ };
89
+ globalMiddlewares.push(dynamicApiMiddleware);
90
+ }
91
+ };
92
+ this.api = api;
93
+ }
94
+ }
95
+ export { HonoAdapter };
@@ -1,6 +1,4 @@
1
- export * from "@modern-js/bff-core";
2
1
  import { useHonoContext } from "@modern-js/server-core";
3
- export * from "./operators";
4
- export {
5
- useHonoContext
6
- };
2
+ export * from "@modern-js/bff-core";
3
+ export * from "./operators.mjs";
4
+ export { useHonoContext };
@@ -0,0 +1,31 @@
1
+ import { useHonoContext } from "@modern-js/server-core";
2
+ const Pipe = (func)=>({
3
+ name: 'pipe',
4
+ async execute (executeHelper, next) {
5
+ const { inputs } = executeHelper;
6
+ const ctx = useHonoContext();
7
+ const { res } = ctx;
8
+ if ('function' == typeof func) {
9
+ let isPiped = true;
10
+ const end = (value)=>{
11
+ isPiped = false;
12
+ if ('function' == typeof value) return void value(res);
13
+ return value;
14
+ };
15
+ const output = await func(inputs, end);
16
+ if (!isPiped) if (output) return executeHelper.result = output;
17
+ else return;
18
+ executeHelper.inputs = output;
19
+ await next();
20
+ }
21
+ }
22
+ });
23
+ const Middleware = (middleware)=>({
24
+ name: 'middleware',
25
+ metadata (helper) {
26
+ const middlewares = helper.getMetadata('pipe') || [];
27
+ middlewares.push(middleware);
28
+ helper.setMetadata('middleware', middlewares);
29
+ }
30
+ });
31
+ export { Middleware, Pipe };
@@ -0,0 +1,122 @@
1
+ import path from "path";
2
+ import { ApiRouter } from "@modern-js/bff-core";
3
+ import { API_DIR, isFunction, isProd, isWebOnly, requireExistModule } from "@modern-js/utils";
4
+ import { API_APP_NAME } from "./constants.mjs";
5
+ import { HonoAdapter } from "./runtime/hono/adapter.mjs";
6
+ class Storage {
7
+ reset() {
8
+ this.middlewares = [];
9
+ }
10
+ constructor(){
11
+ this.middlewares = [];
12
+ }
13
+ }
14
+ const createTransformAPI = (storage)=>({
15
+ addMiddleware (fn) {
16
+ storage.middlewares.push(fn);
17
+ }
18
+ });
19
+ const server = ()=>({
20
+ name: '@modern-js/plugin-bff',
21
+ setup: (api)=>{
22
+ const storage = new Storage();
23
+ const transformAPI = createTransformAPI(storage);
24
+ let apiAppPath = '';
25
+ let apiRouter;
26
+ const honoAdapter = new HonoAdapter(api);
27
+ api.onPrepare(async ()=>{
28
+ const appContext = api.getServerContext();
29
+ const { appDirectory, distDirectory, render } = appContext;
30
+ const root = isProd() ? distDirectory : appDirectory;
31
+ const apiPath = path.resolve(root || process.cwd(), API_DIR);
32
+ apiAppPath = path.resolve(apiPath, API_APP_NAME);
33
+ const apiMod = await requireExistModule(apiAppPath);
34
+ if (apiMod && 'function' == typeof apiMod) apiMod(transformAPI);
35
+ const { middlewares } = storage;
36
+ api.updateServerContext({
37
+ ...appContext,
38
+ apiMiddlewares: middlewares
39
+ });
40
+ const config = api.getServerConfig();
41
+ const prefix = config?.bff?.prefix || '/api';
42
+ const enableHandleWeb = config?.bff?.enableHandleWeb;
43
+ const httpMethodDecider = config?.bff?.httpMethodDecider;
44
+ const { distDirectory: pwd, middlewares: globalMiddlewares } = api.getServerContext();
45
+ const webOnly = await isWebOnly();
46
+ let handler;
47
+ if (webOnly) handler = async (c, next)=>{
48
+ c.body('');
49
+ await next();
50
+ };
51
+ else {
52
+ const runner = api.getHooks();
53
+ const renderHandler = enableHandleWeb ? render : null;
54
+ handler = await runner.prepareApiServer.call({
55
+ pwd: pwd,
56
+ prefix,
57
+ render: renderHandler,
58
+ httpMethodDecider
59
+ });
60
+ }
61
+ if (handler && isFunction(handler)) globalMiddlewares.push({
62
+ name: 'bind-bff',
63
+ handler: (c, next)=>{
64
+ if (!c.req.path.startsWith(prefix) && !enableHandleWeb) return next();
65
+ return handler(c, next);
66
+ },
67
+ order: 'post',
68
+ before: [
69
+ 'custom-server-hook',
70
+ 'custom-server-middleware',
71
+ 'render'
72
+ ]
73
+ });
74
+ honoAdapter.registerMiddleware({
75
+ prefix,
76
+ enableHandleWeb
77
+ });
78
+ });
79
+ api.onReset(async ({ event })=>{
80
+ storage.reset();
81
+ const appContext = api.getServerContext();
82
+ const newApiModule = await requireExistModule(apiAppPath);
83
+ if (newApiModule && 'function' == typeof newApiModule) newApiModule(transformAPI);
84
+ const { middlewares } = storage;
85
+ api.updateServerContext({
86
+ ...appContext,
87
+ apiMiddlewares: middlewares
88
+ });
89
+ if ('file-change' === event.type) {
90
+ const apiHandlerInfos = await apiRouter.getApiHandlers();
91
+ const appContext = api.getServerContext();
92
+ api.updateServerContext({
93
+ ...appContext,
94
+ apiHandlerInfos
95
+ });
96
+ await honoAdapter.setHandlers();
97
+ await honoAdapter.registerApiRoutes();
98
+ }
99
+ });
100
+ api.prepareApiServer(async (input, next)=>{
101
+ const { pwd, prefix, httpMethodDecider } = input;
102
+ const apiDir = path.resolve(pwd, API_DIR);
103
+ const appContext = api.getServerContext();
104
+ const { apiDirectory, lambdaDirectory } = appContext;
105
+ apiRouter = new ApiRouter({
106
+ appDir: pwd,
107
+ apiDir: apiDirectory || apiDir,
108
+ lambdaDir: lambdaDirectory,
109
+ prefix,
110
+ httpMethodDecider
111
+ });
112
+ const apiHandlerInfos = await apiRouter.getApiHandlers();
113
+ api.updateServerContext({
114
+ ...appContext,
115
+ apiRouter,
116
+ apiHandlerInfos
117
+ });
118
+ return next(input);
119
+ });
120
+ }
121
+ });
122
+ export { server as default };
@@ -0,0 +1,175 @@
1
+ import path from "path";
2
+ import { generateClient } from "@modern-js/bff-core";
3
+ import { fs, logger } from "@modern-js/utils";
4
+ const API_DIR = 'api';
5
+ const PLUGIN_DIR = 'plugin';
6
+ const RUNTIME_DIR = 'runtime';
7
+ const CLIENT_DIR = 'client';
8
+ const EXPORT_PREFIX = `./${API_DIR}/`;
9
+ const TYPE_PREFIX = `${API_DIR}/`;
10
+ const toPosixPath = (p)=>p.replace(/\\/g, '/');
11
+ const posixJoin = (...args)=>toPosixPath(path.join(...args));
12
+ async function readDirectoryFiles(appDirectory, directory, relativeDistPath) {
13
+ const filesList = [];
14
+ async function readFiles(currentPath) {
15
+ const entries = await fs.readdir(currentPath, {
16
+ withFileTypes: true
17
+ });
18
+ for (const entry of entries){
19
+ if ('_app.ts' === entry.name) continue;
20
+ const resourcePath = path.join(currentPath, entry.name);
21
+ if (entry.isDirectory()) await readFiles(resourcePath);
22
+ else {
23
+ const source = await fs.readFile(resourcePath, 'utf8');
24
+ const relativePath = path.relative(directory, resourcePath);
25
+ const parsedPath = path.parse(relativePath);
26
+ const targetDir = posixJoin(`./${relativeDistPath}/${CLIENT_DIR}`, parsedPath.dir, `${parsedPath.name}.js`);
27
+ const name = parsedPath.name;
28
+ const absTargetDir = path.resolve(targetDir);
29
+ const relativePathFromAppDirectory = path.relative(appDirectory, currentPath);
30
+ const typesFilePath = posixJoin(`./${relativeDistPath}`, relativePathFromAppDirectory, `${name}.d.ts`);
31
+ const relativeTargetDistDir = `./${typesFilePath}`;
32
+ const exportKey = toPosixPath(path.join(parsedPath.dir, name));
33
+ filesList.push({
34
+ resourcePath,
35
+ source,
36
+ targetDir,
37
+ name,
38
+ absTargetDir,
39
+ relativeTargetDistDir,
40
+ exportKey
41
+ });
42
+ }
43
+ }
44
+ }
45
+ await readFiles(directory);
46
+ return filesList;
47
+ }
48
+ function mergePackageJson(packageJson, files, typesVersion, exports) {
49
+ packageJson.files = [
50
+ ...new Set([
51
+ ...packageJson.files || [],
52
+ ...files
53
+ ])
54
+ ];
55
+ packageJson.typesVersions ??= {};
56
+ const starTypes = packageJson.typesVersions['*'] || {};
57
+ Object.keys(starTypes).forEach((k)=>k.startsWith(TYPE_PREFIX) && delete starTypes[k]);
58
+ packageJson.typesVersions['*'] = {
59
+ ...starTypes,
60
+ ...typesVersion['*'] || {}
61
+ };
62
+ packageJson.exports ??= {};
63
+ Object.keys(packageJson.exports).forEach((k)=>k.startsWith(EXPORT_PREFIX) && delete packageJson.exports[k]);
64
+ Object.assign(packageJson.exports, exports);
65
+ }
66
+ async function writeTargetFile(absTargetDir, content) {
67
+ await fs.mkdir(path.dirname(absTargetDir), {
68
+ recursive: true
69
+ });
70
+ await fs.writeFile(absTargetDir, content);
71
+ }
72
+ async function setPackage(files, appDirectory, relativeDistPath) {
73
+ try {
74
+ const packagePath = path.resolve(appDirectory, './package.json');
75
+ const packageContent = await fs.readFile(packagePath, 'utf8');
76
+ const packageJson = JSON.parse(packageContent);
77
+ const addFiles = [
78
+ posixJoin(relativeDistPath, CLIENT_DIR, '**', '*'),
79
+ posixJoin(relativeDistPath, RUNTIME_DIR, '**', '*'),
80
+ posixJoin(relativeDistPath, PLUGIN_DIR, '**', '*')
81
+ ];
82
+ const typesVersions = {
83
+ '*': files.reduce((acc, file)=>{
84
+ const typeFilePath = toPosixPath(`./${file.targetDir}`).replace('js', 'd.ts');
85
+ return {
86
+ ...acc,
87
+ [toPosixPath(`${TYPE_PREFIX}${file.exportKey}`)]: [
88
+ typeFilePath
89
+ ]
90
+ };
91
+ }, {
92
+ [`${API_DIR}/*`]: [
93
+ toPosixPath(`./${relativeDistPath}/${CLIENT_DIR}/*.d.ts`)
94
+ ],
95
+ [RUNTIME_DIR]: [
96
+ toPosixPath(`./${relativeDistPath}/${RUNTIME_DIR}/index.d.ts`)
97
+ ],
98
+ [PLUGIN_DIR]: [
99
+ toPosixPath(`./${relativeDistPath}/${PLUGIN_DIR}/index.d.ts`)
100
+ ]
101
+ })
102
+ };
103
+ const exports = files.reduce((acc, file)=>{
104
+ const exportKey = `${EXPORT_PREFIX}${file.exportKey}`;
105
+ const jsFilePath = toPosixPath(`./${file.targetDir}`);
106
+ return {
107
+ ...acc,
108
+ [toPosixPath(exportKey)]: {
109
+ import: jsFilePath,
110
+ types: toPosixPath(jsFilePath.replace(/\.js$/, '.d.ts'))
111
+ }
112
+ };
113
+ }, {
114
+ [toPosixPath(`./${API_DIR}/*`)]: {
115
+ import: toPosixPath(`./${relativeDistPath}/${CLIENT_DIR}/*.js`),
116
+ types: toPosixPath(`./${relativeDistPath}/${CLIENT_DIR}/*.d.ts`)
117
+ },
118
+ [toPosixPath(`./${PLUGIN_DIR}`)]: {
119
+ import: toPosixPath(`./${relativeDistPath}/${PLUGIN_DIR}/index.js`),
120
+ require: toPosixPath(`./${relativeDistPath}/${PLUGIN_DIR}/index.js`),
121
+ types: toPosixPath(`./${relativeDistPath}/${PLUGIN_DIR}/index.d.ts`)
122
+ },
123
+ [toPosixPath(`./${RUNTIME_DIR}`)]: {
124
+ import: toPosixPath(`./${relativeDistPath}/${RUNTIME_DIR}/index.js`),
125
+ require: toPosixPath(`./${relativeDistPath}/${RUNTIME_DIR}/index.js`),
126
+ types: toPosixPath(`./${relativeDistPath}/${RUNTIME_DIR}/index.d.ts`)
127
+ }
128
+ });
129
+ mergePackageJson(packageJson, addFiles, typesVersions, exports);
130
+ await fs.promises.writeFile(packagePath, JSON.stringify(packageJson, null, 2));
131
+ } catch (error) {
132
+ logger.error(`package.json update failed: ${error}`);
133
+ }
134
+ }
135
+ async function copyFiles(from, to) {
136
+ if (await fs.pathExists(from)) await fs.copy(toPosixPath(from), toPosixPath(to));
137
+ }
138
+ async function clientGenerator(draftOptions) {
139
+ const sourceList = await readDirectoryFiles(draftOptions.appDir, draftOptions.lambdaDir, draftOptions.relativeDistPath);
140
+ const getClitentCode = async (resourcePath, source)=>{
141
+ const warning = `The file ${resourcePath} is not allowd to be imported in src directory, only API definition files are allowed.`;
142
+ if (!draftOptions.existLambda) return void logger.warn(warning);
143
+ const options = {
144
+ prefix: Array.isArray(draftOptions.prefix) ? draftOptions.prefix[0] : draftOptions.prefix,
145
+ appDir: draftOptions.appDir,
146
+ apiDir: draftOptions.apiDir,
147
+ lambdaDir: draftOptions.lambdaDir,
148
+ port: Number(draftOptions.port),
149
+ source,
150
+ resourcePath,
151
+ target: 'bundle',
152
+ httpMethodDecider: draftOptions.httpMethodDecider,
153
+ requestCreator: draftOptions.requestCreator
154
+ };
155
+ const { lambdaDir } = draftOptions;
156
+ if (!resourcePath.startsWith(lambdaDir)) return void logger.warn(warning);
157
+ const result = await generateClient(options);
158
+ return result;
159
+ };
160
+ try {
161
+ for (const source of sourceList){
162
+ const code = await getClitentCode(source.resourcePath, source.source);
163
+ if (code?.value) {
164
+ await writeTargetFile(source.absTargetDir, code.value);
165
+ await copyFiles(source.relativeTargetDistDir, source.targetDir.replace("js", 'd.ts'));
166
+ }
167
+ }
168
+ logger.info("Client bundle generate succeed");
169
+ } catch (error) {
170
+ logger.error(`Client bundle generate failed: ${error}`);
171
+ }
172
+ setPackage(sourceList, draftOptions.appDir, draftOptions.relativeDistPath);
173
+ }
174
+ const utils_clientGenerator = clientGenerator;
175
+ export { copyFiles, utils_clientGenerator as default, readDirectoryFiles };
@@ -0,0 +1,91 @@
1
+ import { HttpMetadata, ResponseMetaType, ValidationError, isWithMetaHandler } from "@modern-js/bff-core";
2
+ import { parse } from "@modern-js/create-request/qs";
3
+ import type_is from "type-is";
4
+ const createHonoRoutes = (handlerInfos = [])=>handlerInfos.map(({ routePath, handler, httpMethod })=>{
5
+ const routeMiddlwares = Reflect.getMetadata('middleware', handler) || [];
6
+ const honoHandler = createHonoHandler(handler);
7
+ return {
8
+ method: httpMethod.toLowerCase(),
9
+ path: routePath,
10
+ handler: routeMiddlwares.length > 0 ? [
11
+ ...routeMiddlwares,
12
+ honoHandler
13
+ ] : honoHandler
14
+ };
15
+ });
16
+ const handleResponseMeta = (c, handler)=>{
17
+ const responseMeta = Reflect.getMetadata(HttpMetadata.Response, handler);
18
+ if (Array.isArray(responseMeta)) for (const meta of responseMeta)switch(meta.type){
19
+ case ResponseMetaType.Headers:
20
+ for (const [key, value] of Object.entries(meta.value))c.header(key, value);
21
+ break;
22
+ case ResponseMetaType.Redirect:
23
+ return c.redirect(meta.value);
24
+ case ResponseMetaType.StatusCode:
25
+ c.status(meta.value);
26
+ break;
27
+ default:
28
+ break;
29
+ }
30
+ return null;
31
+ };
32
+ const createHonoHandler = (handler)=>async (c)=>{
33
+ const input = await getHonoInput(c);
34
+ if (isWithMetaHandler(handler)) try {
35
+ const response = handleResponseMeta(c, handler);
36
+ if (response) return response;
37
+ if (c.finalized) return;
38
+ const result = await handler(input);
39
+ if (result instanceof Response) return result;
40
+ return result && 'object' == typeof result ? c.json(result) : c.body(result);
41
+ } catch (error) {
42
+ if (error instanceof ValidationError) {
43
+ c.status(error.status);
44
+ return c.json({
45
+ message: error.message
46
+ });
47
+ }
48
+ throw error;
49
+ }
50
+ {
51
+ const routePath = c.req.routePath;
52
+ const paramNames = routePath.match(/:\w+/g)?.map((s)=>s.slice(1)) || [];
53
+ const params = Object.fromEntries(paramNames.map((name)=>[
54
+ name,
55
+ input.params[name]
56
+ ]));
57
+ const args = Object.values(params).concat(input);
58
+ const body = await handler(...args);
59
+ if (c.finalized) return await Promise.resolve();
60
+ if (void 0 !== body) {
61
+ if (body instanceof Response) return body;
62
+ return c.json(body);
63
+ }
64
+ }
65
+ };
66
+ const getHonoInput = async (c)=>{
67
+ const draft = {
68
+ params: c.req.param(),
69
+ query: parse(c.req.query()),
70
+ headers: c.req.header(),
71
+ cookies: c.req.header('cookie')
72
+ };
73
+ try {
74
+ const contentType = c.req.header('content-type') || '';
75
+ if (type_is.is(contentType, [
76
+ 'application/json'
77
+ ])) draft.data = await c.req.json();
78
+ else if (type_is.is(contentType, [
79
+ 'multipart/form-data'
80
+ ])) draft.formData = await c.req.parseBody();
81
+ else if (type_is.is(contentType, [
82
+ 'application/x-www-form-urlencoded'
83
+ ])) draft.formUrlencoded = await c.req.parseBody();
84
+ else draft.body = await c.req.json();
85
+ } catch (error) {
86
+ draft.body = null;
87
+ }
88
+ return draft;
89
+ };
90
+ const utils_createHonoRoutes = createHonoRoutes;
91
+ export { createHonoHandler, utils_createHonoRoutes as default };
@@ -0,0 +1,34 @@
1
+ import path from "path";
2
+ const PACKAGE_NAME = '{packageName}';
3
+ const PREFIX = '{prefix}';
4
+ const API_DIR = '{apiDirectory}';
5
+ const LAMBDA_DIR = '{lambdaDirectory}';
6
+ const DIST_DIR = '{distDirectory}';
7
+ const NODE_MODULES = 'node_modules';
8
+ const crossProjectApiPlugin = ()=>({
9
+ name: '@modern-js/plugin-independent-bff',
10
+ post: [
11
+ '@modern-js/plugin-bff'
12
+ ],
13
+ setup: (api)=>{
14
+ api.modifyResolvedConfig((resolvedConfig)=>{
15
+ const { appDirectory: originAppDirectory } = api.getAppContext();
16
+ const sdkPath = path.join(originAppDirectory, NODE_MODULES, PACKAGE_NAME);
17
+ const sdkDistPath = path.join(sdkPath, DIST_DIR);
18
+ const apiDirectory = path.join(sdkDistPath, API_DIR);
19
+ const lambdaDirectory = path.resolve(sdkDistPath, LAMBDA_DIR);
20
+ api.updateAppContext({
21
+ apiDirectory,
22
+ lambdaDirectory
23
+ });
24
+ const config = api.getConfig();
25
+ if (config?.bff?.prefix) console.warn(`[WARNING] Detected bff.prefix configuration: "${config.bff.prefix}".
26
+ When using cross-project BFF, you should not configure bff.prefix as it may cause API path conflicts or access issues. Please remove the bff.prefix configuration.`);
27
+ resolvedConfig.bff.prefix = PREFIX;
28
+ resolvedConfig.bff.isCrossProjectServer = true;
29
+ return resolvedConfig;
30
+ });
31
+ }
32
+ });
33
+ const utils_crossProjectApiPlugin = crossProjectApiPlugin;
34
+ export { API_DIR, DIST_DIR, LAMBDA_DIR, PACKAGE_NAME, PREFIX, crossProjectApiPlugin, utils_crossProjectApiPlugin as default };
@@ -0,0 +1,29 @@
1
+ import path from "path";
2
+ import { fs, logger, normalizeToPosixPath } from "@modern-js/utils";
3
+ import { API_DIR, DIST_DIR, LAMBDA_DIR, PACKAGE_NAME, PREFIX } from "./crossProjectApiPlugin.mjs";
4
+ function replaceContent(source, packageName, prefix, relativeDistPath, relativeApiPath, relativeLambdaPath) {
5
+ const updatedSource = source.replace(new RegExp(PACKAGE_NAME, 'g'), packageName).replace(new RegExp(PREFIX, 'g'), prefix).replace(new RegExp(DIST_DIR, 'g'), normalizeToPosixPath(relativeDistPath)).replace(new RegExp(API_DIR, 'g'), normalizeToPosixPath(relativeApiPath)).replace(new RegExp(LAMBDA_DIR, 'g'), normalizeToPosixPath(relativeLambdaPath));
6
+ return updatedSource;
7
+ }
8
+ async function pluginGenerator({ prefix, appDirectory, relativeDistPath, relativeApiPath, relativeLambdaPath }) {
9
+ try {
10
+ const packageContent = await fs.readFile(path.resolve(appDirectory, './package.json'), 'utf8');
11
+ const packageJson = JSON.parse(packageContent);
12
+ const pluginDir = path.resolve(appDirectory, `./${relativeDistPath}`, 'plugin');
13
+ const pluginPath = path.join(pluginDir, 'index.js');
14
+ const pluginTemplate = await fs.readFile(path.resolve(__dirname, 'crossProjectApiPlugin.js'), 'utf8');
15
+ const updatedPlugin = replaceContent(pluginTemplate, packageJson.name, prefix, relativeDistPath, relativeApiPath, relativeLambdaPath);
16
+ await fs.ensureFile(pluginPath);
17
+ await fs.writeFile(pluginPath, updatedPlugin);
18
+ const typeContent = `import type { AppTools, CliPlugin } from '@modern-js/app-tools';
19
+ export declare const crossProjectApiPlugin: () => CliPlugin<AppTools>`;
20
+ const pluginTypePath = path.join(pluginDir, 'index.d.ts');
21
+ await fs.ensureFile(pluginTypePath);
22
+ await fs.writeFile(pluginTypePath, typeContent);
23
+ logger.info('Api plugin generate succeed');
24
+ } catch (error) {
25
+ logger.error('Api plugin generate failed:', error);
26
+ }
27
+ }
28
+ const utils_pluginGenerator = pluginGenerator;
29
+ export { utils_pluginGenerator as default };
@@ -0,0 +1,43 @@
1
+ import path from "path";
2
+ import { fs } from "@modern-js/utils";
3
+ const getPackageName = (appDirectory)=>{
4
+ try {
5
+ const packageJsonPath = path.resolve(appDirectory, './package.json');
6
+ const packageJson = require(packageJsonPath);
7
+ return packageJson.name;
8
+ } catch (error) {
9
+ return;
10
+ }
11
+ };
12
+ async function runtimeGenerator({ runtime, appDirectory, relativeDistPath, packageName }) {
13
+ const pluginDir = path.resolve(appDirectory, `./${relativeDistPath}`, 'runtime');
14
+ const requestId = packageName || getPackageName(appDirectory) || process.env.npm_package_name || 'default';
15
+ const source = `import { configure as _configure } from '${runtime}'
16
+ const configure = (options) => {
17
+ return _configure({
18
+ ...options,
19
+ requestId: '${requestId}',
20
+ });
21
+ }
22
+ export { configure }
23
+ `;
24
+ const pluginPath = path.join(pluginDir, 'index.js');
25
+ await fs.ensureFile(pluginPath);
26
+ await fs.writeFile(pluginPath, source);
27
+ const tsSource = `type IOptions<F = typeof fetch> = {
28
+ request?: F;
29
+ interceptor?: (request: F) => F;
30
+ allowedHeaders?: string[];
31
+ setDomain?: (ops?: {
32
+ target: 'node' | 'browser';
33
+ requestId: string;
34
+ }) => string;
35
+ requestId?: string;
36
+ };
37
+ export declare const configure: (options: IOptions) => void;`;
38
+ const pluginTypePath = path.join(pluginDir, 'index.d.ts');
39
+ await fs.ensureFile(pluginTypePath);
40
+ await fs.writeFile(pluginTypePath, tsSource);
41
+ }
42
+ const utils_runtimeGenerator = runtimeGenerator;
43
+ export { utils_runtimeGenerator as default };
@@ -1,5 +1,5 @@
1
1
  import type { HttpMethodDecider } from '@modern-js/types';
2
- import type { LoaderContext } from 'webpack';
2
+ import type { Rspack } from '@rsbuild/core';
3
3
  export type APILoaderOptions = {
4
4
  prefix: string;
5
5
  appDir: string;
@@ -12,5 +12,5 @@ export type APILoaderOptions = {
12
12
  target: string;
13
13
  httpMethodDecider?: HttpMethodDecider;
14
14
  };
15
- declare function loader(this: LoaderContext<APILoaderOptions>, source: string): Promise<void>;
15
+ declare function loader(this: Rspack.LoaderContext<APILoaderOptions>, source: string): Promise<void>;
16
16
  export default loader;
@@ -1,4 +1,4 @@
1
- import type { MiddlewareHandler, PluginAPI, ServerMiddleware } from '@modern-js/server-core';
1
+ import type { MiddlewareHandler, ServerMiddleware, ServerPluginAPI } from '@modern-js/server-core';
2
2
  import { Hono } from '@modern-js/server-core';
3
3
  interface MiddlewareOptions {
4
4
  prefix: string;
@@ -7,9 +7,9 @@ interface MiddlewareOptions {
7
7
  export declare class HonoAdapter {
8
8
  apiMiddleware: ServerMiddleware[];
9
9
  apiServer: Hono | null;
10
- api: PluginAPI;
10
+ api: ServerPluginAPI;
11
11
  isHono: boolean;
12
- constructor(api: PluginAPI);
12
+ constructor(api: ServerPluginAPI);
13
13
  setHandlers: () => Promise<void>;
14
14
  registerApiRoutes: () => Promise<void>;
15
15
  registerMiddleware: (options: MiddlewareOptions) => Promise<void>;
@@ -1,3 +1,3 @@
1
- import type { ServerPluginLegacy } from '@modern-js/server-core';
2
- declare const _default: () => ServerPluginLegacy;
1
+ import type { ServerPlugin } from '@modern-js/server-core';
2
+ declare const _default: () => ServerPlugin;
3
3
  export default _default;