hono-takibi 0.9.1 → 0.9.5

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.
Files changed (41) hide show
  1. package/README.md +133 -82
  2. package/dist/cli/index.d.ts +14 -15
  3. package/dist/cli/index.js +42 -17
  4. package/dist/config/index.d.ts +29 -14
  5. package/dist/config/index.js +19 -9
  6. package/dist/core/route.d.ts +7 -0
  7. package/dist/core/route.js +99 -0
  8. package/dist/core/rpc.d.ts +11 -5
  9. package/dist/core/rpc.js +339 -15
  10. package/dist/core/schema.d.ts +27 -0
  11. package/dist/core/schema.js +125 -0
  12. package/dist/core/takibi.d.ts +4 -4
  13. package/dist/core/takibi.js +91 -49
  14. package/dist/format/index.d.ts +4 -6
  15. package/dist/fsp/index.d.ts +11 -11
  16. package/dist/generator/zod-openapi-hono/app/index.js +53 -20
  17. package/dist/generator/zod-openapi-hono/openapi/index.d.ts +1 -1
  18. package/dist/generator/zod-openapi-hono/openapi/index.js +1 -1
  19. package/dist/generator/zod-openapi-hono/openapi/route/index.d.ts +0 -12
  20. package/dist/generator/zod-openapi-hono/openapi/route/index.js +37 -29
  21. package/dist/generator/zod-openapi-hono/openapi/route/params/params-object.d.ts +1 -1
  22. package/dist/generator/zod-openapi-hono/openapi/route/params/request-parameter.d.ts +1 -1
  23. package/dist/generator/zod-openapi-hono/openapi/route/params/request-parameter.js +1 -0
  24. package/dist/generator/zod-openapi-hono/openapi/route/response/index.js +66 -33
  25. package/dist/generator/zod-openapi-hono/openapi/route/route.js +2 -2
  26. package/dist/generator/zod-to-openapi/z/object.js +21 -1
  27. package/dist/helper/index.d.ts +0 -1
  28. package/dist/helper/index.js +0 -1
  29. package/dist/helper/properties-schema.d.ts +1 -1
  30. package/dist/openapi/index.d.ts +7 -7
  31. package/dist/openapi/index.js +4 -4
  32. package/dist/typespec/index.d.ts +4 -4
  33. package/dist/utils/index.d.ts +95 -184
  34. package/dist/utils/index.js +188 -228
  35. package/dist/vite-plugin/index.d.ts +1 -41
  36. package/dist/vite-plugin/index.js +458 -70
  37. package/package.json +4 -4
  38. package/dist/generator/rpc/index.d.ts +0 -2
  39. package/dist/generator/rpc/index.js +0 -338
  40. package/dist/helper/get-route-maps.d.ts +0 -12
  41. package/dist/helper/get-route-maps.js +0 -20
package/README.md CHANGED
@@ -33,31 +33,6 @@ If you have OpenAPI specifications, Hono Takibi automates the conversion process
33
33
  npx hono-takibi path/to/input.{yaml,json,tsp} -o path/to/output.ts
34
34
  ```
35
35
 
36
- ## CLI
37
-
38
- ### Options
39
-
40
- basic
41
-
42
- ```bash
43
- Options:
44
- --export-type export TypeScript type aliases
45
- --export-schema export Zod schema objects
46
- --template generate app file and handler stubs
47
- --test generate empty *.test.ts files
48
- --base-path <path> api prefix (default: /)
49
- ```
50
-
51
- template
52
-
53
- > **⚠️** When using the `--template` option, you must specify a valid directory path. Ensure the directory exists before executing the
54
-
55
- ### Example
56
-
57
- ```bash
58
- npx hono-takibi path/to/input.{yaml,json,tsp} -o path/to/output.ts --export-type --export-schema --template --base-path '/api/v1'
59
- ```
60
-
61
36
  input:
62
37
 
63
38
  ```yaml
@@ -112,101 +87,177 @@ export const getRoute = createRoute({
112
87
 
113
88
  ![](https://raw.githubusercontent.com/nakita628/hono-takibi/refs/heads/main/assets/demo/hono-takibi.gif)
114
89
 
115
- ## HonoTakibiVite
90
+ ## CLI
116
91
 
117
- ### Automatic Code Regeneration & HMR
92
+ ### Options
118
93
 
119
- With **HonoTakibiVite**, saving your OpenAPI spec while the Vite dev server is running instantly regenerates the code.
94
+ basic
120
95
 
121
- ### OpenAPI Schema Requirements
96
+ ```bash
97
+ Options:
98
+ --export-type export TypeScript type aliases
99
+ --export-schema export Zod schema objects
100
+ --template generate app file and handler stubs
101
+ --test generate empty *.test.ts files
102
+ --base-path <path> api prefix (default: /)
103
+ ```
122
104
 
123
- - Your OpenAPI definition must include **only the `#/components/schemas/` section**.
124
- - It must be fully compliant with **OpenAPI 3.0 or later (e.g., 3.0 or 3.1)**.
125
- - Do **not** include `paths`, `tags`, or any other OpenAPI sections.
105
+ template
126
106
 
127
- ### Supported Input Formats
107
+ > **⚠️** When using the `--template` option, you must specify a valid directory path. Ensure the directory exists before executing the
128
108
 
129
- You may specify the input file in one of the following formats:
109
+ ### Example
130
110
 
131
- - `.yaml` — OpenAPI YAML (schemas only)
132
- - `.json` OpenAPI JSON (schemas only)
133
- - `.tsp` — TypeSpec source file
111
+ ```bash
112
+ npx hono-takibi path/to/input.{yaml,json,tsp} -o path/to/output.ts --export-type --export-schema --template --base-path '/api/v1'
113
+ ```
134
114
 
135
- ### TypeSpec Setup (if using `.tsp`)
115
+ ## Configuration File (`hono-takibi.config.ts`)
136
116
 
137
- If you use a `.tsp` TypeSpec file, you must set up the TypeSpec environment and install required libraries:
117
+ Config used by both the CLI and the Vite plugin.
138
118
 
139
- - @typespec/http
140
- - @typespec/rest
141
- - ...other
119
+ ## Essentials
142
120
 
143
- ### Example
121
+ * Put **`hono-takibi.config.ts`** at repo root.
122
+ * Default‑export with `defineConfig(...)`.
123
+ * `input`: **`openapi.yaml`** (recommended), or `*.json` / `*.tsp`.
144
124
 
145
- `vite.config.ts`
125
+ > **About `split`**
126
+ >
127
+ > * `split: true` → `output` is a **directory**; many files + `index.ts`.
128
+ > * `split` **omitted** or `false` → `output` is a **single `*.ts` file** (one file only).
129
+
130
+ ---
131
+
132
+ ## Single‑file
133
+
134
+ One file. Set top‑level `output` (don’t define `schema`/`route`).
146
135
 
147
136
  ```ts
148
- import { defineConfig } from 'vite'
149
- import HonoTakibiVite from 'hono-takibi/vite-plugin'
137
+ import { defineConfig } from 'hono-takibi/config'
150
138
 
151
139
  export default defineConfig({
152
- plugins: [
153
- HonoTakibiVite({
154
- input: 'main.tsp',
155
- output: 'index.ts',
156
- exportType: true,
157
- exportSchema: true,
158
- }),
159
- ],
140
+ input: 'openapi.yaml',
141
+ 'zod-openapi': {
142
+ output: './src/index.ts',
143
+ exportSchema: true,
144
+ exportType: true,
145
+ },
160
146
  })
161
147
  ```
162
148
 
163
- ### Demo
149
+ ---
164
150
 
165
- ![](https://raw.githubusercontent.com/nakita628/hono-takibi/refs/heads/main/assets/vite/hono-takibi-vite.gif)
151
+ ## Schemas & Routes
152
+
153
+ Define **both** `schema` and `route` (don’t set top‑level `output`).
154
+
155
+ ```ts
156
+ import { defineConfig } from 'hono-takibi/config'
157
+
158
+ export default defineConfig({
159
+ input: 'openapi.yaml',
160
+ 'zod-openapi': {
161
+ // split ON → outputs are directories
162
+ schema: { output: './src/schemas', split: true },
163
+ route: { output: './src/routes', import: '../schemas', split: true },
164
+
165
+ // split OFF example (one file each):
166
+ // schema: { output: './src/schemas/index.ts' },
167
+ // route: { output: './src/routes/index.ts', import: '../schemas' },
168
+ },
169
+ })
170
+ ```
171
+
172
+ ---
173
+
174
+ ## RPC (optional)
175
+
176
+ Works with either pattern.
177
+
178
+ * `split: true` → `output` is a **directory**; many files + `index.ts`.
179
+ * `split` **omitted** or `false` → `output` is **one `*.ts` file**.
180
+
181
+ **Example (split: true)**
182
+
183
+ ```ts
184
+ import { defineConfig } from 'hono-takibi/config'
166
185
 
186
+ export default defineConfig({
187
+ input: 'openapi.yaml',
188
+ 'zod-openapi': { output: './src/index.ts', exportSchema: true, exportType: true },
189
+ rpc: { output: './src/rpc', import: '../client', split: true },
190
+ })
191
+ ```
167
192
 
168
- ## With AI Prompt
193
+ **Example (single file; split omitted/false)**
169
194
 
170
- ### Sample Prompt — Schemas-Only Extractor (OpenAPI 3+)
195
+ ```ts
196
+ import { defineConfig } from 'hono-takibi/config'
171
197
 
172
- A copy‑and‑paste prompt for **any LLM** that extracts **only** the contents of `#/components/schemas/` from an OpenAPI document.
198
+ export default defineConfig({
199
+ input: 'openapi.yaml',
200
+ 'zod-openapi': { output: './src/index.ts', exportSchema: true, exportType: true },
201
+ rpc: { output: './src/rpc/index.ts', import: '../client' },
202
+ })
203
+ ```
204
+
205
+ ---
206
+
207
+ ## Vite Plugin (`honoTakibiVite`)
173
208
 
174
- ## Prompt Example
209
+ Auto‑regenerates on changes and reloads dev server.
175
210
 
176
- ```md
177
- You are a **Schemas‑Only Extractor** for OpenAPI 3+.
211
+ ```ts
212
+ // vite.config.ts
213
+ import { honoTakibiVite } from 'hono-takibi/vite-plugin'
214
+ import { defineConfig } from 'vite'
215
+
216
+ export default defineConfig({
217
+ plugins: [honoTakibiVite()],
218
+ })
219
+ ```
220
+
221
+ **What it does**
222
+
223
+ * **Watches**: the config, your `input`, and nearby `**/*.{yaml,json,tsp}`.
224
+ * **Generates** outputs per your config (single‑file or split, plus `rpc`).
225
+ * **Cleans** old generated files safely when paths or `split` change.
226
+
227
+ That’s it — set `input`, choose one of the two patterns, and (optionally) add `rpc`. ✅
228
+
229
+ ### Demo (Vite + HMR)
230
+
231
+ ![](https://raw.githubusercontent.com/nakita628/hono-takibi/refs/heads/main/assets/vite/hono-takibi-vite.gif)
178
232
 
179
- ## 1. Version
180
- - Accept files that start with `openapi: "3.0.0"` or newer.
181
- - Otherwise reply with: `Unsupported OpenAPI version (must be 3.0+).`
182
233
 
183
- ## 2. Scope
184
- - Look **only** inside `#/components/schemas/`. Ignore everything else.
185
- - `$ref` must also point inside that section.
234
+ ## AI Prompt Example
186
235
 
187
- ## 3. Schemas section present?
188
- - If `components.schemas` is missing, reply with: `Missing '#/components/schemas/' section. Cannot proceed.`
236
+ ```sh
237
+ Generate one **OpenAPI 3.x+** YAML (prefer **3.1.0**).
189
238
 
190
- ## 4. File type
191
- - Accept **.yaml**, **.json**, or **.tsp** files.
192
- - Otherwise reply with: `Unsupported input file extension.`
239
+ Rules:
240
+ - Use only `components.schemas` (no other `components`).
241
+ - Never include `parameters:`.
242
+ - No path params; all inputs in `requestBody` (`application/json`) with `$ref: '#/components/schemas/*'`.
243
+ - All responses use `application/json` with `$ref: '#/components/schemas/*'`.
244
+ - POST-only action routes: `/resource/create|get|search|update|delete`.
245
+ - No inline schemas in `paths`.
193
246
 
194
- ## Format tips
195
- - `format: uuid` usually means **UUID v4**.
196
- - Other accepted identifiers include `uuidv6`, `uuidv7`, `ulid`, `cuid`, etc.
197
- - With **hono‑takibi**, you can generate **Zod schemas** directly from a custom OpenAPI file.
247
+ Fill, then generate:
248
+ - title / version / tags
249
+ - resources & fields
250
+ - ops per resource: create / get / search / update / delete
198
251
 
199
- ## What the LLM should do
200
- 1. Validate the file with the four rules above.
201
- 2. If it passes, output **only** the YAML/JSON fragment under `#/components/schemas/` (preserve indentation).
202
- 3. Otherwise, output the exact error message above—nothing more.
252
+ **Output format (strict):**
253
+ - Return a **single fenced code block** labeled `yaml` that contains **only** the YAML.
254
+ - No text before or after the code block.
203
255
  ```
204
256
 
205
257
  ### ⚠️ WARNING: Potential Breaking Changes Without Notice
206
258
 
207
259
  **This package is in active development and may introduce breaking changes without prior notice.**
208
260
  Specifically:
209
- - Query parameter coercion behavior may change
210
261
  - Schema generation logic might be updated
211
262
  - Output code structure could be modified
212
263
  - Example value handling might be altered
@@ -5,17 +5,16 @@
5
5
  *
6
6
  * ```mermaid
7
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 }"]
8
+ * A["Start honoTakibi"] --> B["Parse args"]
9
+ * B --> C{"Help requested?"}
10
+ * C -- Yes --> D["Return help text"]
11
+ * C -- No --> E["parseCli(args)"]
12
+ * E --> F{"cliResult.ok?"}
13
+ * F -- No --> G["Return parse error"]
14
+ * F -- Yes --> H["Run takibi"]
15
+ * H --> I{"takibi.ok?"}
16
+ * I -- No --> J["Return takibi error"]
17
+ * I -- Yes --> K["Return success message"]
19
18
  * ```
20
19
  *
21
20
  * **Options**
@@ -31,9 +30,9 @@
31
30
  * - `{ ok: false, error: string }` on validation or generation errors
32
31
  */
33
32
  export declare function honoTakibi(): Promise<{
34
- ok: true;
35
- value: string;
33
+ readonly ok: true;
34
+ readonly value: string;
36
35
  } | {
37
- ok: false;
38
- error: string;
36
+ readonly ok: false;
37
+ readonly error: string;
39
38
  }>;
package/dist/cli/index.js CHANGED
@@ -1,9 +1,16 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { resolve } from 'node:path';
3
3
  import { config } from '../config/index.js';
4
+ import { route } from '../core/route.js';
4
5
  import { rpc } from '../core/rpc.js';
6
+ import { schema } from '../core/schema.js';
5
7
  import { takibi } from '../core/takibi.js';
8
+ // import { honoRpcWithSWR } from '../generator/swr/index.js'
6
9
  import { parseCli } from '../utils/index.js';
10
+ /**
11
+ * CLI usage help text shown when `-h`/`--help` is provided.
12
+ * Kept as a single template for easy updates and snapshot stability.
13
+ */
7
14
  const HELP_TEXT = `Usage: hono-takibi <input.{yaml,json,tsp}> -o <routes.ts> [options]
8
15
 
9
16
  Options:
@@ -20,17 +27,16 @@ Options:
20
27
  *
21
28
  * ```mermaid
22
29
  * flowchart TD
23
- * A["Start honoTakibi()"] --> B["args = process.argv.slice(2)"]
24
- * B --> C{"isHelpRequested(args) ?"}
25
- * C -->|Yes| D["return { ok:true, value: HELP_TEXT }"]
26
- * C -->|No| E["cliResult = parseCli(args)"]
27
- * E --> F{"cliResult.ok ?"}
28
- * F -->|No| G["return { ok:false, error: cliResult.error }"]
29
- * F -->|Yes| H["cli = cliResult.value"]
30
- * H --> I["takibiResult = await takibi(cli.input, cli.output, ...options)"]
31
- * I --> J{"takibiResult.ok ?"}
32
- * J -->|No| K["return { ok:false, error: takibiResult.error }"]
33
- * J -->|Yes| L["return { ok:true, value: takibiResult.value }"]
30
+ * A["Start honoTakibi"] --> B["Parse args"]
31
+ * B --> C{"Help requested?"}
32
+ * C -- Yes --> D["Return help text"]
33
+ * C -- No --> E["parseCli(args)"]
34
+ * E --> F{"cliResult.ok?"}
35
+ * F -- No --> G["Return parse error"]
36
+ * F -- Yes --> H["Run takibi"]
37
+ * H --> I{"takibi.ok?"}
38
+ * I -- No --> J["Return takibi error"]
39
+ * I -- Yes --> K["Return success message"]
34
40
  * ```
35
41
  *
36
42
  * **Options**
@@ -46,7 +52,7 @@ Options:
46
52
  * - `{ ok: false, error: string }` on validation or generation errors
47
53
  */
48
54
  export async function honoTakibi() {
49
- // Slice the arguments to remove the first two (node and script path)
55
+ /** Slice the arguments to remove the first two (node and script path) */
50
56
  const args = process.argv.slice(2);
51
57
  const isHelpRequested = (args) => {
52
58
  return args.length === 1 && (args[0] === '--help' || args[0] === '-h');
@@ -59,11 +65,11 @@ export async function honoTakibi() {
59
65
  };
60
66
  }
61
67
  const abs = resolve(process.cwd(), 'hono-takibi.config.ts');
68
+ /** If config file does not exist, parse CLI arguments */
62
69
  if (!existsSync(abs)) {
63
70
  const cliResult = parseCli(args);
64
- if (!cliResult.ok) {
71
+ if (!cliResult.ok)
65
72
  return { ok: false, error: cliResult.error };
66
- }
67
73
  const cli = cliResult.value;
68
74
  const takibiResult = await takibi(cli.input, cli.output, cli.exportSchema ?? false, cli.exportType ?? false, cli.template ?? false, cli.test ?? false, cli.basePath);
69
75
  if (!takibiResult.ok) {
@@ -74,19 +80,38 @@ export async function honoTakibi() {
74
80
  value: takibiResult.value,
75
81
  };
76
82
  }
83
+ /** If config file exists, parse config file */
77
84
  const configResult = await config();
78
85
  if (!configResult.ok) {
79
86
  return { ok: false, error: configResult.error };
80
87
  }
81
88
  const c = configResult.value;
82
- const takibiResult = c['hono-takibi']
83
- ? await takibi(c['hono-takibi']?.input, c['hono-takibi']?.output, c['hono-takibi']?.exportSchema ?? false, c['hono-takibi']?.exportType ?? false, false, // template
89
+ /** takibi */
90
+ const takibiResult = c['zod-openapi']?.output
91
+ ? await takibi(c.input, c['zod-openapi']?.output, c['zod-openapi']?.exportSchema ?? false, c['zod-openapi']?.exportType ?? false, false, // template
84
92
  false)
85
93
  : undefined;
86
94
  if (takibiResult && !takibiResult.ok) {
87
95
  return { ok: false, error: takibiResult.error };
88
96
  }
89
- const rpcResult = c.rpc ? await rpc(c.rpc.input, c.rpc.output, c.rpc.import) : undefined;
97
+ /** schema */
98
+ const schemaResult = c['zod-openapi']?.schema
99
+ ? await schema(c.input, c['zod-openapi'].schema.output, c['zod-openapi'].schema.exportType ?? false, c['zod-openapi']?.schema.split ?? false)
100
+ : undefined;
101
+ if (schemaResult && !schemaResult.ok) {
102
+ return { ok: false, error: schemaResult.error };
103
+ }
104
+ /** route */
105
+ const routeResult = c['zod-openapi']?.route
106
+ ? await route(c.input, c['zod-openapi'].route.output, c['zod-openapi'].route.import, c['zod-openapi'].route.split ?? false)
107
+ : undefined;
108
+ if (routeResult && !routeResult.ok) {
109
+ return { ok: false, error: routeResult.error };
110
+ }
111
+ /** rpc */
112
+ const rpcResult = c.rpc
113
+ ? await rpc(c.input, c.rpc.output, c.rpc.import, c.rpc.split ?? false)
114
+ : undefined;
90
115
  if (rpcResult && !rpcResult.ok) {
91
116
  return { ok: false, error: rpcResult.error };
92
117
  }
@@ -1,22 +1,37 @@
1
1
  type Config = {
2
- 'hono-takibi'?: {
3
- input: `${string}.yaml` | `${string}.json` | `${string}.tsp`;
4
- output: `${string}.ts`;
5
- exportType?: boolean;
6
- exportSchema?: boolean;
2
+ readonly input: `${string}.yaml` | `${string}.json` | `${string}.tsp`;
3
+ readonly 'zod-openapi'?: {
4
+ readonly output?: `${string}.ts`;
5
+ readonly exportType?: boolean;
6
+ readonly exportSchema?: boolean;
7
+ readonly schema?: {
8
+ readonly output: string | `${string}.ts`;
9
+ readonly exportType?: boolean;
10
+ readonly split?: boolean;
11
+ };
12
+ readonly route?: {
13
+ readonly output: string | `${string}.ts`;
14
+ readonly import: string;
15
+ readonly split?: boolean;
16
+ };
7
17
  };
8
- rpc?: {
9
- input: `${string}.yaml` | `${string}.json` | `${string}.tsp`;
10
- output: `${string}.ts`;
11
- import: string;
18
+ readonly rpc?: {
19
+ readonly output: string | `${string}.ts`;
20
+ readonly import: string;
21
+ readonly split?: boolean;
12
22
  };
13
23
  };
14
24
  export declare function config(): Promise<{
15
- ok: true;
16
- value: Config;
25
+ readonly ok: true;
26
+ readonly value: Config;
17
27
  } | {
18
- ok: false;
19
- error: string;
28
+ readonly ok: false;
29
+ readonly error: string;
20
30
  }>;
21
- export declare function defineConfig(config: Config): Config;
31
+ /**
32
+ * Helper to define a config with full type completion.
33
+ *
34
+ * @see config
35
+ */
36
+ export declare function defineConfig(c: Config): Config;
22
37
  export {};
@@ -1,23 +1,33 @@
1
+ import { existsSync } from 'node:fs';
1
2
  import { resolve } from 'node:path';
2
3
  import { pathToFileURL } from 'node:url';
3
4
  import { register } from 'tsx/esm/api';
5
+ import { parseConfig } from '../utils/index.js';
4
6
  export async function config() {
5
7
  const abs = resolve(process.cwd(), 'hono-takibi.config.ts');
6
- // if (!existsSync(abs)) {
7
- // return { ok: false, error: `Config not found: ${abs}` }
8
- // }
9
- register();
8
+ if (!existsSync(abs))
9
+ return { ok: false, error: `Config not found: ${abs}` };
10
10
  try {
11
- const mod = await import(pathToFileURL(abs).href);
12
- if (!('default' in mod)) {
11
+ register();
12
+ const url = pathToFileURL(abs).href;
13
+ const mod = await import(/* @vite-ignore */ url);
14
+ if (!('default' in mod) || mod.default === undefined) {
13
15
  return { ok: false, error: 'Config must export default object' };
14
16
  }
15
- return { ok: true, value: mod.default };
17
+ const result = parseConfig(mod.default);
18
+ if (!result.ok)
19
+ return { ok: false, error: result.error };
20
+ return { ok: true, value: result.value };
16
21
  }
17
22
  catch (e) {
18
23
  return { ok: false, error: e instanceof Error ? e.message : String(e) };
19
24
  }
20
25
  }
21
- export function defineConfig(config) {
22
- return config;
26
+ /**
27
+ * Helper to define a config with full type completion.
28
+ *
29
+ * @see config
30
+ */
31
+ export function defineConfig(c) {
32
+ return c;
23
33
  }
@@ -0,0 +1,7 @@
1
+ export declare function route(input: `${string}.yaml` | `${string}.json` | `${string}.tsp`, output: string | `${string}.ts`, importPath: string, split?: boolean): Promise<{
2
+ readonly ok: true;
3
+ readonly value: string;
4
+ } | {
5
+ readonly ok: false;
6
+ readonly error: string;
7
+ }>;
@@ -0,0 +1,99 @@
1
+ import path from 'node:path';
2
+ import { fmt } from '../format/index.js';
3
+ import { mkdir, writeFile } from '../fsp/index.js';
4
+ import { routeCode } from '../generator/zod-openapi-hono/openapi/route/index.js';
5
+ import { parseOpenAPI } from '../openapi/index.js';
6
+ const findSchemaTokens = (code) => Array.from(new Set(Array.from(code.matchAll(/\b([A-Za-z_$][A-Za-z0-9_$]*Schema)\b/g))
7
+ .map((m) => m[1] ?? '')
8
+ .filter(Boolean)));
9
+ const extractRouteBlocks = (src) => {
10
+ const re = /export\s+const\s+([A-Za-z_$][A-Za-z0-9_$]*)Route\s*=/g;
11
+ const hits = [];
12
+ for (const m of src.matchAll(re)) {
13
+ const name = (m[1] ?? '').trim();
14
+ const start = m.index ?? 0;
15
+ if (name)
16
+ hits.push({ name, start });
17
+ }
18
+ return hits.map((h, i) => {
19
+ const start = h.start;
20
+ const end = i + 1 < hits.length ? (hits[i + 1]?.start ?? src.length) : src.length;
21
+ return { name: h.name, block: src.slice(start, end).trim() };
22
+ });
23
+ };
24
+ const lowerFirst = (s) => (s ? s.charAt(0).toLowerCase() + s.slice(1) : s);
25
+ export async function route(input, output, importPath, split) {
26
+ const openAPIResult = await parseOpenAPI(input);
27
+ if (!openAPIResult.ok) {
28
+ return { ok: false, error: openAPIResult.error };
29
+ }
30
+ const openAPI = openAPIResult.value;
31
+ const routesSrc = routeCode(openAPI.paths);
32
+ if (!split) {
33
+ const includeZ = routesSrc.includes('z.');
34
+ const schemaTokens = findSchemaTokens(routesSrc);
35
+ const importHono = `import { createRoute${includeZ ? ', z' : ''} } from '@hono/zod-openapi'`;
36
+ const importSchemas = schemaTokens.length > 0 ? `import { ${schemaTokens.join(',')} } from '${importPath}'` : '';
37
+ const finalSrc = [importHono, importSchemas, '\n', routesSrc].filter(Boolean).join('\n');
38
+ const fmtResult = await fmt(finalSrc);
39
+ if (!fmtResult.ok)
40
+ return { ok: false, error: fmtResult.error };
41
+ const mkdirResult = await mkdir(path.dirname(output));
42
+ if (!mkdirResult.ok)
43
+ return { ok: false, error: mkdirResult.error };
44
+ const writeResult = await writeFile(output, fmtResult.value);
45
+ return writeResult.ok
46
+ ? { ok: true, value: `Generated route code written to ${output}` }
47
+ : { ok: false, error: writeResult.error };
48
+ }
49
+ const outDir = output.replace(/\.ts$/, '');
50
+ const blocks = extractRouteBlocks(routesSrc);
51
+ if (blocks.length === 0) {
52
+ const includeZ = routesSrc.includes('z.');
53
+ const schemaTokens = findSchemaTokens(routesSrc);
54
+ const importHono = `import { createRoute${includeZ ? ', z' : ''} } from '@hono/zod-openapi'`;
55
+ const importSchemas = schemaTokens.length > 0 ? `import { ${schemaTokens.join(',')} } from '${importPath}'` : '';
56
+ const finalSrc = [importHono, importSchemas, '\n', routesSrc].filter(Boolean).join('\n');
57
+ const fmtResult = await fmt(finalSrc);
58
+ if (!fmtResult.ok)
59
+ return { ok: false, error: fmtResult.error };
60
+ const mkdirResult = await mkdir(path.dirname(output));
61
+ if (!mkdirResult.ok)
62
+ return { ok: false, error: mkdirResult.error };
63
+ const writeResult = await writeFile(output, fmtResult.value);
64
+ return writeResult.ok
65
+ ? { ok: true, value: `Generated route code written to ${output}` }
66
+ : { ok: false, error: writeResult.error };
67
+ }
68
+ for (const { name, block } of blocks) {
69
+ const includeZ = block.includes('z.');
70
+ const schemaTokens = findSchemaTokens(block);
71
+ const importHono = `import { createRoute${includeZ ? ', z' : ''} } from '@hono/zod-openapi'`;
72
+ const importSchemas = schemaTokens.length > 0 ? `import { ${schemaTokens.join(',')} } from '${importPath}'` : '';
73
+ const fileSrc = [importHono, importSchemas, '\n', block, ''].filter(Boolean).join('\n');
74
+ const fmtResult = await fmt(fileSrc);
75
+ if (!fmtResult.ok)
76
+ return { ok: false, error: fmtResult.error };
77
+ const filePath = `${outDir}/${lowerFirst(name)}.ts`;
78
+ const mkdirResult = await mkdir(path.dirname(filePath));
79
+ if (!mkdirResult.ok)
80
+ return { ok: false, error: mkdirResult.error };
81
+ const writeResult = await writeFile(filePath, fmtResult.value);
82
+ if (!writeResult.ok)
83
+ return { ok: false, error: writeResult.error };
84
+ }
85
+ const indexBody = `${blocks
86
+ .sort()
87
+ .map(({ name }) => `export * from './${lowerFirst(name)}'`)
88
+ .join('\n')}\n`;
89
+ const fmtResult = await fmt(indexBody);
90
+ if (!fmtResult.ok)
91
+ return { ok: false, error: fmtResult.error };
92
+ const mkdirResult = await mkdir(path.dirname(`${outDir}/index.ts`));
93
+ if (!mkdirResult.ok)
94
+ return { ok: false, error: mkdirResult.error };
95
+ const writeResult = await writeFile(`${outDir}/index.ts`, fmtResult.value);
96
+ if (!writeResult.ok)
97
+ return { ok: false, error: writeResult.error };
98
+ return { ok: true, value: `Generated route code written to ${outDir}/*.ts (index.ts included)` };
99
+ }
@@ -1,7 +1,13 @@
1
- export declare function rpc(input: `${string}.yaml` | `${string}.json` | `${string}.tsp`, output: `${string}.ts`, importCode: string): Promise<{
2
- ok: true;
3
- value: string;
1
+ /**
2
+ * Generate RPC client wrappers from an OpenAPI/TypeSpec source.
3
+ *
4
+ * - When `split=true`, writes one file per RPC function under `output` (directory) and an `index.ts` barrel.
5
+ * - Otherwise, emits a single `.ts` file at `output`.
6
+ */
7
+ export declare function rpc(input: `${string}.yaml` | `${string}.json` | `${string}.tsp`, output: string | `${string}.ts`, importPath: string, split?: boolean): Promise<{
8
+ readonly ok: true;
9
+ readonly value: string;
4
10
  } | {
5
- ok: false;
6
- error: string;
11
+ readonly ok: false;
12
+ readonly error: string;
7
13
  }>;