hono-takibi 0.8.7 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.d.ts +28 -1
- package/dist/cli/index.js +41 -8
- package/dist/cli/takibi.d.ts +35 -0
- package/dist/cli/takibi.js +81 -14
- package/dist/generator/zod-openapi-hono/handler/zod-openapi-hono-handler.d.ts +7 -2
- package/dist/generator/zod-openapi-hono/openapi/components/index.js +2 -2
- package/dist/generator/zod-openapi-hono/openapi/route/params/params-object.js +2 -4
- package/dist/generator/zod-openapi-hono/openapi/route/params/request-parameter.js +2 -2
- package/dist/generator/zod-openapi-hono/openapi/route/request/body/index.js +1 -1
- package/dist/generator/zod-openapi-hono/openapi/route/response/index.js +3 -3
- package/dist/generator/zod-to-openapi/index.js +14 -21
- package/dist/generator/zod-to-openapi/z/array.d.ts +48 -0
- package/dist/generator/zod-to-openapi/z/array.js +63 -12
- package/dist/generator/zod-to-openapi/z/string.d.ts +39 -1
- package/dist/generator/zod-to-openapi/z/string.js +39 -1
- package/dist/index.js +16 -2
- package/dist/openapi/index.d.ts +20 -3
- package/dist/openapi/index.js +26 -3
- package/dist/typespec/index.d.ts +22 -3
- package/dist/typespec/index.js +22 -3
- package/dist/utils/index.d.ts +217 -23
- package/dist/utils/index.js +217 -57
- package/dist/vite-plugin/index.d.ts +30 -0
- package/dist/vite-plugin/index.js +31 -1
- package/package.json +8 -8
- package/dist/cli/template-code.d.ts +0 -12
- package/dist/cli/template-code.js +0 -22
- package/dist/result/index.d.ts +0 -51
- package/dist/result/index.js +0 -49
package/dist/cli/index.d.ts
CHANGED
|
@@ -1,7 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CLI entry point for `hono-takibi`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Executes the CLI flow: parse args → optional help → validate options → run generator (`takibi`) → return result.
|
|
5
|
+
*
|
|
6
|
+
* ```mermaid
|
|
7
|
+
* flowchart TD
|
|
8
|
+
* A["Start honoTakibi()"] --> B["args = process.argv.slice(2)"]
|
|
9
|
+
* B --> C{"isHelpRequested(args) ?"}
|
|
10
|
+
* C -->|Yes| D["return { ok:true, value: HELP_TEXT }"]
|
|
11
|
+
* C -->|No| E["cliResult = parseCli(args)"]
|
|
12
|
+
* E --> F{"cliResult.ok ?"}
|
|
13
|
+
* F -->|No| G["return { ok:false, error: cliResult.error }"]
|
|
14
|
+
* F -->|Yes| H["cli = cliResult.value"]
|
|
15
|
+
* H --> I["takibiResult = await takibi(cli.input, cli.output, ...options)"]
|
|
16
|
+
* I --> J{"takibiResult.ok ?"}
|
|
17
|
+
* J -->|No| K["return { ok:false, error: takibiResult.error }"]
|
|
18
|
+
* J -->|Yes| L["return { ok:true, value: takibiResult.value }"]
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* **Options**
|
|
22
|
+
* - `--export-type` Export TypeScript type aliases
|
|
23
|
+
* - `--export-schema` Export Zod schema objects
|
|
24
|
+
* - `--template` Generate app file and handler stubs
|
|
25
|
+
* - `--test` Generate empty `*.test.ts` files
|
|
26
|
+
* - `--base-path <path>` API prefix (default: `/`)
|
|
27
|
+
* - `-h, --help` Show help and exit
|
|
28
|
+
*
|
|
29
|
+
* @returns A Result-like object:
|
|
30
|
+
* - `{ ok: true, value: string }` with either help text or generation message
|
|
31
|
+
* - `{ ok: false, error: string }` on validation or generation errors
|
|
5
32
|
*/
|
|
6
33
|
export declare function honoTakibi(): Promise<{
|
|
7
34
|
ok: true;
|
package/dist/cli/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { asyncAndThen } from '../result/index.js';
|
|
2
1
|
import { parseCli } from '../utils/index.js';
|
|
3
2
|
import { takibi } from './takibi.js';
|
|
4
3
|
const HELP_TEXT = `Usage: hono-takibi <input.{yaml,json,tsp}> -o <routes.ts> [options]
|
|
@@ -13,7 +12,34 @@ Options:
|
|
|
13
12
|
/**
|
|
14
13
|
* CLI entry point for `hono-takibi`.
|
|
15
14
|
*
|
|
16
|
-
*
|
|
15
|
+
* Executes the CLI flow: parse args → optional help → validate options → run generator (`takibi`) → return result.
|
|
16
|
+
*
|
|
17
|
+
* ```mermaid
|
|
18
|
+
* flowchart TD
|
|
19
|
+
* A["Start honoTakibi()"] --> B["args = process.argv.slice(2)"]
|
|
20
|
+
* B --> C{"isHelpRequested(args) ?"}
|
|
21
|
+
* C -->|Yes| D["return { ok:true, value: HELP_TEXT }"]
|
|
22
|
+
* C -->|No| E["cliResult = parseCli(args)"]
|
|
23
|
+
* E --> F{"cliResult.ok ?"}
|
|
24
|
+
* F -->|No| G["return { ok:false, error: cliResult.error }"]
|
|
25
|
+
* F -->|Yes| H["cli = cliResult.value"]
|
|
26
|
+
* H --> I["takibiResult = await takibi(cli.input, cli.output, ...options)"]
|
|
27
|
+
* I --> J{"takibiResult.ok ?"}
|
|
28
|
+
* J -->|No| K["return { ok:false, error: takibiResult.error }"]
|
|
29
|
+
* J -->|Yes| L["return { ok:true, value: takibiResult.value }"]
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* **Options**
|
|
33
|
+
* - `--export-type` Export TypeScript type aliases
|
|
34
|
+
* - `--export-schema` Export Zod schema objects
|
|
35
|
+
* - `--template` Generate app file and handler stubs
|
|
36
|
+
* - `--test` Generate empty `*.test.ts` files
|
|
37
|
+
* - `--base-path <path>` API prefix (default: `/`)
|
|
38
|
+
* - `-h, --help` Show help and exit
|
|
39
|
+
*
|
|
40
|
+
* @returns A Result-like object:
|
|
41
|
+
* - `{ ok: true, value: string }` with either help text or generation message
|
|
42
|
+
* - `{ ok: false, error: string }` on validation or generation errors
|
|
17
43
|
*/
|
|
18
44
|
export async function honoTakibi() {
|
|
19
45
|
// Slice the arguments to remove the first two (node and script path)
|
|
@@ -27,10 +53,17 @@ export async function honoTakibi() {
|
|
|
27
53
|
value: HELP_TEXT,
|
|
28
54
|
};
|
|
29
55
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
56
|
+
const cliResult = parseCli(args);
|
|
57
|
+
if (!cliResult.ok) {
|
|
58
|
+
return { ok: false, error: cliResult.error };
|
|
59
|
+
}
|
|
60
|
+
const cli = cliResult.value;
|
|
61
|
+
const takibiResult = await takibi(cli.input, cli.output, cli.exportSchema ?? false, cli.exportType ?? false, cli.template ?? false, cli.test ?? false, cli.basePath);
|
|
62
|
+
if (!takibiResult.ok) {
|
|
63
|
+
return { ok: false, error: takibiResult.error };
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
ok: true,
|
|
67
|
+
value: takibiResult.value,
|
|
68
|
+
};
|
|
36
69
|
}
|
package/dist/cli/takibi.d.ts
CHANGED
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generates TypeScript code from an OpenAPI spec and optional templates.
|
|
3
3
|
*
|
|
4
|
+
* ```mermaid
|
|
5
|
+
* flowchart TD
|
|
6
|
+
* A["takibi(input, output, flags)"] --> B["openAPIResult = parseOpenAPI(input)"]
|
|
7
|
+
* B --> C{"openAPIResult.ok ?"}
|
|
8
|
+
* C -->|No| D["return { ok:false, error: openAPIResult.error }"]
|
|
9
|
+
* C -->|Yes| E["openAPI = openAPIResult.value"]
|
|
10
|
+
* E --> F["honoResult = fmt(zodOpenAPIHono(openAPI, exportSchema, exportType))"]
|
|
11
|
+
* F --> G{"honoResult.ok ?"}
|
|
12
|
+
* G -->|No| H["return { ok:false, error: honoResult.error }"]
|
|
13
|
+
* G -->|Yes| I["mkdirResult = mkdir(dirname(output))"]
|
|
14
|
+
* I --> J{"mkdirResult.ok ?"}
|
|
15
|
+
* J -->|No| K["return { ok:false, error: mkdirResult.error }"]
|
|
16
|
+
* J -->|Yes| L["writeResult = writeFile(output, honoResult.value)"]
|
|
17
|
+
* L --> M{"writeResult.ok ?"}
|
|
18
|
+
* M -->|No| N["return { ok:false, error: writeResult.error }"]
|
|
19
|
+
* M -->|Yes| O{"template && output includes '/' ?"}
|
|
20
|
+
* O -->|No| P["return { ok:true, value: 'Generated code written to ' + output }"]
|
|
21
|
+
* O -->|Yes| Q["appResult = fmt(app(openAPI, output, basePath))"]
|
|
22
|
+
* Q --> R{"appResult.ok ?"}
|
|
23
|
+
* R -->|No| S["return { ok:false, error: appResult.error }"]
|
|
24
|
+
* R -->|Yes| T["dir = dirname(output)"]
|
|
25
|
+
* T --> U["readdirResult = readdir(dir)"]
|
|
26
|
+
* U --> V{"readdirResult.ok ?"}
|
|
27
|
+
* V -->|No| W["return { ok:false, error: readdirResult.error }"]
|
|
28
|
+
* V -->|Yes| X["files = readdirResult.value"]
|
|
29
|
+
* X --> Y["target = join(dir, files includes 'index.ts' ? 'main.ts' : 'index.ts')"]
|
|
30
|
+
* Y --> Z["writeResult2 = writeFile(target, appResult.value)"]
|
|
31
|
+
* Z --> ZA{"writeResult2.ok ?"}
|
|
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 }"]
|
|
36
|
+
* ZD -->|Yes| ZF["return { ok:true, value: 'Generated code and template files written' }"]
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
4
39
|
* @param input - Input OpenAPI file (`.yaml`, `.json`, or `.tsp`).
|
|
5
40
|
* @param output - Output `.ts` file path.
|
|
6
41
|
* @param exportSchema - Whether to export schemas.
|
package/dist/cli/takibi.js
CHANGED
|
@@ -1,13 +1,48 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { fmt } from '../format/index.js';
|
|
3
|
-
import { mkdir, writeFile } from '../fsp/index.js';
|
|
3
|
+
import { mkdir, readdir, writeFile } from '../fsp/index.js';
|
|
4
|
+
import { app } from '../generator/zod-openapi-hono/app/index.js';
|
|
5
|
+
import { zodOpenapiHonoHandler } from '../generator/zod-openapi-hono/handler/zod-openapi-hono-handler.js';
|
|
4
6
|
import zodOpenAPIHono from '../generator/zod-openapi-hono/openapi/index.js';
|
|
5
7
|
import { parseOpenAPI } from '../openapi/index.js';
|
|
6
|
-
import { asyncAndThen } from '../result/index.js';
|
|
7
|
-
import { templateCode } from './template-code.js';
|
|
8
8
|
/**
|
|
9
9
|
* Generates TypeScript code from an OpenAPI spec and optional templates.
|
|
10
10
|
*
|
|
11
|
+
* ```mermaid
|
|
12
|
+
* flowchart TD
|
|
13
|
+
* A["takibi(input, output, flags)"] --> B["openAPIResult = parseOpenAPI(input)"]
|
|
14
|
+
* B --> C{"openAPIResult.ok ?"}
|
|
15
|
+
* C -->|No| D["return { ok:false, error: openAPIResult.error }"]
|
|
16
|
+
* C -->|Yes| E["openAPI = openAPIResult.value"]
|
|
17
|
+
* E --> F["honoResult = fmt(zodOpenAPIHono(openAPI, exportSchema, exportType))"]
|
|
18
|
+
* F --> G{"honoResult.ok ?"}
|
|
19
|
+
* G -->|No| H["return { ok:false, error: honoResult.error }"]
|
|
20
|
+
* G -->|Yes| I["mkdirResult = mkdir(dirname(output))"]
|
|
21
|
+
* I --> J{"mkdirResult.ok ?"}
|
|
22
|
+
* J -->|No| K["return { ok:false, error: mkdirResult.error }"]
|
|
23
|
+
* J -->|Yes| L["writeResult = writeFile(output, honoResult.value)"]
|
|
24
|
+
* L --> M{"writeResult.ok ?"}
|
|
25
|
+
* M -->|No| N["return { ok:false, error: writeResult.error }"]
|
|
26
|
+
* M -->|Yes| O{"template && output includes '/' ?"}
|
|
27
|
+
* O -->|No| P["return { ok:true, value: 'Generated code written to ' + output }"]
|
|
28
|
+
* O -->|Yes| Q["appResult = fmt(app(openAPI, output, basePath))"]
|
|
29
|
+
* Q --> R{"appResult.ok ?"}
|
|
30
|
+
* R -->|No| S["return { ok:false, error: appResult.error }"]
|
|
31
|
+
* R -->|Yes| T["dir = dirname(output)"]
|
|
32
|
+
* T --> U["readdirResult = readdir(dir)"]
|
|
33
|
+
* U --> V{"readdirResult.ok ?"}
|
|
34
|
+
* V -->|No| W["return { ok:false, error: readdirResult.error }"]
|
|
35
|
+
* V -->|Yes| X["files = readdirResult.value"]
|
|
36
|
+
* X --> Y["target = join(dir, files includes 'index.ts' ? 'main.ts' : 'index.ts')"]
|
|
37
|
+
* Y --> Z["writeResult2 = writeFile(target, appResult.value)"]
|
|
38
|
+
* Z --> ZA{"writeResult2.ok ?"}
|
|
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 }"]
|
|
43
|
+
* ZD -->|Yes| ZF["return { ok:true, value: 'Generated code and template files written' }"]
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
11
46
|
* @param input - Input OpenAPI file (`.yaml`, `.json`, or `.tsp`).
|
|
12
47
|
* @param output - Output `.ts` file path.
|
|
13
48
|
* @param exportSchema - Whether to export schemas.
|
|
@@ -18,15 +53,47 @@ import { templateCode } from './template-code.js';
|
|
|
18
53
|
* @returns A `Result` containing a success message or an error string.
|
|
19
54
|
*/
|
|
20
55
|
export async function takibi(input, output, exportSchema, exportType, template, test, basePath) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
56
|
+
const openAPIResult = await parseOpenAPI(input);
|
|
57
|
+
if (!openAPIResult.ok) {
|
|
58
|
+
return { ok: false, error: openAPIResult.error };
|
|
59
|
+
}
|
|
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
|
+
}
|
|
65
|
+
const mkdirResult = await mkdir(path.dirname(output));
|
|
66
|
+
if (!mkdirResult.ok) {
|
|
67
|
+
return { ok: false, error: mkdirResult.error };
|
|
68
|
+
}
|
|
69
|
+
const writeResult = await writeFile(output, honoResult.value);
|
|
70
|
+
if (!writeResult.ok) {
|
|
71
|
+
return { ok: false, error: writeResult.error };
|
|
72
|
+
}
|
|
73
|
+
if (template && output.includes('/')) {
|
|
74
|
+
const appResult = await fmt(app(openAPI, output, basePath));
|
|
75
|
+
if (!appResult.ok) {
|
|
76
|
+
return { ok: false, error: appResult.error };
|
|
77
|
+
}
|
|
78
|
+
const dir = path.dirname(output);
|
|
79
|
+
const readdirResult = await readdir(dir);
|
|
80
|
+
if (!readdirResult.ok) {
|
|
81
|
+
return { ok: false, error: readdirResult.error };
|
|
82
|
+
}
|
|
83
|
+
const files = readdirResult.value;
|
|
84
|
+
const target = path.join(dir, files.includes('index.ts') ? 'main.ts' : 'index.ts');
|
|
85
|
+
const writeResult = await writeFile(target, appResult.value);
|
|
86
|
+
if (!writeResult.ok) {
|
|
87
|
+
return { ok: false, error: writeResult.error };
|
|
88
|
+
}
|
|
89
|
+
const handlerResult = await zodOpenapiHonoHandler(openAPI, output, test);
|
|
90
|
+
if (!handlerResult.ok) {
|
|
91
|
+
return { ok: false, error: handlerResult.error };
|
|
92
|
+
}
|
|
93
|
+
return { ok: true, value: 'Generated code and template files written' };
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
ok: true,
|
|
97
|
+
value: `Generated code written to ${output}`,
|
|
98
|
+
};
|
|
32
99
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { OpenAPI } from '../../../openapi/index.js';
|
|
2
|
-
import type { Result } from '../../../result/index.js';
|
|
3
2
|
/**
|
|
4
3
|
* Generates route handler files for a Hono app using Zod and OpenAPI.
|
|
5
4
|
*
|
|
@@ -8,4 +7,10 @@ import type { Result } from '../../../result/index.js';
|
|
|
8
7
|
* @param test - Whether to generate corresponding empty test files.
|
|
9
8
|
* @returns A `Result` indicating success or error with message.
|
|
10
9
|
*/
|
|
11
|
-
export declare function zodOpenapiHonoHandler(openapi: OpenAPI, output: string, test: boolean): Promise<
|
|
10
|
+
export declare function zodOpenapiHonoHandler(openapi: OpenAPI, output: string, test: boolean): Promise<{
|
|
11
|
+
ok: true;
|
|
12
|
+
value: undefined;
|
|
13
|
+
} | {
|
|
14
|
+
ok: false;
|
|
15
|
+
error: string;
|
|
16
|
+
}>;
|
|
@@ -32,9 +32,9 @@ export function componentsCode(components, exportSchema, exportType) {
|
|
|
32
32
|
// 4.1 get schema definition corresponding to schema name
|
|
33
33
|
const schema = schemas[schemaName];
|
|
34
34
|
// 4.2 generate zod schema
|
|
35
|
-
const
|
|
35
|
+
const z = zodToOpenAPI(schema);
|
|
36
36
|
// 4.3 generate zod schema definition
|
|
37
|
-
return zodToOpenAPISchema(schemaName,
|
|
37
|
+
return zodToOpenAPISchema(schemaName, z, exportSchema, exportType);
|
|
38
38
|
})
|
|
39
39
|
.join('\n\n');
|
|
40
40
|
// 5. return code
|
|
@@ -16,8 +16,6 @@ import { queryParameter } from './index.js';
|
|
|
16
16
|
*/
|
|
17
17
|
export function paramsObject(parameters) {
|
|
18
18
|
return parameters.reduce((acc, param) => {
|
|
19
|
-
// const z = zod(param.schema)
|
|
20
|
-
const optionalSuffix = param.required ? '' : '.optional()';
|
|
21
19
|
// path params are generated with the param name
|
|
22
20
|
const baseSchema = param.in
|
|
23
21
|
? zodToOpenAPI(param.schema, param.name, param.in)
|
|
@@ -27,9 +25,9 @@ export function paramsObject(parameters) {
|
|
|
27
25
|
acc[param.in] = {};
|
|
28
26
|
}
|
|
29
27
|
// queryParameter check
|
|
30
|
-
const
|
|
28
|
+
const z = queryParameter(baseSchema, param);
|
|
31
29
|
// Add parameter to its section
|
|
32
|
-
acc[param.in][getToSafeIdentifier(param.name)] = `${
|
|
30
|
+
acc[param.in][getToSafeIdentifier(param.name)] = `${z}${param.required ? '' : '.optional()'}`;
|
|
33
31
|
return acc;
|
|
34
32
|
}, {});
|
|
35
33
|
}
|
|
@@ -38,9 +38,9 @@ export function requestParameter(parameters, body) {
|
|
|
38
38
|
const z = zodToOpenAPI(schema);
|
|
39
39
|
uniqueSchemas.set(z, z);
|
|
40
40
|
}
|
|
41
|
-
const
|
|
41
|
+
const required = body.required ?? false;
|
|
42
42
|
const [firstSchema] = uniqueSchemas.values();
|
|
43
|
-
const requestBodyCode = requestBody(
|
|
43
|
+
const requestBodyCode = requestBody(required, body.content, firstSchema);
|
|
44
44
|
if (params) {
|
|
45
45
|
return params.replace('request:{', `request:{${requestBodyCode}`);
|
|
46
46
|
}
|
|
@@ -25,7 +25,7 @@ export function requestBody(required, content, schema) {
|
|
|
25
25
|
contentParts.push(`'${contentType}':{schema:${schema}}`);
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
-
return `body:{required:${required},content:{${contentParts.join(',')}}
|
|
28
|
+
return `body:{required:${required},content:{${contentParts.join(',')}}},`;
|
|
29
29
|
}
|
|
30
30
|
return '';
|
|
31
31
|
}
|
|
@@ -29,7 +29,7 @@ export function response(responses) {
|
|
|
29
29
|
const contentParts = [];
|
|
30
30
|
for (const contentType of contentTypes) {
|
|
31
31
|
const content = response.content[contentType];
|
|
32
|
-
const
|
|
32
|
+
const z = zodToOpenAPI(content.schema);
|
|
33
33
|
const examples = content.examples;
|
|
34
34
|
const exampleString = examples && Object.keys(examples).length > 0
|
|
35
35
|
? `,examples:{${Object.entries(examples)
|
|
@@ -43,9 +43,9 @@ export function response(responses) {
|
|
|
43
43
|
})
|
|
44
44
|
.join(',')}}`
|
|
45
45
|
: '';
|
|
46
|
-
contentParts.push(`'${contentType}':{schema:${
|
|
46
|
+
contentParts.push(`'${contentType}':{schema:${z}${exampleString}}`);
|
|
47
47
|
}
|
|
48
|
-
return `${code}:{description:'${escapeStringLiteral(response.description ?? '')}',content:{${contentParts.join(',')}}
|
|
48
|
+
return `${code}:{description:'${escapeStringLiteral(response.description ?? '')}',content:{${contentParts.join(',')}}},`;
|
|
49
49
|
}
|
|
50
50
|
});
|
|
51
51
|
// 3.combine all response definitions
|
|
@@ -6,30 +6,20 @@ import { integer } from './z/integer.js';
|
|
|
6
6
|
import { number } from './z/number.js';
|
|
7
7
|
import { object } from './z/object.js';
|
|
8
8
|
import { string } from './z/string.js';
|
|
9
|
+
// Test run
|
|
10
|
+
// pnpm vitest run ./src/generator/zod-to-openapi/index.test.ts
|
|
9
11
|
export function zodToOpenAPI(schema, paramName, paramIn) {
|
|
10
12
|
if (schema === undefined)
|
|
11
13
|
throw new Error('hono-takibi: only #/components/schemas/* is supported');
|
|
12
14
|
// ref
|
|
13
15
|
if (schema.$ref) {
|
|
14
|
-
|
|
15
|
-
return wrap(refSchema(schema.$ref), schema, paramName, paramIn);
|
|
16
|
-
}
|
|
17
|
-
if (schema.type === 'array' && Boolean(schema.items?.$ref)) {
|
|
18
|
-
if (schema.items?.$ref) {
|
|
19
|
-
const ref = wrap(refSchema(schema.items.$ref), schema.items);
|
|
20
|
-
return `z.array(${ref})`;
|
|
21
|
-
}
|
|
22
|
-
const z = 'z.array(z.any())';
|
|
23
|
-
return wrap(z, schema, paramName, paramIn);
|
|
24
|
-
}
|
|
25
|
-
const z = 'z.any()';
|
|
26
|
-
return wrap(z, schema, paramName, paramIn);
|
|
16
|
+
return wrap(refSchema(schema.$ref), schema, paramName, paramIn);
|
|
27
17
|
}
|
|
28
18
|
/* combinators */
|
|
29
19
|
// allOf
|
|
30
20
|
if (schema.allOf) {
|
|
31
21
|
if (!schema.allOf || schema.allOf.length === 0) {
|
|
32
|
-
return wrap('z.any()', schema);
|
|
22
|
+
return wrap('z.any()', schema, paramName, paramIn);
|
|
33
23
|
}
|
|
34
24
|
const { schemas, nullable } = schema.allOf.reduce((acc, s) => {
|
|
35
25
|
const isOnlyNullable = (typeof s === 'object' && s.type === 'null') ||
|
|
@@ -42,7 +32,7 @@ export function zodToOpenAPI(schema, paramName, paramIn) {
|
|
|
42
32
|
}
|
|
43
33
|
const z = zodToOpenAPI(s, paramName, paramIn);
|
|
44
34
|
return {
|
|
45
|
-
schemas: [...acc.schemas,
|
|
35
|
+
schemas: [...acc.schemas, z],
|
|
46
36
|
nullable: acc.nullable,
|
|
47
37
|
};
|
|
48
38
|
}, {
|
|
@@ -62,18 +52,18 @@ export function zodToOpenAPI(schema, paramName, paramIn) {
|
|
|
62
52
|
// anyOf
|
|
63
53
|
if (schema.anyOf) {
|
|
64
54
|
if (!schema.anyOf || schema.anyOf.length === 0) {
|
|
65
|
-
return 'z.any()';
|
|
55
|
+
return wrap('z.any()', schema, paramName, paramIn);
|
|
66
56
|
}
|
|
67
57
|
const schemas = schema.anyOf.map((subSchema) => {
|
|
68
58
|
return zodToOpenAPI(subSchema, paramName, paramIn);
|
|
69
59
|
});
|
|
70
60
|
const z = `z.union([${schemas.join(',')}])`;
|
|
71
|
-
return wrap(z, schema);
|
|
61
|
+
return wrap(z, schema, paramName, paramIn);
|
|
72
62
|
}
|
|
73
63
|
// oneOf
|
|
74
64
|
if (schema.oneOf) {
|
|
75
65
|
if (!schema.oneOf || schema.oneOf.length === 0) {
|
|
76
|
-
return 'z.any()';
|
|
66
|
+
return wrap('z.any()', schema, paramName, paramIn);
|
|
77
67
|
}
|
|
78
68
|
const schemas = schema.oneOf.map((schema) => {
|
|
79
69
|
return zodToOpenAPI(schema, paramName, paramIn);
|
|
@@ -84,6 +74,7 @@ export function zodToOpenAPI(schema, paramName, paramIn) {
|
|
|
84
74
|
// const z = discriminator
|
|
85
75
|
// ? `z.discriminatedUnion('${discriminator}',[${schemas.join(',')}])`
|
|
86
76
|
// : `z.union([${schemas.join(',')}])`
|
|
77
|
+
// return wrap(z, schema, paramName, paramIn)
|
|
87
78
|
const z = `z.union([${schemas.join(',')}])`;
|
|
88
79
|
return wrap(z, schema, paramName, paramIn);
|
|
89
80
|
}
|
|
@@ -91,14 +82,16 @@ export function zodToOpenAPI(schema, paramName, paramIn) {
|
|
|
91
82
|
if (schema.not) {
|
|
92
83
|
if (typeof schema.not === 'object' && schema.not.type && typeof schema.not.type === 'string') {
|
|
93
84
|
const predicate = `(v) => typeof v !== '${schema.not.type}'`;
|
|
94
|
-
|
|
85
|
+
const z = `z.any().refine(${predicate})`;
|
|
86
|
+
return wrap(z, schema, paramName, paramIn);
|
|
95
87
|
}
|
|
96
88
|
if (typeof schema.not === 'object' && Array.isArray(schema.not.enum)) {
|
|
97
89
|
const list = JSON.stringify(schema.not.enum);
|
|
98
90
|
const predicate = `(v) => !${list}.includes(v)`;
|
|
99
|
-
|
|
91
|
+
const z = `z.any().refine(${predicate})`;
|
|
92
|
+
return wrap(z, schema, paramName, paramIn);
|
|
100
93
|
}
|
|
101
|
-
return 'z.any()';
|
|
94
|
+
return wrap('z.any()', schema, paramName, paramIn);
|
|
102
95
|
}
|
|
103
96
|
// const
|
|
104
97
|
if (schema.const) {
|
|
@@ -1,2 +1,50 @@
|
|
|
1
1
|
import type { Schema } from '../../../openapi/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Converts an OpenAPI/JSON Schema `array` definition into a Zod schema string.
|
|
4
|
+
*
|
|
5
|
+
* ### Conversion rules
|
|
6
|
+
* ```mermaid
|
|
7
|
+
* flowchart TD
|
|
8
|
+
* A["array(schema)"] --> B[const item =]
|
|
9
|
+
* B --> C{schema.items?.$ref}
|
|
10
|
+
* C -- "Yes" --> D["refSchema(schema.items.$ref)"]
|
|
11
|
+
* C -- "No" --> E{schema.items}
|
|
12
|
+
* E -- "Yes" --> F["zodToOpenAPI(schema.items)"]
|
|
13
|
+
* E -- "No" --> G["z.any()"]
|
|
14
|
+
* D --> B
|
|
15
|
+
* F --> B
|
|
16
|
+
* G --> B
|
|
17
|
+
* B --> I["const z = z.array(${item})"]
|
|
18
|
+
* I --> J{"typeof schema.minItems === number && typeof schema.maxItems === number"}
|
|
19
|
+
* J -- "Yes" --> K{"schema.minItems === schema.maxItems"}
|
|
20
|
+
* K -- "Yes" --> L["${z}.length(${schema.minItems})"]
|
|
21
|
+
* K -- "No" --> M["${z}.min(${schema.minItems}).max(${schema.maxItems})"]
|
|
22
|
+
* J -- "No" --> N{"typeof schema.minItems === number"}
|
|
23
|
+
* N -- "Yes" --> O["${z}.min(${schema.minItems})"]
|
|
24
|
+
* N -- "No" --> P{"typeof schema.maxItems === number"}
|
|
25
|
+
* P -- "Yes" --> Q["${z}.max(${schema.maxItems})"]
|
|
26
|
+
* P -- "No" --> R[z]
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @param schema - OpenAPI or JSON Schema object representing an array type.
|
|
30
|
+
* - `items` may be a `$ref` or an inline schema.
|
|
31
|
+
* - `minItems` / `maxItems` control `.min()`, `.max()`, or `.length()`.
|
|
32
|
+
* @returns Zod schema string for the array, e.g. `z.array(z.string()).min(1).max(5)`.
|
|
33
|
+
*
|
|
34
|
+
* @remarks
|
|
35
|
+
* - Does not currently handle tuple schemas (`items: Schema[]` or `prefixItems`).
|
|
36
|
+
* - Does not handle `uniqueItems`, `contains`, `minContains`, or `maxContains`.
|
|
37
|
+
* - Nullable arrays should be wrapped at a higher level (e.g. via `wrap()`).
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* array({
|
|
42
|
+
* type: 'array',
|
|
43
|
+
* items: { type: 'string' },
|
|
44
|
+
* minItems: 1,
|
|
45
|
+
* maxItems: 3
|
|
46
|
+
* })
|
|
47
|
+
* // → "z.array(z.string()).min(1).max(3)"
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
2
50
|
export declare function array(schema: Schema): string;
|
|
@@ -1,17 +1,68 @@
|
|
|
1
|
+
import { refSchema } from '../../../utils/index.js';
|
|
1
2
|
import { zodToOpenAPI } from '../index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Converts an OpenAPI/JSON Schema `array` definition into a Zod schema string.
|
|
5
|
+
*
|
|
6
|
+
* ### Conversion rules
|
|
7
|
+
* ```mermaid
|
|
8
|
+
* flowchart TD
|
|
9
|
+
* A["array(schema)"] --> B[const item =]
|
|
10
|
+
* B --> C{schema.items?.$ref}
|
|
11
|
+
* C -- "Yes" --> D["refSchema(schema.items.$ref)"]
|
|
12
|
+
* C -- "No" --> E{schema.items}
|
|
13
|
+
* E -- "Yes" --> F["zodToOpenAPI(schema.items)"]
|
|
14
|
+
* E -- "No" --> G["z.any()"]
|
|
15
|
+
* D --> B
|
|
16
|
+
* F --> B
|
|
17
|
+
* G --> B
|
|
18
|
+
* B --> I["const z = z.array(${item})"]
|
|
19
|
+
* I --> J{"typeof schema.minItems === number && typeof schema.maxItems === number"}
|
|
20
|
+
* J -- "Yes" --> K{"schema.minItems === schema.maxItems"}
|
|
21
|
+
* K -- "Yes" --> L["${z}.length(${schema.minItems})"]
|
|
22
|
+
* K -- "No" --> M["${z}.min(${schema.minItems}).max(${schema.maxItems})"]
|
|
23
|
+
* J -- "No" --> N{"typeof schema.minItems === number"}
|
|
24
|
+
* N -- "Yes" --> O["${z}.min(${schema.minItems})"]
|
|
25
|
+
* N -- "No" --> P{"typeof schema.maxItems === number"}
|
|
26
|
+
* P -- "Yes" --> Q["${z}.max(${schema.maxItems})"]
|
|
27
|
+
* P -- "No" --> R[z]
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @param schema - OpenAPI or JSON Schema object representing an array type.
|
|
31
|
+
* - `items` may be a `$ref` or an inline schema.
|
|
32
|
+
* - `minItems` / `maxItems` control `.min()`, `.max()`, or `.length()`.
|
|
33
|
+
* @returns Zod schema string for the array, e.g. `z.array(z.string()).min(1).max(5)`.
|
|
34
|
+
*
|
|
35
|
+
* @remarks
|
|
36
|
+
* - Does not currently handle tuple schemas (`items: Schema[]` or `prefixItems`).
|
|
37
|
+
* - Does not handle `uniqueItems`, `contains`, `minContains`, or `maxContains`.
|
|
38
|
+
* - Nullable arrays should be wrapped at a higher level (e.g. via `wrap()`).
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* array({
|
|
43
|
+
* type: 'array',
|
|
44
|
+
* items: { type: 'string' },
|
|
45
|
+
* minItems: 1,
|
|
46
|
+
* maxItems: 3
|
|
47
|
+
* })
|
|
48
|
+
* // → "z.array(z.string()).min(1).max(3)"
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
2
51
|
export function array(schema) {
|
|
3
|
-
const
|
|
52
|
+
const item = schema.items?.$ref
|
|
53
|
+
? refSchema(schema.items.$ref)
|
|
54
|
+
: schema.items
|
|
55
|
+
? zodToOpenAPI(schema.items)
|
|
56
|
+
: 'z.any()';
|
|
57
|
+
const z = `z.array(${item})`;
|
|
4
58
|
if (typeof schema.minItems === 'number' && typeof schema.maxItems === 'number') {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
return `${array}.min(${schema.minItems}).max(${schema.maxItems})`;
|
|
59
|
+
return schema.minItems === schema.maxItems
|
|
60
|
+
? `${z}.length(${schema.minItems})`
|
|
61
|
+
: `${z}.min(${schema.minItems}).max(${schema.maxItems})`;
|
|
9
62
|
}
|
|
10
|
-
if (typeof schema.minItems === 'number')
|
|
11
|
-
return `${
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
return array;
|
|
63
|
+
if (typeof schema.minItems === 'number')
|
|
64
|
+
return `${z}.min(${schema.minItems})`;
|
|
65
|
+
if (typeof schema.maxItems === 'number')
|
|
66
|
+
return `${z}.max(${schema.maxItems})`;
|
|
67
|
+
return z;
|
|
17
68
|
}
|
|
@@ -1,3 +1,41 @@
|
|
|
1
1
|
import type { Schema } from '../../../openapi/index.js';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Builds a Zod string schema from an OpenAPI string schema definition.
|
|
4
|
+
*
|
|
5
|
+
* - If `schema.format` exists and matches `FORMAT_STRING`, uses `z.<format>()`; otherwise falls back to `z.string()`
|
|
6
|
+
* - If `schema.pattern` is set, appends `.regex(...)` using the `regex()` helper
|
|
7
|
+
* - If `minLength` and `maxLength` are defined and equal, appends `.length(n)`
|
|
8
|
+
* - Otherwise, appends `.min(n)` and/or `.max(n)` individually
|
|
9
|
+
* - Returns the concatenated Zod schema string
|
|
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
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* // With format, pattern, and minLength
|
|
34
|
+
* string({ type: 'string', format: 'email', pattern: '^.+@example\\.com$', minLength: 5 })
|
|
35
|
+
* // => 'z.email().regex(/^.+@example\\.com$/).min(5)'
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @param schema - OpenAPI string schema
|
|
39
|
+
* @returns Concatenated Zod string schema
|
|
40
|
+
*/
|
|
3
41
|
export declare function string(schema: Schema): string;
|