hono-takibi 0.9.60 → 0.9.61
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 +6 -1
- package/dist/config/index.d.ts +3 -0
- package/dist/core/rpc.js +61 -79
- package/dist/core/takibi.d.ts +3 -3
- package/dist/core/takibi.js +7 -7
- package/dist/core/type.d.ts +7 -0
- package/dist/core/type.js +96 -0
- package/dist/generator/zod-openapi-hono/openapi/route/params/request-parameter.js +4 -7
- package/dist/generator/zod-openapi-hono/openapi/route/request/body/index.js +7 -16
- package/dist/generator/zod-openapi-hono/openapi/route/response/index.js +15 -51
- package/dist/generator/zod-to-openapi/z/enum.js +6 -6
- package/dist/generator/zod-to-openapi/z/integer.js +47 -69
- package/dist/generator/zod-to-openapi/z/number.js +43 -72
- package/dist/generator/zod-to-openapi/z/object.js +1 -1
- package/dist/generator/zod-to-openapi/z/string.d.ts +0 -20
- package/dist/generator/zod-to-openapi/z/string.js +14 -40
- package/dist/helper/docs.js +1 -1
- package/dist/helper/properties-schema.js +2 -2
- package/dist/helper/wrap.js +21 -17
- package/dist/openapi/index.d.ts +2 -2
- package/dist/utils/index.d.ts +11 -0
- package/dist/utils/index.js +16 -0
- package/dist/vite-plugin/index.js +1 -1
- package/package.json +14 -14
package/dist/cli/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { route } from '../core/route.js';
|
|
|
5
5
|
import { rpc } from '../core/rpc.js';
|
|
6
6
|
import { schema } from '../core/schema.js';
|
|
7
7
|
import { takibi } from '../core/takibi.js';
|
|
8
|
+
import { type } from '../core/type.js';
|
|
8
9
|
// import { honoRpcWithSWR } from '../generator/swr/index.js'
|
|
9
10
|
import { parseCli } from '../utils/index.js';
|
|
10
11
|
/**
|
|
@@ -104,13 +105,17 @@ export async function honoTakibi() {
|
|
|
104
105
|
: undefined;
|
|
105
106
|
if (routeResult && !routeResult.ok)
|
|
106
107
|
return { ok: false, error: routeResult.error };
|
|
108
|
+
/** type */
|
|
109
|
+
const typeResult = c.type ? await type(c.input, c.type.output) : undefined;
|
|
110
|
+
if (typeResult && !typeResult.ok)
|
|
111
|
+
return { ok: false, error: typeResult.error };
|
|
107
112
|
/** rpc */
|
|
108
113
|
const rpcResult = c.rpc
|
|
109
114
|
? await rpc(c.input, c.rpc.output, c.rpc.import, c.rpc.split ?? false)
|
|
110
115
|
: undefined;
|
|
111
116
|
if (rpcResult && !rpcResult.ok)
|
|
112
117
|
return { ok: false, error: rpcResult.error };
|
|
113
|
-
const results = [takibiResult?.value, rpcResult?.value].filter((v) =>
|
|
118
|
+
const results = [takibiResult?.value, typeResult?.value, rpcResult?.value].filter((v) => v !== undefined);
|
|
114
119
|
return {
|
|
115
120
|
ok: true,
|
|
116
121
|
value: results.join('\n'),
|
package/dist/config/index.d.ts
CHANGED
package/dist/core/rpc.js
CHANGED
|
@@ -139,14 +139,7 @@ const createResolveParameter = (componentsParameters) => (p) => {
|
|
|
139
139
|
const cand = name ? componentsParameters[name] : undefined;
|
|
140
140
|
return isParameterObject(cand) ? cand : undefined;
|
|
141
141
|
};
|
|
142
|
-
const createToParameterLikes = (resolveParam) => (arr) => Array.isArray(arr)
|
|
143
|
-
? arr.reduce((acc, x) => {
|
|
144
|
-
const r = resolveParam(x);
|
|
145
|
-
if (r)
|
|
146
|
-
acc.push(r);
|
|
147
|
-
return acc;
|
|
148
|
-
}, [])
|
|
149
|
-
: [];
|
|
142
|
+
const createToParameterLikes = (resolveParam) => (arr) => Array.isArray(arr) ? arr.map((x) => resolveParam(x)).filter((param) => param !== undefined) : [];
|
|
150
143
|
const isOperationLike = (v) => isRecord(v) && 'responses' in v;
|
|
151
144
|
const HTTP_METHODS = [
|
|
152
145
|
'get',
|
|
@@ -183,16 +176,14 @@ const pickBodySchema = (op) => {
|
|
|
183
176
|
};
|
|
184
177
|
/* ─────────────────────────────── Args builders ─────────────────────────────── */
|
|
185
178
|
const createBuildParamsType = (tsTypeFromSchema) => (pathParams, queryParams) => {
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
return parts.length ? `{${parts.join(',')}}` : '';
|
|
179
|
+
const pathPart = pathParams.length === 0
|
|
180
|
+
? []
|
|
181
|
+
: [`path:{${pathParams.map((p) => `${p.name}:${tsTypeFromSchema(p.schema)}`).join(',')}}`];
|
|
182
|
+
const queryPart = queryParams.length === 0
|
|
183
|
+
? []
|
|
184
|
+
: [`query:{${queryParams.map((p) => `${p.name}:${tsTypeFromSchema(p.schema)}`).join(',')}}`];
|
|
185
|
+
const parts = [...pathPart, ...queryPart];
|
|
186
|
+
return parts.length === 0 ? '' : `{${parts.join(',')}}`;
|
|
196
187
|
};
|
|
197
188
|
const buildArgSignature = (paramsType, bodyType) => paramsType && bodyType
|
|
198
189
|
? `params:${paramsType},body:${bodyType}`
|
|
@@ -203,14 +194,11 @@ const buildArgSignature = (paramsType, bodyType) => paramsType && bodyType
|
|
|
203
194
|
: '';
|
|
204
195
|
/** pass path/query as-is (keep numbers/arrays) */
|
|
205
196
|
const buildClientArgs = (pathParams, queryParams, hasBody) => {
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (hasBody)
|
|
212
|
-
pieces.push('json:body');
|
|
213
|
-
return pieces.length ? `{${pieces.join(',')}}` : '';
|
|
197
|
+
const paramPiece = pathParams.length ? ['param:params.path'] : [];
|
|
198
|
+
const queryPiece = queryParams.length ? ['query:params.query'] : [];
|
|
199
|
+
const bodyPiece = hasBody ? ['json:body'] : [];
|
|
200
|
+
const pieces = [...paramPiece, ...queryPiece, ...bodyPiece];
|
|
201
|
+
return pieces.length === 0 ? '' : `{${pieces.join(',')}}`;
|
|
214
202
|
};
|
|
215
203
|
/* ─────────────────────────────── Single-operation generator ─────────────────────────────── */
|
|
216
204
|
const generateOperationCode = (pathStr, method, item, deps) => {
|
|
@@ -246,6 +234,25 @@ const generateOperationCode = (pathStr, method, item, deps) => {
|
|
|
246
234
|
const func = `export async function ${funcName}(${argSig}){return await ${call}}`;
|
|
247
235
|
return `${docs}\n${func}`;
|
|
248
236
|
};
|
|
237
|
+
const buildOperationCodes = (paths, deps) => Object.entries(paths)
|
|
238
|
+
.filter((entry) => isRecord(entry[1]))
|
|
239
|
+
.flatMap(([p, rawItem]) => {
|
|
240
|
+
const pathItem = {
|
|
241
|
+
parameters: rawItem.parameters,
|
|
242
|
+
get: isOperationLike(rawItem.get) ? rawItem.get : undefined,
|
|
243
|
+
put: isOperationLike(rawItem.put) ? rawItem.put : undefined,
|
|
244
|
+
post: isOperationLike(rawItem.post) ? rawItem.post : undefined,
|
|
245
|
+
delete: isOperationLike(rawItem.delete) ? rawItem.delete : undefined,
|
|
246
|
+
options: isOperationLike(rawItem.options) ? rawItem.options : undefined,
|
|
247
|
+
head: isOperationLike(rawItem.head) ? rawItem.head : undefined,
|
|
248
|
+
patch: isOperationLike(rawItem.patch) ? rawItem.patch : undefined,
|
|
249
|
+
trace: isOperationLike(rawItem.trace) ? rawItem.trace : undefined,
|
|
250
|
+
};
|
|
251
|
+
return HTTP_METHODS.map((method) => {
|
|
252
|
+
const code = generateOperationCode(p, method, pathItem, deps);
|
|
253
|
+
return code ? { funcName: methodPath(method, p), code } : null;
|
|
254
|
+
}).filter((item) => item !== null);
|
|
255
|
+
});
|
|
249
256
|
/* ─────────────────────────────── Split ─────────────────────────────── */
|
|
250
257
|
const resolveSplitOutDir = (output) => {
|
|
251
258
|
const looksLikeFile = output.endsWith('.ts');
|
|
@@ -268,7 +275,6 @@ export async function rpc(input, output, importPath, split) {
|
|
|
268
275
|
const client = 'client';
|
|
269
276
|
const s = `import { client } from '${importPath}'`;
|
|
270
277
|
const header = s.length ? `${s}\n\n` : '';
|
|
271
|
-
const combinedOut = [];
|
|
272
278
|
const pathsMaybe = openAPI.paths;
|
|
273
279
|
if (!isOpenAPIPaths(pathsMaybe)) {
|
|
274
280
|
return { ok: false, error: 'Invalid OpenAPI paths' };
|
|
@@ -279,60 +285,19 @@ export async function rpc(input, output, importPath, split) {
|
|
|
279
285
|
const componentsParameters = openAPI.components?.parameters ?? {};
|
|
280
286
|
const resolveParameter = createResolveParameter(componentsParameters);
|
|
281
287
|
const toParameterLikes = createToParameterLikes(resolveParameter);
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
continue;
|
|
289
|
-
const pathItem = {
|
|
290
|
-
parameters: rawItem.parameters,
|
|
291
|
-
get: isOperationLike(rawItem.get) ? rawItem.get : undefined,
|
|
292
|
-
put: isOperationLike(rawItem.put) ? rawItem.put : undefined,
|
|
293
|
-
post: isOperationLike(rawItem.post) ? rawItem.post : undefined,
|
|
294
|
-
delete: isOperationLike(rawItem.delete) ? rawItem.delete : undefined,
|
|
295
|
-
options: isOperationLike(rawItem.options) ? rawItem.options : undefined,
|
|
296
|
-
head: isOperationLike(rawItem.head) ? rawItem.head : undefined,
|
|
297
|
-
patch: isOperationLike(rawItem.patch) ? rawItem.patch : undefined,
|
|
298
|
-
trace: isOperationLike(rawItem.trace) ? rawItem.trace : undefined,
|
|
299
|
-
};
|
|
300
|
-
for (const method of HTTP_METHODS) {
|
|
301
|
-
const code = generateOperationCode(p, method, pathItem, {
|
|
302
|
-
client,
|
|
303
|
-
tsTypeFromSchema,
|
|
304
|
-
toParameterLikes,
|
|
305
|
-
});
|
|
306
|
-
if (!code)
|
|
307
|
-
continue;
|
|
308
|
-
if (split) {
|
|
309
|
-
// One file per RPC function: <funcName>.ts
|
|
310
|
-
const funcName = methodPath(method, p);
|
|
311
|
-
const fileSrc = `${header}${code}\n`;
|
|
312
|
-
const fmtResult = await fmt(fileSrc);
|
|
313
|
-
if (!fmtResult.ok)
|
|
314
|
-
return { ok: false, error: fmtResult.error };
|
|
315
|
-
const { outDir } = resolveSplitOutDir(output);
|
|
316
|
-
const filePath = path.join(outDir, `${funcName}.ts`);
|
|
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 };
|
|
323
|
-
splitExports.add(`export * from './${funcName}'`);
|
|
324
|
-
}
|
|
325
|
-
else {
|
|
326
|
-
combinedOut.push(code);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
}
|
|
288
|
+
const deps = {
|
|
289
|
+
client,
|
|
290
|
+
tsTypeFromSchema,
|
|
291
|
+
toParameterLikes,
|
|
292
|
+
};
|
|
293
|
+
const operationCodes = buildOperationCodes(pathsMaybe, deps);
|
|
330
294
|
// Non-split: write single file
|
|
331
295
|
if (!split) {
|
|
332
296
|
const mkdirResult = await mkdir(path.dirname(output));
|
|
333
297
|
if (!mkdirResult.ok)
|
|
334
298
|
return { ok: false, error: mkdirResult.error };
|
|
335
|
-
const
|
|
299
|
+
const body = operationCodes.map(({ code }) => code).join('\n\n');
|
|
300
|
+
const code = `${header}${body}${operationCodes.length ? '\n' : ''}`;
|
|
336
301
|
const fmtResult = await fmt(code);
|
|
337
302
|
if (!fmtResult.ok)
|
|
338
303
|
return { ok: false, error: fmtResult.error };
|
|
@@ -341,9 +306,23 @@ export async function rpc(input, output, importPath, split) {
|
|
|
341
306
|
return { ok: false, error: writeResult.error };
|
|
342
307
|
return { ok: true, value: `Generated rpc code written to ${output}` };
|
|
343
308
|
}
|
|
344
|
-
// Split: write index.ts (barrel)
|
|
309
|
+
// Split: write each file + index.ts (barrel)
|
|
345
310
|
const { outDir, indexPath } = resolveSplitOutDir(output);
|
|
346
|
-
const
|
|
311
|
+
for (const { funcName, code } of operationCodes) {
|
|
312
|
+
const fileSrc = `${header}${code}\n`;
|
|
313
|
+
const fmtResult = await fmt(fileSrc);
|
|
314
|
+
if (!fmtResult.ok)
|
|
315
|
+
return { ok: false, error: fmtResult.error };
|
|
316
|
+
const filePath = path.join(outDir, `${funcName}.ts`);
|
|
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 };
|
|
323
|
+
}
|
|
324
|
+
const exportLines = Array.from(new Set(operationCodes.map(({ funcName }) => `export * from './${funcName}'`))).sort();
|
|
325
|
+
const index = `${exportLines.join('\n')}\n`;
|
|
347
326
|
const fmtResult = await fmt(index);
|
|
348
327
|
if (!fmtResult.ok)
|
|
349
328
|
return { ok: false, error: fmtResult.error };
|
|
@@ -353,5 +332,8 @@ export async function rpc(input, output, importPath, split) {
|
|
|
353
332
|
const writeResult = await writeFile(indexPath, fmtResult.value);
|
|
354
333
|
if (!writeResult.ok)
|
|
355
334
|
return { ok: false, error: writeResult.error };
|
|
356
|
-
return {
|
|
335
|
+
return {
|
|
336
|
+
ok: true,
|
|
337
|
+
value: `Generated rpc code written to ${outDir}/*.ts (index.ts included)`,
|
|
338
|
+
};
|
|
357
339
|
}
|
package/dist/core/takibi.d.ts
CHANGED
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
* Y --> Z["writeResult2 = writeFile(target, appResult.value)"]
|
|
31
31
|
* Z --> ZA{"writeResult2.ok ?"}
|
|
32
32
|
* ZA -->|No| ZB["return { ok:false, error: writeResult2.error }"]
|
|
33
|
-
* ZA -->|Yes| ZC["
|
|
34
|
-
* ZC --> ZD{"
|
|
35
|
-
* ZD -->|No| ZE["return { ok:false, error:
|
|
33
|
+
* ZA -->|Yes| ZC["zodOpenAPIHonoHandlerResult = zodOpenAPIHonoHandler(openAPI, output, test)"]
|
|
34
|
+
* ZC --> ZD{"zodOpenAPIHonoHandlerResult.ok ?"}
|
|
35
|
+
* ZD -->|No| ZE["return { ok:false, error: zodOpenAPIHonoHandlerResult.error }"]
|
|
36
36
|
* ZD -->|Yes| ZF["return { ok:true, value: 'Generated code and template files written' }"]
|
|
37
37
|
* ```
|
|
38
38
|
*
|
package/dist/core/takibi.js
CHANGED
|
@@ -37,9 +37,9 @@ import { isHttpMethod, methodPath } from '../utils/index.js';
|
|
|
37
37
|
* Y --> Z["writeResult2 = writeFile(target, appResult.value)"]
|
|
38
38
|
* Z --> ZA{"writeResult2.ok ?"}
|
|
39
39
|
* ZA -->|No| ZB["return { ok:false, error: writeResult2.error }"]
|
|
40
|
-
* ZA -->|Yes| ZC["
|
|
41
|
-
* ZC --> ZD{"
|
|
42
|
-
* ZD -->|No| ZE["return { ok:false, error:
|
|
40
|
+
* ZA -->|Yes| ZC["zodOpenAPIHonoHandlerResult = zodOpenAPIHonoHandler(openAPI, output, test)"]
|
|
41
|
+
* ZC --> ZD{"zodOpenAPIHonoHandlerResult.ok ?"}
|
|
42
|
+
* ZD -->|No| ZE["return { ok:false, error: zodOpenAPIHonoHandlerResult.error }"]
|
|
43
43
|
* ZD -->|Yes| ZF["return { ok:true, value: 'Generated code and template files written' }"]
|
|
44
44
|
* ```
|
|
45
45
|
*
|
|
@@ -80,9 +80,9 @@ export async function takibi(input, output, exportSchema, exportType, template,
|
|
|
80
80
|
const writeResult = await writeFile(target, appResult.value);
|
|
81
81
|
if (!writeResult.ok)
|
|
82
82
|
return { ok: false, error: writeResult.error };
|
|
83
|
-
const
|
|
84
|
-
if (!
|
|
85
|
-
return { ok: false, error:
|
|
83
|
+
const zodOpenAPIHonoHandlerResult = await zodOpenAPIHonoHandler(openAPI, output, test);
|
|
84
|
+
if (!zodOpenAPIHonoHandlerResult.ok)
|
|
85
|
+
return { ok: false, error: zodOpenAPIHonoHandlerResult.error };
|
|
86
86
|
return { ok: true, value: 'Generated code and template files written' };
|
|
87
87
|
}
|
|
88
88
|
return {
|
|
@@ -102,7 +102,7 @@ export async function takibi(input, output, exportSchema, exportType, template,
|
|
|
102
102
|
* @param test - Whether to generate corresponding empty test files.
|
|
103
103
|
* @returns A `Result` indicating success or error with message.
|
|
104
104
|
*/
|
|
105
|
-
async function
|
|
105
|
+
async function zodOpenAPIHonoHandler(openapi, output, test) {
|
|
106
106
|
const paths = openapi.paths;
|
|
107
107
|
const handlers = Object.entries(paths).flatMap(([p, pathItem]) => Object.entries(pathItem)
|
|
108
108
|
.filter(([m]) => isHttpMethod(m))
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import ts from 'typescript';
|
|
3
|
+
import { fmt } from '../format/index.js';
|
|
4
|
+
import { mkdir, writeFile } from '../fsp/index.js';
|
|
5
|
+
import { zodOpenAPIHono } from '../generator/zod-openapi-hono/openapi/index.js';
|
|
6
|
+
import { parseOpenAPI } from '../openapi/index.js';
|
|
7
|
+
import { isHttpMethod, methodPath } from '../utils/index.js';
|
|
8
|
+
export async function type(input, output) {
|
|
9
|
+
try {
|
|
10
|
+
const openAPIResult = await parseOpenAPI(input);
|
|
11
|
+
if (!openAPIResult.ok)
|
|
12
|
+
return { ok: false, error: openAPIResult.error };
|
|
13
|
+
const openAPI = openAPIResult.value;
|
|
14
|
+
const hono = zodOpenAPIHono(openAPI, false, false);
|
|
15
|
+
const paths = openAPI.paths;
|
|
16
|
+
const routes = Object.entries(paths).flatMap(([p, pathItem]) => Object.entries(pathItem)
|
|
17
|
+
.filter(([m]) => isHttpMethod(m))
|
|
18
|
+
.map(([method]) => {
|
|
19
|
+
const routeId = methodPath(method, p);
|
|
20
|
+
return `export const ${routeId}RouteHandler:RouteHandler<typeof ${routeId}Route>=async(c)=>{}`;
|
|
21
|
+
}));
|
|
22
|
+
const getRouteMaps = (openapi) => {
|
|
23
|
+
const openapiPaths = openapi.paths;
|
|
24
|
+
const routeMappings = Object.entries(openapiPaths).flatMap(([path, pathItem]) => {
|
|
25
|
+
return Object.entries(pathItem).flatMap(([method]) => {
|
|
26
|
+
if (!isHttpMethod(method)) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
const base = methodPath(method, path);
|
|
30
|
+
return {
|
|
31
|
+
routeName: `${base}Route`,
|
|
32
|
+
handlerName: `${base}RouteHandler`,
|
|
33
|
+
path,
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
return routeMappings;
|
|
38
|
+
};
|
|
39
|
+
const routeMappings = getRouteMaps(openAPI);
|
|
40
|
+
const appInit = 'export const api = app' +
|
|
41
|
+
routeMappings
|
|
42
|
+
.sort((a, b) => (a.routeName < b.routeName ? -1 : a.routeName > b.routeName ? 1 : 0))
|
|
43
|
+
.map(({ routeName, handlerName }) => `.openapi(${routeName},${handlerName})`)
|
|
44
|
+
.join('');
|
|
45
|
+
const code = `import { OpenAPIHono, type RouteHandler } from '@hono/zod-openapi'\n${hono}\nconst app = new OpenAPIHono()\n${routes.join('\n')}\n${appInit}\nexport type AddType = typeof api`;
|
|
46
|
+
const honoType = apiType(code);
|
|
47
|
+
if (honoType === undefined)
|
|
48
|
+
return { ok: false, error: 'not generated type' };
|
|
49
|
+
const mkdirResult = await mkdir(path.dirname(output));
|
|
50
|
+
if (!mkdirResult.ok)
|
|
51
|
+
return { ok: false, error: mkdirResult.error };
|
|
52
|
+
const type = `declare const routes:\n${honoType}\nexport default routes`;
|
|
53
|
+
const fmtResult = await fmt(type);
|
|
54
|
+
if (!fmtResult.ok)
|
|
55
|
+
return { ok: false, error: fmtResult.error };
|
|
56
|
+
const writeResult = await writeFile(output, fmtResult.value);
|
|
57
|
+
if (!writeResult.ok)
|
|
58
|
+
return { ok: false, error: writeResult.error };
|
|
59
|
+
return {
|
|
60
|
+
ok: true,
|
|
61
|
+
value: `Generated type code written to ${output}`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function apiType(code) {
|
|
69
|
+
const VIRTUAL_FILE_NAME = 'virtual.ts';
|
|
70
|
+
const compilerOptions = {
|
|
71
|
+
target: ts.ScriptTarget.ESNext,
|
|
72
|
+
module: ts.ModuleKind.NodeNext,
|
|
73
|
+
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
74
|
+
strict: true,
|
|
75
|
+
};
|
|
76
|
+
const sourceFile = ts.createSourceFile(VIRTUAL_FILE_NAME, code, compilerOptions.target ?? ts.ScriptTarget.ESNext);
|
|
77
|
+
const host = ts.createCompilerHost(compilerOptions);
|
|
78
|
+
const originalGetSourceFile = host.getSourceFile.bind(host);
|
|
79
|
+
host.getSourceFile = (fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile) => {
|
|
80
|
+
if (fileName === VIRTUAL_FILE_NAME)
|
|
81
|
+
return sourceFile;
|
|
82
|
+
return originalGetSourceFile(fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile);
|
|
83
|
+
};
|
|
84
|
+
const program = ts.createProgram([VIRTUAL_FILE_NAME], compilerOptions, host);
|
|
85
|
+
const checker = program.getTypeChecker();
|
|
86
|
+
const moduleSymbol = checker.getSymbolAtLocation(sourceFile);
|
|
87
|
+
if (!moduleSymbol)
|
|
88
|
+
return undefined;
|
|
89
|
+
const apiSymbol = checker.getExportsOfModule(moduleSymbol).find((s) => s.getName() === 'api');
|
|
90
|
+
if (!apiSymbol)
|
|
91
|
+
return undefined;
|
|
92
|
+
const type = checker.getTypeOfSymbolAtLocation(apiSymbol, sourceFile);
|
|
93
|
+
return checker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation |
|
|
94
|
+
ts.TypeFormatFlags.UseFullyQualifiedType |
|
|
95
|
+
ts.TypeFormatFlags.WriteTypeArgumentsOfSignature);
|
|
96
|
+
}
|
|
@@ -16,18 +16,16 @@ import { paramsObject } from './index.js';
|
|
|
16
16
|
*/
|
|
17
17
|
export function requestParameter(parameters, body) {
|
|
18
18
|
// Early return if no parameters or content
|
|
19
|
-
if (!(parameters || body?.content))
|
|
19
|
+
if (!(parameters || body?.content))
|
|
20
20
|
return '';
|
|
21
|
-
}
|
|
22
21
|
const requestBodyContentTypes = Object.keys(body?.content || {});
|
|
23
22
|
const params = parameters
|
|
24
23
|
? (() => {
|
|
25
24
|
const paramsObj = paramsObject(parameters);
|
|
26
25
|
const requestParamsArr = requestParamsArray(paramsObj);
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
// params
|
|
27
|
+
if (requestParamsArr.length)
|
|
29
28
|
return `request:{${requestParamsArr.join(',')}},`;
|
|
30
|
-
}
|
|
31
29
|
return '';
|
|
32
30
|
})()
|
|
33
31
|
: '';
|
|
@@ -42,9 +40,8 @@ export function requestParameter(parameters, body) {
|
|
|
42
40
|
const required = body.required ?? false;
|
|
43
41
|
const [firstSchema] = uniqueSchemas.values();
|
|
44
42
|
const requestBodyCode = requestBody(required, body.content, firstSchema);
|
|
45
|
-
if (params)
|
|
43
|
+
if (params)
|
|
46
44
|
return params.replace('request:{', `request:{${requestBodyCode}`);
|
|
47
|
-
}
|
|
48
45
|
return `request:{${requestBodyCode}},`;
|
|
49
46
|
}
|
|
50
47
|
return params;
|
|
@@ -11,21 +11,12 @@ export function requestBody(required, content, schema) {
|
|
|
11
11
|
const contentTypes = Object.keys(content);
|
|
12
12
|
if (contentTypes.length === 0)
|
|
13
13
|
return '';
|
|
14
|
-
// check duplication
|
|
15
14
|
const isUniqueSchema = isUniqueContentSchema(contentTypes, content);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
else {
|
|
25
|
-
contentParts.push(`'${contentType}':{schema:${schema}}`);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return `body:{required:${required},content:{${contentParts.join(',')}}},`;
|
|
29
|
-
}
|
|
30
|
-
return '';
|
|
15
|
+
if (!isUniqueSchema)
|
|
16
|
+
return '';
|
|
17
|
+
// if z.date() → z.coerce.date()
|
|
18
|
+
const contentParts = contentTypes
|
|
19
|
+
.map((contentType) => `'${contentType}':{schema:${schema.includes('z.date()') ? `z.coerce.${schema.replace('z.', '')}` : schema}}`)
|
|
20
|
+
.join(',');
|
|
21
|
+
return `body:{required:${required},content:{${contentParts}}},`;
|
|
31
22
|
}
|
|
@@ -12,53 +12,14 @@ import { zodToOpenAPI } from '../../../../zod-to-openapi/index.js';
|
|
|
12
12
|
* - Deduplicates content types if all share the same schema.
|
|
13
13
|
* - Escapes all descriptions safely for inline code.
|
|
14
14
|
*/
|
|
15
|
-
// export function response(responses: Responses): string {
|
|
16
|
-
// // 1. get response codes (200, 404, etc.)
|
|
17
|
-
// const responseCodes = Object.keys(responses)
|
|
18
|
-
// // 2. processing for each response code
|
|
19
|
-
// const responseEntries = responseCodes.map((code) => {
|
|
20
|
-
// const response = responses[code]
|
|
21
|
-
// // 2.1 no content (description only response)
|
|
22
|
-
// if (!response.content)
|
|
23
|
-
// return `${code}:{description:'${escapeStringLiteral(response.description ?? '')}',},`
|
|
24
|
-
// // check duplication
|
|
25
|
-
// const contentTypes = Object.keys(response.content)
|
|
26
|
-
// const isUniqueSchema = isUniqueContentSchema(contentTypes, response.content)
|
|
27
|
-
// // all duplication same schema
|
|
28
|
-
// if (isUniqueSchema) {
|
|
29
|
-
// const contentParts: string[] = []
|
|
30
|
-
// for (const contentType of contentTypes) {
|
|
31
|
-
// const content = response.content[contentType]
|
|
32
|
-
// const z = zodToOpenAPI(content.schema)
|
|
33
|
-
// const examples = content.examples
|
|
34
|
-
// const exampleString =
|
|
35
|
-
// examples && Object.keys(examples).length > 0
|
|
36
|
-
// ? `,examples:{${Object.entries(examples)
|
|
37
|
-
// .map(([key, example]) => {
|
|
38
|
-
// const parts = []
|
|
39
|
-
// if (example.summary) parts.push(`summary:${JSON.stringify(example.summary)}`)
|
|
40
|
-
// if (example.value !== undefined)
|
|
41
|
-
// parts.push(`value:${JSON.stringify(example.value)}`)
|
|
42
|
-
// return `${JSON.stringify(key)}:{${parts.join(',')}}`
|
|
43
|
-
// })
|
|
44
|
-
// .join(',')}}`
|
|
45
|
-
// : ''
|
|
46
|
-
// contentParts.push(`'${contentType}':{schema:${z}${exampleString}}`)
|
|
47
|
-
// }
|
|
48
|
-
// return `${code}:{description:'${escapeStringLiteral(response.description ?? '')}',content:{${contentParts.join(',')}}},`
|
|
49
|
-
// }
|
|
50
|
-
// })
|
|
51
|
-
// // 3.combine all response definitions
|
|
52
|
-
// return responseEntries.join('')
|
|
53
|
-
// }
|
|
54
15
|
export function response(responses) {
|
|
55
16
|
// 1. get response codes (200, 404, etc.)
|
|
56
|
-
|
|
17
|
+
return Object.keys(responses)
|
|
18
|
+
.map((code) => {
|
|
57
19
|
const res = responses[code];
|
|
58
20
|
const content = res.content;
|
|
59
|
-
if (!content)
|
|
21
|
+
if (!content)
|
|
60
22
|
return `${code}:{description:'${escapeStringLiteral(res.description ?? '')}',},`;
|
|
61
|
-
}
|
|
62
23
|
const contentTypes = Object.keys(content);
|
|
63
24
|
const isUnique = isUniqueContentSchema(contentTypes, content);
|
|
64
25
|
const sharedZ = isUnique ? zodToOpenAPI(content[contentTypes[0]].schema) : undefined;
|
|
@@ -68,19 +29,22 @@ export function response(responses) {
|
|
|
68
29
|
const examples = media.examples;
|
|
69
30
|
const exampleString = examples && Object.keys(examples).length > 0
|
|
70
31
|
? `,examples:{${Object.entries(examples)
|
|
71
|
-
.map(([
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
32
|
+
.map(([exampleKey, example]) => {
|
|
33
|
+
const fields = [
|
|
34
|
+
example.summary !== undefined
|
|
35
|
+
? `summary:${JSON.stringify(example.summary)}`
|
|
36
|
+
: undefined,
|
|
37
|
+
example.value !== undefined
|
|
38
|
+
? `value:${JSON.stringify(example.value)}`
|
|
39
|
+
: undefined,
|
|
40
|
+
].filter((field) => field !== undefined);
|
|
41
|
+
return `${JSON.stringify(exampleKey)}:{${fields.join(',')}}`;
|
|
78
42
|
})
|
|
79
43
|
.join(',')}}`
|
|
80
44
|
: '';
|
|
81
45
|
return `'${ct}':{schema:${z}${exampleString}}`;
|
|
82
46
|
});
|
|
83
47
|
return `${code}:{description:'${escapeStringLiteral(res.description ?? '')}',content:{${contentParts.join(',')}}},`;
|
|
84
|
-
})
|
|
85
|
-
|
|
48
|
+
})
|
|
49
|
+
.join('');
|
|
86
50
|
}
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
export function _enum(schema) {
|
|
2
|
-
/*
|
|
3
|
-
const
|
|
2
|
+
/* ht */
|
|
3
|
+
const ht = (t) => schema.type === t || (Array.isArray(schema.type) && schema.type.some((v) => v === t));
|
|
4
4
|
/* lit */
|
|
5
5
|
const lit = (v) => v === null ? 'null' : typeof v === 'string' ? `'${v}'` : String(v);
|
|
6
6
|
/* tuple */
|
|
7
|
-
const tuple = (arr) => `z.tuple([${arr.map((
|
|
7
|
+
const tuple = (arr) => `z.tuple([${arr.map((v) => `z.literal(${lit(v)})`).join(',')}])`;
|
|
8
8
|
/* guard */
|
|
9
9
|
if (!schema.enum || schema.enum.length === 0)
|
|
10
10
|
return 'z.any()';
|
|
11
11
|
/* number / integer enum */
|
|
12
|
-
if (
|
|
12
|
+
if (ht('number') || ht('integer')) {
|
|
13
13
|
return schema.enum.length > 1
|
|
14
14
|
? `z.union([${schema.enum.map((v) => `z.literal(${v})`).join(',')}])`
|
|
15
15
|
: `z.literal(${schema.enum[0]})`;
|
|
16
16
|
}
|
|
17
17
|
/* boolean enum */
|
|
18
|
-
if (
|
|
18
|
+
if (ht('boolean')) {
|
|
19
19
|
return schema.enum.length > 1
|
|
20
20
|
? `z.union([${schema.enum.map((v) => `z.literal(${v})`).join(',')}])`
|
|
21
21
|
: `z.literal(${schema.enum[0]})`;
|
|
22
22
|
}
|
|
23
23
|
/* array enum */
|
|
24
|
-
if (
|
|
24
|
+
if (ht('array')) {
|
|
25
25
|
if (schema.enum.length === 1 && Array.isArray(schema.enum[0])) {
|
|
26
26
|
return tuple(schema.enum[0]);
|
|
27
27
|
}
|
|
@@ -6,86 +6,64 @@
|
|
|
6
6
|
* @returns The Zod schema string
|
|
7
7
|
*/
|
|
8
8
|
export function integer(schema) {
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
const base = schema.format === 'int32'
|
|
10
|
+
? 'z.int32()'
|
|
11
|
+
: schema.format === 'int64'
|
|
12
|
+
? 'z.int64()'
|
|
13
|
+
: schema.format === 'bigint'
|
|
14
|
+
? 'z.bigint()'
|
|
15
|
+
: 'z.int()';
|
|
15
16
|
const lit = (n) => {
|
|
16
|
-
if (
|
|
17
|
+
if (schema.format === 'bigint')
|
|
17
18
|
return `BigInt(${n})`;
|
|
18
|
-
if (
|
|
19
|
+
if (schema.format === 'int64')
|
|
19
20
|
return `${n}n`;
|
|
20
21
|
return `${n}`;
|
|
21
22
|
};
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// z.int().positive().safeParse(1) // { success: true }
|
|
26
|
-
// z.int().positive().safeParse(0) // { success: false }
|
|
27
|
-
if ((schema.minimum ?? schema.exclusiveMinimum) === 0 && schema.exclusiveMinimum === true) {
|
|
28
|
-
o.push('.positive()');
|
|
23
|
+
const minimum = (() => {
|
|
24
|
+
if (schema.minimum === undefined && schema.exclusiveMinimum === undefined) {
|
|
25
|
+
return undefined;
|
|
29
26
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
else if ((schema.minimum ?? schema.exclusiveMinimum) === 0 &&
|
|
34
|
-
schema.exclusiveMinimum === false) {
|
|
35
|
-
o.push('.nonnegative()');
|
|
27
|
+
const value = schema.minimum ?? schema.exclusiveMinimum;
|
|
28
|
+
if (value === 0 && schema.exclusiveMinimum === true) {
|
|
29
|
+
return '.positive()';
|
|
36
30
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
// z.int().gt(100).safeParse(101) // { success: true }
|
|
40
|
-
// z.int().gt(100).safeParse(100) // { success: false }
|
|
41
|
-
else if ((schema.exclusiveMinimum === true || schema.minimum === undefined) &&
|
|
42
|
-
typeof (schema.minimum ?? schema.exclusiveMinimum) === 'number') {
|
|
43
|
-
o.push(`.gt(${lit((schema.minimum ?? schema.exclusiveMinimum))})`);
|
|
31
|
+
if (value === 0 && schema.exclusiveMinimum === false) {
|
|
32
|
+
return '.nonnegative()';
|
|
44
33
|
}
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
else if (typeof schema.minimum === 'number') {
|
|
50
|
-
o.push(`.min(${lit(schema.minimum)})`);
|
|
34
|
+
// > value → .gt(...)
|
|
35
|
+
if ((schema.exclusiveMinimum === true || schema.minimum === undefined) &&
|
|
36
|
+
typeof value === 'number') {
|
|
37
|
+
return `.gt(${lit(value)})`;
|
|
51
38
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
// < 0
|
|
56
|
-
// z.int().negative().safeParse(-1) // { success: true }
|
|
57
|
-
// z.int().negative().safeParse(0) // { success: false }
|
|
58
|
-
if ((schema.maximum ?? schema.exclusiveMaximum) === 0 && schema.exclusiveMaximum === true) {
|
|
59
|
-
o.push('.negative()');
|
|
39
|
+
// >= value → .min(...)
|
|
40
|
+
if (typeof schema.minimum === 'number') {
|
|
41
|
+
return `.min(${lit(schema.minimum)})`;
|
|
60
42
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
o.push('.nonpositive()');
|
|
43
|
+
return undefined;
|
|
44
|
+
})();
|
|
45
|
+
const maximum = (() => {
|
|
46
|
+
if (schema.maximum === undefined && schema.exclusiveMaximum === undefined) {
|
|
47
|
+
return undefined;
|
|
67
48
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// z.int().lt(100).safeParse(100) -> { success: false }
|
|
72
|
-
else if ((schema.exclusiveMaximum === true || schema.maximum === undefined) &&
|
|
73
|
-
typeof (schema.maximum ?? schema.exclusiveMaximum) === 'number') {
|
|
74
|
-
o.push(`.lt(${lit((schema.maximum ?? schema.exclusiveMaximum))})`);
|
|
49
|
+
const value = schema.maximum ?? schema.exclusiveMaximum;
|
|
50
|
+
if (value === 0 && schema.exclusiveMaximum === true) {
|
|
51
|
+
return '.negative()';
|
|
75
52
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// z.int().max(100).safeParse(100) -> { success: true }
|
|
79
|
-
// z.int().max(100).safeParse(101) -> { success: false }
|
|
80
|
-
else if (typeof schema.maximum === 'number') {
|
|
81
|
-
o.push(`.max(${lit(schema.maximum)})`);
|
|
53
|
+
if (value === 0 && schema.exclusiveMaximum === false) {
|
|
54
|
+
return '.nonpositive()';
|
|
82
55
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
56
|
+
if ((schema.exclusiveMaximum === true || schema.maximum === undefined) &&
|
|
57
|
+
typeof value === 'number') {
|
|
58
|
+
return `.lt(${lit(value)})`;
|
|
59
|
+
}
|
|
60
|
+
if (typeof schema.maximum === 'number') {
|
|
61
|
+
return `.max(${lit(schema.maximum)})`;
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
})();
|
|
65
|
+
const multipleOf = schema.multipleOf !== undefined && typeof schema.multipleOf === 'number'
|
|
66
|
+
? `.multipleOf(${lit(schema.multipleOf)})`
|
|
67
|
+
: undefined;
|
|
68
|
+
return [base, minimum, maximum, multipleOf].filter((v) => v !== undefined).join('');
|
|
91
69
|
}
|
|
@@ -6,76 +6,47 @@
|
|
|
6
6
|
* @returns The Zod schema string
|
|
7
7
|
*/
|
|
8
8
|
export function number(schema) {
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
// <= 0
|
|
54
|
-
// z.number().nonpositive().safeParse(0) // { success: true }
|
|
55
|
-
// z.number().nonpositive().safeParse(1) // { success: false }
|
|
56
|
-
else if (schema.maximum === 0 && schema.exclusiveMaximum === false) {
|
|
57
|
-
o.push('.nonpositive()');
|
|
58
|
-
}
|
|
59
|
-
// < value
|
|
60
|
-
// z.number().lt(100) // value < 100
|
|
61
|
-
else if (schema.exclusiveMaximum === true) {
|
|
62
|
-
o.push(`.lt(${schema.maximum})`);
|
|
63
|
-
}
|
|
64
|
-
// <= value
|
|
65
|
-
// z.number().max(100) // value <= 100
|
|
66
|
-
else {
|
|
67
|
-
o.push(`.max(${schema.maximum})`);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
else if (typeof schema.exclusiveMaximum === 'number') {
|
|
71
|
-
// < value (no maximum)
|
|
72
|
-
o.push(`.lt(${schema.exclusiveMaximum})`);
|
|
73
|
-
}
|
|
74
|
-
// multipleOf
|
|
75
|
-
// z.number().multipleOf(2).safeParse(2) // { success: true }
|
|
76
|
-
// z.number().multipleOf(2).safeParse(1) // { success: false }
|
|
77
|
-
if (schema.multipleOf !== undefined) {
|
|
78
|
-
o.push(`.multipleOf(${schema.multipleOf})`);
|
|
79
|
-
}
|
|
80
|
-
return o.join('');
|
|
9
|
+
const base = schema.format === 'float' || schema.format === 'float32'
|
|
10
|
+
? 'z.float32()'
|
|
11
|
+
: schema.format === 'float64'
|
|
12
|
+
? 'z.float64()'
|
|
13
|
+
: 'z.number()';
|
|
14
|
+
const minimum = (() => {
|
|
15
|
+
if (schema.minimum !== undefined) {
|
|
16
|
+
if (schema.minimum === 0 && schema.exclusiveMinimum === true) {
|
|
17
|
+
return '.positive()';
|
|
18
|
+
}
|
|
19
|
+
if (schema.minimum === 0 && schema.exclusiveMinimum === false) {
|
|
20
|
+
return '.nonnegative()';
|
|
21
|
+
}
|
|
22
|
+
if (schema.exclusiveMinimum === true) {
|
|
23
|
+
return `.gt(${schema.minimum})`;
|
|
24
|
+
}
|
|
25
|
+
return `.min(${schema.minimum})`;
|
|
26
|
+
}
|
|
27
|
+
if (typeof schema.exclusiveMinimum === 'number') {
|
|
28
|
+
return `.gt(${schema.exclusiveMinimum})`;
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
})();
|
|
32
|
+
const maximum = (() => {
|
|
33
|
+
if (schema.maximum !== undefined) {
|
|
34
|
+
if (schema.maximum === 0 && schema.exclusiveMaximum === true) {
|
|
35
|
+
return '.negative()';
|
|
36
|
+
}
|
|
37
|
+
if (schema.maximum === 0 && schema.exclusiveMaximum === false) {
|
|
38
|
+
return '.nonpositive()';
|
|
39
|
+
}
|
|
40
|
+
if (schema.exclusiveMaximum === true) {
|
|
41
|
+
return `.lt(${schema.maximum})`;
|
|
42
|
+
}
|
|
43
|
+
return `.max(${schema.maximum})`;
|
|
44
|
+
}
|
|
45
|
+
if (typeof schema.exclusiveMaximum === 'number') {
|
|
46
|
+
return `.lt(${schema.exclusiveMaximum})`;
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
})();
|
|
50
|
+
const multipleOf = schema.multipleOf !== undefined ? `.multipleOf(${schema.multipleOf})` : undefined;
|
|
51
|
+
return [base, minimum, maximum, multipleOf].filter((v) => v !== undefined).join('');
|
|
81
52
|
}
|
|
@@ -7,7 +7,7 @@ import { zodToOpenAPI } from '../index.js';
|
|
|
7
7
|
* @returns The Zod object schema string.
|
|
8
8
|
*/
|
|
9
9
|
export function object(schema) {
|
|
10
|
-
//
|
|
10
|
+
// allOf, oneOf, anyOf, not
|
|
11
11
|
if (schema.oneOf)
|
|
12
12
|
return zodToOpenAPI(schema);
|
|
13
13
|
if (schema.anyOf)
|
|
@@ -8,26 +8,6 @@ import type { Schema } from '../../../openapi/index.js';
|
|
|
8
8
|
* - Otherwise, appends `.min(n)` and/or `.max(n)` individually
|
|
9
9
|
* - Returns the concatenated Zod schema string
|
|
10
10
|
*
|
|
11
|
-
* ```mermaid
|
|
12
|
-
* flowchart TD
|
|
13
|
-
* A["string(schema)"] --> B["format = schema.format && FORMAT_STRING[format]"]
|
|
14
|
-
* B --> C{"Format exists?"}
|
|
15
|
-
* C -- "Yes" --> D["o.push(z.${format})"]
|
|
16
|
-
* C -- "No" --> E["o.push(z.string())"]
|
|
17
|
-
* D --> F{"Pattern exists?"}
|
|
18
|
-
* E --> F
|
|
19
|
-
* F -- "Yes" --> G["o.push(regex(schema.pattern))"]
|
|
20
|
-
* F -- "No" --> I
|
|
21
|
-
* G --> I{"minLength and maxLength are equal?"}
|
|
22
|
-
* I -- "Yes" --> J["o.push(length(minLength))"]
|
|
23
|
-
* I -- "No" --> K{"minLength or maxLength?"}
|
|
24
|
-
* K -- "min" --> L["o.push(min(minLength))"]
|
|
25
|
-
* K -- "max" --> M["o.push(max(maxLength))"]
|
|
26
|
-
* L --> N["return o.join('')"]
|
|
27
|
-
* M --> N
|
|
28
|
-
* J --> N
|
|
29
|
-
* ```
|
|
30
|
-
*
|
|
31
11
|
* @example
|
|
32
12
|
* ```ts
|
|
33
13
|
* // With format, pattern, and minLength
|
|
@@ -36,26 +36,6 @@ const FORMAT_STRING = {
|
|
|
36
36
|
* - Otherwise, appends `.min(n)` and/or `.max(n)` individually
|
|
37
37
|
* - Returns the concatenated Zod schema string
|
|
38
38
|
*
|
|
39
|
-
* ```mermaid
|
|
40
|
-
* flowchart TD
|
|
41
|
-
* A["string(schema)"] --> B["format = schema.format && FORMAT_STRING[format]"]
|
|
42
|
-
* B --> C{"Format exists?"}
|
|
43
|
-
* C -- "Yes" --> D["o.push(z.${format})"]
|
|
44
|
-
* C -- "No" --> E["o.push(z.string())"]
|
|
45
|
-
* D --> F{"Pattern exists?"}
|
|
46
|
-
* E --> F
|
|
47
|
-
* F -- "Yes" --> G["o.push(regex(schema.pattern))"]
|
|
48
|
-
* F -- "No" --> I
|
|
49
|
-
* G --> I{"minLength and maxLength are equal?"}
|
|
50
|
-
* I -- "Yes" --> J["o.push(length(minLength))"]
|
|
51
|
-
* I -- "No" --> K{"minLength or maxLength?"}
|
|
52
|
-
* K -- "min" --> L["o.push(min(minLength))"]
|
|
53
|
-
* K -- "max" --> M["o.push(max(maxLength))"]
|
|
54
|
-
* L --> N["return o.join('')"]
|
|
55
|
-
* M --> N
|
|
56
|
-
* J --> N
|
|
57
|
-
* ```
|
|
58
|
-
*
|
|
59
39
|
* @example
|
|
60
40
|
* ```ts
|
|
61
41
|
* // With format, pattern, and minLength
|
|
@@ -67,26 +47,20 @@ const FORMAT_STRING = {
|
|
|
67
47
|
* @returns Concatenated Zod string schema
|
|
68
48
|
*/
|
|
69
49
|
export function string(schema) {
|
|
70
|
-
const o = [];
|
|
71
50
|
const format = schema.format && FORMAT_STRING[schema.format];
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
o.push(regex(schema.pattern));
|
|
76
|
-
}
|
|
77
|
-
// length
|
|
78
|
-
if (schema.minLength !== undefined &&
|
|
51
|
+
const base = format ? `z.${format}` : 'z.string()';
|
|
52
|
+
const pattern = schema.pattern ? regex(schema.pattern) : undefined;
|
|
53
|
+
const isFixedLength = schema.minLength !== undefined &&
|
|
79
54
|
schema.maxLength !== undefined &&
|
|
80
|
-
schema.minLength === schema.maxLength
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return o.join('');
|
|
55
|
+
schema.minLength === schema.maxLength;
|
|
56
|
+
return [
|
|
57
|
+
base,
|
|
58
|
+
pattern,
|
|
59
|
+
// minLength === maxLength → .length(n)
|
|
60
|
+
isFixedLength ? `.length(${schema.minLength})` : undefined,
|
|
61
|
+
!isFixedLength && schema.minLength !== undefined ? `.min(${schema.minLength})` : undefined,
|
|
62
|
+
!isFixedLength && schema.maxLength !== undefined ? `.max(${schema.maxLength})` : undefined,
|
|
63
|
+
]
|
|
64
|
+
.filter((v) => v !== undefined)
|
|
65
|
+
.join('');
|
|
92
66
|
}
|
package/dist/helper/docs.js
CHANGED
|
@@ -51,10 +51,10 @@ export function propertiesSchema(properties, required) {
|
|
|
51
51
|
return `${safeKey}:${zodToOpenAPI(schema)}${isRequired ? '' : '.optional()'}`;
|
|
52
52
|
});
|
|
53
53
|
// Check if all properties are optional
|
|
54
|
-
const allOptional = objectProperties.every((
|
|
54
|
+
const allOptional = objectProperties.every((v) => v.includes('.optional()'));
|
|
55
55
|
// If all properties are optional and no required properties, return partial schema
|
|
56
56
|
if (required.length === 0 && allOptional) {
|
|
57
|
-
const cleanProperties = objectProperties.map((
|
|
57
|
+
const cleanProperties = objectProperties.map((v) => v.replace('.optional()', ''));
|
|
58
58
|
return `z.object({${cleanProperties}}).partial()`;
|
|
59
59
|
}
|
|
60
60
|
return `z.object({${objectProperties}})`;
|
package/dist/helper/wrap.js
CHANGED
|
@@ -30,22 +30,26 @@ export function wrap(zod, schema, paramName, paramIn) {
|
|
|
30
30
|
const isNullable = schema.nullable === true ||
|
|
31
31
|
(Array.isArray(schema.type) ? schema.type.includes('null') : schema.type === 'null');
|
|
32
32
|
const z = isNullable ? `${s}.nullable()` : s;
|
|
33
|
-
const openapiProps = [
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
33
|
+
const openapiProps = [
|
|
34
|
+
// param
|
|
35
|
+
paramIn && paramName
|
|
36
|
+
? (() => {
|
|
37
|
+
const required = paramIn === 'path' ? true : !!schema.required;
|
|
38
|
+
return `param:{in:"${paramIn}",name:${JSON.stringify(paramName)},required:${required}}`;
|
|
39
|
+
})()
|
|
40
|
+
: undefined,
|
|
41
|
+
// example
|
|
42
|
+
'example' in schema && schema.example !== undefined
|
|
43
|
+
? `example:${JSON.stringify(schema.example)}`
|
|
44
|
+
: undefined,
|
|
45
|
+
// examples
|
|
46
|
+
'examples' in schema && Array.isArray(schema.examples) && schema.examples.length > 0
|
|
47
|
+
? `examples:${JSON.stringify(schema.examples)}`
|
|
48
|
+
: undefined,
|
|
49
|
+
// description
|
|
50
|
+
'description' in schema && schema.description !== undefined
|
|
51
|
+
? `description:${JSON.stringify(schema.description)}`
|
|
52
|
+
: undefined,
|
|
53
|
+
].filter((v) => v !== undefined);
|
|
50
54
|
return openapiProps.length === 0 ? z : `${z}.openapi({${openapiProps.join(',')}})`;
|
|
51
55
|
}
|
package/dist/openapi/index.d.ts
CHANGED
|
@@ -128,7 +128,7 @@ export type Schema = {
|
|
|
128
128
|
name?: string;
|
|
129
129
|
description?: string;
|
|
130
130
|
type?: Type | [Type, ...Type[]];
|
|
131
|
-
format?: Format
|
|
131
|
+
format?: Format;
|
|
132
132
|
pattern?: string;
|
|
133
133
|
minLength?: number;
|
|
134
134
|
maxLength?: number;
|
|
@@ -143,7 +143,7 @@ export type Schema = {
|
|
|
143
143
|
example?: unknown;
|
|
144
144
|
examples?: unknown;
|
|
145
145
|
properties?: Record<string, Schema>;
|
|
146
|
-
required?: string[];
|
|
146
|
+
required?: string[] | boolean;
|
|
147
147
|
items?: Schema;
|
|
148
148
|
enum?: (string | number | boolean | null | (string | number | boolean | null)[])[];
|
|
149
149
|
nullable?: boolean;
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse the config object.
|
|
3
|
+
*
|
|
4
|
+
* @param config - The config object to parse.
|
|
5
|
+
*/
|
|
1
6
|
export declare function parseConfig(config: {
|
|
2
7
|
readonly input: `${string}.yaml` | `${string}.json` | `${string}.tsp`;
|
|
3
8
|
readonly 'zod-openapi'?: {
|
|
@@ -15,6 +20,9 @@ export declare function parseConfig(config: {
|
|
|
15
20
|
readonly split?: boolean;
|
|
16
21
|
};
|
|
17
22
|
};
|
|
23
|
+
readonly type?: {
|
|
24
|
+
readonly output: `${string}.ts`;
|
|
25
|
+
};
|
|
18
26
|
readonly rpc?: {
|
|
19
27
|
readonly output: string | `${string}.ts`;
|
|
20
28
|
readonly import: string;
|
|
@@ -39,6 +47,9 @@ export declare function parseConfig(config: {
|
|
|
39
47
|
readonly split?: boolean;
|
|
40
48
|
};
|
|
41
49
|
};
|
|
50
|
+
readonly type?: {
|
|
51
|
+
readonly output: `${string}.ts`;
|
|
52
|
+
};
|
|
42
53
|
readonly rpc?: {
|
|
43
54
|
readonly output: string | `${string}.ts`;
|
|
44
55
|
readonly import: string;
|
package/dist/utils/index.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse the config object.
|
|
3
|
+
*
|
|
4
|
+
* @param config - The config object to parse.
|
|
5
|
+
*/
|
|
1
6
|
export function parseConfig(config) {
|
|
2
7
|
const isYamlOrJsonOrTsp = (i) => typeof i === 'string' && (i.endsWith('.yaml') || i.endsWith('.json') || i.endsWith('.tsp'));
|
|
8
|
+
// ts
|
|
3
9
|
const isTs = (o) => typeof o === 'string' && o.endsWith('.ts');
|
|
4
10
|
const c = config;
|
|
5
11
|
if (!isYamlOrJsonOrTsp(c.input)) {
|
|
@@ -118,6 +124,16 @@ export function parseConfig(config) {
|
|
|
118
124
|
}
|
|
119
125
|
}
|
|
120
126
|
}
|
|
127
|
+
// type
|
|
128
|
+
const t = c.type;
|
|
129
|
+
if (t !== undefined) {
|
|
130
|
+
if (!isTs(t.output)) {
|
|
131
|
+
return {
|
|
132
|
+
ok: false,
|
|
133
|
+
error: `Invalid type output format: ${String(t.output)} (must be .ts file)`,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
121
137
|
// rpc
|
|
122
138
|
const rpc = c.rpc;
|
|
123
139
|
if (rpc !== undefined) {
|
|
@@ -44,7 +44,7 @@ const loadConfigHot = async (server) => {
|
|
|
44
44
|
const def = isRecord(mod) ? Reflect.get(mod, 'default') : undefined;
|
|
45
45
|
if (def === undefined)
|
|
46
46
|
return { ok: false, error: 'Config must export default object' };
|
|
47
|
-
// as
|
|
47
|
+
// as Conf
|
|
48
48
|
const parsed = parseConfig(def);
|
|
49
49
|
return parsed.ok ? { ok: true, value: parsed.value } : { ok: false, error: parsed.error };
|
|
50
50
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hono-takibi",
|
|
3
3
|
"description": "Hono Takibi is a CLI tool that generates Hono routes from OpenAPI specifications.",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.61",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"keywords": [
|
|
@@ -47,22 +47,22 @@
|
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@apidevtools/swagger-parser": "^12.1.0",
|
|
50
|
-
"@typespec/compiler": "^1.
|
|
51
|
-
"@typespec/openapi3": "^1.
|
|
52
|
-
"prettier": "^3.
|
|
53
|
-
"tsx": "^4.
|
|
50
|
+
"@typespec/compiler": "^1.7.0",
|
|
51
|
+
"@typespec/openapi3": "^1.7.0",
|
|
52
|
+
"prettier": "^3.7.4",
|
|
53
|
+
"tsx": "^4.21.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@hono/zod-openapi": "1.1.
|
|
57
|
-
"@types/node": "^
|
|
58
|
-
"@typespec/http": "^1.
|
|
59
|
-
"@typespec/rest": "^0.
|
|
60
|
-
"@typespec/versioning": "^0.
|
|
61
|
-
"@vitest/coverage-v8": "^4.0.
|
|
56
|
+
"@hono/zod-openapi": "1.1.5",
|
|
57
|
+
"@types/node": "^25.0.0",
|
|
58
|
+
"@typespec/http": "^1.7.0",
|
|
59
|
+
"@typespec/rest": "^0.77.0",
|
|
60
|
+
"@typespec/versioning": "^0.77.0",
|
|
61
|
+
"@vitest/coverage-v8": "^4.0.15",
|
|
62
62
|
"typescript": "^5.9.3",
|
|
63
|
-
"vite": "^7.
|
|
64
|
-
"vitest": "^4.0.
|
|
65
|
-
"zod": "^4.1.
|
|
63
|
+
"vite": "^7.2.7",
|
|
64
|
+
"vitest": "^4.0.15",
|
|
65
|
+
"zod": "^4.1.13"
|
|
66
66
|
},
|
|
67
67
|
"scripts": {
|
|
68
68
|
"dev": "vite --host",
|