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.
@@ -1,7 +1,34 @@
1
1
  /**
2
2
  * CLI entry point for `hono-takibi`.
3
3
  *
4
- * @returns A `Result` containing help text or CLI execution result.
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
- * @returns A `Result` containing help text or CLI execution result.
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
- return await asyncAndThen(parseCli(args), async (cli) => asyncAndThen(await takibi(cli.input, cli.output, cli.exportSchema ?? false, cli.exportType ?? false, cli.template ?? false, cli.test ?? false, cli.basePath), async (result) => {
31
- return {
32
- ok: true,
33
- value: result,
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
  }
@@ -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.
@@ -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
- return await asyncAndThen(await parseOpenAPI(input), async (openAPI) => asyncAndThen(await fmt(zodOpenAPIHono(openAPI, exportSchema, exportType)), async (code) => asyncAndThen(await mkdir(path.dirname(output)), async () => asyncAndThen(await writeFile(output, code), async () => template && output.includes('/')
22
- ? asyncAndThen(await templateCode(openAPI, output, test, basePath), async () => {
23
- return {
24
- ok: true,
25
- value: 'Generated code and template files written',
26
- };
27
- })
28
- : {
29
- ok: true,
30
- value: `Generated code written to ${output}`,
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<Result<void, string>>;
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 zodSchema = zodToOpenAPI(schema);
35
+ const z = zodToOpenAPI(schema);
36
36
  // 4.3 generate zod schema definition
37
- return zodToOpenAPISchema(schemaName, zodSchema, exportSchema, exportType);
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 zodSchema = queryParameter(baseSchema, param);
28
+ const z = queryParameter(baseSchema, param);
31
29
  // Add parameter to its section
32
- acc[param.in][getToSafeIdentifier(param.name)] = `${zodSchema}${optionalSuffix}`;
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 request_body_required = body.required ?? false;
41
+ const required = body.required ?? false;
42
42
  const [firstSchema] = uniqueSchemas.values();
43
- const requestBodyCode = requestBody(request_body_required, body.content, firstSchema);
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 zodSchema = zodToOpenAPI(content.schema);
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:${zodSchema}${exampleString}}`);
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
- if (Boolean(schema.$ref) === true) {
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, wrap(z, s)],
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
- return `z.any().refine(${predicate})`;
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
- return `z.any().refine(${predicate})`;
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 array = `z.array(${schema.items ? zodToOpenAPI(schema.items) : 'z.any()'})`;
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
- if (schema.minItems === schema.maxItems) {
6
- return `${array}.length(${schema.minItems})`;
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 `${array}.min(${schema.minItems})`;
12
- }
13
- if (typeof schema.maxItems === 'number') {
14
- return `${array}.max(${schema.maxItems})`;
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
- /** Build a Zod string schema from an OpenAPI string schema. */
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;