hono-takibi 0.9.30 → 0.9.51

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/README.md CHANGED
@@ -112,101 +112,152 @@ template
112
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
113
  ```
114
114
 
115
- ## HonoTakibiVite
115
+ ## Configuration File (`hono-takibi.config.ts`)
116
116
 
117
- ### Automatic Code Regeneration & HMR
117
+ Config used by both the CLI and the Vite plugin.
118
118
 
119
- With **HonoTakibiVite**, saving your OpenAPI spec while the Vite dev server is running instantly regenerates the code.
119
+ ## Essentials
120
120
 
121
- ### OpenAPI Schema Requirements
121
+ * Put **`hono-takibi.config.ts`** at repo root.
122
+ * Default‑export with `defineConfig(...)`.
123
+ * `input`: **`openapi.yaml`** (recommended), or `*.json` / `*.tsp`.
122
124
 
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.
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).
126
129
 
127
- ### Supported Input Formats
130
+ ---
128
131
 
129
- You may specify the input file in one of the following formats:
132
+ ## Single‑file
130
133
 
131
- - `.yaml` OpenAPI YAML (schemas only)
132
- - `.json` — OpenAPI JSON (schemas only)
133
- - `.tsp` — TypeSpec source file
134
+ One file. Set top‑level `output` (don’t define `schema`/`route`).
134
135
 
135
- ### TypeSpec Setup (if using `.tsp`)
136
+ ```ts
137
+ import { defineConfig } from 'hono-takibi/config'
136
138
 
137
- If you use a `.tsp` TypeSpec file, you must set up the TypeSpec environment and install required libraries:
139
+ export default defineConfig({
140
+ input: 'openapi.yaml',
141
+ 'zod-openapi': {
142
+ output: './src/index.ts',
143
+ exportSchema: true,
144
+ exportType: true,
145
+ },
146
+ })
147
+ ```
138
148
 
139
- - @typespec/http
140
- - @typespec/rest
141
- - ...other
149
+ ---
142
150
 
143
- ### Example
151
+ ## Schemas & Routes
144
152
 
145
- `vite.config.ts`
153
+ Define **both** `schema` and `route` (don’t set top‑level `output`).
146
154
 
147
155
  ```ts
148
- import { defineConfig } from 'vite'
149
- import { HonoTakibiVite } from 'hono-takibi/vite-plugin'
156
+ import { defineConfig } from 'hono-takibi/config'
150
157
 
151
158
  export default defineConfig({
152
- plugins: [
153
- HonoTakibiVite({
154
- input: 'main.tsp',
155
- output: 'index.ts',
156
- exportType: true,
157
- exportSchema: true,
158
- }),
159
- ],
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
+ },
160
169
  })
161
170
  ```
162
171
 
163
- ### Demo
172
+ ---
164
173
 
165
- ![](https://raw.githubusercontent.com/nakita628/hono-takibi/refs/heads/main/assets/vite/hono-takibi-vite.gif)
174
+ ## RPC (optional)
166
175
 
176
+ Works with either pattern.
167
177
 
168
- ## With AI Prompt
178
+ * `split: true` → `output` is a **directory**; many files + `index.ts`.
179
+ * `split` **omitted** or `false` → `output` is **one `*.ts` file**.
169
180
 
170
- ### Sample Prompt — Schemas-Only Extractor (OpenAPI 3+)
181
+ **Example (split: true)**
171
182
 
172
- A copy‑and‑paste prompt for **any LLM** that extracts **only** the contents of `#/components/schemas/` from an OpenAPI document.
183
+ ```ts
184
+ import { defineConfig } from 'hono-takibi/config'
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
+ ```
173
192
 
174
- ## Prompt Example
193
+ **Example (single file; split omitted/false)**
175
194
 
176
- ```md
177
- You are a **Schemas‑Only Extractor** for OpenAPI 3+.
195
+ ```ts
196
+ import { defineConfig } from 'hono-takibi/config'
197
+
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`)
208
+
209
+ Auto‑regenerates on changes and reloads dev server.
210
+
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
@@ -4,7 +4,7 @@ import { mkdir, readdir, writeFile } from '../fsp/index.js';
4
4
  import { app } from '../generator/zod-openapi-hono/app/index.js';
5
5
  import { zodOpenAPIHono } from '../generator/zod-openapi-hono/openapi/index.js';
6
6
  import { parseOpenAPI } from '../openapi/index.js';
7
- import { groupHandlersByFileName, isHttpMethod, methodPath } from '../utils/index.js';
7
+ import { isHttpMethod, methodPath } from '../utils/index.js';
8
8
  /**
9
9
  * Generates TypeScript code from an OpenAPI spec and optional templates.
10
10
  *
@@ -81,14 +81,14 @@ export async function takibi(input, output, exportSchema, exportType, template,
81
81
  if (!readdirResult.ok) {
82
82
  return { ok: false, error: readdirResult.error };
83
83
  }
84
- const target = path.join(dir, readdirResult.value.includes('index.ts') ? 'main.ts' : 'index.ts');
84
+ const target = path.join(dir, 'index.ts');
85
85
  const writeResult = await writeFile(target, appResult.value);
86
86
  if (!writeResult.ok) {
87
87
  return { ok: false, error: writeResult.error };
88
88
  }
89
- const handlerResult = await zodOpenapiHonoHandler(openAPI, output, test);
90
- if (!handlerResult.ok) {
91
- return { ok: false, error: handlerResult.error };
89
+ const zodOpenapiHonoHandlerResult = await zodOpenapiHonoHandler(openAPI, output, test);
90
+ if (!zodOpenapiHonoHandlerResult.ok) {
91
+ return { ok: false, error: zodOpenapiHonoHandlerResult.error };
92
92
  }
93
93
  return { ok: true, value: 'Generated code and template files written' };
94
94
  }
@@ -107,57 +107,98 @@ export async function takibi(input, output, exportSchema, exportType, template,
107
107
  */
108
108
  async function zodOpenapiHonoHandler(openapi, output, test) {
109
109
  const paths = openapi.paths;
110
- const handlers = [];
111
- for (const [path, pathItem] of Object.entries(paths)) {
112
- for (const [method] of Object.entries(pathItem)) {
113
- if (!isHttpMethod(method))
114
- continue;
115
- const routeHandlerContent = `export const ${methodPath(method, path)}RouteHandler:RouteHandler<typeof ${methodPath(method, path)}Route>=async(c)=>{}`;
116
- const rawSegment = path.replace(/^\/+/, '').split('/')[0] ?? '';
117
- const pathName = (rawSegment === '' ? 'index' : rawSegment)
118
- .replace(/\{([^}]+)\}/g, '$1')
119
- .replace(/[^0-9A-Za-z._-]/g, '_')
120
- .replace(/^[._-]+|[._-]+$/g, '')
121
- .replace(/__+/g, '_')
122
- .replace(/[-._](\w)/g, (_, c) => c.toUpperCase());
123
- const fileName = pathName.length === 0 ? 'indexHandler.ts' : `${pathName}Handler.ts`;
124
- const testFileName = pathName.length === 0 ? 'indexHandler.test.ts' : `${pathName}Handler.test.ts`;
125
- handlers.push({
126
- fileName,
127
- testFileName,
128
- routeHandlerContents: [routeHandlerContent],
129
- routeNames: [`${methodPath(method, path)}Route`],
130
- });
131
- }
132
- }
133
- const mergedHandlers = groupHandlersByFileName(handlers);
110
+ const handlers = Object.entries(paths).flatMap(([p, pathItem]) => Object.entries(pathItem)
111
+ .filter(([m]) => isHttpMethod(m))
112
+ .map(([method]) => {
113
+ const routeId = methodPath(method, p);
114
+ const routeHandlerContent = `export const ${routeId}RouteHandler:RouteHandler<typeof ${routeId}Route>=async(c)=>{}`;
115
+ const rawSegment = p.replace(/^\/+/, '').split('/')[0] ?? '';
116
+ const sanitized = rawSegment
117
+ .replace(/\{([^}]+)\}/g, '$1')
118
+ .replace(/[^0-9A-Za-z._-]/g, '_')
119
+ .replace(/^[._-]+|[._-]+$/g, '')
120
+ .replace(/__+/g, '_')
121
+ .replace(/[-._](\w)/g, (_, c) => c.toUpperCase());
122
+ const pathName = sanitized === '' ? '__root' : sanitized;
123
+ const fileName = `${pathName}.ts`;
124
+ const testFileName = `${pathName}.test.ts`;
125
+ return {
126
+ fileName,
127
+ testFileName,
128
+ routeHandlerContents: [routeHandlerContent],
129
+ routeNames: [`${routeId}Route`],
130
+ };
131
+ }));
132
+ const mergedHandlers = Array.from(handlers
133
+ .reduce((map, h) => {
134
+ const prev = map.get(h.fileName);
135
+ const next = prev
136
+ ? {
137
+ fileName: h.fileName,
138
+ testFileName: h.testFileName,
139
+ routeHandlerContents: [...prev.routeHandlerContents, ...h.routeHandlerContents],
140
+ routeNames: Array.from(new Set([...prev.routeNames, ...h.routeNames])),
141
+ }
142
+ : {
143
+ fileName: h.fileName,
144
+ testFileName: h.testFileName,
145
+ routeHandlerContents: [...h.routeHandlerContents],
146
+ routeNames: [...h.routeNames],
147
+ };
148
+ map.set(h.fileName, next);
149
+ return map;
150
+ }, new Map())
151
+ .values());
152
+ const isDot = output === '.' || output === './';
153
+ const baseDir = isDot ? '.' : (output.match(/^(.*)\/[^/]+\.ts$/)?.[1] ?? '.');
154
+ const handlerPath = baseDir === '.' ? 'handlers' : `${baseDir}/handlers`;
155
+ const routeEntryBasename = output.match(/[^/]+\.ts$/)?.[0] ?? 'index.ts';
156
+ const importFrom = `../${routeEntryBasename.replace(/\.ts$/, '')}`;
157
+ const mkdirResult = await mkdir(handlerPath);
158
+ if (!mkdirResult.ok)
159
+ return {
160
+ ok: false,
161
+ error: mkdirResult.error,
162
+ };
134
163
  for (const handler of mergedHandlers) {
135
- const dirPath = output?.replace(/\/[^/]+\.ts$/, '');
136
- const handlerPath = dirPath === 'index.ts' ? 'handlers' : `${dirPath}/handlers`;
137
- const mkdirResult = await mkdir(handlerPath);
138
- if (!mkdirResult.ok) {
139
- return { ok: false, error: mkdirResult.error };
140
- }
141
- const routeTypes = handler.routeNames.map((routeName) => `${routeName}`).join(', ');
142
- const match = output?.match(/[^/]+\.ts$/);
143
- const matchPath = match ? match[0] : '';
144
- const path = output === '.' || output === './' ? output : `../${matchPath}`;
145
- const importRouteTypes = routeTypes ? `import type { ${routeTypes} } from '${path}';` : '';
164
+ const routeTypes = Array.from(new Set(handler.routeNames)).join(', ');
165
+ const importRouteTypes = routeTypes ? `import type { ${routeTypes} } from '${importFrom}';` : '';
146
166
  const importStatements = `import type { RouteHandler } from '@hono/zod-openapi'\n${importRouteTypes}`;
147
167
  const fileContent = `${importStatements}\n\n${handler.routeHandlerContents.join('\n\n')}`;
148
- const formatCode = await fmt(fileContent);
149
- if (!formatCode.ok) {
150
- return { ok: false, error: formatCode.error };
151
- }
152
- const writeResult = await writeFile(`${handlerPath}/${handler.fileName}`, formatCode.value);
168
+ const fmtResult = await fmt(fileContent);
169
+ if (!fmtResult.ok)
170
+ return {
171
+ ok: false,
172
+ error: fmtResult.error,
173
+ };
174
+ const writeResult = await writeFile(`${handlerPath}/${handler.fileName}`, fmtResult.value);
153
175
  if (!writeResult.ok)
154
- writeResult;
176
+ return {
177
+ ok: false,
178
+ error: writeResult.error,
179
+ };
155
180
  if (test) {
156
181
  const writeResult = await writeFile(`${handlerPath}/${handler.testFileName}`, '');
157
- if (!writeResult.ok) {
158
- return { ok: false, error: writeResult.error };
159
- }
182
+ if (!writeResult.ok)
183
+ return {
184
+ ok: false,
185
+ error: writeResult.error,
186
+ };
160
187
  }
161
188
  }
189
+ const sorted = mergedHandlers.map((h) => h.fileName).sort();
190
+ const exports = sorted.map((h) => `export * from './${h}'`).join('\n');
191
+ const fmtResult = await fmt(exports);
192
+ if (!fmtResult.ok)
193
+ return {
194
+ ok: false,
195
+ error: fmtResult.error,
196
+ };
197
+ const writeResult = await writeFile(`${handlerPath}/index.ts`, fmtResult.value);
198
+ if (!writeResult.ok)
199
+ return {
200
+ ok: false,
201
+ error: writeResult.error,
202
+ };
162
203
  return { ok: true, value: undefined };
163
204
  }
@@ -1,6 +1,5 @@
1
1
  import { docs } from '../../../helper/docs.js';
2
- import { getRouteMaps } from '../../../helper/get-route-maps.js';
3
- import { getHandlerImports, importHandlers, importMap, importRoutes, registerComponent, } from '../../../utils/index.js';
2
+ import { isHttpMethod, methodPath, registerComponent } from '../../../utils/index.js';
4
3
  /**
5
4
  * Generates a Hono app with OpenAPI and Swagger UI integration.
6
5
  *
@@ -10,7 +9,27 @@ import { getHandlerImports, importHandlers, importMap, importRoutes, registerCom
10
9
  * @returns The generated application code as a string.
11
10
  */
12
11
  export function app(openapi, output, basePath) {
12
+ const getRouteMaps = (openapi) => {
13
+ const paths = openapi.paths;
14
+ const routeMappings = Object.entries(paths).flatMap(([path, pathItem]) => {
15
+ return Object.entries(pathItem).flatMap(([method]) => {
16
+ if (!isHttpMethod(method))
17
+ return [];
18
+ return {
19
+ routeName: `${methodPath(method, path)}Route`,
20
+ handlerName: `${methodPath(method, path)}RouteHandler`,
21
+ path,
22
+ };
23
+ });
24
+ });
25
+ return routeMappings;
26
+ };
13
27
  const routeMappings = getRouteMaps(openapi);
28
+ const handlerNames = Array.from(new Set(routeMappings.map((m) => m.handlerName))).sort();
29
+ const handlerImport = handlerNames.length > 0 ? `import { ${handlerNames.join(',')} } from './handlers'` : '';
30
+ const routeNames = Array.from(new Set(routeMappings.map((m) => m.routeName))).sort();
31
+ const routeModule = `./${output.replace(/^.*\//, '').replace(/\.ts$/, '')}`;
32
+ const routesImport = routeNames.length > 0 ? `import { ${routeNames.join(',')} } from '${routeModule}'` : '';
14
33
  const path = basePath !== undefined ? `${basePath}/doc` : '/doc';
15
34
  const registerComponentCode = openapi.components?.securitySchemes
16
35
  ? registerComponent(openapi.components.securitySchemes)
@@ -21,25 +40,33 @@ export function app(openapi, output, basePath) {
21
40
  const minor = Number.isFinite(Number(minStr)) ? Number(minStr) : 0;
22
41
  const is31Plus = major > 3 || (major === 3 && minor >= 1);
23
42
  const doc = is31Plus ? 'doc31' : 'doc';
24
- const swagger = `if(process.env.NODE_ENV === 'development'){${registerComponentCode}\napp.${doc}('${'/doc'}',${JSON.stringify(docs(openapi))}).get('/ui',swaggerUI({url:'${path}'}))}`;
25
- const sections = [
26
- [
27
- `import { swaggerUI } from '@hono/swagger-ui'`,
28
- `import { OpenAPIHono } from '@hono/zod-openapi'`,
29
- importHandlers(getHandlerImports(routeMappings), output).join('\n'),
30
- importRoutes(importMap(routeMappings, output)).join(''),
31
- ].join('\n'),
32
- basePath
33
- ? `${'const app = new OpenAPIHono()'}.basePath('${basePath}')`
34
- : 'const app = new OpenAPIHono()',
35
- `export const api = ${`app${routeMappings
36
- .map(({ routeName, handlerName }) => {
37
- return `.openapi(${routeName},${handlerName})`;
38
- })
39
- .join('\n')}`}`,
43
+ const importSection = [
44
+ `import { swaggerUI } from '@hono/swagger-ui'`,
45
+ `import { OpenAPIHono } from '@hono/zod-openapi'`,
46
+ handlerImport,
47
+ routesImport,
48
+ ]
49
+ .filter(Boolean)
50
+ .join('\n');
51
+ const appInit = basePath
52
+ ? `const app = new OpenAPIHono().basePath('${basePath}')`
53
+ : 'const app = new OpenAPIHono()';
54
+ const apiInit = 'export const api = app' +
55
+ routeMappings
56
+ .sort((a, b) => (a.routeName < b.routeName ? -1 : a.routeName > b.routeName ? 1 : 0))
57
+ .map(({ routeName, handlerName }) => `.openapi(${routeName},${handlerName})`)
58
+ .join('\n');
59
+ const swagger = `if(process.env.NODE_ENV === 'development'){` +
60
+ `${registerComponentCode}\n` +
61
+ `app.${doc}('/doc',${JSON.stringify(docs(openapi))})` +
62
+ `.get('/ui',swaggerUI({url:'${path}'}))` +
63
+ '}';
64
+ return [
65
+ importSection,
66
+ appInit,
67
+ apiInit,
40
68
  swagger,
41
69
  'export type AddType = typeof api',
42
70
  'export default app',
43
- ];
44
- return sections.join('\n\n');
71
+ ].join('\n\n');
45
72
  }
@@ -1,5 +1,4 @@
1
1
  export { docs } from './docs.js';
2
- export { getRouteMaps } from './get-route-maps.js';
3
2
  export { propertiesSchema } from './properties-schema.js';
4
3
  export { resolveSchemasDependencies } from './resolve-schemas-dependencies.js';
5
4
  export { wrap } from './wrap.js';
@@ -1,5 +1,4 @@
1
1
  export { docs } from './docs.js';
2
- export { getRouteMaps } from './get-route-maps.js';
3
2
  export { propertiesSchema } from './properties-schema.js';
4
3
  export { resolveSchemasDependencies } from './resolve-schemas-dependencies.js';
5
4
  export { wrap } from './wrap.js';
@@ -108,28 +108,6 @@ export declare function normalizeTypes(t?: 'string' | 'number' | 'integer' | 'da
108
108
  'string' | 'number' | 'integer' | 'date' | 'boolean' | 'array' | 'object' | 'null',
109
109
  ...('string' | 'number' | 'integer' | 'date' | 'boolean' | 'array' | 'object' | 'null')[]
110
110
  ]): ("string" | "number" | "boolean" | "object" | "integer" | "date" | "array" | "null")[];
111
- /**
112
- * Generates import statements for route handler modules.
113
- *
114
- * ```mermaid
115
- * graph TD
116
- * A[Start importRoutes] --> B[Init result list]
117
- * B --> C[Iterate entries]
118
- * C --> D[Make unique names]
119
- * D --> E[Normalize path if needed]
120
- * E --> F[Build import line]
121
- * F --> G[Append to result list]
122
- * G --> H[More entries]
123
- * H -->|Yes| C
124
- * H -->|No| I[Return result list]
125
- * ```
126
- *
127
- * @param importsMap - Map of file paths to exported identifiers.
128
- * @returns Array of import statements.
129
- */
130
- export declare function importRoutes(importsMap: {
131
- [importPath: `${string}.ts`]: readonly string[];
132
- }): readonly string[];
133
111
  /**
134
112
  * Generates registration code for OpenAPI `securitySchemes`.
135
113
  *
@@ -158,116 +136,6 @@ export declare function registerComponent(securitySchemes: {
158
136
  bearerFormat?: string;
159
137
  };
160
138
  }): string;
161
- /**
162
- * Generates an import map that associates route names with their corresponding import file path.
163
- *
164
- * This is useful for dynamically constructing import statements in code generation,
165
- * ensuring that each route is grouped under its appropriate file.
166
- *
167
- * ```mermaid
168
- * graph TD
169
- * A[Start importMap] --> B[Initialize empty map]
170
- * B --> C[Iterate routeMappings]
171
- * C --> D[Extract output file name]
172
- * D --> E[Check if map has key]
173
- * E --> F[Create array if missing]
174
- * F --> G[Add route name to array]
175
- * G --> H[More routeMappings]
176
- * H --> C
177
- * C --> I[Return importsMap]
178
- * ```
179
- *
180
- * @param routeMappings - An array of route mapping objects containing route name, handler name, and path.
181
- * @param output - The output TypeScript file name (e.g., 'user.ts'). Used to determine the import path.
182
- * @returns A record where each key is an import path (e.g., 'user.ts') and the value is an array of route names imported from that path.
183
- */
184
- export declare function importMap(routeMappings: {
185
- readonly routeName: string;
186
- readonly handlerName: string;
187
- readonly path: string;
188
- }[], output: `${string}.ts`): {
189
- [importPath: `${string}.ts`]: readonly string[];
190
- };
191
- /**
192
- * Generates import statements for handler functions.
193
- *
194
- * ```mermaid
195
- * graph TD
196
- * A[Start importHandlers] --> B[Init empty list]
197
- * B --> C[Iterate handlerImportsMap]
198
- * C --> D[Make unique handler names]
199
- * D --> E[Get dir path from output]
200
- * E --> F[Set handler path base]
201
- * F --> G[Build import statement]
202
- * G --> H[Append to list]
203
- * H --> I[More entries]
204
- * I --> C
205
- * C --> J[Return list]
206
- * ```
207
- *
208
- * @param handlerImportsMap - A map from file names to handler names.
209
- * @param output - The output file path (e.g., 'api.ts').
210
- * @returns An array of import statement strings.
211
- */
212
- export declare function importHandlers(handlerImportsMap: {
213
- [fileName: string]: readonly string[];
214
- }, output: `${string}.ts`): readonly string[];
215
- /**
216
- * Groups route handlers by file name.
217
- *
218
- * ```mermaid
219
- * graph TD
220
- * A[Start groupHandlersByFileName] --> B[Create empty map]
221
- * B --> C[Iterate handlers]
222
- * C --> D[Find existing entry by file name]
223
- * D --> E[Merge contents and route names]
224
- * E --> F[Set merged entry back to map]
225
- * F --> G[More handlers]
226
- * G --> C
227
- * C --> H[Return map values as array]
228
- * ```
229
- *
230
- * @param handlers - An array of route handler definitions including file name, test file name, contents, and route names.
231
- * @returns A deduplicated array of grouped handler definitions per file.
232
- */
233
- export declare function groupHandlersByFileName(handlers: {
234
- readonly fileName: `${string}.ts`;
235
- readonly testFileName: `${string}.ts`;
236
- readonly routeHandlerContents: readonly string[];
237
- readonly routeNames: readonly string[];
238
- }[]): {
239
- readonly fileName: `${string}.ts`;
240
- readonly testFileName: `${string}.ts`;
241
- readonly routeHandlerContents: readonly string[];
242
- readonly routeNames: readonly string[];
243
- }[];
244
- /**
245
- * Generates a map of handler file names to handler names.
246
- *
247
- * ```mermaid
248
- * graph TD
249
- * A[Start getHandlerImports] --> B[Init empty map]
250
- * B --> C[Iterate handler maps]
251
- * C --> D[Take first path segment]
252
- * D --> E[Sanitize segment to path name]
253
- * E --> F[Make file name or index handler]
254
- * F --> G[Ensure array exists in map]
255
- * G --> H[Append handler name]
256
- * H --> I[More items]
257
- * I --> C
258
- * C --> J[Return map]
259
- * ```
260
- *
261
- * @param handlerMaps - Array of route mappings including route name, handler name, and path.
262
- * @returns A map where keys are handler file names and values are arrays of handler names.
263
- */
264
- export declare function getHandlerImports(handlerMaps: {
265
- readonly routeName: string;
266
- readonly handlerName: string;
267
- readonly path: string;
268
- }[]): {
269
- [fileName: `${string}.ts`]: readonly string[];
270
- };
271
139
  /**
272
140
  * Checks if a value is a non-null object (e.g., a potential `$ref` object).
273
141
  *
@@ -411,25 +279,10 @@ export declare function createRoute(args: {
411
279
  }): string;
412
280
  /**
413
281
  * Generates an array of Zod validator strings from OpenAPI parameter objects.
414
- *
415
- * ```mermaid
416
- * graph TD
417
- * A[Start requestParamsArray] --> B[Collect non empty sections]
418
- * B --> C[Iterate sections]
419
- * C --> D[Build z object string from entries]
420
- * D --> E[If section is path rename to params]
421
- * E --> F[Push section colon z object string]
422
- * F --> G[Continue loop]
423
- * G --> H[Filter out null values]
424
- * H --> I[Return array of strings]
425
- * ```
426
- *
427
282
  * @param parameters - An object containing `query`, `path`, and `header` parameters.
428
283
  * @returns An array of strings like `'query:z.object({...})'` or `'params:z.object({...})'`.
429
284
  */
430
- export declare function requestParamsArray(parameters: {
431
- [section: string]: Record<string, string>;
432
- }): readonly string[];
285
+ export declare const requestParamsArray: (parameters: Record<string, Record<string, string>>) => readonly string[];
433
286
  /**
434
287
  * Escapes a string for safe use in TypeScript string literals.
435
288
  *
@@ -225,39 +225,6 @@ export function parseCli(args) {
225
225
  export function normalizeTypes(t) {
226
226
  return t === undefined ? [] : Array.isArray(t) ? t : [t];
227
227
  }
228
- /**
229
- * Generates import statements for route handler modules.
230
- *
231
- * ```mermaid
232
- * graph TD
233
- * A[Start importRoutes] --> B[Init result list]
234
- * B --> C[Iterate entries]
235
- * C --> D[Make unique names]
236
- * D --> E[Normalize path if needed]
237
- * E --> F[Build import line]
238
- * F --> G[Append to result list]
239
- * G --> H[More entries]
240
- * H -->|Yes| C
241
- * H -->|No| I[Return result list]
242
- * ```
243
- *
244
- * @param importsMap - Map of file paths to exported identifiers.
245
- * @returns Array of import statements.
246
- */
247
- export function importRoutes(importsMap) {
248
- const importRoutes = [];
249
- for (const [importPath, names] of Object.entries(importsMap)) {
250
- const uniqueNames = Array.from(new Set(names));
251
- if (importPath.includes('index.ts')) {
252
- const normalizedPath = importPath.replace('index.ts', '');
253
- importRoutes.push(`import { ${uniqueNames.join(',')} } from './${normalizedPath}'`);
254
- }
255
- else {
256
- importRoutes.push(`import { ${uniqueNames.join(',')} } from './${importPath}'`);
257
- }
258
- }
259
- return importRoutes;
260
- }
261
228
  /**
262
229
  * Generates registration code for OpenAPI `securitySchemes`.
263
230
  *
@@ -285,152 +252,6 @@ export function registerComponent(securitySchemes) {
285
252
  })
286
253
  .join('\n');
287
254
  }
288
- /**
289
- * Generates an import map that associates route names with their corresponding import file path.
290
- *
291
- * This is useful for dynamically constructing import statements in code generation,
292
- * ensuring that each route is grouped under its appropriate file.
293
- *
294
- * ```mermaid
295
- * graph TD
296
- * A[Start importMap] --> B[Initialize empty map]
297
- * B --> C[Iterate routeMappings]
298
- * C --> D[Extract output file name]
299
- * D --> E[Check if map has key]
300
- * E --> F[Create array if missing]
301
- * F --> G[Add route name to array]
302
- * G --> H[More routeMappings]
303
- * H --> C
304
- * C --> I[Return importsMap]
305
- * ```
306
- *
307
- * @param routeMappings - An array of route mapping objects containing route name, handler name, and path.
308
- * @param output - The output TypeScript file name (e.g., 'user.ts'). Used to determine the import path.
309
- * @returns A record where each key is an import path (e.g., 'user.ts') and the value is an array of route names imported from that path.
310
- */
311
- export function importMap(routeMappings, output) {
312
- const importsMap = {};
313
- for (const { routeName } of routeMappings) {
314
- const match = output.match(/[^/]+\.ts$/);
315
- const importPath = match ? match[0] : output;
316
- if (!importsMap[importPath]) {
317
- importsMap[importPath] = [];
318
- }
319
- importsMap[importPath].push(routeName);
320
- }
321
- return importsMap;
322
- }
323
- /**
324
- * Generates import statements for handler functions.
325
- *
326
- * ```mermaid
327
- * graph TD
328
- * A[Start importHandlers] --> B[Init empty list]
329
- * B --> C[Iterate handlerImportsMap]
330
- * C --> D[Make unique handler names]
331
- * D --> E[Get dir path from output]
332
- * E --> F[Set handler path base]
333
- * F --> G[Build import statement]
334
- * G --> H[Append to list]
335
- * H --> I[More entries]
336
- * I --> C
337
- * C --> J[Return list]
338
- * ```
339
- *
340
- * @param handlerImportsMap - A map from file names to handler names.
341
- * @param output - The output file path (e.g., 'api.ts').
342
- * @returns An array of import statement strings.
343
- */
344
- export function importHandlers(handlerImportsMap, output) {
345
- const importHandlers = [];
346
- for (const [fileName, handlers] of Object.entries(handlerImportsMap)) {
347
- const uniqueHandlers = Array.from(new Set(handlers));
348
- const replacePath = output?.replace(/\/[^/]+\.ts$/, '');
349
- const dirPath = replacePath === undefined ? '.' : replacePath;
350
- const handlerPath = 'handlers';
351
- if (dirPath === '.') {
352
- importHandlers.push(`import { ${uniqueHandlers.join(',')} } from '${handlerPath}/${fileName}'`);
353
- }
354
- else {
355
- importHandlers.push(`import { ${uniqueHandlers.join(',')} } from './${handlerPath}/${fileName}'`);
356
- }
357
- }
358
- return importHandlers;
359
- }
360
- /**
361
- * Groups route handlers by file name.
362
- *
363
- * ```mermaid
364
- * graph TD
365
- * A[Start groupHandlersByFileName] --> B[Create empty map]
366
- * B --> C[Iterate handlers]
367
- * C --> D[Find existing entry by file name]
368
- * D --> E[Merge contents and route names]
369
- * E --> F[Set merged entry back to map]
370
- * F --> G[More handlers]
371
- * G --> C
372
- * C --> H[Return map values as array]
373
- * ```
374
- *
375
- * @param handlers - An array of route handler definitions including file name, test file name, contents, and route names.
376
- * @returns A deduplicated array of grouped handler definitions per file.
377
- */
378
- export function groupHandlersByFileName(handlers) {
379
- return Array.from(handlers
380
- .reduce((acc, handler) => {
381
- const existing = acc.get(handler.fileName);
382
- const mergedHandler = {
383
- fileName: handler.fileName,
384
- testFileName: handler.testFileName,
385
- routeHandlerContents: existing
386
- ? [...existing.routeHandlerContents, ...handler.routeHandlerContents]
387
- : [...handler.routeHandlerContents],
388
- routeNames: existing
389
- ? [...existing.routeNames, ...handler.routeNames]
390
- : [...handler.routeNames],
391
- };
392
- return acc.set(handler.fileName, mergedHandler);
393
- }, new Map())
394
- .values());
395
- }
396
- /**
397
- * Generates a map of handler file names to handler names.
398
- *
399
- * ```mermaid
400
- * graph TD
401
- * A[Start getHandlerImports] --> B[Init empty map]
402
- * B --> C[Iterate handler maps]
403
- * C --> D[Take first path segment]
404
- * D --> E[Sanitize segment to path name]
405
- * E --> F[Make file name or index handler]
406
- * F --> G[Ensure array exists in map]
407
- * G --> H[Append handler name]
408
- * H --> I[More items]
409
- * I --> C
410
- * C --> J[Return map]
411
- * ```
412
- *
413
- * @param handlerMaps - Array of route mappings including route name, handler name, and path.
414
- * @returns A map where keys are handler file names and values are arrays of handler names.
415
- */
416
- export function getHandlerImports(handlerMaps) {
417
- const getHandlerImports = {};
418
- for (const { handlerName, path } of handlerMaps) {
419
- const rawSegment = path.replace(/^\/+/, '').split('/')[0] ?? '';
420
- const pathName = (rawSegment === '' ? 'index' : rawSegment)
421
- .replace(/\{([^}]+)\}/g, '$1')
422
- .replace(/[^0-9A-Za-z._-]/g, '_')
423
- .replace(/^[._-]+|[._-]+$/g, '')
424
- .replace(/__+/g, '_')
425
- .replace(/[-._](\w)/g, (_, c) => c.toUpperCase());
426
- const fileName = pathName.length === 0 ? 'indexHandler.ts' : `${pathName}Handler.ts`;
427
- if (!getHandlerImports[fileName]) {
428
- getHandlerImports[fileName] = [];
429
- }
430
- getHandlerImports[fileName].push(handlerName);
431
- }
432
- return getHandlerImports;
433
- }
434
255
  /**
435
256
  * Checks if a value is a non-null object (e.g., a potential `$ref` object).
436
257
  *
@@ -560,7 +381,7 @@ export function methodPath(method, path) {
560
381
  .split(/\s+/)
561
382
  .map((str) => `${str.charAt(0).toUpperCase()}${str.slice(1)}`)
562
383
  .join('');
563
- return apiPath ? `${method}${apiPath}` : `${method}Index`;
384
+ return apiPath ? `${method}${apiPath}` : `${method}`;
564
385
  }
565
386
  /**
566
387
  * Generates a Hono route definition as a TypeScript export string.
@@ -602,47 +423,18 @@ export function createRoute(args) {
602
423
  }
603
424
  /**
604
425
  * Generates an array of Zod validator strings from OpenAPI parameter objects.
605
- *
606
- * ```mermaid
607
- * graph TD
608
- * A[Start requestParamsArray] --> B[Collect non empty sections]
609
- * B --> C[Iterate sections]
610
- * C --> D[Build z object string from entries]
611
- * D --> E[If section is path rename to params]
612
- * E --> F[Push section colon z object string]
613
- * F --> G[Continue loop]
614
- * G --> H[Filter out null values]
615
- * H --> I[Return array of strings]
616
- * ```
617
- *
618
426
  * @param parameters - An object containing `query`, `path`, and `header` parameters.
619
427
  * @returns An array of strings like `'query:z.object({...})'` or `'params:z.object({...})'`.
620
428
  */
621
- export function requestParamsArray(parameters) {
622
- // 1. define sections to be processed
623
- const sections = Object.entries(parameters)
624
- .filter(([_, obj]) => obj && Object.keys(obj).length > 0)
625
- .map(([section]) => section);
626
- // 2. processing of each section
627
- return (sections
628
- .map((section) => {
629
- const obj = parameters[section];
630
- // 2.1 process only if object is not empty
631
- if (Object.keys(obj).length) {
632
- const s = `z.object({${Object.entries(obj)
633
- .map(([k, v]) => `${k}:${v}`)
634
- .join(',')}})`;
635
- // path is params convention
636
- if (section === 'path') {
637
- return `params:${s}`;
638
- }
639
- return `${section}:${s}`;
640
- }
641
- return null;
642
- })
643
- // 3. exclude null and return only an array of strings
644
- .filter((item) => item !== null));
645
- }
429
+ export const requestParamsArray = (parameters) => Object.entries(parameters)
430
+ .filter(([, obj]) => obj && Object.keys(obj).length)
431
+ .map(([section, obj]) => {
432
+ const name = section === 'path' ? 'params' : section;
433
+ const fields = Object.entries(obj)
434
+ .map(([k, v]) => `${k}:${v}`)
435
+ .join(',');
436
+ return `${name}:z.object({${fields}})`;
437
+ });
646
438
  /**
647
439
  * Escapes a string for safe use in TypeScript string literals.
648
440
  *
@@ -1 +1 @@
1
- export declare function HonoTakibiVite(): any;
1
+ export declare function honoTakibiVite(): any;
@@ -397,7 +397,7 @@ const outputDirsFromConf = (c) => {
397
397
  * Plugin (return `any`, no `let`)
398
398
  * ────────────────────────────────────────────────────────────── */
399
399
  // biome-ignore lint: plugin
400
- export function HonoTakibiVite() {
400
+ export function honoTakibiVite() {
401
401
  const state = { current: null };
402
402
  const absConfig = path.resolve(process.cwd(), 'hono-takibi.config.ts');
403
403
  const run = async () => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hono-takibi",
3
3
  "description": "Hono Takibi is a CLI tool that generates Hono routes from OpenAPI specifications.",
4
- "version": "0.9.30",
4
+ "version": "0.9.51",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "keywords": [
@@ -1,12 +0,0 @@
1
- import type { OpenAPI } from '../openapi/index.js';
2
- /**
3
- * Extracts route mappings from an OpenAPI specification.
4
- *
5
- * @param openapi - The OpenAPI specification object.
6
- * @returns An array of route mappings, each with route name, handler name, and path.
7
- */
8
- export declare function getRouteMaps(openapi: OpenAPI): {
9
- routeName: string;
10
- handlerName: string;
11
- path: string;
12
- }[];
@@ -1,22 +0,0 @@
1
- import { isHttpMethod, methodPath } from '../utils/index.js';
2
- /**
3
- * Extracts route mappings from an OpenAPI specification.
4
- *
5
- * @param openapi - The OpenAPI specification object.
6
- * @returns An array of route mappings, each with route name, handler name, and path.
7
- */
8
- export function getRouteMaps(openapi) {
9
- const paths = openapi.paths;
10
- const routeMappings = Object.entries(paths).flatMap(([path, pathItem]) => {
11
- return Object.entries(pathItem).flatMap(([method]) => {
12
- if (!isHttpMethod(method))
13
- return [];
14
- return {
15
- routeName: `${methodPath(method, path)}Route`,
16
- handlerName: `${methodPath(method, path)}RouteHandler`,
17
- path,
18
- };
19
- });
20
- });
21
- return routeMappings;
22
- }