@codewithagents/openapi-server 1.2.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/config.js CHANGED
@@ -1,14 +1,32 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { join, resolve } from 'node:path';
3
3
  const FORBIDDEN_OUTPUT_PREFIXES = [
4
- '/etc', '/usr', '/bin', '/sbin', '/lib', '/lib64',
5
- '/sys', '/proc', '/dev', '/boot', '/run',
6
- 'C:\\Windows', 'C:\\Program Files',
4
+ '/etc',
5
+ '/usr',
6
+ '/bin',
7
+ '/sbin',
8
+ '/lib',
9
+ '/lib64',
10
+ '/sys',
11
+ '/proc',
12
+ '/dev',
13
+ '/boot',
14
+ '/run',
15
+ 'C:\\Windows',
16
+ 'C:\\Program Files',
7
17
  ];
8
18
  const FORBIDDEN_INPUT_PREFIXES = [
9
- '/etc', '/usr', '/bin', '/sbin', '/lib', '/lib64',
10
- '/sys', '/proc', '/dev',
11
- 'C:\\Windows', 'C:\\Program Files',
19
+ '/etc',
20
+ '/usr',
21
+ '/bin',
22
+ '/sbin',
23
+ '/lib',
24
+ '/lib64',
25
+ '/sys',
26
+ '/proc',
27
+ '/dev',
28
+ 'C:\\Windows',
29
+ 'C:\\Program Files',
12
30
  ];
13
31
  export function validateConfigPath(configPath) {
14
32
  if (!configPath.endsWith('.json')) {
@@ -35,6 +53,7 @@ export function validateInputPath(resolvedInput) {
35
53
  }
36
54
  }
37
55
  }
56
+ // fallow-ignore-next-line complexity
38
57
  export async function loadConfig(cwd, configPath) {
39
58
  const resolvedConfigPath = configPath ?? join(cwd, 'openapi-server.config.json');
40
59
  if (configPath !== undefined) {
@@ -66,8 +85,10 @@ export async function loadConfig(cwd, configPath) {
66
85
  }
67
86
  if (config['framework'] !== undefined &&
68
87
  config['framework'] !== 'hono' &&
88
+ config['framework'] !== 'express' &&
89
+ config['framework'] !== 'fastify' &&
69
90
  config['framework'] !== 'none') {
70
- throw new Error('"framework" must be either "hono" or "none"');
91
+ throw new Error('"framework" must be one of: "hono", "express", "fastify", or "none"');
71
92
  }
72
93
  if (config['input_schema'] !== undefined &&
73
94
  (typeof config['input_schema'] !== 'string' || !config['input_schema'])) {
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"AAaA,wBAAsB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkE9E"}
1
+ {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"AAcA,wBAAsB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgF9E"}
package/dist/generator.js CHANGED
@@ -8,7 +8,8 @@ async function formatTs(content, filePath) {
8
8
  }
9
9
  import { parseSpec } from '@codewithagents/openapi-gen';
10
10
  import { generateService } from './plugins/service.js';
11
- import { generateRouter } from './plugins/router.js';
11
+ import { generateRouter, generateExpressRouter, generateFastifyRouter } from './plugins/router.js';
12
+ // fallow-ignore-next-line complexity
12
13
  export async function generate(cwd, configPath) {
13
14
  console.log('Loading config...');
14
15
  const config = await loadConfig(cwd, configPath);
@@ -23,6 +24,14 @@ export async function generate(cwd, configPath) {
23
24
  // First pass: generate router without schema validation
24
25
  generatedFiles.push(generateRouter(spec));
25
26
  }
27
+ else if (framework === 'express') {
28
+ // First pass: generate router without schema validation
29
+ generatedFiles.push(generateExpressRouter(spec));
30
+ }
31
+ else if (framework === 'fastify') {
32
+ // First pass: generate router without schema validation
33
+ generatedFiles.push(generateFastifyRouter(spec));
34
+ }
26
35
  console.log(`Writing output to: ${outputDir}`);
27
36
  await mkdir(outputDir, { recursive: true });
28
37
  for (const file of generatedFiles) {
@@ -31,7 +40,8 @@ export async function generate(cwd, configPath) {
31
40
  console.log(` ✓ ${file.filename}`);
32
41
  }
33
42
  // Second pass: if input_schema is configured and file exists, re-generate router with Zod validation
34
- if (framework === 'hono' && config.input_schema !== undefined) {
43
+ if ((framework === 'hono' || framework === 'express' || framework === 'fastify') &&
44
+ config.input_schema !== undefined) {
35
45
  const schemaPath = resolve(cwd, config.input_schema);
36
46
  let schemaContent;
37
47
  try {
@@ -54,10 +64,17 @@ export async function generate(cwd, configPath) {
54
64
  const schemaImportPath = relPath.startsWith('.') ? relPath : `./${relPath}`;
55
65
  // Strip .ts extension for import (use .js for NodeNext compatibility)
56
66
  const schemaImportPathJs = schemaImportPath.replace(/\.ts$/, '.js');
57
- const routerFile = generateRouter(spec, {
58
- schemaNames: exportedSchemas,
59
- schemaImportPath: schemaImportPathJs,
60
- });
67
+ const routerOptions = { schemaNames: exportedSchemas, schemaImportPath: schemaImportPathJs };
68
+ let routerFile;
69
+ if (framework === 'hono') {
70
+ routerFile = generateRouter(spec, routerOptions);
71
+ }
72
+ else if (framework === 'express') {
73
+ routerFile = generateExpressRouter(spec, routerOptions);
74
+ }
75
+ else {
76
+ routerFile = generateFastifyRouter(spec, routerOptions);
77
+ }
61
78
  const routerPath = join(outputDir, routerFile.filename);
62
79
  await writeFile(routerPath, await formatTs(routerFile.content, routerPath), 'utf-8');
63
80
  console.log(` ✓ router.ts (with Zod validation for ${exportedSchemas.size} schema(s))`);
@@ -1 +1 @@
1
- {"version":3,"file":"generator.js","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"generator.js","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,8 @@ interface RouterOptions {
4
4
  schemaNames?: Set<string>;
5
5
  schemaImportPath?: string;
6
6
  }
7
+ export declare function generateExpressRouter(spec: OpenAPIV3_1.Document, options?: RouterOptions): GeneratedFile;
8
+ export declare function generateFastifyRouter(spec: OpenAPIV3_1.Document, options?: RouterOptions): GeneratedFile;
7
9
  export declare function generateRouter(spec: OpenAPIV3_1.Document, options?: RouterOptions): GeneratedFile;
8
10
  export {};
9
11
  //# sourceMappingURL=router.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/plugins/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AA4VhE,UAAU,aAAa;IACrB,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,WAAW,CAAC,QAAQ,EAC1B,OAAO,CAAC,EAAE,aAAa,GACtB,aAAa,CA4Df"}
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/plugins/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAoWhE,UAAU,aAAa;IACrB,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAsFD,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,WAAW,CAAC,QAAQ,EAC1B,OAAO,CAAC,EAAE,aAAa,GACtB,aAAa,CA8Df;AAkGD,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,WAAW,CAAC,QAAQ,EAC1B,OAAO,CAAC,EAAE,aAAa,GACtB,aAAa,CAuDf;AAKD,wBAAgB,cAAc,CAAC,IAAI,EAAE,WAAW,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,aAAa,CA0DjG"}
@@ -12,7 +12,7 @@ function extractPathParamsFromPath(path) {
12
12
  const matches = path.match(/\{([^}]+)\}/g);
13
13
  if (matches === null)
14
14
  return [];
15
- // Keep raw param names they are used in c.req.param() which must match
15
+ // Keep raw param names: they are used in c.req.param() which must match
16
16
  // the actual Hono route pattern (e.g. :job-id requires c.req.param('job-id'))
17
17
  return matches.map((m) => m.slice(1, -1));
18
18
  }
@@ -89,7 +89,7 @@ function deriveOperationName(method, path) {
89
89
  const prefix = prefixMap[method] ?? method;
90
90
  const segments = path.replace(/^\/api\/v\d+\//, '').replace(/^\//, '');
91
91
  const parts = segments.split('/').map((seg) => {
92
- // Handle mixed segments like "{maxLat}.{format}" extract each {param} inside
92
+ // Handle mixed segments like "{maxLat}.{format}": extract each {param} inside
93
93
  const paramMatches = seg.match(/\{([^}]+)\}/g);
94
94
  if (paramMatches !== null && !(seg.startsWith('{') && seg.endsWith('}'))) {
95
95
  return paramMatches
@@ -112,12 +112,25 @@ function deriveOperationName(method, path) {
112
112
  * Strips trailing [] (array marker), converts separators to camelCase.
113
113
  */
114
114
  function normalizeParamName(name) {
115
- return name
116
- .replace(/\[\]$/, '') // strip trailing []
117
- .replace(/'/g, '')
118
- .replace(/[^a-zA-Z0-9]+([a-zA-Z])/g, (_, char) => char.toUpperCase())
119
- .replace(/[^a-zA-Z0-9]+$/, '')
120
- .replace(/^[^a-zA-Z_$]/, '_');
115
+ // Split on non-alphanumeric sequences to avoid polynomial ReDoS from [^x]+y patterns.
116
+ const stripped = name.replace(/\[\]$/, '').replace(/'/g, '');
117
+ const parts = stripped.split(/[^a-zA-Z0-9]+/).filter(Boolean);
118
+ if (parts.length === 0)
119
+ return '_';
120
+ const camel = parts
121
+ .map((part, i) => (i === 0 ? part : part[0].toUpperCase() + part.slice(1)))
122
+ .join('');
123
+ return /^[^a-zA-Z_$]/.test(camel) ? `_${camel}` : camel;
124
+ }
125
+ function schemaToTsType(schema) {
126
+ if (schema === undefined || isRef(schema))
127
+ return 'string';
128
+ const s = schema;
129
+ if (s.type === 'number' || s.type === 'integer')
130
+ return 'number';
131
+ if (s.type === 'boolean')
132
+ return 'boolean';
133
+ return 'string';
121
134
  }
122
135
  function getQueryParams(operation, spec) {
123
136
  const parameters = operation.parameters;
@@ -129,17 +142,9 @@ function getQueryParams(operation, spec) {
129
142
  if (resolved === undefined || resolved.in !== 'query')
130
143
  continue;
131
144
  const schema = resolved.schema;
132
- let tsType = 'string';
133
- if (schema !== undefined && !isRef(schema)) {
134
- const s = schema;
135
- if (s.type === 'number' || s.type === 'integer')
136
- tsType = 'number';
137
- else if (s.type === 'boolean')
138
- tsType = 'boolean';
139
- }
140
145
  result.push({
141
146
  name: normalizeParamName(resolved.name),
142
- tsType,
147
+ tsType: schemaToTsType(schema),
143
148
  required: resolved.required === true,
144
149
  });
145
150
  }
@@ -164,32 +169,29 @@ function getBodyInfo(operation) {
164
169
  }
165
170
  return { typeName: undefined };
166
171
  }
172
+ function response200IsVoid(resp) {
173
+ if (isRef(resp))
174
+ return false;
175
+ const r = resp;
176
+ const content = r.content;
177
+ return content === undefined || Object.keys(content).length === 0;
178
+ }
167
179
  function getResponseStatus(operation, httpMethod) {
168
180
  const responses = operation.responses;
169
- if (responses !== undefined) {
170
- // Explicit 201
171
- if (responses['201'] !== undefined)
172
- return { status: 201, isVoid: false };
173
- // Explicit 204 or delete with no meaningful body
174
- if (responses['204'] !== undefined)
181
+ if (responses === undefined) {
182
+ return httpMethod === 'delete' ? { status: 204, isVoid: true } : { status: 200, isVoid: false };
183
+ }
184
+ if (responses['201'] !== undefined)
185
+ return { status: 201, isVoid: false };
186
+ if (responses['204'] !== undefined)
187
+ return { status: 204, isVoid: true };
188
+ if (responses['200'] !== undefined) {
189
+ if (response200IsVoid(responses['200']))
175
190
  return { status: 204, isVoid: true };
176
- // Check if 200 has content
177
- if (responses['200'] !== undefined) {
178
- const resp = responses['200'];
179
- if (!isRef(resp)) {
180
- const r = resp;
181
- const content = r.content;
182
- if (content === undefined || Object.keys(content).length === 0) {
183
- return { status: 204, isVoid: true };
184
- }
185
- }
186
- return { status: 200, isVoid: false };
187
- }
191
+ return { status: 200, isVoid: false };
188
192
  }
189
193
  // Default: delete -> 204, otherwise 200
190
- if (httpMethod === 'delete')
191
- return { status: 204, isVoid: true };
192
- return { status: 200, isVoid: false };
194
+ return httpMethod === 'delete' ? { status: 204, isVoid: true } : { status: 200, isVoid: false };
193
195
  }
194
196
  function collectOperations(spec) {
195
197
  const paths = spec.paths;
@@ -220,6 +222,7 @@ function collectOperations(spec) {
220
222
  }
221
223
  return operations;
222
224
  }
225
+ // fallow-ignore-next-line complexity
223
226
  function buildRouteHandler(op, indent, schemaNames) {
224
227
  const lines = [];
225
228
  lines.push(`${indent}app.${op.httpMethod}('${op.honoPath}', async (c) => {`);
@@ -245,7 +248,7 @@ function buildRouteHandler(op, indent, schemaNames) {
245
248
  // Zod validation when schema is available
246
249
  const schemaName = op.bodyInfo.typeName !== undefined ? `${op.bodyInfo.typeName}Schema` : undefined;
247
250
  if (schemaName !== undefined && schemaNames !== undefined && schemaNames.has(schemaName)) {
248
- lines.push(`${indent} // Validate request body returns 422 with Zod issues on failure`);
251
+ lines.push(`${indent} // Validate request body: returns 422 with Zod issues on failure`);
249
252
  lines.push(`${indent} const parseResult = ${schemaName}.safeParse(body)`);
250
253
  lines.push(`${indent} if (!parseResult.success) {`);
251
254
  lines.push(`${indent} return c.json({ error: 'Invalid request body', issues: parseResult.error.issues }, 422)`);
@@ -280,6 +283,260 @@ function buildRouteHandler(op, indent, schemaNames) {
280
283
  lines.push(`${indent}})`);
281
284
  return lines.join('\n');
282
285
  }
286
+ // ── Express router generator ───────────────────────────────────────────────────
287
+ // fallow-ignore-next-line complexity
288
+ function buildExpressRouteHandler(op, indent, schemaNames) {
289
+ const lines = [];
290
+ lines.push(`${indent}router.${op.httpMethod}('${op.honoPath}', async (req: Request, res: Response) => {`);
291
+ // Query params extraction
292
+ if (op.queryParams.length > 0) {
293
+ const fields = op.queryParams
294
+ .map((q) => {
295
+ if (q.tsType === 'number') {
296
+ return ` ${q.name}: Number(req.query['${q.name}'] as string)`;
297
+ }
298
+ if (q.tsType === 'boolean') {
299
+ return ` ${q.name}: req.query['${q.name}'] === 'true'`;
300
+ }
301
+ return ` ${q.name}: req.query['${q.name}'] as string | undefined`;
302
+ })
303
+ .join(',\n');
304
+ lines.push(`${indent} const params = {`);
305
+ lines.push(fields);
306
+ lines.push(`${indent} }`);
307
+ }
308
+ // Body extraction, with optional Zod validation
309
+ let bodyVarName = 'body';
310
+ if (op.bodyInfo !== undefined) {
311
+ const schemaName = op.bodyInfo.typeName !== undefined ? `${op.bodyInfo.typeName}Schema` : undefined;
312
+ const useZod = schemaName !== undefined && schemaNames !== undefined && schemaNames.has(schemaName);
313
+ if (useZod) {
314
+ lines.push(`${indent} // Validate request body: returns 422 with Zod issues on failure`);
315
+ lines.push(`${indent} const parseResult = ${schemaName}.safeParse(req.body)`);
316
+ lines.push(`${indent} if (!parseResult.success) {`);
317
+ lines.push(`${indent} return void res.status(422).json({ error: 'Invalid request body', issues: parseResult.error.issues })`);
318
+ lines.push(`${indent} }`);
319
+ lines.push(`${indent} const validatedBody = parseResult.data`);
320
+ bodyVarName = 'validatedBody';
321
+ }
322
+ else {
323
+ const typeAnnotation = op.bodyInfo.typeName !== undefined ? ` as ${op.bodyInfo.typeName}` : '';
324
+ lines.push(`${indent} const body = req.body${typeAnnotation}`);
325
+ }
326
+ }
327
+ // Build service call args
328
+ const serviceArgs = [];
329
+ for (const p of op.pathParams) {
330
+ serviceArgs.push(`req.params['${p}']!`);
331
+ }
332
+ if (op.bodyInfo !== undefined) {
333
+ serviceArgs.push(bodyVarName);
334
+ }
335
+ if (op.queryParams.length > 0) {
336
+ serviceArgs.push('params');
337
+ }
338
+ const serviceCall = `service.${op.methodName}(${serviceArgs.join(', ')})`;
339
+ // Response
340
+ if (op.responseStatus.isVoid) {
341
+ lines.push(`${indent} await ${serviceCall}`);
342
+ lines.push(`${indent} res.status(${op.responseStatus.status}).end()`);
343
+ }
344
+ else if (op.responseStatus.status === 201) {
345
+ lines.push(`${indent} res.status(201).json(await ${serviceCall})`);
346
+ }
347
+ else {
348
+ lines.push(`${indent} res.json(await ${serviceCall})`);
349
+ }
350
+ lines.push(`${indent}})`);
351
+ return lines.join('\n');
352
+ }
353
+ // fallow-ignore-next-line complexity
354
+ export function generateExpressRouter(spec, options) {
355
+ const serviceName = deriveServiceName(spec);
356
+ const operations = collectOperations(spec);
357
+ // Collect body type names for import from models.js
358
+ const bodyTypes = new Set();
359
+ for (const op of operations) {
360
+ if (op.bodyInfo?.typeName !== undefined) {
361
+ bodyTypes.add(op.bodyInfo.typeName);
362
+ }
363
+ }
364
+ const sortedBodyTypes = Array.from(bodyTypes).sort();
365
+ // Collect which schema names are actually needed (only for ops with a matching schema)
366
+ const usedSchemaNames = new Set();
367
+ if (options?.schemaNames !== undefined) {
368
+ for (const op of operations) {
369
+ const typeName = op.bodyInfo?.typeName;
370
+ if (typeName !== undefined) {
371
+ const schemaName = `${typeName}Schema`;
372
+ if (options.schemaNames.has(schemaName)) {
373
+ usedSchemaNames.add(schemaName);
374
+ }
375
+ }
376
+ }
377
+ }
378
+ const lines = [];
379
+ lines.push('// This file is auto-generated. Do not edit manually.');
380
+ lines.push('// Express: apply express.json() middleware before mounting this router so req.body is populated.');
381
+ lines.push('');
382
+ lines.push("import { Router } from 'express'");
383
+ lines.push("import type { Request, Response } from 'express'");
384
+ if (sortedBodyTypes.length > 0) {
385
+ lines.push(`import type { ${sortedBodyTypes.join(', ')} } from './models.js'`);
386
+ }
387
+ lines.push(`import type { ${serviceName} } from './service.js'`);
388
+ if (usedSchemaNames.size > 0 && options?.schemaImportPath !== undefined) {
389
+ lines.push(`import { z } from 'zod'`);
390
+ const sortedUsedSchemas = Array.from(usedSchemaNames).sort();
391
+ lines.push(`import { ${sortedUsedSchemas.join(', ')} } from '${options.schemaImportPath}'`);
392
+ }
393
+ lines.push('');
394
+ lines.push(`export function createRouter(service: ${serviceName}): Router {`);
395
+ lines.push(' const router = Router()');
396
+ lines.push('');
397
+ for (const op of operations) {
398
+ lines.push(buildExpressRouteHandler(op, ' ', options?.schemaNames));
399
+ lines.push('');
400
+ }
401
+ lines.push(' return router');
402
+ lines.push('}');
403
+ lines.push('');
404
+ return {
405
+ filename: 'router.ts',
406
+ content: lines.join('\n'),
407
+ };
408
+ }
409
+ // ── Fastify router generator ───────────────────────────────────────────────────
410
+ // fallow-ignore-next-line complexity
411
+ function buildFastifyRouteHandler(op, indent, schemaNames) {
412
+ const lines = [];
413
+ // Build generic type argument
414
+ const genericParts = [];
415
+ if (op.queryParams.length > 0) {
416
+ const queryFields = op.queryParams
417
+ .map((q) => {
418
+ if (q.tsType === 'number')
419
+ return `${q.name}?: number`;
420
+ if (q.tsType === 'boolean')
421
+ return `${q.name}?: boolean`;
422
+ return `${q.name}?: string`;
423
+ })
424
+ .join('; ');
425
+ genericParts.push(`Querystring: { ${queryFields} }`);
426
+ }
427
+ if (op.bodyInfo !== undefined && op.bodyInfo.typeName !== undefined) {
428
+ genericParts.push(`Body: ${op.bodyInfo.typeName}`);
429
+ }
430
+ else if (op.bodyInfo !== undefined) {
431
+ genericParts.push('Body: unknown');
432
+ }
433
+ if (op.pathParams.length > 0) {
434
+ const paramFields = op.pathParams.map((p) => `${p}: string`).join('; ');
435
+ genericParts.push(`Params: { ${paramFields} }`);
436
+ }
437
+ const generic = genericParts.length > 0 ? `<{ ${genericParts.join('; ')} }>` : '';
438
+ lines.push(`${indent}app.${op.httpMethod}${generic}('${op.honoPath}', async (req, reply) => {`);
439
+ // Query params extraction
440
+ if (op.queryParams.length > 0) {
441
+ const fields = op.queryParams.map((q) => ` ${q.name}: req.query.${q.name}`).join(',\n');
442
+ lines.push(`${indent} const params = {`);
443
+ lines.push(fields);
444
+ lines.push(`${indent} }`);
445
+ }
446
+ // Body handling, with optional Zod validation
447
+ let bodyVarName = 'req.body';
448
+ if (op.bodyInfo !== undefined) {
449
+ const schemaName = op.bodyInfo.typeName !== undefined ? `${op.bodyInfo.typeName}Schema` : undefined;
450
+ const useZod = schemaName !== undefined && schemaNames !== undefined && schemaNames.has(schemaName);
451
+ if (useZod) {
452
+ lines.push(`${indent} // Validate request body: returns 422 with Zod issues on failure`);
453
+ lines.push(`${indent} const parseResult = ${schemaName}.safeParse(req.body)`);
454
+ lines.push(`${indent} if (!parseResult.success) {`);
455
+ lines.push(`${indent} return reply.status(422).send({ error: 'Invalid request body', issues: parseResult.error.issues })`);
456
+ lines.push(`${indent} }`);
457
+ bodyVarName = 'parseResult.data';
458
+ }
459
+ }
460
+ // Build service call args
461
+ const serviceArgs = [];
462
+ for (const p of op.pathParams) {
463
+ serviceArgs.push(`req.params.${p}`);
464
+ }
465
+ if (op.bodyInfo !== undefined) {
466
+ serviceArgs.push(bodyVarName);
467
+ }
468
+ if (op.queryParams.length > 0) {
469
+ serviceArgs.push('params');
470
+ }
471
+ const serviceCall = `service.${op.methodName}(${serviceArgs.join(', ')})`;
472
+ // Response
473
+ if (op.responseStatus.isVoid) {
474
+ lines.push(`${indent} await ${serviceCall}`);
475
+ lines.push(`${indent} reply.status(${op.responseStatus.status}).send()`);
476
+ }
477
+ else if (op.responseStatus.status === 201) {
478
+ lines.push(`${indent} reply.status(201)`);
479
+ lines.push(`${indent} return ${serviceCall}`);
480
+ }
481
+ else {
482
+ lines.push(`${indent} return ${serviceCall}`);
483
+ }
484
+ lines.push(`${indent}})`);
485
+ return lines.join('\n');
486
+ }
487
+ // fallow-ignore-next-line complexity
488
+ export function generateFastifyRouter(spec, options) {
489
+ const serviceName = deriveServiceName(spec);
490
+ const operations = collectOperations(spec);
491
+ // Collect body type names for import from models.js
492
+ const bodyTypes = new Set();
493
+ for (const op of operations) {
494
+ if (op.bodyInfo?.typeName !== undefined) {
495
+ bodyTypes.add(op.bodyInfo.typeName);
496
+ }
497
+ }
498
+ const sortedBodyTypes = Array.from(bodyTypes).sort();
499
+ // Collect which schema names are actually needed (only for ops with a matching schema)
500
+ const usedSchemaNames = new Set();
501
+ if (options?.schemaNames !== undefined) {
502
+ for (const op of operations) {
503
+ const typeName = op.bodyInfo?.typeName;
504
+ if (typeName !== undefined) {
505
+ const schemaName = `${typeName}Schema`;
506
+ if (options.schemaNames.has(schemaName)) {
507
+ usedSchemaNames.add(schemaName);
508
+ }
509
+ }
510
+ }
511
+ }
512
+ const lines = [];
513
+ lines.push('// This file is auto-generated. Do not edit manually.');
514
+ lines.push('');
515
+ lines.push("import type { FastifyInstance } from 'fastify'");
516
+ if (sortedBodyTypes.length > 0) {
517
+ lines.push(`import type { ${sortedBodyTypes.join(', ')} } from './models.js'`);
518
+ }
519
+ lines.push(`import type { ${serviceName} } from './service.js'`);
520
+ if (usedSchemaNames.size > 0 && options?.schemaImportPath !== undefined) {
521
+ lines.push(`import { z } from 'zod'`);
522
+ const sortedUsedSchemas = Array.from(usedSchemaNames).sort();
523
+ lines.push(`import { ${sortedUsedSchemas.join(', ')} } from '${options.schemaImportPath}'`);
524
+ }
525
+ lines.push('');
526
+ lines.push(`export function createRouter(app: FastifyInstance, service: ${serviceName}): void {`);
527
+ for (const op of operations) {
528
+ lines.push('');
529
+ lines.push(buildFastifyRouteHandler(op, ' ', options?.schemaNames));
530
+ }
531
+ lines.push('}');
532
+ lines.push('');
533
+ return {
534
+ filename: 'router.ts',
535
+ content: lines.join('\n'),
536
+ };
537
+ }
538
+ // ── Hono router generator ─────────────────────────────────────────────────────
539
+ // fallow-ignore-next-line complexity
283
540
  export function generateRouter(spec, options) {
284
541
  const serviceName = deriveServiceName(spec);
285
542
  const operations = collectOperations(spec);