@bleedingdev/modern-js-plugin-bff 3.2.0-ultramodern.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 (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +26 -0
  3. package/cli.js +1 -0
  4. package/dist/cjs/cli.js +294 -0
  5. package/dist/cjs/constants.js +48 -0
  6. package/dist/cjs/index.js +58 -0
  7. package/dist/cjs/loader.js +106 -0
  8. package/dist/cjs/runtime/create-request/index.js +48 -0
  9. package/dist/cjs/runtime/data-platform/index.js +693 -0
  10. package/dist/cjs/runtime/effect/adapter.js +311 -0
  11. package/dist/cjs/runtime/effect/context.js +48 -0
  12. package/dist/cjs/runtime/effect/index.js +608 -0
  13. package/dist/cjs/runtime/effect-client/index.js +178 -0
  14. package/dist/cjs/runtime/hono/adapter.js +168 -0
  15. package/dist/cjs/runtime/hono/index.js +65 -0
  16. package/dist/cjs/runtime/hono/operators.js +68 -0
  17. package/dist/cjs/server.js +179 -0
  18. package/dist/cjs/utils/clientGenerator.js +342 -0
  19. package/dist/cjs/utils/createHonoRoutes.js +138 -0
  20. package/dist/cjs/utils/crossProjectApiPlugin.js +118 -0
  21. package/dist/cjs/utils/effectClientGenerator.js +673 -0
  22. package/dist/cjs/utils/pluginGenerator.js +73 -0
  23. package/dist/cjs/utils/runtimeGenerator.js +133 -0
  24. package/dist/esm/cli.mjs +245 -0
  25. package/dist/esm/constants.mjs +11 -0
  26. package/dist/esm/index.mjs +1 -0
  27. package/dist/esm/loader.mjs +62 -0
  28. package/dist/esm/runtime/create-request/index.mjs +1 -0
  29. package/dist/esm/runtime/data-platform/index.mjs +599 -0
  30. package/dist/esm/runtime/effect/adapter.mjs +267 -0
  31. package/dist/esm/runtime/effect/context.mjs +11 -0
  32. package/dist/esm/runtime/effect/index.mjs +438 -0
  33. package/dist/esm/runtime/effect-client/index.mjs +90 -0
  34. package/dist/esm/runtime/hono/adapter.mjs +124 -0
  35. package/dist/esm/runtime/hono/index.mjs +2 -0
  36. package/dist/esm/runtime/hono/operators.mjs +31 -0
  37. package/dist/esm/server.mjs +135 -0
  38. package/dist/esm/utils/clientGenerator.mjs +293 -0
  39. package/dist/esm/utils/createHonoRoutes.mjs +92 -0
  40. package/dist/esm/utils/crossProjectApiPlugin.mjs +54 -0
  41. package/dist/esm/utils/effectClientGenerator.mjs +623 -0
  42. package/dist/esm/utils/pluginGenerator.mjs +29 -0
  43. package/dist/esm/utils/runtimeGenerator.mjs +89 -0
  44. package/dist/esm-node/cli.mjs +249 -0
  45. package/dist/esm-node/constants.mjs +12 -0
  46. package/dist/esm-node/index.mjs +2 -0
  47. package/dist/esm-node/loader.mjs +64 -0
  48. package/dist/esm-node/runtime/create-request/index.mjs +2 -0
  49. package/dist/esm-node/runtime/data-platform/index.mjs +600 -0
  50. package/dist/esm-node/runtime/effect/adapter.mjs +269 -0
  51. package/dist/esm-node/runtime/effect/context.mjs +12 -0
  52. package/dist/esm-node/runtime/effect/index.mjs +439 -0
  53. package/dist/esm-node/runtime/effect-client/index.mjs +91 -0
  54. package/dist/esm-node/runtime/hono/adapter.mjs +125 -0
  55. package/dist/esm-node/runtime/hono/index.mjs +3 -0
  56. package/dist/esm-node/runtime/hono/operators.mjs +32 -0
  57. package/dist/esm-node/server.mjs +136 -0
  58. package/dist/esm-node/utils/clientGenerator.mjs +294 -0
  59. package/dist/esm-node/utils/createHonoRoutes.mjs +93 -0
  60. package/dist/esm-node/utils/crossProjectApiPlugin.mjs +55 -0
  61. package/dist/esm-node/utils/effectClientGenerator.mjs +625 -0
  62. package/dist/esm-node/utils/pluginGenerator.mjs +33 -0
  63. package/dist/esm-node/utils/runtimeGenerator.mjs +91 -0
  64. package/dist/types/cli.d.ts +3 -0
  65. package/dist/types/constants.d.ts +2 -0
  66. package/dist/types/index.d.ts +1 -0
  67. package/dist/types/loader.d.ts +27 -0
  68. package/dist/types/runtime/create-request/index.d.ts +2 -0
  69. package/dist/types/runtime/data-platform/index.d.ts +187 -0
  70. package/dist/types/runtime/effect/adapter.d.ts +22 -0
  71. package/dist/types/runtime/effect/context.d.ts +8 -0
  72. package/dist/types/runtime/effect/index.d.ts +171 -0
  73. package/dist/types/runtime/effect-client/index.d.ts +47 -0
  74. package/dist/types/runtime/hono/adapter.d.ts +19 -0
  75. package/dist/types/runtime/hono/index.d.ts +2 -0
  76. package/dist/types/runtime/hono/operators.d.ts +10 -0
  77. package/dist/types/server.d.ts +3 -0
  78. package/dist/types/utils/clientGenerator.d.ts +37 -0
  79. package/dist/types/utils/createHonoRoutes.d.ts +10 -0
  80. package/dist/types/utils/crossProjectApiPlugin.d.ts +9 -0
  81. package/dist/types/utils/effectClientGenerator.d.ts +27 -0
  82. package/dist/types/utils/pluginGenerator.d.ts +9 -0
  83. package/dist/types/utils/runtimeGenerator.d.ts +7 -0
  84. package/docs/data-platform-architecture.md +61 -0
  85. package/package.json +172 -0
  86. package/rslib.config.mts +4 -0
  87. package/rstest.config.mts +10 -0
  88. package/server.js +1 -0
@@ -0,0 +1,32 @@
1
+ import "node:module";
2
+ import { useHonoContext } from "@modern-js/server-core";
3
+ const Pipe = (func)=>({
4
+ name: 'pipe',
5
+ async execute (executeHelper, next) {
6
+ const { inputs } = executeHelper;
7
+ const ctx = useHonoContext();
8
+ const { res } = ctx;
9
+ if ('function' == typeof func) {
10
+ let isPiped = true;
11
+ const end = (value)=>{
12
+ isPiped = false;
13
+ if ('function' == typeof value) return void value(res);
14
+ return value;
15
+ };
16
+ const output = await func(inputs, end);
17
+ if (!isPiped) if (output) return executeHelper.result = output;
18
+ else return;
19
+ executeHelper.inputs = output;
20
+ await next();
21
+ }
22
+ }
23
+ });
24
+ const Middleware = (middleware)=>({
25
+ name: 'middleware',
26
+ metadata (helper) {
27
+ const middlewares = helper.getMetadata('pipe') || [];
28
+ middlewares.push(middleware);
29
+ helper.setMetadata('middleware', middlewares);
30
+ }
31
+ });
32
+ export { Middleware, Pipe };
@@ -0,0 +1,136 @@
1
+ import "node:module";
2
+ import { ApiRouter } from "@modern-js/bff-core";
3
+ import { API_DIR, isFunction, isWebOnly } from "@modern-js/utils";
4
+ import path from "path";
5
+ import { EffectAdapter } from "./runtime/effect/adapter.mjs";
6
+ import { HonoAdapter } from "./runtime/hono/adapter.mjs";
7
+ const RUNTIME_ADAPTER_FACTORIES = {
8
+ hono: [
9
+ (api)=>new HonoAdapter(api)
10
+ ],
11
+ effect: [
12
+ (api)=>new EffectAdapter(api)
13
+ ]
14
+ };
15
+ const normalizePrefixList = (prefix)=>{
16
+ if (Array.isArray(prefix)) return prefix.filter(Boolean);
17
+ return [
18
+ prefix || '/api'
19
+ ];
20
+ };
21
+ const getPrimaryPrefix = (prefix)=>normalizePrefixList(prefix)[0] || '/api';
22
+ function resolveRuntimeFramework(runtimeFramework) {
23
+ return 'hono' === runtimeFramework ? 'hono' : 'effect';
24
+ }
25
+ class Storage {
26
+ reset() {
27
+ this.middlewares = [];
28
+ }
29
+ constructor(){
30
+ this.middlewares = [];
31
+ }
32
+ }
33
+ const server = ()=>({
34
+ name: '@modern-js/plugin-bff',
35
+ setup: (api)=>{
36
+ const storage = new Storage();
37
+ let apiRouter = null;
38
+ const appContext = api.getServerContext();
39
+ const runtimeFramework = resolveRuntimeFramework(appContext.bffRuntimeFramework);
40
+ const runtimeAdapters = RUNTIME_ADAPTER_FACTORIES[runtimeFramework].map((createAdapter)=>createAdapter(api));
41
+ api.onPrepare(async ()=>{
42
+ const appContext = api.getServerContext();
43
+ const { render } = appContext;
44
+ const { middlewares } = storage;
45
+ api.updateServerContext({
46
+ ...appContext,
47
+ apiMiddlewares: middlewares
48
+ });
49
+ const config = api.getServerConfig();
50
+ const prefixList = normalizePrefixList(config?.bff?.prefix);
51
+ const prefix = getPrimaryPrefix(config?.bff?.prefix);
52
+ const enableHandleWeb = config?.bff?.enableHandleWeb;
53
+ const httpMethodDecider = config?.bff?.httpMethodDecider;
54
+ const { distDirectory: pwd, middlewares: globalMiddlewares } = api.getServerContext();
55
+ const webOnly = await isWebOnly();
56
+ if ('hono' === runtimeFramework) {
57
+ let handler;
58
+ if (webOnly) handler = async (c, next)=>{
59
+ c.body('');
60
+ await next();
61
+ };
62
+ else {
63
+ const runner = api.getHooks();
64
+ const renderHandler = enableHandleWeb ? render : null;
65
+ handler = await runner.prepareApiServer.call({
66
+ pwd: pwd,
67
+ prefix,
68
+ render: renderHandler,
69
+ httpMethodDecider
70
+ });
71
+ }
72
+ if (handler && isFunction(handler)) globalMiddlewares.push({
73
+ name: 'bind-bff',
74
+ handler: (c, next)=>{
75
+ if (!prefixList.some((item)=>c.req.path.startsWith(item)) && !enableHandleWeb) return next();
76
+ return handler(c, next);
77
+ },
78
+ order: 'post',
79
+ before: [
80
+ 'custom-server-hook',
81
+ 'custom-server-middleware',
82
+ 'render'
83
+ ]
84
+ });
85
+ }
86
+ await Promise.all(runtimeAdapters.map((adapter)=>adapter.registerMiddleware({
87
+ prefix,
88
+ enableHandleWeb
89
+ })));
90
+ });
91
+ api.onReset(async ({ event })=>{
92
+ storage.reset();
93
+ const appContext = api.getServerContext();
94
+ const { middlewares } = storage;
95
+ api.updateServerContext({
96
+ ...appContext,
97
+ apiMiddlewares: middlewares
98
+ });
99
+ if ('file-change' === event.type) {
100
+ if ('hono' === runtimeFramework && apiRouter) {
101
+ const apiHandlerInfos = await apiRouter.getApiHandlers();
102
+ const appContext = api.getServerContext();
103
+ api.updateServerContext({
104
+ ...appContext,
105
+ apiHandlerInfos
106
+ });
107
+ }
108
+ await Promise.all(runtimeAdapters.map((adapter)=>adapter.onApiHandlersUpdated?.()));
109
+ }
110
+ });
111
+ const prepareApiServer = async (input, next)=>{
112
+ if ('hono' !== runtimeFramework) return next(input);
113
+ const { pwd, prefix, httpMethodDecider } = input;
114
+ const defaultApiDirectory = path.resolve(pwd, API_DIR);
115
+ const appContext = api.getServerContext();
116
+ const apiDirectory = 'string' == typeof appContext.apiDirectory ? appContext.apiDirectory : defaultApiDirectory;
117
+ const lambdaDirectory = 'string' == typeof appContext.lambdaDirectory ? appContext.lambdaDirectory : void 0;
118
+ apiRouter = new ApiRouter({
119
+ appDir: pwd,
120
+ apiDir: apiDirectory,
121
+ lambdaDir: lambdaDirectory,
122
+ prefix,
123
+ httpMethodDecider
124
+ });
125
+ const apiHandlerInfos = await apiRouter.getApiHandlers();
126
+ api.updateServerContext({
127
+ ...appContext,
128
+ apiRouter,
129
+ apiHandlerInfos
130
+ });
131
+ return next(input);
132
+ };
133
+ api.prepareApiServer(prepareApiServer);
134
+ }
135
+ });
136
+ export default server;
@@ -0,0 +1,294 @@
1
+ import "node:module";
2
+ import { generateClient } from "@modern-js/bff-core";
3
+ import { fs, logger } from "@modern-js/utils";
4
+ import path from "path";
5
+ import { generateEffectClientCode, renderEffectClientDeclaration, resolveEffectEntryFile } from "./effectClientGenerator.mjs";
6
+ const API_DIR = 'api';
7
+ const PLUGIN_DIR = 'plugin';
8
+ const RUNTIME_DIR = 'runtime';
9
+ const CLIENT_DIR = 'client';
10
+ const EXPORT_PREFIX = `./${API_DIR}/`;
11
+ const TYPE_PREFIX = `${API_DIR}/`;
12
+ const GENERATED_RUNTIME_DIRS = [
13
+ CLIENT_DIR,
14
+ PLUGIN_DIR,
15
+ RUNTIME_DIR
16
+ ];
17
+ const toPosixPath = (p)=>p.replace(/\\/g, '/');
18
+ const posixJoin = (...args)=>toPosixPath(path.join(...args));
19
+ function getPackageName(appDirectory) {
20
+ try {
21
+ const packageJsonPath = path.resolve(appDirectory, './package.json');
22
+ const packageJson = fs.readJSONSync(packageJsonPath);
23
+ return packageJson.name;
24
+ } catch {
25
+ return;
26
+ }
27
+ }
28
+ function createFileDetails(options) {
29
+ const { appDirectory, baseDirectory, resourcePath, source, relativeDistPath } = options;
30
+ const relativePath = path.relative(baseDirectory, resourcePath);
31
+ const parsedPath = path.parse(relativePath);
32
+ const targetDir = posixJoin(`./${relativeDistPath}/${CLIENT_DIR}`, parsedPath.dir, `${parsedPath.name}.js`);
33
+ const absTargetDir = path.resolve(targetDir);
34
+ const relativePathFromAppDirectory = path.relative(appDirectory, path.dirname(resourcePath));
35
+ const typesFilePath = posixJoin(`./${relativeDistPath}`, relativePathFromAppDirectory, `${parsedPath.name}.d.ts`);
36
+ return {
37
+ resourcePath,
38
+ source,
39
+ targetDir,
40
+ name: parsedPath.name,
41
+ absTargetDir,
42
+ relativeTargetDistDir: `./${typesFilePath}`,
43
+ exportKey: toPosixPath(path.join(parsedPath.dir, parsedPath.name))
44
+ };
45
+ }
46
+ async function readDirectoryFiles(appDirectory, directory, relativeDistPath) {
47
+ const filesList = [];
48
+ async function readFiles(currentPath) {
49
+ const entries = await fs.readdir(currentPath, {
50
+ withFileTypes: true
51
+ });
52
+ for (const entry of entries){
53
+ if ('_app.ts' === entry.name) continue;
54
+ const resourcePath = path.join(currentPath, entry.name);
55
+ if (entry.isDirectory()) await readFiles(resourcePath);
56
+ else {
57
+ const source = await fs.readFile(resourcePath, 'utf8');
58
+ filesList.push(createFileDetails({
59
+ appDirectory,
60
+ baseDirectory: directory,
61
+ resourcePath,
62
+ source,
63
+ relativeDistPath
64
+ }));
65
+ }
66
+ }
67
+ }
68
+ await readFiles(directory);
69
+ return filesList;
70
+ }
71
+ function mergePackageJson(packageJson, files, typesVersion, exports, relativeDistPath) {
72
+ const distPrefix = toPosixPath(`./${relativeDistPath}/`);
73
+ const generatedPrefixes = GENERATED_RUNTIME_DIRS.map((dir)=>toPosixPath(`${distPrefix}${dir}/`));
74
+ const isManagedExportEntry = (value)=>{
75
+ if (!value) return false;
76
+ const values = [
77
+ value.import,
78
+ value.require,
79
+ value.types
80
+ ].filter(Boolean);
81
+ return values.every((entry)=>generatedPrefixes.some((prefix)=>entry.startsWith(prefix)));
82
+ };
83
+ const isManagedTypeEntry = (value)=>Array.isArray(value) && value.length > 0 && value.every((entry)=>generatedPrefixes.some((prefix)=>entry.startsWith(prefix)));
84
+ const normalizedFiles = [
85
+ ...new Set(files.map((file)=>toPosixPath(file)))
86
+ ];
87
+ const currentFiles = packageJson.files || [];
88
+ packageJson.files = [
89
+ ...new Set([
90
+ ...currentFiles.map((file)=>toPosixPath(file)),
91
+ ...normalizedFiles
92
+ ])
93
+ ];
94
+ packageJson.typesVersions ??= {};
95
+ const typesVersions = packageJson.typesVersions;
96
+ const starTypes = typesVersions['*'] || {};
97
+ const generatedTypeEntries = typesVersion['*'] || {};
98
+ const generatedTypeKeys = new Set(Object.keys(generatedTypeEntries));
99
+ const typeConflicts = Object.entries(starTypes).filter(([key, value])=>{
100
+ if (!generatedTypeKeys.has(key) && !key.startsWith(TYPE_PREFIX)) return false;
101
+ const generatedValue = generatedTypeEntries[key];
102
+ if (generatedValue) return JSON.stringify(value) !== JSON.stringify(generatedValue) && !isManagedTypeEntry(value);
103
+ return !isManagedTypeEntry(value);
104
+ }).map(([key])=>key);
105
+ if (typeConflicts.length > 0) throw new Error(`[plugin-bff] package.json typesVersions conflict on keys: ${typeConflicts.sort().join(', ')}. Rename these keys or move them outside "${TYPE_PREFIX}" namespace.`);
106
+ Object.keys(starTypes).forEach((key)=>{
107
+ if (generatedTypeKeys.has(key) || key.startsWith(TYPE_PREFIX)) delete starTypes[key];
108
+ });
109
+ typesVersions['*'] = {
110
+ ...starTypes,
111
+ ...generatedTypeEntries
112
+ };
113
+ packageJson.exports ??= {};
114
+ const packageExports = packageJson.exports;
115
+ const generatedExportKeys = new Set(Object.keys(exports));
116
+ const exportConflicts = Object.entries(packageExports).filter(([key, value])=>{
117
+ if (!generatedExportKeys.has(key) && !key.startsWith(EXPORT_PREFIX)) return false;
118
+ const generatedValue = exports[key];
119
+ if (generatedValue) return JSON.stringify(value) !== JSON.stringify(generatedValue) && !isManagedExportEntry(value);
120
+ return !isManagedExportEntry(value);
121
+ }).map(([key])=>key);
122
+ if (exportConflicts.length > 0) throw new Error(`[plugin-bff] package.json exports conflict on keys: ${exportConflicts.sort().join(', ')}. Rename these exports or move them outside "${EXPORT_PREFIX}" namespace.`);
123
+ Object.keys(packageExports).forEach((key)=>{
124
+ if (generatedExportKeys.has(key) || key.startsWith(EXPORT_PREFIX)) delete packageExports[key];
125
+ });
126
+ Object.assign(packageExports, exports);
127
+ }
128
+ async function writeTargetFile(absTargetDir, content) {
129
+ await fs.mkdir(path.dirname(absTargetDir), {
130
+ recursive: true
131
+ });
132
+ await fs.writeFile(absTargetDir, content);
133
+ }
134
+ function getClientPackageName(appDirectory) {
135
+ const packageName = getPackageName(appDirectory) || path.basename(appDirectory);
136
+ if (packageName.startsWith('@') && packageName.includes('/')) {
137
+ const [scope, name] = packageName.split('/');
138
+ return `${scope}/${name}-bff-client`;
139
+ }
140
+ return `${packageName}-bff-client`;
141
+ }
142
+ async function writeClientModuleBoundary(appDirectory, relativeDistPath) {
143
+ await writeTargetFile(path.resolve(appDirectory, relativeDistPath, CLIENT_DIR, 'package.json'), `${JSON.stringify({
144
+ private: true,
145
+ name: getClientPackageName(appDirectory),
146
+ type: 'module'
147
+ }, null, 2)}\n`);
148
+ }
149
+ async function setPackage(files, appDirectory, relativeDistPath) {
150
+ const packagePath = path.resolve(appDirectory, './package.json');
151
+ const packageContent = await fs.readFile(packagePath, 'utf8');
152
+ const packageJson = JSON.parse(packageContent);
153
+ const sortedFiles = [
154
+ ...files
155
+ ].sort((a, b)=>a.exportKey.localeCompare(b.exportKey));
156
+ const addFiles = [
157
+ posixJoin(relativeDistPath, CLIENT_DIR, '**', '*'),
158
+ posixJoin(relativeDistPath, RUNTIME_DIR, '**', '*'),
159
+ posixJoin(relativeDistPath, PLUGIN_DIR, '**', '*')
160
+ ];
161
+ const typesVersions = {
162
+ '*': sortedFiles.reduce((acc, file)=>{
163
+ const typeFilePath = toPosixPath(`./${file.targetDir}`).replace(/\.js$/, '.d.ts');
164
+ return {
165
+ ...acc,
166
+ [toPosixPath(`${TYPE_PREFIX}${file.exportKey}`)]: [
167
+ typeFilePath
168
+ ]
169
+ };
170
+ }, {
171
+ [`${API_DIR}/*`]: [
172
+ toPosixPath(`./${relativeDistPath}/${CLIENT_DIR}/*.d.ts`)
173
+ ],
174
+ [RUNTIME_DIR]: [
175
+ toPosixPath(`./${relativeDistPath}/${RUNTIME_DIR}/index.d.ts`)
176
+ ],
177
+ [PLUGIN_DIR]: [
178
+ toPosixPath(`./${relativeDistPath}/${PLUGIN_DIR}/index.d.ts`)
179
+ ]
180
+ })
181
+ };
182
+ const exports = sortedFiles.reduce((acc, file)=>{
183
+ const exportKey = `${EXPORT_PREFIX}${file.exportKey}`;
184
+ const jsFilePath = toPosixPath(`./${file.targetDir}`);
185
+ return {
186
+ ...acc,
187
+ [toPosixPath(exportKey)]: {
188
+ import: jsFilePath,
189
+ types: toPosixPath(jsFilePath.replace(/\.js$/, '.d.ts'))
190
+ }
191
+ };
192
+ }, {
193
+ [toPosixPath(`./${API_DIR}/*`)]: {
194
+ import: toPosixPath(`./${relativeDistPath}/${CLIENT_DIR}/*.js`),
195
+ types: toPosixPath(`./${relativeDistPath}/${CLIENT_DIR}/*.d.ts`)
196
+ },
197
+ [toPosixPath(`./${PLUGIN_DIR}`)]: {
198
+ import: toPosixPath(`./${relativeDistPath}/${PLUGIN_DIR}/index.js`),
199
+ require: toPosixPath(`./${relativeDistPath}/${PLUGIN_DIR}/index.js`),
200
+ types: toPosixPath(`./${relativeDistPath}/${PLUGIN_DIR}/index.d.ts`)
201
+ },
202
+ [toPosixPath(`./${RUNTIME_DIR}`)]: {
203
+ import: toPosixPath(`./${relativeDistPath}/${RUNTIME_DIR}/index.js`),
204
+ require: toPosixPath(`./${relativeDistPath}/${RUNTIME_DIR}/index.js`),
205
+ types: toPosixPath(`./${relativeDistPath}/${RUNTIME_DIR}/index.d.ts`)
206
+ }
207
+ });
208
+ mergePackageJson(packageJson, addFiles, typesVersions, exports, relativeDistPath);
209
+ await fs.promises.writeFile(packagePath, JSON.stringify(packageJson, null, 2));
210
+ }
211
+ async function copyFiles(from, to) {
212
+ if (await fs.pathExists(from)) await fs.copy(toPosixPath(from), toPosixPath(to));
213
+ }
214
+ async function clientGenerator(draftOptions) {
215
+ const generatedClientDir = path.resolve(draftOptions.appDir, draftOptions.relativeDistPath, CLIENT_DIR);
216
+ await fs.remove(generatedClientDir);
217
+ const requestId = getPackageName(draftOptions.appDir) || process.env.npm_package_name;
218
+ const lambdaSourceList = draftOptions.existLambda ? await readDirectoryFiles(draftOptions.appDir, draftOptions.lambdaDir, draftOptions.relativeDistPath) : [];
219
+ const generatedSourceList = [
220
+ ...lambdaSourceList
221
+ ];
222
+ const getClitentCode = async (resourcePath, source)=>{
223
+ const warning = `The file ${resourcePath} is not allowed to be imported in src directory, only API definition files are allowed.`;
224
+ if (!draftOptions.existLambda) return void logger.warn(warning);
225
+ const options = {
226
+ prefix: Array.isArray(draftOptions.prefix) ? draftOptions.prefix[0] : draftOptions.prefix,
227
+ appDir: draftOptions.appDir,
228
+ apiDir: draftOptions.apiDir,
229
+ lambdaDir: draftOptions.lambdaDir,
230
+ port: Number(draftOptions.port),
231
+ source,
232
+ resourcePath,
233
+ target: 'bundle',
234
+ httpMethodDecider: draftOptions.httpMethodDecider,
235
+ requestCreator: draftOptions.requestCreator,
236
+ requestId
237
+ };
238
+ const { lambdaDir } = draftOptions;
239
+ if (!resourcePath.startsWith(lambdaDir)) return void logger.warn(warning);
240
+ const result = await generateClient(options);
241
+ return result;
242
+ };
243
+ try {
244
+ for (const source of lambdaSourceList){
245
+ const code = await getClitentCode(source.resourcePath, source.source);
246
+ if (code?.value) {
247
+ await writeTargetFile(source.absTargetDir, code.value);
248
+ await copyFiles(source.relativeTargetDistDir, source.targetDir.replace("js", 'd.ts'));
249
+ }
250
+ }
251
+ if ('effect' === draftOptions.bffRuntimeFramework) {
252
+ const effectEntryFile = resolveEffectEntryFile({
253
+ appDir: draftOptions.appDir,
254
+ apiDir: draftOptions.apiDir,
255
+ effectEntry: draftOptions.effectEntry
256
+ });
257
+ if (effectEntryFile) {
258
+ const effectSource = await fs.readFile(effectEntryFile, 'utf8');
259
+ const effectFileDetails = createFileDetails({
260
+ appDirectory: draftOptions.appDir,
261
+ baseDirectory: draftOptions.apiDir,
262
+ resourcePath: effectEntryFile,
263
+ source: effectSource,
264
+ relativeDistPath: draftOptions.relativeDistPath
265
+ });
266
+ const effectClientCode = await generateEffectClientCode({
267
+ appDir: draftOptions.appDir,
268
+ apiDir: draftOptions.apiDir,
269
+ resourcePath: effectEntryFile,
270
+ prefix: Array.isArray(draftOptions.prefix) ? draftOptions.prefix[0] : draftOptions.prefix,
271
+ port: Number(draftOptions.port),
272
+ target: 'bundle',
273
+ requestCreator: draftOptions.requestCreator,
274
+ httpMethodDecider: draftOptions.httpMethodDecider,
275
+ dataPlatformBatch: draftOptions.effectDataPlatformBatch
276
+ });
277
+ if (effectClientCode) {
278
+ const targetTypeFile = effectFileDetails.targetDir.replace(/\.js$/, '.d.ts');
279
+ await writeTargetFile(effectFileDetails.absTargetDir, effectClientCode);
280
+ await writeTargetFile(path.resolve(targetTypeFile), renderEffectClientDeclaration());
281
+ generatedSourceList.push(effectFileDetails);
282
+ }
283
+ }
284
+ }
285
+ logger.info("Client bundle generate succeed");
286
+ } catch (error) {
287
+ logger.error(`Client bundle generate failed: ${error}`);
288
+ }
289
+ if (generatedSourceList.length > 0) await writeClientModuleBoundary(draftOptions.appDir, draftOptions.relativeDistPath);
290
+ await setPackage(generatedSourceList, draftOptions.appDir, draftOptions.relativeDistPath);
291
+ }
292
+ const utils_clientGenerator = clientGenerator;
293
+ export default utils_clientGenerator;
294
+ export { copyFiles, readDirectoryFiles };
@@ -0,0 +1,93 @@
1
+ import "node:module";
2
+ import { HttpMetadata, ResponseMetaType, ValidationError, isWithMetaHandler } from "@modern-js/bff-core";
3
+ import { parse } from "qs";
4
+ import type_is from "type-is";
5
+ const createHonoRoutes = (handlerInfos = [])=>handlerInfos.map(({ routePath, handler, httpMethod })=>{
6
+ const routeMiddlwares = Reflect.getMetadata('middleware', handler) || [];
7
+ const honoHandler = createHonoHandler(handler);
8
+ return {
9
+ method: httpMethod.toLowerCase(),
10
+ path: routePath,
11
+ handler: routeMiddlwares.length > 0 ? [
12
+ ...routeMiddlwares,
13
+ honoHandler
14
+ ] : honoHandler
15
+ };
16
+ });
17
+ const handleResponseMeta = (c, handler)=>{
18
+ const responseMeta = Reflect.getMetadata(HttpMetadata.Response, handler);
19
+ if (Array.isArray(responseMeta)) for (const meta of responseMeta)switch(meta.type){
20
+ case ResponseMetaType.Headers:
21
+ for (const [key, value] of Object.entries(meta.value))c.header(key, value);
22
+ break;
23
+ case ResponseMetaType.Redirect:
24
+ return c.redirect(meta.value);
25
+ case ResponseMetaType.StatusCode:
26
+ c.status(meta.value);
27
+ break;
28
+ default:
29
+ break;
30
+ }
31
+ return null;
32
+ };
33
+ const createHonoHandler = (handler)=>async (c)=>{
34
+ const input = await getHonoInput(c);
35
+ if (isWithMetaHandler(handler)) try {
36
+ const response = handleResponseMeta(c, handler);
37
+ if (response) return response;
38
+ if (c.finalized) return;
39
+ const result = await handler(input);
40
+ if (result instanceof Response) return result;
41
+ return result && 'object' == typeof result ? c.json(result) : c.body(result);
42
+ } catch (error) {
43
+ if (error instanceof ValidationError) {
44
+ c.status(error.status);
45
+ return c.json({
46
+ message: error.message
47
+ });
48
+ }
49
+ throw error;
50
+ }
51
+ {
52
+ const routePath = c.req.routePath;
53
+ const paramNames = routePath.match(/:\w+/g)?.map((s)=>s.slice(1)) || [];
54
+ const params = Object.fromEntries(paramNames.map((name)=>[
55
+ name,
56
+ input.params[name]
57
+ ]));
58
+ const args = Object.values(params).concat(input);
59
+ const body = await handler(...args);
60
+ if (c.finalized) return await Promise.resolve();
61
+ if (void 0 !== body) {
62
+ if (body instanceof Response) return body;
63
+ return c.json(body);
64
+ }
65
+ }
66
+ };
67
+ const getHonoInput = async (c)=>{
68
+ const draft = {
69
+ params: c.req.param(),
70
+ query: parse(c.req.query()),
71
+ headers: c.req.header(),
72
+ cookies: c.req.header('cookie')
73
+ };
74
+ try {
75
+ const contentType = c.req.header('content-type') || '';
76
+ if (type_is.is(contentType, [
77
+ 'application/json'
78
+ ])) draft.data = await c.req.json();
79
+ else if (type_is.is(contentType, [
80
+ 'multipart/form-data'
81
+ ])) draft.formData = await c.req.parseBody();
82
+ else if (type_is.is(contentType, [
83
+ 'application/x-www-form-urlencoded'
84
+ ])) draft.formUrlencoded = await c.req.parseBody();
85
+ else draft.body = await c.req.json();
86
+ } catch (error) {
87
+ draft.body = null;
88
+ }
89
+ return draft;
90
+ };
91
+ const utils_createHonoRoutes = createHonoRoutes;
92
+ export default utils_createHonoRoutes;
93
+ export { createHonoHandler };
@@ -0,0 +1,55 @@
1
+ import "node:module";
2
+ import path from "path";
3
+ const PACKAGE_NAME = '{packageName}';
4
+ const PREFIX = '{prefix}';
5
+ const API_DIR = '{apiDirectory}';
6
+ const LAMBDA_DIR = '{lambdaDirectory}';
7
+ const DIST_DIR = '{distDirectory}';
8
+ const RUNTIME_FRAMEWORK = '{runtimeFramework}';
9
+ const NODE_MODULES = 'node_modules';
10
+ const crossProjectApiPlugin = ()=>({
11
+ name: '@modern-js/plugin-independent-bff',
12
+ post: [
13
+ '@modern-js/plugin-bff'
14
+ ],
15
+ setup: (api)=>{
16
+ api.modifyResolvedConfig((resolvedConfig)=>{
17
+ const { appDirectory: originAppDirectory } = api.getAppContext();
18
+ const sdkPath = path.join(originAppDirectory, NODE_MODULES, PACKAGE_NAME);
19
+ const sdkDistPath = path.join(sdkPath, DIST_DIR);
20
+ const apiDirectory = path.join(sdkDistPath, API_DIR);
21
+ const lambdaDirectory = path.resolve(sdkDistPath, LAMBDA_DIR);
22
+ api.updateAppContext({
23
+ apiDirectory,
24
+ lambdaDirectory,
25
+ bffRuntimeFramework: RUNTIME_FRAMEWORK
26
+ });
27
+ const config = api.getConfig();
28
+ const configuredPrefix = config?.bff?.prefix;
29
+ if (configuredPrefix) {
30
+ const isSamePrefix = Array.isArray(configuredPrefix) ? 1 === configuredPrefix.length && configuredPrefix[0] === PREFIX : configuredPrefix === PREFIX;
31
+ if (!isSamePrefix) throw new Error(`[${PACKAGE_NAME}] Invalid bff.prefix for cross-project BFF. Detected "${configuredPrefix}", expected "${PREFIX}". Remove bff.prefix from the consumer app, or set it exactly to "${PREFIX}".`);
32
+ }
33
+ const configuredRuntimeFramework = config?.bff?.runtimeFramework;
34
+ 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}".`);
35
+ resolvedConfig.bff.prefix = PREFIX;
36
+ resolvedConfig.bff.runtimeFramework = RUNTIME_FRAMEWORK;
37
+ resolvedConfig.bff.isCrossProjectServer = true;
38
+ resolvedConfig.bff.requestId = resolvedConfig.bff.requestId || config?.bff?.requestId || PACKAGE_NAME || 'default';
39
+ resolvedConfig.bff.crossProjectPolicy = {
40
+ ...resolvedConfig.bff.crossProjectPolicy || {},
41
+ enabled: resolvedConfig.bff.crossProjectPolicy?.enabled ?? true,
42
+ requireEnvelope: resolvedConfig.bff.crossProjectPolicy?.requireEnvelope ?? true,
43
+ requireOperationContext: resolvedConfig.bff.crossProjectPolicy?.requireOperationContext ?? true,
44
+ requireOperationContextDetails: resolvedConfig.bff.crossProjectPolicy?.requireOperationContextDetails ?? true,
45
+ requireOperationSchemaHash: resolvedConfig.bff.crossProjectPolicy?.requireOperationSchemaHash ?? true,
46
+ requireOperationVersion: resolvedConfig.bff.crossProjectPolicy?.requireOperationVersion ?? true,
47
+ allowUnknownOperations: resolvedConfig.bff.crossProjectPolicy?.allowUnknownOperations ?? false
48
+ };
49
+ return resolvedConfig;
50
+ });
51
+ }
52
+ });
53
+ const utils_crossProjectApiPlugin = crossProjectApiPlugin;
54
+ export default utils_crossProjectApiPlugin;
55
+ export { API_DIR, DIST_DIR, LAMBDA_DIR, PACKAGE_NAME, PREFIX, RUNTIME_FRAMEWORK, crossProjectApiPlugin };