hono-takibi 0.9.52 → 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 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) => Boolean(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'),
@@ -15,6 +15,9 @@ type Config = {
15
15
  readonly split?: boolean;
16
16
  };
17
17
  };
18
+ readonly type?: {
19
+ readonly output: `${string}.ts`;
20
+ };
18
21
  readonly rpc?: {
19
22
  readonly output: string | `${string}.ts`;
20
23
  readonly import: string;
@@ -41,9 +41,9 @@ export async function route(input, output, importPath, split) {
41
41
  if (!mkdirResult.ok)
42
42
  return { ok: false, error: mkdirResult.error };
43
43
  const writeResult = await writeFile(output, fmtResult.value);
44
- return writeResult.ok
45
- ? { ok: true, value: `Generated route code written to ${output}` }
46
- : { ok: false, error: writeResult.error };
44
+ if (!writeResult.ok)
45
+ return { ok: false, error: writeResult.error };
46
+ return { ok: true, value: `Generated route code written to ${output}` };
47
47
  }
48
48
  const outDir = output.replace(/\.ts$/, '');
49
49
  const blocks = extractRouteBlocks(routesSrc);
@@ -60,9 +60,9 @@ export async function route(input, output, importPath, split) {
60
60
  if (!mkdirResult.ok)
61
61
  return { ok: false, error: mkdirResult.error };
62
62
  const writeResult = await writeFile(output, fmtResult.value);
63
- return writeResult.ok
64
- ? { ok: true, value: `Generated route code written to ${output}` }
65
- : { ok: false, error: writeResult.error };
63
+ if (!writeResult.ok)
64
+ return { ok: false, error: writeResult.error };
65
+ return { ok: true, value: `Generated route code written to ${output}` };
66
66
  }
67
67
  for (const { name, block } of blocks) {
68
68
  const includeZ = block.includes('z.');
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 parts = [];
187
- if (pathParams.length) {
188
- const inner = pathParams.map((p) => `${p.name}:${tsTypeFromSchema(p.schema)}`).join(',');
189
- parts.push(`path:{${inner}}`);
190
- }
191
- if (queryParams.length) {
192
- const inner = queryParams.map((p) => `${p.name}:${tsTypeFromSchema(p.schema)}`).join(',');
193
- parts.push(`query:{${inner}}`);
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 pieces = [];
207
- if (pathParams.length)
208
- pieces.push('param:params.path');
209
- if (queryParams.length)
210
- pieces.push('query:params.query');
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
- // Split-mode: prepare an index file exports collector.
283
- const splitExports = new Set();
284
- // Iterate paths and generate per-operation code
285
- for (const p in pathsMaybe) {
286
- const rawItem = pathsMaybe[p];
287
- if (!isRecord(rawItem))
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 code = `${header}${combinedOut.join('\n\n')}${combinedOut.length ? '\n' : ''}`;
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 index = `${Array.from(splitExports).sort().join('\n')}\n`;
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 { ok: true, value: `Generated rpc code written to ${outDir}/*.ts (index.ts included)` };
335
+ return {
336
+ ok: true,
337
+ value: `Generated rpc code written to ${outDir}/*.ts (index.ts included)`,
338
+ };
357
339
  }
@@ -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["handlerResult = zodOpenapiHonoHandler(openAPI, output, test)"]
34
- * ZC --> ZD{"handlerResult.ok ?"}
35
- * ZD -->|No| ZE["return { ok:false, error: handlerResult.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
  *
@@ -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["handlerResult = zodOpenapiHonoHandler(openAPI, output, test)"]
41
- * ZC --> ZD{"handlerResult.ok ?"}
42
- * ZD -->|No| ZE["return { ok:false, error: handlerResult.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
  *
@@ -53,41 +53,46 @@ import { isHttpMethod, methodPath } from '../utils/index.js';
53
53
  * @returns A `Result` containing a success message or an error string.
54
54
  */
55
55
  export async function takibi(input, output, exportSchema, exportType, template, test, basePath) {
56
- const openAPIResult = await parseOpenAPI(input);
57
- if (!openAPIResult.ok)
58
- return { ok: false, error: openAPIResult.error };
59
- const openAPI = openAPIResult.value;
60
- const honoResult = await fmt(zodOpenAPIHono(openAPI, exportSchema, exportType));
61
- if (!honoResult.ok)
62
- return { ok: false, error: honoResult.error };
63
- const mkdirResult = await mkdir(path.dirname(output));
64
- if (!mkdirResult.ok)
65
- return { ok: false, error: mkdirResult.error };
66
- const writeResult = await writeFile(output, honoResult.value);
67
- if (!writeResult.ok)
68
- return { ok: false, error: writeResult.error };
69
- /** template */
70
- if (template && output.includes('/')) {
71
- const appResult = await fmt(app(openAPI, output, basePath));
72
- if (!appResult.ok)
73
- return { ok: false, error: appResult.error };
74
- const dir = path.dirname(output);
75
- const readdirResult = await readdir(dir);
76
- if (!readdirResult.ok)
77
- return { ok: false, error: readdirResult.error };
78
- const target = path.join(dir, 'index.ts');
79
- const writeResult = await writeFile(target, appResult.value);
56
+ try {
57
+ const openAPIResult = await parseOpenAPI(input);
58
+ if (!openAPIResult.ok)
59
+ return { ok: false, error: openAPIResult.error };
60
+ const openAPI = openAPIResult.value;
61
+ const honoResult = await fmt(zodOpenAPIHono(openAPI, exportSchema, exportType));
62
+ if (!honoResult.ok)
63
+ return { ok: false, error: honoResult.error };
64
+ const mkdirResult = await mkdir(path.dirname(output));
65
+ if (!mkdirResult.ok)
66
+ return { ok: false, error: mkdirResult.error };
67
+ const writeResult = await writeFile(output, honoResult.value);
80
68
  if (!writeResult.ok)
81
69
  return { ok: false, error: writeResult.error };
82
- const zodOpenapiHonoHandlerResult = await zodOpenapiHonoHandler(openAPI, output, test);
83
- if (!zodOpenapiHonoHandlerResult.ok)
84
- return { ok: false, error: zodOpenapiHonoHandlerResult.error };
85
- return { ok: true, value: 'Generated code and template files written' };
70
+ /** template */
71
+ if (template && output.includes('/')) {
72
+ const appResult = await fmt(app(openAPI, output, basePath));
73
+ if (!appResult.ok)
74
+ return { ok: false, error: appResult.error };
75
+ const dir = path.dirname(output);
76
+ const readdirResult = await readdir(dir);
77
+ if (!readdirResult.ok)
78
+ return { ok: false, error: readdirResult.error };
79
+ const target = path.join(dir, 'index.ts');
80
+ const writeResult = await writeFile(target, appResult.value);
81
+ if (!writeResult.ok)
82
+ return { ok: false, error: writeResult.error };
83
+ const zodOpenAPIHonoHandlerResult = await zodOpenAPIHonoHandler(openAPI, output, test);
84
+ if (!zodOpenAPIHonoHandlerResult.ok)
85
+ return { ok: false, error: zodOpenAPIHonoHandlerResult.error };
86
+ return { ok: true, value: 'Generated code and template files written' };
87
+ }
88
+ return {
89
+ ok: true,
90
+ value: `Generated code written to ${output}`,
91
+ };
92
+ }
93
+ catch (e) {
94
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
86
95
  }
87
- return {
88
- ok: true,
89
- value: `Generated code written to ${output}`,
90
- };
91
96
  }
92
97
  /**
93
98
  * Generates route handler files for a Hono app using Zod and OpenAPI.
@@ -97,7 +102,7 @@ export async function takibi(input, output, exportSchema, exportType, template,
97
102
  * @param test - Whether to generate corresponding empty test files.
98
103
  * @returns A `Result` indicating success or error with message.
99
104
  */
100
- async function zodOpenapiHonoHandler(openapi, output, test) {
105
+ async function zodOpenAPIHonoHandler(openapi, output, test) {
101
106
  const paths = openapi.paths;
102
107
  const handlers = Object.entries(paths).flatMap(([p, pathItem]) => Object.entries(pathItem)
103
108
  .filter(([m]) => isHttpMethod(m))
@@ -0,0 +1,7 @@
1
+ export declare function type(input: `${string}.yaml` | `${string}.json` | `${string}.tsp`, output: `${string}.ts`): Promise<{
2
+ ok: true;
3
+ value: string;
4
+ } | {
5
+ ok: false;
6
+ error: string;
7
+ }>;
@@ -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
- if (requestParamsArr.length) {
28
- // params
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
- // all duplication same schema
17
- if (isUniqueSchema) {
18
- const contentParts = [];
19
- for (const contentType of contentTypes) {
20
- // z.date() z.coerce.date()
21
- if (schema.includes('z.date()')) {
22
- contentParts.push(`'${contentType}':{schema:z.coerce.${schema.replace('z.', '')}}`);
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
- const responseEntries = Object.keys(responses).map((code) => {
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(([k, v]) => {
72
- const parts = [];
73
- if (v.summary)
74
- parts.push(`summary:${JSON.stringify(v.summary)}`);
75
- if (v.value !== undefined)
76
- parts.push(`value:${JSON.stringify(v.value)}`);
77
- return `${JSON.stringify(k)}:{${parts.join(',')}}`;
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
- return responseEntries.join('');
48
+ })
49
+ .join('');
86
50
  }