hono-takibi 0.9.40 → 0.9.52
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.
- package/dist/cli/index.js +5 -10
- package/dist/config/index.js +1 -2
- package/dist/core/route.js +1 -2
- package/dist/core/rpc.js +21 -16
- package/dist/core/schema.js +13 -23
- package/dist/core/takibi.js +78 -63
- package/dist/generator/zod-openapi-hono/app/index.js +47 -20
- package/dist/helper/index.d.ts +0 -1
- package/dist/helper/index.js +0 -1
- package/dist/utils/index.d.ts +1 -148
- package/dist/utils/index.js +10 -218
- package/package.json +1 -1
- package/dist/helper/get-route-maps.d.ts +0 -12
- package/dist/helper/get-route-maps.js +0 -22
package/dist/cli/index.js
CHANGED
|
@@ -72,9 +72,8 @@ export async function honoTakibi() {
|
|
|
72
72
|
return { ok: false, error: cliResult.error };
|
|
73
73
|
const cli = cliResult.value;
|
|
74
74
|
const takibiResult = await takibi(cli.input, cli.output, cli.exportSchema ?? false, cli.exportType ?? false, cli.template ?? false, cli.test ?? false, cli.basePath);
|
|
75
|
-
if (!takibiResult.ok)
|
|
75
|
+
if (!takibiResult.ok)
|
|
76
76
|
return { ok: false, error: takibiResult.error };
|
|
77
|
-
}
|
|
78
77
|
return {
|
|
79
78
|
ok: true,
|
|
80
79
|
value: takibiResult.value,
|
|
@@ -91,30 +90,26 @@ export async function honoTakibi() {
|
|
|
91
90
|
? await takibi(c.input, c['zod-openapi']?.output, c['zod-openapi']?.exportSchema ?? false, c['zod-openapi']?.exportType ?? false, false, // template
|
|
92
91
|
false)
|
|
93
92
|
: undefined;
|
|
94
|
-
if (takibiResult && !takibiResult.ok)
|
|
93
|
+
if (takibiResult && !takibiResult.ok)
|
|
95
94
|
return { ok: false, error: takibiResult.error };
|
|
96
|
-
}
|
|
97
95
|
/** schema */
|
|
98
96
|
const schemaResult = c['zod-openapi']?.schema
|
|
99
97
|
? await schema(c.input, c['zod-openapi'].schema.output, c['zod-openapi'].schema.exportType ?? false, c['zod-openapi']?.schema.split ?? false)
|
|
100
98
|
: undefined;
|
|
101
|
-
if (schemaResult && !schemaResult.ok)
|
|
99
|
+
if (schemaResult && !schemaResult.ok)
|
|
102
100
|
return { ok: false, error: schemaResult.error };
|
|
103
|
-
}
|
|
104
101
|
/** route */
|
|
105
102
|
const routeResult = c['zod-openapi']?.route
|
|
106
103
|
? await route(c.input, c['zod-openapi'].route.output, c['zod-openapi'].route.import, c['zod-openapi'].route.split ?? false)
|
|
107
104
|
: undefined;
|
|
108
|
-
if (routeResult && !routeResult.ok)
|
|
105
|
+
if (routeResult && !routeResult.ok)
|
|
109
106
|
return { ok: false, error: routeResult.error };
|
|
110
|
-
}
|
|
111
107
|
/** rpc */
|
|
112
108
|
const rpcResult = c.rpc
|
|
113
109
|
? await rpc(c.input, c.rpc.output, c.rpc.import, c.rpc.split ?? false)
|
|
114
110
|
: undefined;
|
|
115
|
-
if (rpcResult && !rpcResult.ok)
|
|
111
|
+
if (rpcResult && !rpcResult.ok)
|
|
116
112
|
return { ok: false, error: rpcResult.error };
|
|
117
|
-
}
|
|
118
113
|
const results = [takibiResult?.value, rpcResult?.value].filter((v) => Boolean(v));
|
|
119
114
|
return {
|
|
120
115
|
ok: true,
|
package/dist/config/index.js
CHANGED
|
@@ -11,9 +11,8 @@ export async function config() {
|
|
|
11
11
|
register();
|
|
12
12
|
const url = pathToFileURL(abs).href;
|
|
13
13
|
const mod = await import(/* @vite-ignore */ url);
|
|
14
|
-
if (!('default' in mod) || mod.default === undefined)
|
|
14
|
+
if (!('default' in mod) || mod.default === undefined)
|
|
15
15
|
return { ok: false, error: 'Config must export default object' };
|
|
16
|
-
}
|
|
17
16
|
const result = parseConfig(mod.default);
|
|
18
17
|
if (!result.ok)
|
|
19
18
|
return { ok: false, error: result.error };
|
package/dist/core/route.js
CHANGED
|
@@ -24,9 +24,8 @@ const extractRouteBlocks = (src) => {
|
|
|
24
24
|
const lowerFirst = (s) => (s ? s.charAt(0).toLowerCase() + s.slice(1) : s);
|
|
25
25
|
export async function route(input, output, importPath, split) {
|
|
26
26
|
const openAPIResult = await parseOpenAPI(input);
|
|
27
|
-
if (!openAPIResult.ok)
|
|
27
|
+
if (!openAPIResult.ok)
|
|
28
28
|
return { ok: false, error: openAPIResult.error };
|
|
29
|
-
}
|
|
30
29
|
const openAPI = openAPIResult.value;
|
|
31
30
|
const routesSrc = routeCode(openAPI.paths);
|
|
32
31
|
if (!split) {
|
package/dist/core/rpc.js
CHANGED
|
@@ -236,9 +236,15 @@ const generateOperationCode = (pathStr, method, item, deps) => {
|
|
|
236
236
|
: `${deps.client}${clientAccess}.$${method}()`;
|
|
237
237
|
const summary = typeof op.summary === 'string' ? op.summary : '';
|
|
238
238
|
const description = typeof op.description === 'string' ? op.description : '';
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
239
|
+
const docs = [
|
|
240
|
+
'/**',
|
|
241
|
+
` * ${method.toUpperCase()} ${pathStr}`,
|
|
242
|
+
...(summary ? [' *', ` * ${summary.trimEnd()}`] : []),
|
|
243
|
+
...(description ? [' *', ` * ${description.trimEnd()}`] : []),
|
|
244
|
+
' */',
|
|
245
|
+
].join('\n');
|
|
246
|
+
const func = `export async function ${funcName}(${argSig}){return await ${call}}`;
|
|
247
|
+
return `${docs}\n${func}`;
|
|
242
248
|
};
|
|
243
249
|
/* ─────────────────────────────── Split ─────────────────────────────── */
|
|
244
250
|
const resolveSplitOutDir = (output) => {
|
|
@@ -256,9 +262,8 @@ const resolveSplitOutDir = (output) => {
|
|
|
256
262
|
*/
|
|
257
263
|
export async function rpc(input, output, importPath, split) {
|
|
258
264
|
const openAPIResult = await parseOpenAPI(input);
|
|
259
|
-
if (!openAPIResult.ok)
|
|
265
|
+
if (!openAPIResult.ok)
|
|
260
266
|
return { ok: false, error: openAPIResult.error };
|
|
261
|
-
}
|
|
262
267
|
const openAPI = openAPIResult.value;
|
|
263
268
|
const client = 'client';
|
|
264
269
|
const s = `import { client } from '${importPath}'`;
|
|
@@ -309,12 +314,12 @@ export async function rpc(input, output, importPath, split) {
|
|
|
309
314
|
return { ok: false, error: fmtResult.error };
|
|
310
315
|
const { outDir } = resolveSplitOutDir(output);
|
|
311
316
|
const filePath = path.join(outDir, `${funcName}.ts`);
|
|
312
|
-
const
|
|
313
|
-
if (!
|
|
314
|
-
return { ok: false, error:
|
|
315
|
-
const
|
|
316
|
-
if (!
|
|
317
|
-
return { ok: false, error:
|
|
317
|
+
const mkdirResult = await mkdir(path.dirname(filePath));
|
|
318
|
+
if (!mkdirResult.ok)
|
|
319
|
+
return { ok: false, error: mkdirResult.error };
|
|
320
|
+
const writeResult = await writeFile(filePath, fmtResult.value);
|
|
321
|
+
if (!writeResult.ok)
|
|
322
|
+
return { ok: false, error: writeResult.error };
|
|
318
323
|
splitExports.add(`export * from './${funcName}'`);
|
|
319
324
|
}
|
|
320
325
|
else {
|
|
@@ -324,9 +329,9 @@ export async function rpc(input, output, importPath, split) {
|
|
|
324
329
|
}
|
|
325
330
|
// Non-split: write single file
|
|
326
331
|
if (!split) {
|
|
327
|
-
const
|
|
328
|
-
if (!
|
|
329
|
-
return { ok: false, error:
|
|
332
|
+
const mkdirResult = await mkdir(path.dirname(output));
|
|
333
|
+
if (!mkdirResult.ok)
|
|
334
|
+
return { ok: false, error: mkdirResult.error };
|
|
330
335
|
const code = `${header}${combinedOut.join('\n\n')}${combinedOut.length ? '\n' : ''}`;
|
|
331
336
|
const fmtResult = await fmt(code);
|
|
332
337
|
if (!fmtResult.ok)
|
|
@@ -338,8 +343,8 @@ export async function rpc(input, output, importPath, split) {
|
|
|
338
343
|
}
|
|
339
344
|
// Split: write index.ts (barrel)
|
|
340
345
|
const { outDir, indexPath } = resolveSplitOutDir(output);
|
|
341
|
-
const
|
|
342
|
-
const fmtResult = await fmt(
|
|
346
|
+
const index = `${Array.from(splitExports).sort().join('\n')}\n`;
|
|
347
|
+
const fmtResult = await fmt(index);
|
|
343
348
|
if (!fmtResult.ok)
|
|
344
349
|
return { ok: false, error: fmtResult.error };
|
|
345
350
|
const mkdirResult = await mkdir(path.dirname(indexPath));
|
package/dist/core/schema.js
CHANGED
|
@@ -43,9 +43,8 @@ export async function schema(input, output, exportType, split) {
|
|
|
43
43
|
}
|
|
44
44
|
const openAPI = openAPIResult.value;
|
|
45
45
|
const { schemas } = openAPI.components ? openAPI.components : {};
|
|
46
|
-
if (!schemas)
|
|
46
|
+
if (!schemas)
|
|
47
47
|
return { ok: false, error: 'No schemas found' };
|
|
48
|
-
}
|
|
49
48
|
// split
|
|
50
49
|
if (split) {
|
|
51
50
|
const outDir = output.replace(/\.ts$/, '');
|
|
@@ -60,37 +59,31 @@ export async function schema(input, output, exportType, split) {
|
|
|
60
59
|
? deps.map((d) => `import { ${d}Schema } from './${lowerFirst(d)}'`).join('\n')
|
|
61
60
|
: '';
|
|
62
61
|
const fileCode = [importZ, depImports, '\n', zs].filter(Boolean).join('\n');
|
|
62
|
+
const filePath = `${outDir}/${lowerFirst(schemaName)}.ts`;
|
|
63
63
|
const fmtResult = await fmt(fileCode);
|
|
64
|
-
if (!fmtResult.ok)
|
|
64
|
+
if (!fmtResult.ok)
|
|
65
65
|
return { ok: false, error: fmtResult.error };
|
|
66
|
-
}
|
|
67
|
-
const filePath = `${outDir}/${lowerFirst(schemaName)}.ts`;
|
|
68
66
|
const mkdirResult = await mkdir(path.dirname(filePath));
|
|
69
|
-
if (!mkdirResult.ok)
|
|
67
|
+
if (!mkdirResult.ok)
|
|
70
68
|
return { ok: false, error: mkdirResult.error };
|
|
71
|
-
}
|
|
72
69
|
const writeResult = await writeFile(filePath, fmtResult.value);
|
|
73
|
-
if (!writeResult.ok)
|
|
70
|
+
if (!writeResult.ok)
|
|
74
71
|
return { ok: false, error: writeResult.error };
|
|
75
|
-
}
|
|
76
72
|
}
|
|
77
73
|
// index.ts
|
|
78
|
-
const
|
|
74
|
+
const index = `${Object.keys(schemas)
|
|
79
75
|
.sort()
|
|
80
76
|
.map((n) => `export * from './${lowerFirst(n)}'`)
|
|
81
77
|
.join('\n')}\n`;
|
|
82
|
-
const fmtResult = await fmt(
|
|
83
|
-
if (!fmtResult.ok)
|
|
78
|
+
const fmtResult = await fmt(index);
|
|
79
|
+
if (!fmtResult.ok)
|
|
84
80
|
return { ok: false, error: fmtResult.error };
|
|
85
|
-
}
|
|
86
81
|
const mkdirResult = await mkdir(path.dirname(`${outDir}/index.ts`));
|
|
87
|
-
if (!mkdirResult.ok)
|
|
82
|
+
if (!mkdirResult.ok)
|
|
88
83
|
return { ok: false, error: mkdirResult.error };
|
|
89
|
-
}
|
|
90
84
|
const writeResult = await writeFile(`${outDir}/index.ts`, fmtResult.value);
|
|
91
|
-
if (!writeResult.ok)
|
|
85
|
+
if (!writeResult.ok)
|
|
92
86
|
return { ok: false, error: writeResult.error };
|
|
93
|
-
}
|
|
94
87
|
return {
|
|
95
88
|
ok: true,
|
|
96
89
|
value: `Generated schema code written to ${outDir}/*.ts (index.ts included)`,
|
|
@@ -110,16 +103,13 @@ export async function schema(input, output, exportType, split) {
|
|
|
110
103
|
const importCode = `import { z } from '@hono/zod-openapi'`;
|
|
111
104
|
const schemaDefinitionsCode = `${importCode}\n\n${schemaDefinitions}`;
|
|
112
105
|
const fmtResult = await fmt(schemaDefinitionsCode);
|
|
113
|
-
if (!fmtResult.ok)
|
|
106
|
+
if (!fmtResult.ok)
|
|
114
107
|
return { ok: false, error: fmtResult.error };
|
|
115
|
-
}
|
|
116
108
|
const mkdirResult = await mkdir(path.dirname(output));
|
|
117
|
-
if (!mkdirResult.ok)
|
|
109
|
+
if (!mkdirResult.ok)
|
|
118
110
|
return { ok: false, error: mkdirResult.error };
|
|
119
|
-
}
|
|
120
111
|
const writeResult = await writeFile(output, fmtResult.value);
|
|
121
|
-
if (!writeResult.ok)
|
|
112
|
+
if (!writeResult.ok)
|
|
122
113
|
return { ok: false, error: writeResult.error };
|
|
123
|
-
}
|
|
124
114
|
return { ok: true, value: `Generated schema code written to ${output}` };
|
|
125
115
|
}
|
package/dist/core/takibi.js
CHANGED
|
@@ -4,7 +4,7 @@ import { mkdir, readdir, writeFile } from '../fsp/index.js';
|
|
|
4
4
|
import { app } from '../generator/zod-openapi-hono/app/index.js';
|
|
5
5
|
import { zodOpenAPIHono } from '../generator/zod-openapi-hono/openapi/index.js';
|
|
6
6
|
import { parseOpenAPI } from '../openapi/index.js';
|
|
7
|
-
import {
|
|
7
|
+
import { isHttpMethod, methodPath } from '../utils/index.js';
|
|
8
8
|
/**
|
|
9
9
|
* Generates TypeScript code from an OpenAPI spec and optional templates.
|
|
10
10
|
*
|
|
@@ -54,42 +54,34 @@ import { groupHandlersByFileName, isHttpMethod, methodPath } from '../utils/inde
|
|
|
54
54
|
*/
|
|
55
55
|
export async function takibi(input, output, exportSchema, exportType, template, test, basePath) {
|
|
56
56
|
const openAPIResult = await parseOpenAPI(input);
|
|
57
|
-
if (!openAPIResult.ok)
|
|
57
|
+
if (!openAPIResult.ok)
|
|
58
58
|
return { ok: false, error: openAPIResult.error };
|
|
59
|
-
}
|
|
60
59
|
const openAPI = openAPIResult.value;
|
|
61
60
|
const honoResult = await fmt(zodOpenAPIHono(openAPI, exportSchema, exportType));
|
|
62
|
-
if (!honoResult.ok)
|
|
61
|
+
if (!honoResult.ok)
|
|
63
62
|
return { ok: false, error: honoResult.error };
|
|
64
|
-
}
|
|
65
63
|
const mkdirResult = await mkdir(path.dirname(output));
|
|
66
|
-
if (!mkdirResult.ok)
|
|
64
|
+
if (!mkdirResult.ok)
|
|
67
65
|
return { ok: false, error: mkdirResult.error };
|
|
68
|
-
}
|
|
69
66
|
const writeResult = await writeFile(output, honoResult.value);
|
|
70
|
-
if (!writeResult.ok)
|
|
67
|
+
if (!writeResult.ok)
|
|
71
68
|
return { ok: false, error: writeResult.error };
|
|
72
|
-
}
|
|
73
69
|
/** template */
|
|
74
70
|
if (template && output.includes('/')) {
|
|
75
71
|
const appResult = await fmt(app(openAPI, output, basePath));
|
|
76
|
-
if (!appResult.ok)
|
|
72
|
+
if (!appResult.ok)
|
|
77
73
|
return { ok: false, error: appResult.error };
|
|
78
|
-
}
|
|
79
74
|
const dir = path.dirname(output);
|
|
80
75
|
const readdirResult = await readdir(dir);
|
|
81
|
-
if (!readdirResult.ok)
|
|
76
|
+
if (!readdirResult.ok)
|
|
82
77
|
return { ok: false, error: readdirResult.error };
|
|
83
|
-
|
|
84
|
-
const target = path.join(dir, readdirResult.value.includes('index.ts') ? 'main.ts' : 'index.ts');
|
|
78
|
+
const target = path.join(dir, 'index.ts');
|
|
85
79
|
const writeResult = await writeFile(target, appResult.value);
|
|
86
|
-
if (!writeResult.ok)
|
|
80
|
+
if (!writeResult.ok)
|
|
87
81
|
return { ok: false, error: writeResult.error };
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return { ok: false, error: handlerResult.error };
|
|
92
|
-
}
|
|
82
|
+
const zodOpenapiHonoHandlerResult = await zodOpenapiHonoHandler(openAPI, output, test);
|
|
83
|
+
if (!zodOpenapiHonoHandlerResult.ok)
|
|
84
|
+
return { ok: false, error: zodOpenapiHonoHandlerResult.error };
|
|
93
85
|
return { ok: true, value: 'Generated code and template files written' };
|
|
94
86
|
}
|
|
95
87
|
return {
|
|
@@ -107,57 +99,80 @@ export async function takibi(input, output, exportSchema, exportType, template,
|
|
|
107
99
|
*/
|
|
108
100
|
async function zodOpenapiHonoHandler(openapi, output, test) {
|
|
109
101
|
const paths = openapi.paths;
|
|
110
|
-
const handlers = []
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
102
|
+
const handlers = Object.entries(paths).flatMap(([p, pathItem]) => Object.entries(pathItem)
|
|
103
|
+
.filter(([m]) => isHttpMethod(m))
|
|
104
|
+
.map(([method]) => {
|
|
105
|
+
const routeId = methodPath(method, p);
|
|
106
|
+
const routeHandlerContent = `export const ${routeId}RouteHandler:RouteHandler<typeof ${routeId}Route>=async(c)=>{}`;
|
|
107
|
+
const rawSegment = p.replace(/^\/+/, '').split('/')[0] ?? '';
|
|
108
|
+
const sanitized = rawSegment
|
|
109
|
+
.replace(/\{([^}]+)\}/g, '$1')
|
|
110
|
+
.replace(/[^0-9A-Za-z._-]/g, '_')
|
|
111
|
+
.replace(/^[._-]+|[._-]+$/g, '')
|
|
112
|
+
.replace(/__+/g, '_')
|
|
113
|
+
.replace(/[-._](\w)/g, (_, c) => c.toUpperCase());
|
|
114
|
+
const pathName = sanitized === '' ? '__root' : sanitized;
|
|
115
|
+
const fileName = `${pathName}.ts`;
|
|
116
|
+
const testFileName = `${pathName}.test.ts`;
|
|
117
|
+
return {
|
|
118
|
+
fileName,
|
|
119
|
+
testFileName,
|
|
120
|
+
routeHandlerContents: [routeHandlerContent],
|
|
121
|
+
routeNames: [`${routeId}Route`],
|
|
122
|
+
};
|
|
123
|
+
}));
|
|
124
|
+
const mergedHandlers = Array.from(handlers
|
|
125
|
+
.reduce((map, h) => {
|
|
126
|
+
const prev = map.get(h.fileName);
|
|
127
|
+
const next = prev
|
|
128
|
+
? {
|
|
129
|
+
fileName: h.fileName,
|
|
130
|
+
testFileName: h.testFileName,
|
|
131
|
+
routeHandlerContents: [...prev.routeHandlerContents, ...h.routeHandlerContents],
|
|
132
|
+
routeNames: Array.from(new Set([...prev.routeNames, ...h.routeNames])),
|
|
133
|
+
}
|
|
134
|
+
: {
|
|
135
|
+
fileName: h.fileName,
|
|
136
|
+
testFileName: h.testFileName,
|
|
137
|
+
routeHandlerContents: [...h.routeHandlerContents],
|
|
138
|
+
routeNames: [...h.routeNames],
|
|
139
|
+
};
|
|
140
|
+
map.set(h.fileName, next);
|
|
141
|
+
return map;
|
|
142
|
+
}, new Map())
|
|
143
|
+
.values());
|
|
144
|
+
const isDot = output === '.' || output === './';
|
|
145
|
+
const baseDir = isDot ? '.' : (output.match(/^(.*)\/[^/]+\.ts$/)?.[1] ?? '.');
|
|
146
|
+
const handlerPath = baseDir === '.' ? 'handlers' : `${baseDir}/handlers`;
|
|
147
|
+
const routeEntryBasename = output.match(/[^/]+\.ts$/)?.[0] ?? 'index.ts';
|
|
148
|
+
const importFrom = `../${routeEntryBasename.replace(/\.ts$/, '')}`;
|
|
149
|
+
const mkdirResult = await mkdir(handlerPath);
|
|
150
|
+
if (!mkdirResult.ok)
|
|
151
|
+
return { ok: false, error: mkdirResult.error };
|
|
134
152
|
for (const handler of mergedHandlers) {
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
const mkdirResult = await mkdir(handlerPath);
|
|
138
|
-
if (!mkdirResult.ok) {
|
|
139
|
-
return { ok: false, error: mkdirResult.error };
|
|
140
|
-
}
|
|
141
|
-
const routeTypes = handler.routeNames.map((routeName) => `${routeName}`).join(', ');
|
|
142
|
-
const match = output?.match(/[^/]+\.ts$/);
|
|
143
|
-
const matchPath = match ? match[0] : '';
|
|
144
|
-
const path = output === '.' || output === './' ? output : `../${matchPath}`;
|
|
145
|
-
const importRouteTypes = routeTypes ? `import type { ${routeTypes} } from '${path}';` : '';
|
|
153
|
+
const routeTypes = Array.from(new Set(handler.routeNames)).join(', ');
|
|
154
|
+
const importRouteTypes = routeTypes ? `import type { ${routeTypes} } from '${importFrom}';` : '';
|
|
146
155
|
const importStatements = `import type { RouteHandler } from '@hono/zod-openapi'\n${importRouteTypes}`;
|
|
147
156
|
const fileContent = `${importStatements}\n\n${handler.routeHandlerContents.join('\n\n')}`;
|
|
148
|
-
const
|
|
149
|
-
if (!
|
|
150
|
-
return { ok: false, error:
|
|
151
|
-
}
|
|
152
|
-
const writeResult = await writeFile(`${handlerPath}/${handler.fileName}`, formatCode.value);
|
|
157
|
+
const fmtResult = await fmt(fileContent);
|
|
158
|
+
if (!fmtResult.ok)
|
|
159
|
+
return { ok: false, error: fmtResult.error };
|
|
160
|
+
const writeResult = await writeFile(`${handlerPath}/${handler.fileName}`, fmtResult.value);
|
|
153
161
|
if (!writeResult.ok)
|
|
154
|
-
writeResult;
|
|
162
|
+
return { ok: false, error: writeResult.error };
|
|
155
163
|
if (test) {
|
|
156
164
|
const writeResult = await writeFile(`${handlerPath}/${handler.testFileName}`, '');
|
|
157
|
-
if (!writeResult.ok)
|
|
165
|
+
if (!writeResult.ok)
|
|
158
166
|
return { ok: false, error: writeResult.error };
|
|
159
|
-
}
|
|
160
167
|
}
|
|
161
168
|
}
|
|
169
|
+
const sorted = mergedHandlers.map((h) => h.fileName).sort();
|
|
170
|
+
const exports = sorted.map((h) => `export * from './${h}'`).join('\n');
|
|
171
|
+
const fmtResult = await fmt(exports);
|
|
172
|
+
if (!fmtResult.ok)
|
|
173
|
+
return { ok: false, error: fmtResult.error };
|
|
174
|
+
const writeResult = await writeFile(`${handlerPath}/index.ts`, fmtResult.value);
|
|
175
|
+
if (!writeResult.ok)
|
|
176
|
+
return { ok: false, error: writeResult.error };
|
|
162
177
|
return { ok: true, value: undefined };
|
|
163
178
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { docs } from '../../../helper/docs.js';
|
|
2
|
-
import {
|
|
3
|
-
import { getHandlerImports, importHandlers, importMap, importRoutes, registerComponent, } from '../../../utils/index.js';
|
|
2
|
+
import { isHttpMethod, methodPath, registerComponent } from '../../../utils/index.js';
|
|
4
3
|
/**
|
|
5
4
|
* Generates a Hono app with OpenAPI and Swagger UI integration.
|
|
6
5
|
*
|
|
@@ -10,7 +9,27 @@ import { getHandlerImports, importHandlers, importMap, importRoutes, registerCom
|
|
|
10
9
|
* @returns The generated application code as a string.
|
|
11
10
|
*/
|
|
12
11
|
export function app(openapi, output, basePath) {
|
|
12
|
+
const getRouteMaps = (openapi) => {
|
|
13
|
+
const paths = openapi.paths;
|
|
14
|
+
const routeMappings = Object.entries(paths).flatMap(([path, pathItem]) => {
|
|
15
|
+
return Object.entries(pathItem).flatMap(([method]) => {
|
|
16
|
+
if (!isHttpMethod(method))
|
|
17
|
+
return [];
|
|
18
|
+
return {
|
|
19
|
+
routeName: `${methodPath(method, path)}Route`,
|
|
20
|
+
handlerName: `${methodPath(method, path)}RouteHandler`,
|
|
21
|
+
path,
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
return routeMappings;
|
|
26
|
+
};
|
|
13
27
|
const routeMappings = getRouteMaps(openapi);
|
|
28
|
+
const handlerNames = Array.from(new Set(routeMappings.map((m) => m.handlerName))).sort();
|
|
29
|
+
const handlerImport = handlerNames.length > 0 ? `import { ${handlerNames.join(',')} } from './handlers'` : '';
|
|
30
|
+
const routeNames = Array.from(new Set(routeMappings.map((m) => m.routeName))).sort();
|
|
31
|
+
const routeModule = `./${output.replace(/^.*\//, '').replace(/\.ts$/, '')}`;
|
|
32
|
+
const routesImport = routeNames.length > 0 ? `import { ${routeNames.join(',')} } from '${routeModule}'` : '';
|
|
14
33
|
const path = basePath !== undefined ? `${basePath}/doc` : '/doc';
|
|
15
34
|
const registerComponentCode = openapi.components?.securitySchemes
|
|
16
35
|
? registerComponent(openapi.components.securitySchemes)
|
|
@@ -21,25 +40,33 @@ export function app(openapi, output, basePath) {
|
|
|
21
40
|
const minor = Number.isFinite(Number(minStr)) ? Number(minStr) : 0;
|
|
22
41
|
const is31Plus = major > 3 || (major === 3 && minor >= 1);
|
|
23
42
|
const doc = is31Plus ? 'doc31' : 'doc';
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
.join('\n')
|
|
43
|
+
const importSection = [
|
|
44
|
+
`import { swaggerUI } from '@hono/swagger-ui'`,
|
|
45
|
+
`import { OpenAPIHono } from '@hono/zod-openapi'`,
|
|
46
|
+
handlerImport,
|
|
47
|
+
routesImport,
|
|
48
|
+
]
|
|
49
|
+
.filter(Boolean)
|
|
50
|
+
.join('\n');
|
|
51
|
+
const appInit = basePath
|
|
52
|
+
? `const app = new OpenAPIHono().basePath('${basePath}')`
|
|
53
|
+
: 'const app = new OpenAPIHono()';
|
|
54
|
+
const apiInit = 'export const api = app' +
|
|
55
|
+
routeMappings
|
|
56
|
+
.sort((a, b) => (a.routeName < b.routeName ? -1 : a.routeName > b.routeName ? 1 : 0))
|
|
57
|
+
.map(({ routeName, handlerName }) => `.openapi(${routeName},${handlerName})`)
|
|
58
|
+
.join('\n');
|
|
59
|
+
const swagger = `if(process.env.NODE_ENV === 'development'){` +
|
|
60
|
+
`${registerComponentCode}\n` +
|
|
61
|
+
`app.${doc}('/doc',${JSON.stringify(docs(openapi))})` +
|
|
62
|
+
`.get('/ui',swaggerUI({url:'${path}'}))` +
|
|
63
|
+
'}';
|
|
64
|
+
return [
|
|
65
|
+
importSection,
|
|
66
|
+
appInit,
|
|
67
|
+
apiInit,
|
|
40
68
|
swagger,
|
|
41
69
|
'export type AddType = typeof api',
|
|
42
70
|
'export default app',
|
|
43
|
-
];
|
|
44
|
-
return sections.join('\n\n');
|
|
71
|
+
].join('\n\n');
|
|
45
72
|
}
|
package/dist/helper/index.d.ts
CHANGED
package/dist/helper/index.js
CHANGED
package/dist/utils/index.d.ts
CHANGED
|
@@ -108,28 +108,6 @@ export declare function normalizeTypes(t?: 'string' | 'number' | 'integer' | 'da
|
|
|
108
108
|
'string' | 'number' | 'integer' | 'date' | 'boolean' | 'array' | 'object' | 'null',
|
|
109
109
|
...('string' | 'number' | 'integer' | 'date' | 'boolean' | 'array' | 'object' | 'null')[]
|
|
110
110
|
]): ("string" | "number" | "boolean" | "object" | "integer" | "date" | "array" | "null")[];
|
|
111
|
-
/**
|
|
112
|
-
* Generates import statements for route handler modules.
|
|
113
|
-
*
|
|
114
|
-
* ```mermaid
|
|
115
|
-
* graph TD
|
|
116
|
-
* A[Start importRoutes] --> B[Init result list]
|
|
117
|
-
* B --> C[Iterate entries]
|
|
118
|
-
* C --> D[Make unique names]
|
|
119
|
-
* D --> E[Normalize path if needed]
|
|
120
|
-
* E --> F[Build import line]
|
|
121
|
-
* F --> G[Append to result list]
|
|
122
|
-
* G --> H[More entries]
|
|
123
|
-
* H -->|Yes| C
|
|
124
|
-
* H -->|No| I[Return result list]
|
|
125
|
-
* ```
|
|
126
|
-
*
|
|
127
|
-
* @param importsMap - Map of file paths to exported identifiers.
|
|
128
|
-
* @returns Array of import statements.
|
|
129
|
-
*/
|
|
130
|
-
export declare function importRoutes(importsMap: {
|
|
131
|
-
[importPath: `${string}.ts`]: readonly string[];
|
|
132
|
-
}): readonly string[];
|
|
133
111
|
/**
|
|
134
112
|
* Generates registration code for OpenAPI `securitySchemes`.
|
|
135
113
|
*
|
|
@@ -158,116 +136,6 @@ export declare function registerComponent(securitySchemes: {
|
|
|
158
136
|
bearerFormat?: string;
|
|
159
137
|
};
|
|
160
138
|
}): string;
|
|
161
|
-
/**
|
|
162
|
-
* Generates an import map that associates route names with their corresponding import file path.
|
|
163
|
-
*
|
|
164
|
-
* This is useful for dynamically constructing import statements in code generation,
|
|
165
|
-
* ensuring that each route is grouped under its appropriate file.
|
|
166
|
-
*
|
|
167
|
-
* ```mermaid
|
|
168
|
-
* graph TD
|
|
169
|
-
* A[Start importMap] --> B[Initialize empty map]
|
|
170
|
-
* B --> C[Iterate routeMappings]
|
|
171
|
-
* C --> D[Extract output file name]
|
|
172
|
-
* D --> E[Check if map has key]
|
|
173
|
-
* E --> F[Create array if missing]
|
|
174
|
-
* F --> G[Add route name to array]
|
|
175
|
-
* G --> H[More routeMappings]
|
|
176
|
-
* H --> C
|
|
177
|
-
* C --> I[Return importsMap]
|
|
178
|
-
* ```
|
|
179
|
-
*
|
|
180
|
-
* @param routeMappings - An array of route mapping objects containing route name, handler name, and path.
|
|
181
|
-
* @param output - The output TypeScript file name (e.g., 'user.ts'). Used to determine the import path.
|
|
182
|
-
* @returns A record where each key is an import path (e.g., 'user.ts') and the value is an array of route names imported from that path.
|
|
183
|
-
*/
|
|
184
|
-
export declare function importMap(routeMappings: {
|
|
185
|
-
readonly routeName: string;
|
|
186
|
-
readonly handlerName: string;
|
|
187
|
-
readonly path: string;
|
|
188
|
-
}[], output: `${string}.ts`): {
|
|
189
|
-
[importPath: `${string}.ts`]: readonly string[];
|
|
190
|
-
};
|
|
191
|
-
/**
|
|
192
|
-
* Generates import statements for handler functions.
|
|
193
|
-
*
|
|
194
|
-
* ```mermaid
|
|
195
|
-
* graph TD
|
|
196
|
-
* A[Start importHandlers] --> B[Init empty list]
|
|
197
|
-
* B --> C[Iterate handlerImportsMap]
|
|
198
|
-
* C --> D[Make unique handler names]
|
|
199
|
-
* D --> E[Get dir path from output]
|
|
200
|
-
* E --> F[Set handler path base]
|
|
201
|
-
* F --> G[Build import statement]
|
|
202
|
-
* G --> H[Append to list]
|
|
203
|
-
* H --> I[More entries]
|
|
204
|
-
* I --> C
|
|
205
|
-
* C --> J[Return list]
|
|
206
|
-
* ```
|
|
207
|
-
*
|
|
208
|
-
* @param handlerImportsMap - A map from file names to handler names.
|
|
209
|
-
* @param output - The output file path (e.g., 'api.ts').
|
|
210
|
-
* @returns An array of import statement strings.
|
|
211
|
-
*/
|
|
212
|
-
export declare function importHandlers(handlerImportsMap: {
|
|
213
|
-
[fileName: string]: readonly string[];
|
|
214
|
-
}, output: `${string}.ts`): readonly string[];
|
|
215
|
-
/**
|
|
216
|
-
* Groups route handlers by file name.
|
|
217
|
-
*
|
|
218
|
-
* ```mermaid
|
|
219
|
-
* graph TD
|
|
220
|
-
* A[Start groupHandlersByFileName] --> B[Create empty map]
|
|
221
|
-
* B --> C[Iterate handlers]
|
|
222
|
-
* C --> D[Find existing entry by file name]
|
|
223
|
-
* D --> E[Merge contents and route names]
|
|
224
|
-
* E --> F[Set merged entry back to map]
|
|
225
|
-
* F --> G[More handlers]
|
|
226
|
-
* G --> C
|
|
227
|
-
* C --> H[Return map values as array]
|
|
228
|
-
* ```
|
|
229
|
-
*
|
|
230
|
-
* @param handlers - An array of route handler definitions including file name, test file name, contents, and route names.
|
|
231
|
-
* @returns A deduplicated array of grouped handler definitions per file.
|
|
232
|
-
*/
|
|
233
|
-
export declare function groupHandlersByFileName(handlers: {
|
|
234
|
-
readonly fileName: `${string}.ts`;
|
|
235
|
-
readonly testFileName: `${string}.ts`;
|
|
236
|
-
readonly routeHandlerContents: readonly string[];
|
|
237
|
-
readonly routeNames: readonly string[];
|
|
238
|
-
}[]): {
|
|
239
|
-
readonly fileName: `${string}.ts`;
|
|
240
|
-
readonly testFileName: `${string}.ts`;
|
|
241
|
-
readonly routeHandlerContents: readonly string[];
|
|
242
|
-
readonly routeNames: readonly string[];
|
|
243
|
-
}[];
|
|
244
|
-
/**
|
|
245
|
-
* Generates a map of handler file names to handler names.
|
|
246
|
-
*
|
|
247
|
-
* ```mermaid
|
|
248
|
-
* graph TD
|
|
249
|
-
* A[Start getHandlerImports] --> B[Init empty map]
|
|
250
|
-
* B --> C[Iterate handler maps]
|
|
251
|
-
* C --> D[Take first path segment]
|
|
252
|
-
* D --> E[Sanitize segment to path name]
|
|
253
|
-
* E --> F[Make file name or index handler]
|
|
254
|
-
* F --> G[Ensure array exists in map]
|
|
255
|
-
* G --> H[Append handler name]
|
|
256
|
-
* H --> I[More items]
|
|
257
|
-
* I --> C
|
|
258
|
-
* C --> J[Return map]
|
|
259
|
-
* ```
|
|
260
|
-
*
|
|
261
|
-
* @param handlerMaps - Array of route mappings including route name, handler name, and path.
|
|
262
|
-
* @returns A map where keys are handler file names and values are arrays of handler names.
|
|
263
|
-
*/
|
|
264
|
-
export declare function getHandlerImports(handlerMaps: {
|
|
265
|
-
readonly routeName: string;
|
|
266
|
-
readonly handlerName: string;
|
|
267
|
-
readonly path: string;
|
|
268
|
-
}[]): {
|
|
269
|
-
[fileName: `${string}.ts`]: readonly string[];
|
|
270
|
-
};
|
|
271
139
|
/**
|
|
272
140
|
* Checks if a value is a non-null object (e.g., a potential `$ref` object).
|
|
273
141
|
*
|
|
@@ -411,25 +279,10 @@ export declare function createRoute(args: {
|
|
|
411
279
|
}): string;
|
|
412
280
|
/**
|
|
413
281
|
* Generates an array of Zod validator strings from OpenAPI parameter objects.
|
|
414
|
-
*
|
|
415
|
-
* ```mermaid
|
|
416
|
-
* graph TD
|
|
417
|
-
* A[Start requestParamsArray] --> B[Collect non empty sections]
|
|
418
|
-
* B --> C[Iterate sections]
|
|
419
|
-
* C --> D[Build z object string from entries]
|
|
420
|
-
* D --> E[If section is path rename to params]
|
|
421
|
-
* E --> F[Push section colon z object string]
|
|
422
|
-
* F --> G[Continue loop]
|
|
423
|
-
* G --> H[Filter out null values]
|
|
424
|
-
* H --> I[Return array of strings]
|
|
425
|
-
* ```
|
|
426
|
-
*
|
|
427
282
|
* @param parameters - An object containing `query`, `path`, and `header` parameters.
|
|
428
283
|
* @returns An array of strings like `'query:z.object({...})'` or `'params:z.object({...})'`.
|
|
429
284
|
*/
|
|
430
|
-
export declare
|
|
431
|
-
[section: string]: Record<string, string>;
|
|
432
|
-
}): readonly string[];
|
|
285
|
+
export declare const requestParamsArray: (parameters: Record<string, Record<string, string>>) => readonly string[];
|
|
433
286
|
/**
|
|
434
287
|
* Escapes a string for safe use in TypeScript string literals.
|
|
435
288
|
*
|
package/dist/utils/index.js
CHANGED
|
@@ -225,39 +225,6 @@ export function parseCli(args) {
|
|
|
225
225
|
export function normalizeTypes(t) {
|
|
226
226
|
return t === undefined ? [] : Array.isArray(t) ? t : [t];
|
|
227
227
|
}
|
|
228
|
-
/**
|
|
229
|
-
* Generates import statements for route handler modules.
|
|
230
|
-
*
|
|
231
|
-
* ```mermaid
|
|
232
|
-
* graph TD
|
|
233
|
-
* A[Start importRoutes] --> B[Init result list]
|
|
234
|
-
* B --> C[Iterate entries]
|
|
235
|
-
* C --> D[Make unique names]
|
|
236
|
-
* D --> E[Normalize path if needed]
|
|
237
|
-
* E --> F[Build import line]
|
|
238
|
-
* F --> G[Append to result list]
|
|
239
|
-
* G --> H[More entries]
|
|
240
|
-
* H -->|Yes| C
|
|
241
|
-
* H -->|No| I[Return result list]
|
|
242
|
-
* ```
|
|
243
|
-
*
|
|
244
|
-
* @param importsMap - Map of file paths to exported identifiers.
|
|
245
|
-
* @returns Array of import statements.
|
|
246
|
-
*/
|
|
247
|
-
export function importRoutes(importsMap) {
|
|
248
|
-
const importRoutes = [];
|
|
249
|
-
for (const [importPath, names] of Object.entries(importsMap)) {
|
|
250
|
-
const uniqueNames = Array.from(new Set(names));
|
|
251
|
-
if (importPath.includes('index.ts')) {
|
|
252
|
-
const normalizedPath = importPath.replace('index.ts', '');
|
|
253
|
-
importRoutes.push(`import { ${uniqueNames.join(',')} } from './${normalizedPath}'`);
|
|
254
|
-
}
|
|
255
|
-
else {
|
|
256
|
-
importRoutes.push(`import { ${uniqueNames.join(',')} } from './${importPath}'`);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
return importRoutes;
|
|
260
|
-
}
|
|
261
228
|
/**
|
|
262
229
|
* Generates registration code for OpenAPI `securitySchemes`.
|
|
263
230
|
*
|
|
@@ -285,152 +252,6 @@ export function registerComponent(securitySchemes) {
|
|
|
285
252
|
})
|
|
286
253
|
.join('\n');
|
|
287
254
|
}
|
|
288
|
-
/**
|
|
289
|
-
* Generates an import map that associates route names with their corresponding import file path.
|
|
290
|
-
*
|
|
291
|
-
* This is useful for dynamically constructing import statements in code generation,
|
|
292
|
-
* ensuring that each route is grouped under its appropriate file.
|
|
293
|
-
*
|
|
294
|
-
* ```mermaid
|
|
295
|
-
* graph TD
|
|
296
|
-
* A[Start importMap] --> B[Initialize empty map]
|
|
297
|
-
* B --> C[Iterate routeMappings]
|
|
298
|
-
* C --> D[Extract output file name]
|
|
299
|
-
* D --> E[Check if map has key]
|
|
300
|
-
* E --> F[Create array if missing]
|
|
301
|
-
* F --> G[Add route name to array]
|
|
302
|
-
* G --> H[More routeMappings]
|
|
303
|
-
* H --> C
|
|
304
|
-
* C --> I[Return importsMap]
|
|
305
|
-
* ```
|
|
306
|
-
*
|
|
307
|
-
* @param routeMappings - An array of route mapping objects containing route name, handler name, and path.
|
|
308
|
-
* @param output - The output TypeScript file name (e.g., 'user.ts'). Used to determine the import path.
|
|
309
|
-
* @returns A record where each key is an import path (e.g., 'user.ts') and the value is an array of route names imported from that path.
|
|
310
|
-
*/
|
|
311
|
-
export function importMap(routeMappings, output) {
|
|
312
|
-
const importsMap = {};
|
|
313
|
-
for (const { routeName } of routeMappings) {
|
|
314
|
-
const match = output.match(/[^/]+\.ts$/);
|
|
315
|
-
const importPath = match ? match[0] : output;
|
|
316
|
-
if (!importsMap[importPath]) {
|
|
317
|
-
importsMap[importPath] = [];
|
|
318
|
-
}
|
|
319
|
-
importsMap[importPath].push(routeName);
|
|
320
|
-
}
|
|
321
|
-
return importsMap;
|
|
322
|
-
}
|
|
323
|
-
/**
|
|
324
|
-
* Generates import statements for handler functions.
|
|
325
|
-
*
|
|
326
|
-
* ```mermaid
|
|
327
|
-
* graph TD
|
|
328
|
-
* A[Start importHandlers] --> B[Init empty list]
|
|
329
|
-
* B --> C[Iterate handlerImportsMap]
|
|
330
|
-
* C --> D[Make unique handler names]
|
|
331
|
-
* D --> E[Get dir path from output]
|
|
332
|
-
* E --> F[Set handler path base]
|
|
333
|
-
* F --> G[Build import statement]
|
|
334
|
-
* G --> H[Append to list]
|
|
335
|
-
* H --> I[More entries]
|
|
336
|
-
* I --> C
|
|
337
|
-
* C --> J[Return list]
|
|
338
|
-
* ```
|
|
339
|
-
*
|
|
340
|
-
* @param handlerImportsMap - A map from file names to handler names.
|
|
341
|
-
* @param output - The output file path (e.g., 'api.ts').
|
|
342
|
-
* @returns An array of import statement strings.
|
|
343
|
-
*/
|
|
344
|
-
export function importHandlers(handlerImportsMap, output) {
|
|
345
|
-
const importHandlers = [];
|
|
346
|
-
for (const [fileName, handlers] of Object.entries(handlerImportsMap)) {
|
|
347
|
-
const uniqueHandlers = Array.from(new Set(handlers));
|
|
348
|
-
const replacePath = output?.replace(/\/[^/]+\.ts$/, '');
|
|
349
|
-
const dirPath = replacePath === undefined ? '.' : replacePath;
|
|
350
|
-
const handlerPath = 'handlers';
|
|
351
|
-
if (dirPath === '.') {
|
|
352
|
-
importHandlers.push(`import { ${uniqueHandlers.join(',')} } from '${handlerPath}/${fileName}'`);
|
|
353
|
-
}
|
|
354
|
-
else {
|
|
355
|
-
importHandlers.push(`import { ${uniqueHandlers.join(',')} } from './${handlerPath}/${fileName}'`);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
return importHandlers;
|
|
359
|
-
}
|
|
360
|
-
/**
|
|
361
|
-
* Groups route handlers by file name.
|
|
362
|
-
*
|
|
363
|
-
* ```mermaid
|
|
364
|
-
* graph TD
|
|
365
|
-
* A[Start groupHandlersByFileName] --> B[Create empty map]
|
|
366
|
-
* B --> C[Iterate handlers]
|
|
367
|
-
* C --> D[Find existing entry by file name]
|
|
368
|
-
* D --> E[Merge contents and route names]
|
|
369
|
-
* E --> F[Set merged entry back to map]
|
|
370
|
-
* F --> G[More handlers]
|
|
371
|
-
* G --> C
|
|
372
|
-
* C --> H[Return map values as array]
|
|
373
|
-
* ```
|
|
374
|
-
*
|
|
375
|
-
* @param handlers - An array of route handler definitions including file name, test file name, contents, and route names.
|
|
376
|
-
* @returns A deduplicated array of grouped handler definitions per file.
|
|
377
|
-
*/
|
|
378
|
-
export function groupHandlersByFileName(handlers) {
|
|
379
|
-
return Array.from(handlers
|
|
380
|
-
.reduce((acc, handler) => {
|
|
381
|
-
const existing = acc.get(handler.fileName);
|
|
382
|
-
const mergedHandler = {
|
|
383
|
-
fileName: handler.fileName,
|
|
384
|
-
testFileName: handler.testFileName,
|
|
385
|
-
routeHandlerContents: existing
|
|
386
|
-
? [...existing.routeHandlerContents, ...handler.routeHandlerContents]
|
|
387
|
-
: [...handler.routeHandlerContents],
|
|
388
|
-
routeNames: existing
|
|
389
|
-
? [...existing.routeNames, ...handler.routeNames]
|
|
390
|
-
: [...handler.routeNames],
|
|
391
|
-
};
|
|
392
|
-
return acc.set(handler.fileName, mergedHandler);
|
|
393
|
-
}, new Map())
|
|
394
|
-
.values());
|
|
395
|
-
}
|
|
396
|
-
/**
|
|
397
|
-
* Generates a map of handler file names to handler names.
|
|
398
|
-
*
|
|
399
|
-
* ```mermaid
|
|
400
|
-
* graph TD
|
|
401
|
-
* A[Start getHandlerImports] --> B[Init empty map]
|
|
402
|
-
* B --> C[Iterate handler maps]
|
|
403
|
-
* C --> D[Take first path segment]
|
|
404
|
-
* D --> E[Sanitize segment to path name]
|
|
405
|
-
* E --> F[Make file name or index handler]
|
|
406
|
-
* F --> G[Ensure array exists in map]
|
|
407
|
-
* G --> H[Append handler name]
|
|
408
|
-
* H --> I[More items]
|
|
409
|
-
* I --> C
|
|
410
|
-
* C --> J[Return map]
|
|
411
|
-
* ```
|
|
412
|
-
*
|
|
413
|
-
* @param handlerMaps - Array of route mappings including route name, handler name, and path.
|
|
414
|
-
* @returns A map where keys are handler file names and values are arrays of handler names.
|
|
415
|
-
*/
|
|
416
|
-
export function getHandlerImports(handlerMaps) {
|
|
417
|
-
const getHandlerImports = {};
|
|
418
|
-
for (const { handlerName, path } of handlerMaps) {
|
|
419
|
-
const rawSegment = path.replace(/^\/+/, '').split('/')[0] ?? '';
|
|
420
|
-
const pathName = (rawSegment === '' ? 'index' : rawSegment)
|
|
421
|
-
.replace(/\{([^}]+)\}/g, '$1')
|
|
422
|
-
.replace(/[^0-9A-Za-z._-]/g, '_')
|
|
423
|
-
.replace(/^[._-]+|[._-]+$/g, '')
|
|
424
|
-
.replace(/__+/g, '_')
|
|
425
|
-
.replace(/[-._](\w)/g, (_, c) => c.toUpperCase());
|
|
426
|
-
const fileName = pathName.length === 0 ? 'indexHandler.ts' : `${pathName}Handler.ts`;
|
|
427
|
-
if (!getHandlerImports[fileName]) {
|
|
428
|
-
getHandlerImports[fileName] = [];
|
|
429
|
-
}
|
|
430
|
-
getHandlerImports[fileName].push(handlerName);
|
|
431
|
-
}
|
|
432
|
-
return getHandlerImports;
|
|
433
|
-
}
|
|
434
255
|
/**
|
|
435
256
|
* Checks if a value is a non-null object (e.g., a potential `$ref` object).
|
|
436
257
|
*
|
|
@@ -560,7 +381,7 @@ export function methodPath(method, path) {
|
|
|
560
381
|
.split(/\s+/)
|
|
561
382
|
.map((str) => `${str.charAt(0).toUpperCase()}${str.slice(1)}`)
|
|
562
383
|
.join('');
|
|
563
|
-
return apiPath ? `${method}${apiPath}` : `${method}
|
|
384
|
+
return apiPath ? `${method}${apiPath}` : `${method}`;
|
|
564
385
|
}
|
|
565
386
|
/**
|
|
566
387
|
* Generates a Hono route definition as a TypeScript export string.
|
|
@@ -602,47 +423,18 @@ export function createRoute(args) {
|
|
|
602
423
|
}
|
|
603
424
|
/**
|
|
604
425
|
* Generates an array of Zod validator strings from OpenAPI parameter objects.
|
|
605
|
-
*
|
|
606
|
-
* ```mermaid
|
|
607
|
-
* graph TD
|
|
608
|
-
* A[Start requestParamsArray] --> B[Collect non empty sections]
|
|
609
|
-
* B --> C[Iterate sections]
|
|
610
|
-
* C --> D[Build z object string from entries]
|
|
611
|
-
* D --> E[If section is path rename to params]
|
|
612
|
-
* E --> F[Push section colon z object string]
|
|
613
|
-
* F --> G[Continue loop]
|
|
614
|
-
* G --> H[Filter out null values]
|
|
615
|
-
* H --> I[Return array of strings]
|
|
616
|
-
* ```
|
|
617
|
-
*
|
|
618
426
|
* @param parameters - An object containing `query`, `path`, and `header` parameters.
|
|
619
427
|
* @returns An array of strings like `'query:z.object({...})'` or `'params:z.object({...})'`.
|
|
620
428
|
*/
|
|
621
|
-
export
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
// 2.1 process only if object is not empty
|
|
631
|
-
if (Object.keys(obj).length) {
|
|
632
|
-
const s = `z.object({${Object.entries(obj)
|
|
633
|
-
.map(([k, v]) => `${k}:${v}`)
|
|
634
|
-
.join(',')}})`;
|
|
635
|
-
// path is params convention
|
|
636
|
-
if (section === 'path') {
|
|
637
|
-
return `params:${s}`;
|
|
638
|
-
}
|
|
639
|
-
return `${section}:${s}`;
|
|
640
|
-
}
|
|
641
|
-
return null;
|
|
642
|
-
})
|
|
643
|
-
// 3. exclude null and return only an array of strings
|
|
644
|
-
.filter((item) => item !== null));
|
|
645
|
-
}
|
|
429
|
+
export const requestParamsArray = (parameters) => Object.entries(parameters)
|
|
430
|
+
.filter(([, obj]) => obj && Object.keys(obj).length)
|
|
431
|
+
.map(([section, obj]) => {
|
|
432
|
+
const name = section === 'path' ? 'params' : section;
|
|
433
|
+
const fields = Object.entries(obj)
|
|
434
|
+
.map(([k, v]) => `${k}:${v}`)
|
|
435
|
+
.join(',');
|
|
436
|
+
return `${name}:z.object({${fields}})`;
|
|
437
|
+
});
|
|
646
438
|
/**
|
|
647
439
|
* Escapes a string for safe use in TypeScript string literals.
|
|
648
440
|
*
|
package/package.json
CHANGED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { OpenAPI } from '../openapi/index.js';
|
|
2
|
-
/**
|
|
3
|
-
* Extracts route mappings from an OpenAPI specification.
|
|
4
|
-
*
|
|
5
|
-
* @param openapi - The OpenAPI specification object.
|
|
6
|
-
* @returns An array of route mappings, each with route name, handler name, and path.
|
|
7
|
-
*/
|
|
8
|
-
export declare function getRouteMaps(openapi: OpenAPI): {
|
|
9
|
-
routeName: string;
|
|
10
|
-
handlerName: string;
|
|
11
|
-
path: string;
|
|
12
|
-
}[];
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { isHttpMethod, methodPath } from '../utils/index.js';
|
|
2
|
-
/**
|
|
3
|
-
* Extracts route mappings from an OpenAPI specification.
|
|
4
|
-
*
|
|
5
|
-
* @param openapi - The OpenAPI specification object.
|
|
6
|
-
* @returns An array of route mappings, each with route name, handler name, and path.
|
|
7
|
-
*/
|
|
8
|
-
export function getRouteMaps(openapi) {
|
|
9
|
-
const paths = openapi.paths;
|
|
10
|
-
const routeMappings = Object.entries(paths).flatMap(([path, pathItem]) => {
|
|
11
|
-
return Object.entries(pathItem).flatMap(([method]) => {
|
|
12
|
-
if (!isHttpMethod(method))
|
|
13
|
-
return [];
|
|
14
|
-
return {
|
|
15
|
-
routeName: `${methodPath(method, path)}Route`,
|
|
16
|
-
handlerName: `${methodPath(method, path)}RouteHandler`,
|
|
17
|
-
path,
|
|
18
|
-
};
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
return routeMappings;
|
|
22
|
-
}
|