@codewithagents/openapi-server 1.8.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -18844,10 +18844,14 @@ function parseServerConfig(raw, base) {
18844
18844
  if (raw["input_schema"] !== void 0 && (typeof raw["input_schema"] !== "string" || !raw["input_schema"])) {
18845
18845
  throw new Error('"input_schema" must be a non-empty string path to your Zod schema file');
18846
18846
  }
18847
+ if (raw["context_type"] !== void 0 && (typeof raw["context_type"] !== "string" || !raw["context_type"])) {
18848
+ throw new Error('"context_type" must be a non-empty string TypeScript type name');
18849
+ }
18847
18850
  return {
18848
18851
  ...base,
18849
18852
  framework,
18850
- input_schema: raw["input_schema"]
18853
+ input_schema: raw["input_schema"],
18854
+ context_type: raw["context_type"]
18851
18855
  };
18852
18856
  }
18853
18857
  async function loadConfigs2(cwd, configPath) {
@@ -18943,6 +18947,7 @@ function schemaToTsType(schema) {
18943
18947
  const s = schema;
18944
18948
  if (s.type === "number" || s.type === "integer") return "number";
18945
18949
  if (s.type === "boolean") return "boolean";
18950
+ if (s.type === "array") return "string[]";
18946
18951
  return "string";
18947
18952
  }
18948
18953
  function getQueryParams(operation, spec) {
@@ -18953,35 +18958,137 @@ function getQueryParams(operation, spec) {
18953
18958
  const resolved = resolveParam(p, spec);
18954
18959
  if (resolved === void 0 || resolved.in !== "query") continue;
18955
18960
  const schema = resolved.schema;
18956
- result.push({
18961
+ const resolvedStyle = resolved.style;
18962
+ const resolvedExplode = resolved.explode;
18963
+ const param = {
18957
18964
  name: normalizeParamName(resolved.name),
18965
+ rawName: resolved.name,
18958
18966
  tsType: schemaToTsType(schema),
18959
18967
  required: resolved.required === true
18960
- });
18968
+ };
18969
+ if (resolvedStyle === "deepObject" && schema !== void 0 && !isRef3(schema)) {
18970
+ const s = schema;
18971
+ if (s.type === "object" && s.properties !== void 0) {
18972
+ param.isDeepObject = true;
18973
+ param.deepObjectProperties = Object.entries(s.properties).map(([key, propSchema]) => ({
18974
+ key,
18975
+ tsType: schemaToTsType(propSchema)
18976
+ }));
18977
+ const propFields = param.deepObjectProperties.map((p2) => `${p2.key}?: ${p2.tsType}`).join("; ");
18978
+ param.tsType = `{ ${propFields} }`;
18979
+ }
18980
+ }
18981
+ if (!param.isDeepObject && schema !== void 0 && !isRef3(schema) && schema.type === "array" && resolvedExplode === false) {
18982
+ if (resolvedStyle === "spaceDelimited") {
18983
+ param.delimiterStyle = "ssv";
18984
+ } else if (resolvedStyle === "pipeDelimited") {
18985
+ param.delimiterStyle = "psv";
18986
+ } else {
18987
+ param.delimiterStyle = "csv";
18988
+ }
18989
+ }
18990
+ if (schema !== void 0 && !isRef3(schema)) {
18991
+ const s = schema;
18992
+ if (Array.isArray(s.enum)) param.enum = s.enum;
18993
+ if (typeof s.minimum === "number") param.minimum = s.minimum;
18994
+ if (typeof s.maximum === "number") param.maximum = s.maximum;
18995
+ if (typeof s.exclusiveMinimum === "number") param.exclusiveMinimum = s.exclusiveMinimum;
18996
+ if (typeof s.exclusiveMaximum === "number") param.exclusiveMaximum = s.exclusiveMaximum;
18997
+ if (typeof s.minLength === "number") param.minLength = s.minLength;
18998
+ if (typeof s.maxLength === "number") param.maxLength = s.maxLength;
18999
+ if (typeof s.pattern === "string") param.pattern = s.pattern;
19000
+ }
19001
+ result.push(param);
18961
19002
  }
18962
19003
  return result;
18963
19004
  }
18964
19005
  function getBodyInfo(operation) {
18965
19006
  const requestBody = operation.requestBody;
18966
19007
  if (requestBody === void 0) return void 0;
18967
- if (isRef3(requestBody)) return { typeName: void 0 };
19008
+ if (isRef3(requestBody)) {
19009
+ return { typeName: void 0, contentType: "application/json", isSynthesized: false };
19010
+ }
18968
19011
  const rb = requestBody;
18969
19012
  const content = rb.content;
18970
- if (content === void 0) return { typeName: void 0 };
19013
+ if (content === void 0) {
19014
+ return { typeName: void 0, contentType: "application/json", isSynthesized: false };
19015
+ }
18971
19016
  const jsonContent = content["application/json"];
18972
- if (jsonContent === void 0 || jsonContent.schema === void 0) return { typeName: void 0 };
18973
- const schema = jsonContent.schema;
18974
- if (isRef3(schema)) {
18975
- return { typeName: refToName(schema.$ref) };
19017
+ if (jsonContent !== void 0 && jsonContent.schema !== void 0) {
19018
+ const schema = jsonContent.schema;
19019
+ if (isRef3(schema)) {
19020
+ return {
19021
+ typeName: refToName(schema.$ref),
19022
+ contentType: "application/json",
19023
+ isSynthesized: false
19024
+ };
19025
+ }
19026
+ const operationId = operation.operationId;
19027
+ if (operationId !== void 0 && operationId.length > 0) {
19028
+ return { typeName: toTypeName(operationId), contentType: "application/json", isSynthesized: true };
19029
+ }
19030
+ return { typeName: void 0, contentType: "application/json", isSynthesized: false };
19031
+ }
19032
+ const formContent = content["application/x-www-form-urlencoded"];
19033
+ if (formContent !== void 0) {
19034
+ const schema = formContent.schema;
19035
+ if (schema !== void 0 && isRef3(schema)) {
19036
+ return {
19037
+ typeName: refToName(schema.$ref),
19038
+ contentType: "application/x-www-form-urlencoded",
19039
+ isSynthesized: false
19040
+ };
19041
+ }
19042
+ const operationId = operation.operationId;
19043
+ if (operationId !== void 0 && operationId.length > 0) {
19044
+ return {
19045
+ typeName: toTypeName(operationId),
19046
+ contentType: "application/x-www-form-urlencoded",
19047
+ isSynthesized: true
19048
+ };
19049
+ }
19050
+ return { typeName: void 0, contentType: "application/x-www-form-urlencoded", isSynthesized: false };
19051
+ }
19052
+ const multipartContent = content["multipart/form-data"];
19053
+ if (multipartContent !== void 0) {
19054
+ const schema = multipartContent.schema;
19055
+ if (schema !== void 0 && isRef3(schema)) {
19056
+ return {
19057
+ typeName: refToName(schema.$ref),
19058
+ contentType: "multipart/form-data",
19059
+ isSynthesized: false
19060
+ };
19061
+ }
19062
+ const operationId = operation.operationId;
19063
+ if (operationId !== void 0 && operationId.length > 0) {
19064
+ return {
19065
+ typeName: toTypeName(operationId),
19066
+ contentType: "multipart/form-data",
19067
+ isSynthesized: true
19068
+ };
19069
+ }
19070
+ return { typeName: void 0, contentType: "multipart/form-data", isSynthesized: false };
19071
+ }
19072
+ const octetContent = content["application/octet-stream"];
19073
+ if (octetContent !== void 0) {
19074
+ return { typeName: void 0, contentType: "application/octet-stream", isSynthesized: false };
18976
19075
  }
18977
- return { typeName: void 0 };
19076
+ return { typeName: void 0, contentType: "application/json", isSynthesized: false };
18978
19077
  }
18979
19078
 
18980
19079
  // src/plugins/service.ts
19080
+ function collectContentfulTwoxxCodes(responses) {
19081
+ return Object.keys(responses).filter((k) => /^2\d\d$/.test(k) && k !== "204").sort();
19082
+ }
18981
19083
  function getReturnInfo(operation) {
18982
19084
  const responses = operation.responses;
18983
19085
  if (responses === void 0) return { typeName: void 0, isArray: false, isVoid: true };
18984
- for (const code of ["200", "201"]) {
19086
+ const contentfulCodes = collectContentfulTwoxxCodes(responses);
19087
+ const isMultiStatus = contentfulCodes.length > 1;
19088
+ const twoxxCodes = ["200", "201", ...Object.keys(responses).filter(
19089
+ (k) => /^2\d\d$/.test(k) && k !== "200" && k !== "201" && k !== "204"
19090
+ )];
19091
+ for (const code of twoxxCodes) {
18985
19092
  const response = responses[code];
18986
19093
  if (response === void 0) continue;
18987
19094
  if (isRef3(response)) continue;
@@ -18989,28 +19096,37 @@ function getReturnInfo(operation) {
18989
19096
  const content = resp.content;
18990
19097
  if (content === void 0) continue;
18991
19098
  const jsonContent = content["application/json"];
18992
- if (jsonContent === void 0 || jsonContent.schema === void 0) continue;
18993
- const schema = jsonContent.schema;
18994
- if (isRef3(schema)) {
18995
- return {
18996
- typeName: refToName(schema.$ref),
18997
- isArray: false,
18998
- isVoid: false
18999
- };
19000
- }
19001
- const s = schema;
19002
- if (s.type === "array") {
19003
- const items = s.items;
19004
- if (items !== void 0 && isRef3(items)) {
19099
+ if (jsonContent !== void 0 && jsonContent.schema !== void 0) {
19100
+ const schema = jsonContent.schema;
19101
+ if (isRef3(schema)) {
19005
19102
  return {
19006
- typeName: refToName(items.$ref),
19007
- isArray: true,
19008
- isVoid: false
19103
+ typeName: refToName(schema.$ref),
19104
+ isArray: false,
19105
+ isVoid: false,
19106
+ isMultiStatus
19009
19107
  };
19010
19108
  }
19011
- return { typeName: void 0, isArray: true, isVoid: false };
19109
+ const s = schema;
19110
+ if (s.type === "array") {
19111
+ const items = s.items;
19112
+ if (items !== void 0 && isRef3(items)) {
19113
+ return {
19114
+ typeName: refToName(items.$ref),
19115
+ isArray: true,
19116
+ isVoid: false,
19117
+ isMultiStatus
19118
+ };
19119
+ }
19120
+ return { typeName: void 0, isArray: true, isVoid: false, isMultiStatus };
19121
+ }
19122
+ return { typeName: void 0, isArray: false, isVoid: false, isMultiStatus };
19123
+ }
19124
+ if (content["text/plain"] !== void 0) {
19125
+ return { typeName: void 0, isArray: false, isVoid: false, primitiveType: "string" };
19126
+ }
19127
+ if (content["application/octet-stream"] !== void 0) {
19128
+ return { typeName: void 0, isArray: false, isVoid: false, primitiveType: "Uint8Array" };
19012
19129
  }
19013
- return { typeName: void 0, isArray: false, isVoid: false };
19014
19130
  }
19015
19131
  if (responses["204"] !== void 0) {
19016
19132
  return { typeName: void 0, isArray: false, isVoid: true };
@@ -19019,6 +19135,16 @@ function getReturnInfo(operation) {
19019
19135
  }
19020
19136
  function buildReturnType(info) {
19021
19137
  if (info.isVoid) return "Promise<void>";
19138
+ if (info.primitiveType !== void 0) return `Promise<${info.primitiveType}>`;
19139
+ if (info.isMultiStatus === true) {
19140
+ let bodyType;
19141
+ if (info.typeName !== void 0) {
19142
+ bodyType = info.isArray ? `${info.typeName}[]` : info.typeName;
19143
+ } else {
19144
+ bodyType = info.isArray ? "unknown[]" : "unknown";
19145
+ }
19146
+ return `Promise<{ status: number; body: ${bodyType} }>`;
19147
+ }
19022
19148
  if (info.typeName !== void 0) {
19023
19149
  return info.isArray ? `Promise<${info.typeName}[]>` : `Promise<${info.typeName}>`;
19024
19150
  }
@@ -19037,6 +19163,11 @@ function collectOperations(spec) {
19037
19163
  const queryParams = getQueryParams(operation, spec);
19038
19164
  const bodyInfo = getBodyInfo(operation);
19039
19165
  const returnInfo = getReturnInfo(operation);
19166
+ if (returnInfo.typeName === void 0 && !returnInfo.isVoid && returnInfo.primitiveType === void 0) {
19167
+ console.warn(
19168
+ `${methodName} (${method.toUpperCase()} ${path}): response type is unknown, no named response schema could be resolved from the spec. Add a named $ref response schema to get a typed return type and enable runtime validation.`
19169
+ );
19170
+ }
19040
19171
  operations.push({
19041
19172
  methodName,
19042
19173
  httpMethod: method,
@@ -19050,13 +19181,13 @@ function collectOperations(spec) {
19050
19181
  }
19051
19182
  return operations;
19052
19183
  }
19053
- function buildMethodSignature(op) {
19184
+ function buildMethodSignature(op, options) {
19054
19185
  const args = [];
19055
19186
  for (const p of op.pathParams) {
19056
19187
  args.push(`${sanitizeOperationId2(p)}: string`);
19057
19188
  }
19058
19189
  if (op.bodyInfo !== void 0) {
19059
- const typeName = op.bodyInfo.typeName ?? "unknown";
19190
+ const typeName = op.bodyInfo.typeName !== void 0 && !op.bodyInfo.isSynthesized ? op.bodyInfo.typeName : "unknown";
19060
19191
  args.push(`body: ${typeName}`);
19061
19192
  }
19062
19193
  if (op.queryParams.length > 0) {
@@ -19065,16 +19196,19 @@ function buildMethodSignature(op) {
19065
19196
  const paramsToken = allOptional ? "params?" : "params";
19066
19197
  args.push(`${paramsToken}: { ${fields} }`);
19067
19198
  }
19199
+ if (options?.contextType !== void 0) {
19200
+ args.push("ctx: Ctx");
19201
+ }
19068
19202
  const returnType = buildReturnType(op.returnInfo);
19069
19203
  const argStr = args.join(", ");
19070
19204
  return `${op.methodName}(${argStr}): ${returnType}`;
19071
19205
  }
19072
- function generateService(spec) {
19206
+ function generateService(spec, options) {
19073
19207
  const serviceName = deriveServiceName(spec);
19074
19208
  const operations = collectOperations(spec);
19075
19209
  const importTypes = /* @__PURE__ */ new Set();
19076
19210
  for (const op of operations) {
19077
- if (op.bodyInfo?.typeName !== void 0) {
19211
+ if (op.bodyInfo?.typeName !== void 0 && !op.bodyInfo.isSynthesized) {
19078
19212
  importTypes.add(op.bodyInfo.typeName);
19079
19213
  }
19080
19214
  if (op.returnInfo.typeName !== void 0) {
@@ -19089,10 +19223,11 @@ function generateService(spec) {
19089
19223
  lines.push(`import type { ${sortedImports.join(", ")} } from './models.js'`);
19090
19224
  lines.push("");
19091
19225
  }
19092
- lines.push(`export interface ${serviceName} {`);
19226
+ const interfaceDecl = options?.contextType !== void 0 ? `export interface ${serviceName}<Ctx = never> {` : `export interface ${serviceName} {`;
19227
+ lines.push(interfaceDecl);
19093
19228
  for (const op of operations) {
19094
19229
  lines.push(` /** ${op.httpMethod.toUpperCase()} ${op.path} */`);
19095
- lines.push(` ${buildMethodSignature(op)}`);
19230
+ lines.push(` ${buildMethodSignature(op, options)}`);
19096
19231
  }
19097
19232
  lines.push("}");
19098
19233
  lines.push("");
@@ -19124,6 +19259,21 @@ function formatToZodModifier(format) {
19124
19259
  function pathParamZodExpr(schema) {
19125
19260
  if (schema === void 0 || isRef3(schema)) return void 0;
19126
19261
  const s = schema;
19262
+ if (s.type === "integer" || s.type === "number") {
19263
+ const hasMin = typeof s.minimum === "number";
19264
+ const hasMax = typeof s.maximum === "number";
19265
+ const hasExcMin = typeof s.exclusiveMinimum === "number";
19266
+ const hasExcMax = typeof s.exclusiveMaximum === "number";
19267
+ if (hasMin || hasMax || hasExcMin || hasExcMax) {
19268
+ let expr = "z.coerce.number()";
19269
+ if (hasMin) expr += `.min(${s.minimum})`;
19270
+ if (hasMax) expr += `.max(${s.maximum})`;
19271
+ if (hasExcMin) expr += `.gt(${s.exclusiveMinimum})`;
19272
+ if (hasExcMax) expr += `.lt(${s.exclusiveMaximum})`;
19273
+ return expr;
19274
+ }
19275
+ return void 0;
19276
+ }
19127
19277
  if (s.type !== "string") return void 0;
19128
19278
  const format = s.format;
19129
19279
  if (format === void 0) return void 0;
@@ -19131,23 +19281,64 @@ function pathParamZodExpr(schema) {
19131
19281
  if (modifier === "") return void 0;
19132
19282
  return `z.string()${modifier}`;
19133
19283
  }
19134
- function paramZodExpr(tsType, required, schema) {
19284
+ function queryParamDelimitedZodBase(_param) {
19285
+ return "z.array(z.string())";
19286
+ }
19287
+ function queryParamDeepObjectZodBase(param) {
19288
+ const propFields = (param.deepObjectProperties ?? []).map((p) => {
19289
+ const coerced = p.tsType === "number" ? "z.coerce.number()" : "z.string()";
19290
+ return `${p.key}: ${coerced}.optional()`;
19291
+ });
19292
+ return `z.object({ ${propFields.join(", ")} })`;
19293
+ }
19294
+ function queryParamNumberZodBase(param) {
19295
+ let base = "z.coerce.number()";
19296
+ if (param.minimum !== void 0) base += `.min(${param.minimum})`;
19297
+ if (param.maximum !== void 0) base += `.max(${param.maximum})`;
19298
+ if (param.exclusiveMinimum !== void 0) base += `.gt(${param.exclusiveMinimum})`;
19299
+ if (param.exclusiveMaximum !== void 0) base += `.lt(${param.exclusiveMaximum})`;
19300
+ return base;
19301
+ }
19302
+ function queryParamStringZodBase(param) {
19303
+ let base;
19304
+ if (param.enum !== void 0 && param.enum.length > 0) {
19305
+ const members = param.enum.map((v) => JSON.stringify(v)).join(", ");
19306
+ base = `z.enum([${members}])`;
19307
+ } else {
19308
+ base = "z.string()";
19309
+ }
19310
+ if (param.minLength !== void 0) base += `.min(${param.minLength})`;
19311
+ if (param.maxLength !== void 0) base += `.max(${param.maxLength})`;
19312
+ if (param.pattern !== void 0) base += `.regex(/${param.pattern}/)`;
19313
+ return base;
19314
+ }
19315
+ function queryParamZodExpr(param) {
19135
19316
  let base;
19136
- if (tsType === "number") {
19137
- base = "z.number()";
19138
- } else if (tsType === "boolean") {
19317
+ if (param.delimiterStyle !== void 0) {
19318
+ base = queryParamDelimitedZodBase(param);
19319
+ } else if (param.isDeepObject === true && param.deepObjectProperties !== void 0) {
19320
+ base = queryParamDeepObjectZodBase(param);
19321
+ } else if (param.tsType === "number") {
19322
+ base = queryParamNumberZodBase(param);
19323
+ } else if (param.tsType === "boolean") {
19139
19324
  base = "z.boolean()";
19140
19325
  } else {
19141
- if (schema !== void 0 && !isRef3(schema)) {
19142
- const s = schema;
19143
- const format = s.format;
19144
- const modifier = format !== void 0 ? formatToZodModifier(format) : "";
19145
- base = `z.string()${modifier}`;
19146
- } else {
19147
- base = "z.string()";
19148
- }
19326
+ base = queryParamStringZodBase(param);
19149
19327
  }
19150
- return required ? base : `${base}.optional()`;
19328
+ return param.required ? base : `${base}.optional()`;
19329
+ }
19330
+ function headerParamZodExpr(param) {
19331
+ let base;
19332
+ if (param.enum !== void 0 && param.enum.length > 0) {
19333
+ const members = param.enum.map((v) => JSON.stringify(v)).join(", ");
19334
+ base = `z.enum([${members}])`;
19335
+ } else {
19336
+ base = "z.string()";
19337
+ }
19338
+ if (param.minLength !== void 0) base += `.min(${param.minLength})`;
19339
+ if (param.maxLength !== void 0) base += `.max(${param.maxLength})`;
19340
+ if (param.pattern !== void 0) base += `.regex(/${param.pattern}/)`;
19341
+ return param.required ? base : `${base}.optional()`;
19151
19342
  }
19152
19343
  function getPathParamValidations(operation, spec, rawPathParamNames) {
19153
19344
  const parameters = operation.parameters;
@@ -19167,6 +19358,19 @@ function getPathParamValidations(operation, spec, rawPathParamNames) {
19167
19358
  }
19168
19359
  return result;
19169
19360
  }
19361
+ function cookieParamZodExpr(param) {
19362
+ let base;
19363
+ if (param.enum !== void 0 && param.enum.length > 0) {
19364
+ const members = param.enum.map((v) => JSON.stringify(v)).join(", ");
19365
+ base = `z.enum([${members}])`;
19366
+ } else {
19367
+ base = "z.string()";
19368
+ }
19369
+ if (param.minLength !== void 0) base += `.min(${param.minLength})`;
19370
+ if (param.maxLength !== void 0) base += `.max(${param.maxLength})`;
19371
+ if (param.pattern !== void 0) base += `.regex(/${param.pattern}/)`;
19372
+ return param.required ? base : `${base}.optional()`;
19373
+ }
19170
19374
  function getHeaderParams(operation, spec) {
19171
19375
  const parameters = operation.parameters;
19172
19376
  if (parameters === void 0) return [];
@@ -19174,21 +19378,74 @@ function getHeaderParams(operation, spec) {
19174
19378
  for (const p of parameters) {
19175
19379
  const resolved = resolveParam(p, spec);
19176
19380
  if (resolved === void 0 || resolved.in !== "header") continue;
19177
- result.push({
19381
+ const param = {
19178
19382
  rawName: resolved.name,
19179
19383
  required: resolved.required === true
19180
- });
19384
+ };
19385
+ const schema = resolved.schema;
19386
+ if (schema !== void 0 && !isRef3(schema)) {
19387
+ const s = schema;
19388
+ if (Array.isArray(s.enum)) param.enum = s.enum;
19389
+ if (typeof s.minLength === "number") param.minLength = s.minLength;
19390
+ if (typeof s.maxLength === "number") param.maxLength = s.maxLength;
19391
+ if (typeof s.pattern === "string") param.pattern = s.pattern;
19392
+ }
19393
+ result.push(param);
19181
19394
  }
19182
19395
  return result;
19183
19396
  }
19397
+ function getCookieParams(operation, spec) {
19398
+ const parameters = operation.parameters;
19399
+ if (parameters === void 0) return [];
19400
+ const result = [];
19401
+ for (const p of parameters) {
19402
+ const resolved = resolveParam(p, spec);
19403
+ if (resolved === void 0 || resolved.in !== "cookie") continue;
19404
+ const param = {
19405
+ rawName: resolved.name,
19406
+ required: resolved.required === true
19407
+ };
19408
+ const schema = resolved.schema;
19409
+ if (schema !== void 0 && !isRef3(schema)) {
19410
+ const s = schema;
19411
+ if (Array.isArray(s.enum)) param.enum = s.enum;
19412
+ if (typeof s.minLength === "number") param.minLength = s.minLength;
19413
+ if (typeof s.maxLength === "number") param.maxLength = s.maxLength;
19414
+ if (typeof s.pattern === "string") param.pattern = s.pattern;
19415
+ }
19416
+ result.push(param);
19417
+ }
19418
+ return result;
19419
+ }
19420
+ function queryParamHasConstraints(q) {
19421
+ const constraintFields = [
19422
+ q.enum,
19423
+ q.minimum,
19424
+ q.maximum,
19425
+ q.exclusiveMinimum,
19426
+ q.exclusiveMaximum,
19427
+ q.minLength,
19428
+ q.maxLength,
19429
+ q.pattern,
19430
+ q.delimiterStyle
19431
+ ];
19432
+ return constraintFields.some((f) => f !== void 0) || q.isDeepObject === true;
19433
+ }
19184
19434
  function queryParamsNeedValidation(queryParams) {
19185
- return queryParams.some((q) => q.required || q.tsType !== "string");
19435
+ return queryParams.some(
19436
+ (q) => q.required || q.tsType !== "string" || queryParamHasConstraints(q)
19437
+ );
19438
+ }
19439
+ function delimiterChar(style) {
19440
+ if (style === "ssv") return " ";
19441
+ if (style === "psv") return "|";
19442
+ return ",";
19186
19443
  }
19187
19444
  function emitQueryValidation(lines, queryParams, indent) {
19188
19445
  const inner = `${indent} `;
19189
19446
  const fieldIndent = `${indent} `;
19190
19447
  const fields = queryParams.map((q) => {
19191
- const expr = paramZodExpr(q.tsType, q.required);
19448
+ const expr = queryParamZodExpr(q);
19192
19449
  return `${fieldIndent}${q.name}: ${expr}`;
19193
19450
  }).join(",\n");
19194
19451
  lines.push(`${inner}// Validate query parameters: returns 422 with Zod issues on failure`);
@@ -19209,7 +19466,7 @@ function emitPathValidation(lines, validations, indent, framework) {
19209
19466
  if (framework === "hono") {
19210
19467
  access = `c.req.param(${JSON.stringify(v.rawName)})`;
19211
19468
  } else if (framework === "express") {
19212
- access = `req.params[${JSON.stringify(v.rawName)}]`;
19469
+ access = `req.params[${JSON.stringify(v.rawName)}] as string`;
19213
19470
  } else {
19214
19471
  access = /[^a-zA-Z0-9_$]/.test(v.rawName) ? `req.params[${JSON.stringify(v.rawName)}]` : `req.params.${v.rawName}`;
19215
19472
  }
@@ -19227,18 +19484,19 @@ function emitHeaderValidation(lines, headerParams, indent, framework) {
19227
19484
  const fieldIndent = `${indent} `;
19228
19485
  const schemaFields = headerParams.map((h) => {
19229
19486
  const key = JSON.stringify(h.rawName);
19230
- const expr = h.required ? "z.string()" : "z.string().optional()";
19487
+ const expr = headerParamZodExpr(h);
19231
19488
  return `${fieldIndent}${key}: ${expr}`;
19232
19489
  }).join(",\n");
19233
19490
  const rawFields = headerParams.map((h) => {
19234
19491
  const key = JSON.stringify(h.rawName);
19492
+ const lookupKey = JSON.stringify(h.rawName.toLowerCase());
19235
19493
  let access;
19236
19494
  if (framework === "hono") {
19237
19495
  access = `c.req.header(${key})`;
19238
19496
  } else if (framework === "express") {
19239
- access = `req.headers[${key}] as string | undefined`;
19497
+ access = `req.headers[${lookupKey}] as string | undefined`;
19240
19498
  } else {
19241
- access = `req.headers[${key}]`;
19499
+ access = `req.headers[${lookupKey}]`;
19242
19500
  }
19243
19501
  return `${fieldIndent}${key}: ${access}`;
19244
19502
  }).join(",\n");
@@ -19249,24 +19507,124 @@ function emitHeaderValidation(lines, headerParams, indent, framework) {
19249
19507
  lines.push(rawFields);
19250
19508
  lines.push(`${inner}})`);
19251
19509
  }
19510
+ function emitCookieValidation(lines, cookieParams, indent, framework) {
19511
+ const inner = `${indent} `;
19512
+ const fieldIndent = `${indent} `;
19513
+ const schemaFields = cookieParams.map((ck) => {
19514
+ const key = JSON.stringify(ck.rawName);
19515
+ const expr = cookieParamZodExpr(ck);
19516
+ return `${fieldIndent}${key}: ${expr}`;
19517
+ }).join(",\n");
19518
+ const rawFields = cookieParams.map((ck) => {
19519
+ const key = JSON.stringify(ck.rawName);
19520
+ let access;
19521
+ if (framework === "hono") {
19522
+ access = `getCookie(c, ${key})`;
19523
+ } else if (framework === "express") {
19524
+ access = `req.cookies[${key}] as string | undefined`;
19525
+ } else {
19526
+ access = `req.cookies[${key}]`;
19527
+ }
19528
+ return `${fieldIndent}${key}: ${access}`;
19529
+ }).join(",\n");
19530
+ lines.push(`${inner}// Validate request cookies: returns 422 with Zod issues on failure`);
19531
+ lines.push(`${inner}const _ckv = z.object({`);
19532
+ lines.push(schemaFields);
19533
+ lines.push(`${inner}}).safeParse({`);
19534
+ lines.push(rawFields);
19535
+ lines.push(`${inner}})`);
19536
+ }
19252
19537
  function response200IsVoid(resp) {
19253
19538
  if (isRef3(resp)) return false;
19254
19539
  const r = resp;
19255
19540
  const content = r.content;
19256
19541
  return content === void 0 || Object.keys(content).length === 0;
19257
19542
  }
19543
+ function detectResponseContentType(resp) {
19544
+ if (isRef3(resp)) return "application/json";
19545
+ const r = resp;
19546
+ const content = r.content;
19547
+ if (content === void 0) return "application/json";
19548
+ if ("text/plain" in content) return "text/plain";
19549
+ if ("application/octet-stream" in content) return "application/octet-stream";
19550
+ return "application/json";
19551
+ }
19258
19552
  function getResponseStatus(operation, httpMethod) {
19259
19553
  const responses = operation.responses;
19260
19554
  if (responses === void 0) {
19261
- return httpMethod === "delete" ? { status: 204, isVoid: true } : { status: 200, isVoid: false };
19555
+ return httpMethod === "delete" ? { status: 204, isVoid: true, responseContentType: "application/json" } : { status: 200, isVoid: false, responseContentType: "application/json" };
19556
+ }
19557
+ const contentfulTwoxxKeys = Object.keys(responses).filter((k) => /^2\d\d$/.test(k) && k !== "204").sort();
19558
+ if (contentfulTwoxxKeys.length > 1) {
19559
+ return {
19560
+ status: 200,
19561
+ isVoid: false,
19562
+ responseContentType: "application/json",
19563
+ isMultiStatus: true
19564
+ };
19565
+ }
19566
+ if (responses["201"] !== void 0) {
19567
+ return {
19568
+ status: 201,
19569
+ isVoid: false,
19570
+ responseContentType: detectResponseContentType(responses["201"])
19571
+ };
19572
+ }
19573
+ if (responses["204"] !== void 0) {
19574
+ return { status: 204, isVoid: true, responseContentType: "application/json" };
19262
19575
  }
19263
- if (responses["201"] !== void 0) return { status: 201, isVoid: false };
19264
- if (responses["204"] !== void 0) return { status: 204, isVoid: true };
19265
19576
  if (responses["200"] !== void 0) {
19266
- if (response200IsVoid(responses["200"])) return { status: 204, isVoid: true };
19267
- return { status: 200, isVoid: false };
19577
+ if (response200IsVoid(responses["200"])) {
19578
+ return { status: 204, isVoid: true, responseContentType: "application/json" };
19579
+ }
19580
+ return {
19581
+ status: 200,
19582
+ isVoid: false,
19583
+ responseContentType: detectResponseContentType(responses["200"])
19584
+ };
19268
19585
  }
19269
- return httpMethod === "delete" ? { status: 204, isVoid: true } : { status: 200, isVoid: false };
19586
+ const twoxxKeys = Object.keys(responses).filter(
19587
+ (k) => /^2\d\d$/.test(k) && k !== "200" && k !== "201" && k !== "204"
19588
+ );
19589
+ if (twoxxKeys.length === 1) {
19590
+ const code = parseInt(twoxxKeys[0], 10);
19591
+ const resp = responses[twoxxKeys[0]];
19592
+ const isVoid = isRef3(resp) ? false : (() => {
19593
+ const r = resp;
19594
+ const content = r.content;
19595
+ return content === void 0 || Object.keys(content).length === 0;
19596
+ })();
19597
+ return { status: code, isVoid, responseContentType: detectResponseContentType(resp) };
19598
+ }
19599
+ return httpMethod === "delete" ? { status: 204, isVoid: true, responseContentType: "application/json" } : { status: 200, isVoid: false, responseContentType: "application/json" };
19600
+ }
19601
+ function getResponseTypeName(operation) {
19602
+ const responses = operation.responses;
19603
+ if (responses === void 0) return void 0;
19604
+ const priority = ["200", "201", ...Object.keys(responses).filter(
19605
+ (k) => /^2\d\d$/.test(k) && k !== "200" && k !== "201" && k !== "204"
19606
+ )];
19607
+ for (const code of priority) {
19608
+ const response = responses[code];
19609
+ if (response === void 0 || isRef3(response)) continue;
19610
+ const resp = response;
19611
+ const content = resp.content;
19612
+ if (content === void 0) continue;
19613
+ const jsonContent = content["application/json"];
19614
+ if (jsonContent === void 0 || jsonContent.schema === void 0) continue;
19615
+ const schema = jsonContent.schema;
19616
+ if (isRef3(schema)) {
19617
+ return { typeName: refToName(schema.$ref), isArray: false };
19618
+ }
19619
+ const s = schema;
19620
+ if (s.type === "array" && s.items !== void 0 && isRef3(s.items)) {
19621
+ return {
19622
+ typeName: refToName(s.items.$ref),
19623
+ isArray: true
19624
+ };
19625
+ }
19626
+ }
19627
+ return void 0;
19270
19628
  }
19271
19629
  function collectOperations2(spec) {
19272
19630
  const paths = spec.paths;
@@ -19281,8 +19639,10 @@ function collectOperations2(spec) {
19281
19639
  const pathParamValidations = getPathParamValidations(operation, spec, pathParams);
19282
19640
  const queryParams = getQueryParams(operation, spec);
19283
19641
  const headerParams = getHeaderParams(operation, spec);
19642
+ const cookieParams = getCookieParams(operation, spec);
19284
19643
  const bodyInfo = getBodyInfo(operation);
19285
19644
  const responseStatus = getResponseStatus(operation, method);
19645
+ const responseTypeInfo = getResponseTypeName(operation);
19286
19646
  operations.push({
19287
19647
  methodName,
19288
19648
  httpMethod: method,
@@ -19292,8 +19652,11 @@ function collectOperations2(spec) {
19292
19652
  pathParamValidations,
19293
19653
  queryParams,
19294
19654
  headerParams,
19655
+ cookieParams,
19295
19656
  bodyInfo,
19296
- responseStatus
19657
+ responseStatus,
19658
+ responseTypeName: responseTypeInfo?.typeName,
19659
+ responseIsArray: responseTypeInfo?.isArray
19297
19660
  });
19298
19661
  }
19299
19662
  }
@@ -19302,7 +19665,9 @@ function collectOperations2(spec) {
19302
19665
  function collectSortedBodyTypes(operations) {
19303
19666
  const bodyTypes = /* @__PURE__ */ new Set();
19304
19667
  for (const op of operations) {
19305
- if (op.bodyInfo?.typeName !== void 0) bodyTypes.add(op.bodyInfo.typeName);
19668
+ if (op.bodyInfo?.typeName !== void 0 && !op.bodyInfo.isSynthesized) {
19669
+ bodyTypes.add(op.bodyInfo.typeName);
19670
+ }
19306
19671
  }
19307
19672
  return Array.from(bodyTypes).sort();
19308
19673
  }
@@ -19316,13 +19681,27 @@ function collectUsedSchemaNames(operations, schemaNames) {
19316
19681
  }
19317
19682
  return used;
19318
19683
  }
19684
+ function collectUsedResponseSchemaNames(operations, schemaNames) {
19685
+ const used = /* @__PURE__ */ new Set();
19686
+ for (const op of operations) {
19687
+ if (op.responseTypeName === void 0) continue;
19688
+ if (op.responseStatus.isMultiStatus === true) continue;
19689
+ const schemaName = `${op.responseTypeName}Schema`;
19690
+ if (schemaNames.has(schemaName)) used.add(schemaName);
19691
+ }
19692
+ return used;
19693
+ }
19319
19694
  function collectGeneratorSetup(operations, options) {
19320
19695
  const sortedBodyTypes = collectSortedBodyTypes(operations);
19321
19696
  const usedSchemaNames = options?.schemaNames !== void 0 ? collectUsedSchemaNames(operations, options.schemaNames) : /* @__PURE__ */ new Set();
19322
- const needsZod = usedSchemaNames.size > 0 && options?.schemaImportPath !== void 0 || operationsNeedZodForParams(operations);
19323
- return { sortedBodyTypes, usedSchemaNames, needsZod };
19697
+ const usedResponseSchemaNames = options?.schemaNames !== void 0 ? collectUsedResponseSchemaNames(operations, options.schemaNames) : /* @__PURE__ */ new Set();
19698
+ const hasArrayResponseSchema = usedResponseSchemaNames.size > 0 && operations.some(
19699
+ (op) => op.responseIsArray === true && op.responseTypeName !== void 0 && usedResponseSchemaNames.has(`${op.responseTypeName}Schema`)
19700
+ );
19701
+ const needsZod = usedSchemaNames.size > 0 && options?.schemaImportPath !== void 0 || operationsNeedZodForParams(operations) || usedResponseSchemaNames.size > 0 && options?.schemaImportPath !== void 0 && hasArrayResponseSchema;
19702
+ return { sortedBodyTypes, usedSchemaNames, usedResponseSchemaNames, needsZod };
19324
19703
  }
19325
- function buildRouteHandler(op, indent, schemaNames) {
19704
+ function buildRouteHandler(op, indent, schemaNames, contextType) {
19326
19705
  const lines = [];
19327
19706
  lines.push(`${indent}app.${op.httpMethod}(${JSON.stringify(op.honoPath)}, async (c) => {`);
19328
19707
  if (op.pathParamValidations.length > 0) {
@@ -19334,7 +19713,27 @@ function buildRouteHandler(op, indent, schemaNames) {
19334
19713
  lines.push(`${indent} }`);
19335
19714
  }
19336
19715
  if (op.queryParams.length > 0) {
19716
+ const deepObjectParams = op.queryParams.filter((q) => q.isDeepObject === true);
19717
+ if (deepObjectParams.length > 0) {
19718
+ lines.push(`${indent} const _dq = c.req.queries()`);
19719
+ for (const q of deepObjectParams) {
19720
+ const prefixLen = q.rawName.length + 1;
19721
+ const bracketPrefix = q.rawName + "[";
19722
+ lines.push(`${indent} const ${q.name} = Object.fromEntries(`);
19723
+ lines.push(
19724
+ `${indent} Object.entries(_dq).filter(([k]) => k.startsWith('${bracketPrefix}') && k.endsWith(']')).map(([k, vs]) => [k.slice(${prefixLen}, -1), vs[0]])`
19725
+ );
19726
+ lines.push(`${indent} )`);
19727
+ }
19728
+ }
19337
19729
  const fields = op.queryParams.map((q) => {
19730
+ if (q.isDeepObject === true) {
19731
+ return ` ${q.name}`;
19732
+ }
19733
+ if (q.delimiterStyle !== void 0) {
19734
+ const delim = JSON.stringify(delimiterChar(q.delimiterStyle));
19735
+ return ` ${q.name}: c.req.query('${q.rawName}') !== undefined ? c.req.query('${q.rawName}')!.split(${delim}) : undefined`;
19736
+ }
19338
19737
  if (q.tsType === "number") {
19339
19738
  return ` ${q.name}: c.req.query('${q.name}') !== undefined ? Number(c.req.query('${q.name}')) : undefined`;
19340
19739
  }
@@ -19360,10 +19759,42 @@ function buildRouteHandler(op, indent, schemaNames) {
19360
19759
  );
19361
19760
  lines.push(`${indent} }`);
19362
19761
  }
19762
+ if (op.cookieParams.length > 0) {
19763
+ emitCookieValidation(lines, op.cookieParams, indent, "hono");
19764
+ lines.push(`${indent} if (!_ckv.success) {`);
19765
+ lines.push(
19766
+ `${indent} return c.json({ error: 'Invalid request cookies', issues: _ckv.error.issues }, 422)`
19767
+ );
19768
+ lines.push(`${indent} }`);
19769
+ }
19363
19770
  let bodyVarName = "body";
19364
19771
  if (op.bodyInfo !== void 0) {
19365
- const typeAnnotation = op.bodyInfo.typeName !== void 0 ? `<${op.bodyInfo.typeName}>` : "";
19366
- lines.push(`${indent} const body = await c.req.json${typeAnnotation}()`);
19772
+ const typeDecl = op.bodyInfo.typeName !== void 0 && !op.bodyInfo.isSynthesized ? op.bodyInfo.typeName : "unknown";
19773
+ if (op.bodyInfo.contentType === "application/x-www-form-urlencoded") {
19774
+ lines.push(`${indent} const _ct = c.req.header('content-type') ?? ''`);
19775
+ lines.push(
19776
+ `${indent} if (!_ct.toLowerCase().startsWith('application/x-www-form-urlencoded')) {`
19777
+ );
19778
+ lines.push(`${indent} return c.json({ error: 'Unsupported Media Type' }, 415)`);
19779
+ lines.push(`${indent} }`);
19780
+ lines.push(`${indent} const body: unknown = await c.req.parseBody()`);
19781
+ } else if (op.bodyInfo.contentType === "multipart/form-data") {
19782
+ lines.push(
19783
+ `${indent} // multipart/form-data: parseBody({ all: true }) collects repeated keys into arrays.`
19784
+ );
19785
+ lines.push(`${indent} const body: unknown = await c.req.parseBody({ all: true })`);
19786
+ } else {
19787
+ lines.push(`${indent} const _ct = c.req.header('content-type') ?? ''`);
19788
+ lines.push(`${indent} if (!_ct.toLowerCase().startsWith('application/json')) {`);
19789
+ lines.push(`${indent} return c.json({ error: 'Unsupported Media Type' }, 415)`);
19790
+ lines.push(`${indent} }`);
19791
+ lines.push(`${indent} let body: ${typeDecl}`);
19792
+ lines.push(`${indent} try {`);
19793
+ lines.push(`${indent} body = JSON.parse(await c.req.text()) as ${typeDecl}`);
19794
+ lines.push(`${indent} } catch {`);
19795
+ lines.push(`${indent} return c.json({ error: 'Invalid JSON body' }, 400)`);
19796
+ lines.push(`${indent} }`);
19797
+ }
19367
19798
  const schemaName = op.bodyInfo.typeName !== void 0 ? `${op.bodyInfo.typeName}Schema` : void 0;
19368
19799
  if (schemaName !== void 0 && schemaNames !== void 0 && schemaNames.has(schemaName)) {
19369
19800
  lines.push(`${indent} // Validate request body: returns 422 with Zod issues on failure`);
@@ -19373,7 +19804,7 @@ function buildRouteHandler(op, indent, schemaNames) {
19373
19804
  `${indent} return c.json({ error: 'Invalid request body', issues: parseResult.error.issues }, 422)`
19374
19805
  );
19375
19806
  lines.push(`${indent} }`);
19376
- lines.push(`${indent} const validatedBody = parseResult.data`);
19807
+ lines.push(`${indent} const validatedBody = parseResult.data as ${typeDecl}`);
19377
19808
  bodyVarName = "validatedBody";
19378
19809
  }
19379
19810
  }
@@ -19385,21 +19816,54 @@ function buildRouteHandler(op, indent, schemaNames) {
19385
19816
  serviceArgs.push(bodyVarName);
19386
19817
  }
19387
19818
  if (op.queryParams.length > 0) {
19388
- serviceArgs.push("params");
19819
+ serviceArgs.push(queryParamsNeedValidation(op.queryParams) ? "_qv.data" : "params");
19820
+ }
19821
+ if (contextType !== void 0) {
19822
+ serviceArgs.push("c");
19389
19823
  }
19390
19824
  const serviceCall = `service.${op.methodName}(${serviceArgs.join(", ")})`;
19825
+ lines.push(`${indent} try {`);
19391
19826
  if (op.responseStatus.isVoid) {
19392
- lines.push(`${indent} await ${serviceCall}`);
19393
- lines.push(`${indent} return new Response(null, { status: ${op.responseStatus.status} })`);
19394
- } else if (op.responseStatus.status === 201) {
19395
- lines.push(`${indent} return c.json(await ${serviceCall}, 201)`);
19827
+ lines.push(`${indent} await ${serviceCall}`);
19828
+ lines.push(`${indent} return new Response(null, { status: ${op.responseStatus.status} })`);
19829
+ } else if (op.responseStatus.isMultiStatus === true) {
19830
+ lines.push(`${indent} const _envelope = await ${serviceCall}`);
19831
+ lines.push(`${indent} return c.json(_envelope.body, _envelope.status as any)`);
19832
+ } else if (op.responseStatus.responseContentType === "text/plain") {
19833
+ if (op.responseStatus.status === 200) {
19834
+ lines.push(`${indent} return c.text(await ${serviceCall})`);
19835
+ } else {
19836
+ lines.push(`${indent} return c.text(await ${serviceCall}, ${op.responseStatus.status})`);
19837
+ }
19838
+ } else if (op.responseStatus.responseContentType === "application/octet-stream") {
19839
+ if (op.responseStatus.status === 200) {
19840
+ lines.push(`${indent} const _result = await ${serviceCall}`);
19841
+ lines.push(
19842
+ `${indent} return new Response(_result, { headers: { 'content-type': 'application/octet-stream' } })`
19843
+ );
19844
+ } else {
19845
+ lines.push(`${indent} const _result = await ${serviceCall}`);
19846
+ lines.push(
19847
+ `${indent} return new Response(_result, { status: ${op.responseStatus.status}, headers: { 'content-type': 'application/octet-stream' } })`
19848
+ );
19849
+ }
19850
+ } else if (op.responseStatus.status === 200) {
19851
+ lines.push(`${indent} return c.json(await ${serviceCall})`);
19396
19852
  } else {
19397
- lines.push(`${indent} return c.json(await ${serviceCall})`);
19853
+ lines.push(`${indent} return c.json(await ${serviceCall}, ${op.responseStatus.status})`);
19398
19854
  }
19855
+ lines.push(`${indent} } catch (err) {`);
19856
+ lines.push(`${indent} if (err instanceof HttpError) {`);
19857
+ lines.push(
19858
+ `${indent} return new Response(JSON.stringify({ error: err.message }), { status: err.status, headers: { 'content-type': 'application/json' } })`
19859
+ );
19860
+ lines.push(`${indent} }`);
19861
+ lines.push(`${indent} throw err`);
19862
+ lines.push(`${indent} }`);
19399
19863
  lines.push(`${indent}})`);
19400
19864
  return lines.join("\n");
19401
19865
  }
19402
- function buildExpressRouteHandler(op, indent, schemaNames) {
19866
+ function buildExpressRouteHandler(op, indent, schemaNames, contextType) {
19403
19867
  const lines = [];
19404
19868
  lines.push(
19405
19869
  `${indent}router.${op.httpMethod}(${JSON.stringify(op.honoPath)}, async (req: Request, res: Response) => {`
@@ -19414,6 +19878,13 @@ function buildExpressRouteHandler(op, indent, schemaNames) {
19414
19878
  }
19415
19879
  if (op.queryParams.length > 0) {
19416
19880
  const fields = op.queryParams.map((q) => {
19881
+ if (q.isDeepObject === true) {
19882
+ return ` ${q.name}: (req.query['${q.rawName}'] ?? {}) as Record<string, string | undefined>`;
19883
+ }
19884
+ if (q.delimiterStyle !== void 0) {
19885
+ const delim = JSON.stringify(delimiterChar(q.delimiterStyle));
19886
+ return ` ${q.name}: typeof req.query['${q.rawName}'] === 'string' ? (req.query['${q.rawName}'] as string).split(${delim}) : undefined`;
19887
+ }
19417
19888
  if (q.tsType === "number") {
19418
19889
  return ` ${q.name}: Number(req.query['${q.name}'] as string)`;
19419
19890
  }
@@ -19442,59 +19913,133 @@ function buildExpressRouteHandler(op, indent, schemaNames) {
19442
19913
  );
19443
19914
  lines.push(`${indent} }`);
19444
19915
  }
19916
+ if (op.cookieParams.length > 0) {
19917
+ emitCookieValidation(lines, op.cookieParams, indent, "express");
19918
+ lines.push(`${indent} if (!_ckv.success) {`);
19919
+ lines.push(
19920
+ `${indent} return void res.status(422).json({ error: 'Invalid request cookies', issues: _ckv.error.issues })`
19921
+ );
19922
+ lines.push(`${indent} }`);
19923
+ }
19445
19924
  let bodyVarName = "body";
19446
19925
  if (op.bodyInfo !== void 0) {
19447
- const schemaName = op.bodyInfo.typeName !== void 0 ? `${op.bodyInfo.typeName}Schema` : void 0;
19448
- const useZod = schemaName !== void 0 && schemaNames !== void 0 && schemaNames.has(schemaName);
19449
- if (useZod) {
19450
- lines.push(`${indent} // Validate request body: returns 422 with Zod issues on failure`);
19451
- lines.push(`${indent} const parseResult = ${schemaName}.safeParse(req.body)`);
19452
- lines.push(`${indent} if (!parseResult.success) {`);
19926
+ if (op.bodyInfo.contentType === "multipart/form-data") {
19453
19927
  lines.push(
19454
- `${indent} return void res.status(422).json({ error: 'Invalid request body', issues: parseResult.error.issues })`
19928
+ `${indent} // multipart/form-data: assumes multer middleware has populated req.files + req.body.`
19455
19929
  );
19456
- lines.push(`${indent} }`);
19457
- lines.push(`${indent} const validatedBody = parseResult.data`);
19458
- bodyVarName = "validatedBody";
19930
+ lines.push(`${indent} const body = { ...req.body, ...(req as any).files } as unknown`);
19459
19931
  } else {
19460
- const typeAnnotation = op.bodyInfo.typeName !== void 0 ? ` as ${op.bodyInfo.typeName}` : "";
19461
- lines.push(`${indent} const body = req.body${typeAnnotation}`);
19932
+ const schemaName = op.bodyInfo.typeName !== void 0 ? `${op.bodyInfo.typeName}Schema` : void 0;
19933
+ const useZod = schemaName !== void 0 && schemaNames !== void 0 && schemaNames.has(schemaName);
19934
+ if (useZod) {
19935
+ lines.push(`${indent} // Validate request body: returns 422 with Zod issues on failure`);
19936
+ lines.push(`${indent} const parseResult = ${schemaName}.safeParse(req.body)`);
19937
+ lines.push(`${indent} if (!parseResult.success) {`);
19938
+ lines.push(
19939
+ `${indent} return void res.status(422).json({ error: 'Invalid request body', issues: parseResult.error.issues })`
19940
+ );
19941
+ lines.push(`${indent} }`);
19942
+ const typeDecl = op.bodyInfo.typeName !== void 0 && !op.bodyInfo.isSynthesized ? op.bodyInfo.typeName : "unknown";
19943
+ lines.push(`${indent} const validatedBody = parseResult.data as ${typeDecl}`);
19944
+ bodyVarName = "validatedBody";
19945
+ } else {
19946
+ const typeAnnotation = op.bodyInfo.typeName !== void 0 && !op.bodyInfo.isSynthesized ? ` as ${op.bodyInfo.typeName}` : "";
19947
+ lines.push(`${indent} const body = req.body${typeAnnotation}`);
19948
+ }
19462
19949
  }
19463
19950
  }
19464
19951
  const serviceArgs = [];
19465
19952
  for (const p of op.pathParams) {
19466
- serviceArgs.push(`req.params['${p}']!`);
19953
+ serviceArgs.push(`(req.params['${p}'] as string)`);
19467
19954
  }
19468
19955
  if (op.bodyInfo !== void 0) {
19469
19956
  serviceArgs.push(bodyVarName);
19470
19957
  }
19471
19958
  if (op.queryParams.length > 0) {
19472
- serviceArgs.push("params");
19959
+ serviceArgs.push(queryParamsNeedValidation(op.queryParams) ? "_qv.data" : "params");
19960
+ }
19961
+ if (contextType !== void 0) {
19962
+ serviceArgs.push("req");
19473
19963
  }
19474
19964
  const serviceCall = `service.${op.methodName}(${serviceArgs.join(", ")})`;
19965
+ lines.push(`${indent} try {`);
19475
19966
  if (op.responseStatus.isVoid) {
19476
- lines.push(`${indent} await ${serviceCall}`);
19477
- lines.push(`${indent} res.status(${op.responseStatus.status}).end()`);
19478
- } else if (op.responseStatus.status === 201) {
19479
- lines.push(`${indent} res.status(201).json(await ${serviceCall})`);
19967
+ lines.push(`${indent} await ${serviceCall}`);
19968
+ lines.push(`${indent} res.status(${op.responseStatus.status}).end()`);
19969
+ } else if (op.responseStatus.isMultiStatus === true) {
19970
+ lines.push(`${indent} const _envelope = await ${serviceCall}`);
19971
+ lines.push(`${indent} res.status(_envelope.status).json(_envelope.body)`);
19972
+ } else if (op.responseStatus.responseContentType === "text/plain") {
19973
+ if (op.responseStatus.status === 200) {
19974
+ lines.push(`${indent} res.type('text/plain').send(await ${serviceCall})`);
19975
+ } else {
19976
+ lines.push(
19977
+ `${indent} res.status(${op.responseStatus.status}).type('text/plain').send(await ${serviceCall})`
19978
+ );
19979
+ }
19980
+ } else if (op.responseStatus.responseContentType === "application/octet-stream") {
19981
+ if (op.responseStatus.status === 200) {
19982
+ lines.push(
19983
+ `${indent} res.setHeader('Content-Type', 'application/octet-stream').send(Buffer.from(await ${serviceCall}))`
19984
+ );
19985
+ } else {
19986
+ lines.push(
19987
+ `${indent} res.status(${op.responseStatus.status}).setHeader('Content-Type', 'application/octet-stream').send(Buffer.from(await ${serviceCall}))`
19988
+ );
19989
+ }
19990
+ } else if (op.responseStatus.status === 200) {
19991
+ lines.push(`${indent} res.json(await ${serviceCall})`);
19480
19992
  } else {
19481
- lines.push(`${indent} res.json(await ${serviceCall})`);
19482
- }
19993
+ lines.push(`${indent} res.status(${op.responseStatus.status}).json(await ${serviceCall})`);
19994
+ }
19995
+ lines.push(`${indent} } catch (err) {`);
19996
+ lines.push(`${indent} if (err instanceof HttpError) {`);
19997
+ lines.push(`${indent} return void res.status(err.status).json({ error: err.message })`);
19998
+ lines.push(`${indent} }`);
19999
+ lines.push(`${indent} throw err`);
20000
+ lines.push(`${indent} }`);
19483
20001
  lines.push(`${indent}})`);
19484
20002
  return lines.join("\n");
19485
20003
  }
19486
- function buildFastifyRouteHandler(op, indent, schemaNames) {
20004
+ function buildFastifyRouteOptions(op, schemaNames) {
20005
+ const parts = [];
20006
+ const responseSchemaExpr = buildFastifyResponseSchemaExpr(op, schemaNames);
20007
+ if (responseSchemaExpr !== void 0) {
20008
+ parts.push(`schema: { response: { ${op.responseStatus.status}: ${responseSchemaExpr} } }`);
20009
+ }
20010
+ parts.push(`config: { operationId: '${op.methodName}' }`);
20011
+ return `{ ${parts.join(", ")} }`;
20012
+ }
20013
+ function buildFastifyResponseSchemaExpr(op, schemaNames) {
20014
+ if (schemaNames === void 0) return void 0;
20015
+ if (op.responseTypeName === void 0) return void 0;
20016
+ if (op.responseStatus.isMultiStatus === true) return void 0;
20017
+ if (op.responseStatus.isVoid) return void 0;
20018
+ const schemaName = `${op.responseTypeName}Schema`;
20019
+ if (!schemaNames.has(schemaName)) return void 0;
20020
+ return op.responseIsArray === true ? `z.array(${schemaName})` : schemaName;
20021
+ }
20022
+ function buildFastifyRouteHandler(op, indent, schemaNames, contextType) {
19487
20023
  const lines = [];
19488
20024
  const genericParts = [];
19489
20025
  if (op.queryParams.length > 0) {
19490
- const queryFields = op.queryParams.map((q) => {
19491
- if (q.tsType === "number") return `${q.name}?: number`;
19492
- if (q.tsType === "boolean") return `${q.name}?: boolean`;
19493
- return `${q.name}?: string`;
19494
- }).join("; ");
19495
- genericParts.push(`Querystring: { ${queryFields} }`);
19496
- }
19497
- if (op.bodyInfo !== void 0 && op.bodyInfo.typeName !== void 0) {
20026
+ const hasDeepOrDelimited = op.queryParams.some(
20027
+ (q) => q.isDeepObject === true || q.delimiterStyle !== void 0
20028
+ );
20029
+ let querystringType;
20030
+ if (hasDeepOrDelimited) {
20031
+ querystringType = "Record<string, string | string[] | undefined>";
20032
+ } else {
20033
+ const queryFields = op.queryParams.map((q) => {
20034
+ if (q.tsType === "number") return `${q.name}?: number`;
20035
+ if (q.tsType === "boolean") return `${q.name}?: boolean`;
20036
+ return `${q.name}?: string`;
20037
+ }).join("; ");
20038
+ querystringType = `{ ${queryFields} }`;
20039
+ }
20040
+ genericParts.push(`Querystring: ${querystringType}`);
20041
+ }
20042
+ if (op.bodyInfo !== void 0 && op.bodyInfo.typeName !== void 0 && !op.bodyInfo.isSynthesized) {
19498
20043
  genericParts.push(`Body: ${op.bodyInfo.typeName}`);
19499
20044
  } else if (op.bodyInfo !== void 0) {
19500
20045
  genericParts.push("Body: unknown");
@@ -19504,27 +20049,60 @@ function buildFastifyRouteHandler(op, indent, schemaNames) {
19504
20049
  genericParts.push(`Params: { ${paramFields} }`);
19505
20050
  }
19506
20051
  const generic = genericParts.length > 0 ? `<{ ${genericParts.join("; ")} }>` : "";
20052
+ const routeOpts = buildFastifyRouteOptions(op, schemaNames);
19507
20053
  lines.push(
19508
- `${indent}app.${op.httpMethod}${generic}(${JSON.stringify(op.honoPath)}, async (req, reply) => {`
20054
+ `${indent}app.${op.httpMethod}${generic}(${JSON.stringify(op.honoPath)}, ${routeOpts}, async (req, reply) => {`
19509
20055
  );
19510
20056
  if (op.pathParamValidations.length > 0) {
19511
20057
  emitPathValidation(lines, op.pathParamValidations, indent, "fastify");
19512
20058
  lines.push(`${indent} if (!_pv.success) {`);
19513
- lines.push(`${indent} return reply.status(422).send({`);
20059
+ lines.push(`${indent} return (reply as FastifyReply).status(422).send({`);
19514
20060
  lines.push(`${indent} error: 'Invalid path parameters',`);
19515
20061
  lines.push(`${indent} issues: _pv.error.issues,`);
19516
20062
  lines.push(`${indent} })`);
19517
20063
  lines.push(`${indent} }`);
19518
20064
  }
19519
20065
  if (op.queryParams.length > 0) {
19520
- const fields = op.queryParams.map((q) => ` ${q.name}: req.query.${q.name}`).join(",\n");
20066
+ const deepObjectParams = op.queryParams.filter((q) => q.isDeepObject === true);
20067
+ const hasDeepOrDelimited = op.queryParams.some(
20068
+ (q) => q.isDeepObject === true || q.delimiterStyle !== void 0
20069
+ );
20070
+ if (hasDeepOrDelimited) {
20071
+ lines.push(
20072
+ `${indent} const _dq = req.query as unknown as Record<string, string | undefined>`
20073
+ );
20074
+ }
20075
+ if (deepObjectParams.length > 0) {
20076
+ for (const q of deepObjectParams) {
20077
+ const prefixLen = q.rawName.length + 1;
20078
+ const bracketPrefix = q.rawName + "[";
20079
+ lines.push(`${indent} const ${q.name} = Object.fromEntries(`);
20080
+ lines.push(
20081
+ `${indent} Object.entries(_dq).filter(([k]) => k.startsWith('${bracketPrefix}') && k.endsWith(']')).map(([k, v]) => [k.slice(${prefixLen}, -1), v])`
20082
+ );
20083
+ lines.push(`${indent} )`);
20084
+ }
20085
+ }
20086
+ const fields = op.queryParams.map((q) => {
20087
+ if (q.isDeepObject === true) {
20088
+ return ` ${q.name}`;
20089
+ }
20090
+ if (q.delimiterStyle !== void 0) {
20091
+ const delim = JSON.stringify(delimiterChar(q.delimiterStyle));
20092
+ return ` ${q.name}: typeof _dq['${q.rawName}'] === 'string' ? _dq['${q.rawName}']!.split(${delim}) : undefined`;
20093
+ }
20094
+ if (q.tsType === "boolean") {
20095
+ return hasDeepOrDelimited ? ` ${q.name}: _dq['${q.rawName}'] === 'true'` : ` ${q.name}: (req.query.${q.name} as unknown as string) === 'true'`;
20096
+ }
20097
+ return hasDeepOrDelimited ? ` ${q.name}: _dq['${q.rawName}']` : ` ${q.name}: req.query.${q.name}`;
20098
+ }).join(",\n");
19521
20099
  lines.push(`${indent} const params = {`);
19522
20100
  lines.push(fields);
19523
20101
  lines.push(`${indent} }`);
19524
20102
  if (queryParamsNeedValidation(op.queryParams)) {
19525
20103
  emitQueryValidation(lines, op.queryParams, indent);
19526
20104
  lines.push(`${indent} if (!_qv.success) {`);
19527
- lines.push(`${indent} return reply.status(422).send({`);
20105
+ lines.push(`${indent} return (reply as FastifyReply).status(422).send({`);
19528
20106
  lines.push(`${indent} error: 'Invalid query parameters',`);
19529
20107
  lines.push(`${indent} issues: _qv.error.issues,`);
19530
20108
  lines.push(`${indent} })`);
@@ -19534,25 +20112,46 @@ function buildFastifyRouteHandler(op, indent, schemaNames) {
19534
20112
  if (op.headerParams.length > 0) {
19535
20113
  emitHeaderValidation(lines, op.headerParams, indent, "fastify");
19536
20114
  lines.push(`${indent} if (!_hv.success) {`);
19537
- lines.push(`${indent} return reply.status(422).send({`);
20115
+ lines.push(`${indent} return (reply as FastifyReply).status(422).send({`);
19538
20116
  lines.push(`${indent} error: 'Invalid request headers',`);
19539
20117
  lines.push(`${indent} issues: _hv.error.issues,`);
19540
20118
  lines.push(`${indent} })`);
19541
20119
  lines.push(`${indent} }`);
19542
20120
  }
20121
+ if (op.cookieParams.length > 0) {
20122
+ emitCookieValidation(lines, op.cookieParams, indent, "fastify");
20123
+ lines.push(`${indent} if (!_ckv.success) {`);
20124
+ lines.push(`${indent} return (reply as FastifyReply).status(422).send({`);
20125
+ lines.push(`${indent} error: 'Invalid request cookies',`);
20126
+ lines.push(`${indent} issues: _ckv.error.issues,`);
20127
+ lines.push(`${indent} })`);
20128
+ lines.push(`${indent} }`);
20129
+ }
19543
20130
  let bodyVarName = "req.body";
19544
20131
  if (op.bodyInfo !== void 0) {
19545
- const schemaName = op.bodyInfo.typeName !== void 0 ? `${op.bodyInfo.typeName}Schema` : void 0;
19546
- const useZod = schemaName !== void 0 && schemaNames !== void 0 && schemaNames.has(schemaName);
19547
- if (useZod) {
19548
- lines.push(`${indent} // Validate request body: returns 422 with Zod issues on failure`);
19549
- lines.push(`${indent} const parseResult = ${schemaName}.safeParse(req.body)`);
19550
- lines.push(`${indent} if (!parseResult.success) {`);
20132
+ if (op.bodyInfo.contentType === "multipart/form-data") {
19551
20133
  lines.push(
19552
- `${indent} return reply.status(422).send({ error: 'Invalid request body', issues: parseResult.error.issues })`
20134
+ `${indent} // multipart/form-data: requires @fastify/multipart registered with { attachFieldsToBody: true }.`
19553
20135
  );
19554
- lines.push(`${indent} }`);
19555
- bodyVarName = "parseResult.data";
20136
+ } else if (op.bodyInfo.contentType === "application/octet-stream") {
20137
+ lines.push(
20138
+ `${indent} // application/octet-stream: req.body is a Buffer from the registered content-type parser.`
20139
+ );
20140
+ } else {
20141
+ const schemaName = op.bodyInfo.typeName !== void 0 ? `${op.bodyInfo.typeName}Schema` : void 0;
20142
+ const useZod = schemaName !== void 0 && schemaNames !== void 0 && schemaNames.has(schemaName);
20143
+ if (useZod) {
20144
+ lines.push(`${indent} // Validate request body: returns 422 with Zod issues on failure`);
20145
+ lines.push(`${indent} const parseResult = ${schemaName}.safeParse(req.body)`);
20146
+ lines.push(`${indent} if (!parseResult.success) {`);
20147
+ lines.push(
20148
+ `${indent} return (reply as FastifyReply).status(422).send({ error: 'Invalid request body', issues: parseResult.error.issues })`
20149
+ );
20150
+ lines.push(`${indent} }`);
20151
+ const bodyCastType = op.bodyInfo.typeName !== void 0 && !op.bodyInfo.isSynthesized ? op.bodyInfo.typeName : "unknown";
20152
+ lines.push(`${indent} const validatedBody = parseResult.data as ${bodyCastType}`);
20153
+ bodyVarName = "validatedBody";
20154
+ }
19556
20155
  }
19557
20156
  }
19558
20157
  const serviceArgs = [];
@@ -19563,26 +20162,62 @@ function buildFastifyRouteHandler(op, indent, schemaNames) {
19563
20162
  serviceArgs.push(bodyVarName);
19564
20163
  }
19565
20164
  if (op.queryParams.length > 0) {
19566
- serviceArgs.push("params");
20165
+ serviceArgs.push(queryParamsNeedValidation(op.queryParams) ? "_qv.data" : "params");
20166
+ }
20167
+ if (contextType !== void 0) {
20168
+ serviceArgs.push("req");
19567
20169
  }
19568
20170
  const serviceCall = `service.${op.methodName}(${serviceArgs.join(", ")})`;
20171
+ lines.push(`${indent} try {`);
19569
20172
  if (op.responseStatus.isVoid) {
19570
- lines.push(`${indent} await ${serviceCall}`);
19571
- lines.push(`${indent} reply.status(${op.responseStatus.status}).send()`);
19572
- } else if (op.responseStatus.status === 201) {
19573
- lines.push(`${indent} reply.status(201)`);
19574
- lines.push(`${indent} return ${serviceCall}`);
20173
+ lines.push(`${indent} await ${serviceCall}`);
20174
+ lines.push(`${indent} reply.status(${op.responseStatus.status}).send()`);
20175
+ } else if (op.responseStatus.isMultiStatus === true) {
20176
+ lines.push(`${indent} const _envelope = await ${serviceCall}`);
20177
+ lines.push(`${indent} return reply.status(_envelope.status).send(_envelope.body)`);
20178
+ } else if (op.responseStatus.responseContentType === "text/plain") {
20179
+ if (op.responseStatus.status === 200) {
20180
+ lines.push(`${indent} return reply.type('text/plain').send(await ${serviceCall})`);
20181
+ } else {
20182
+ lines.push(`${indent} return reply.status(${op.responseStatus.status}).type('text/plain').send(await ${serviceCall})`);
20183
+ }
20184
+ } else if (op.responseStatus.responseContentType === "application/octet-stream") {
20185
+ if (op.responseStatus.status === 200) {
20186
+ lines.push(`${indent} return reply.type('application/octet-stream').send(Buffer.from(await ${serviceCall}))`);
20187
+ } else {
20188
+ lines.push(`${indent} return reply.status(${op.responseStatus.status}).type('application/octet-stream').send(Buffer.from(await ${serviceCall}))`);
20189
+ }
20190
+ } else if (op.responseStatus.status === 200) {
20191
+ lines.push(`${indent} return await ${serviceCall}`);
19575
20192
  } else {
19576
- lines.push(`${indent} return ${serviceCall}`);
19577
- }
20193
+ lines.push(`${indent} reply.status(${op.responseStatus.status})`);
20194
+ lines.push(`${indent} return await ${serviceCall}`);
20195
+ }
20196
+ lines.push(`${indent} } catch (err) {`);
20197
+ lines.push(`${indent} if (err instanceof HttpError) {`);
20198
+ lines.push(`${indent} return (reply as FastifyReply).status(err.status).send({ error: err.message })`);
20199
+ lines.push(`${indent} }`);
20200
+ lines.push(`${indent} throw err`);
20201
+ lines.push(`${indent} }`);
19578
20202
  lines.push(`${indent}})`);
19579
20203
  return lines.join("\n");
19580
20204
  }
20205
+ function httpErrorClassLines() {
20206
+ return [
20207
+ "export class HttpError extends Error {",
20208
+ " constructor(public readonly status: number, message: string) {",
20209
+ " super(message)",
20210
+ " this.name = 'HttpError'",
20211
+ " }",
20212
+ "}"
20213
+ ];
20214
+ }
19581
20215
  function operationsNeedZodForParams(operations) {
19582
20216
  for (const op of operations) {
19583
20217
  if (op.pathParamValidations.length > 0) return true;
19584
20218
  if (queryParamsNeedValidation(op.queryParams)) return true;
19585
20219
  if (op.headerParams.length > 0) return true;
20220
+ if (op.cookieParams.length > 0) return true;
19586
20221
  }
19587
20222
  return false;
19588
20223
  }
@@ -19601,6 +20236,8 @@ function generateExpressRouter(spec, options) {
19601
20236
  if (sortedBodyTypes.length > 0) {
19602
20237
  lines.push(`import type { ${sortedBodyTypes.join(", ")} } from './models.js'`);
19603
20238
  }
20239
+ const ctx = options?.contextType;
20240
+ const serviceRef = ctx !== void 0 ? `${serviceName}<${ctx}>` : serviceName;
19604
20241
  lines.push(`import type { ${serviceName} } from './service.js'`);
19605
20242
  if (needsZod) {
19606
20243
  lines.push(`import { z } from 'zod'`);
@@ -19610,11 +20247,13 @@ function generateExpressRouter(spec, options) {
19610
20247
  lines.push(`import { ${sortedUsedSchemas.join(", ")} } from '${options.schemaImportPath}'`);
19611
20248
  }
19612
20249
  lines.push("");
19613
- lines.push(`export function createRouter(service: ${serviceName}): Router {`);
20250
+ for (const l of httpErrorClassLines()) lines.push(l);
20251
+ lines.push("");
20252
+ lines.push(`export function createRouter(service: ${serviceRef}): Router {`);
19614
20253
  lines.push(" const router = Router()");
19615
20254
  lines.push("");
19616
20255
  for (const op of operations) {
19617
- lines.push(buildExpressRouteHandler(op, " ", options?.schemaNames));
20256
+ lines.push(buildExpressRouteHandler(op, " ", options?.schemaNames, ctx));
19618
20257
  lines.push("");
19619
20258
  }
19620
20259
  lines.push(" return router");
@@ -19628,11 +20267,26 @@ function generateExpressRouter(spec, options) {
19628
20267
  function generateFastifyRouter(spec, options) {
19629
20268
  const serviceName = deriveServiceName(spec);
19630
20269
  const operations = collectOperations2(spec);
19631
- const { sortedBodyTypes, usedSchemaNames, needsZod } = collectGeneratorSetup(operations, options);
20270
+ const { sortedBodyTypes, usedSchemaNames, usedResponseSchemaNames, needsZod } = collectGeneratorSetup(operations, options);
20271
+ const allUsedSchemaNames = /* @__PURE__ */ new Set([...usedSchemaNames, ...usedResponseSchemaNames]);
20272
+ const hasOctetStreamRequestBody = operations.some(
20273
+ (op) => op.bodyInfo?.contentType === "application/octet-stream"
20274
+ );
19632
20275
  const lines = [];
19633
20276
  lines.push("// This file is auto-generated. Do not edit manually.");
20277
+ lines.push(
20278
+ "// Fastify natively parses only application/json and text/plain request bodies."
20279
+ );
20280
+ lines.push(
20281
+ "// For application/x-www-form-urlencoded bodies, register @fastify/formbody before this router."
20282
+ );
20283
+ lines.push(
20284
+ "// For multipart/form-data bodies, register @fastify/multipart with { attachFieldsToBody: true } before this router."
20285
+ );
19634
20286
  lines.push("");
19635
- lines.push("import type { FastifyInstance } from 'fastify'");
20287
+ const ctx = options?.contextType;
20288
+ const serviceRef = ctx !== void 0 ? `${serviceName}<${ctx}>` : serviceName;
20289
+ lines.push("import type { FastifyInstance, FastifyReply } from 'fastify'");
19636
20290
  if (sortedBodyTypes.length > 0) {
19637
20291
  lines.push(`import type { ${sortedBodyTypes.join(", ")} } from './models.js'`);
19638
20292
  }
@@ -19640,15 +20294,41 @@ function generateFastifyRouter(spec, options) {
19640
20294
  if (needsZod) {
19641
20295
  lines.push(`import { z } from 'zod'`);
19642
20296
  }
19643
- if (usedSchemaNames.size > 0 && options?.schemaImportPath !== void 0) {
19644
- const sortedUsedSchemas = Array.from(usedSchemaNames).sort();
20297
+ if (allUsedSchemaNames.size > 0 && options?.schemaImportPath !== void 0) {
20298
+ const sortedUsedSchemas = Array.from(allUsedSchemaNames).sort();
19645
20299
  lines.push(`import { ${sortedUsedSchemas.join(", ")} } from '${options.schemaImportPath}'`);
19646
20300
  }
19647
20301
  lines.push("");
19648
- lines.push(`export function createRouter(app: FastifyInstance, service: ${serviceName}): void {`);
20302
+ lines.push("declare module 'fastify' {");
20303
+ lines.push(" interface FastifyContextConfig {");
20304
+ lines.push(" operationId?: string");
20305
+ lines.push(" }");
20306
+ lines.push("}");
20307
+ lines.push("");
20308
+ for (const l of httpErrorClassLines()) lines.push(l);
20309
+ lines.push("");
20310
+ lines.push(`export function createRouter(app: FastifyInstance, service: ${serviceRef}): void {`);
20311
+ if (hasOctetStreamRequestBody) {
20312
+ lines.push(
20313
+ " app.addContentTypeParser('application/octet-stream', { parseAs: 'buffer' }, (req, body, done) => done(null, body))"
20314
+ );
20315
+ }
20316
+ if (usedResponseSchemaNames.size > 0 && options?.schemaImportPath !== void 0) {
20317
+ lines.push(" app.setSerializerCompiler(({ schema }) => {");
20318
+ lines.push(
20319
+ " if (schema !== null && typeof schema === 'object' && 'parse' in schema) {"
20320
+ );
20321
+ lines.push(
20322
+ " const zodSchema = schema as { parse: (data: unknown) => unknown }"
20323
+ );
20324
+ lines.push(" return (data: unknown) => JSON.stringify(zodSchema.parse(data))");
20325
+ lines.push(" }");
20326
+ lines.push(" return (data: unknown) => JSON.stringify(data)");
20327
+ lines.push(" })");
20328
+ }
19649
20329
  for (const op of operations) {
19650
20330
  lines.push("");
19651
- lines.push(buildFastifyRouteHandler(op, " ", options?.schemaNames));
20331
+ lines.push(buildFastifyRouteHandler(op, " ", options?.schemaNames, ctx));
19652
20332
  }
19653
20333
  lines.push("}");
19654
20334
  lines.push("");
@@ -19664,7 +20344,13 @@ function generateRouter(spec, options) {
19664
20344
  const lines = [];
19665
20345
  lines.push("// This file is auto-generated. Do not edit manually.");
19666
20346
  lines.push("");
20347
+ const ctx = options?.contextType;
20348
+ const serviceRef = ctx !== void 0 ? `${serviceName}<${ctx}>` : serviceName;
20349
+ const needsGetCookie = operations.some((op) => op.cookieParams.length > 0);
19667
20350
  lines.push("import { Hono } from 'hono'");
20351
+ if (needsGetCookie) {
20352
+ lines.push("import { getCookie } from 'hono/cookie'");
20353
+ }
19668
20354
  if (sortedBodyTypes.length > 0) {
19669
20355
  lines.push(`import type { ${sortedBodyTypes.join(", ")} } from './models.js'`);
19670
20356
  }
@@ -19677,11 +20363,13 @@ function generateRouter(spec, options) {
19677
20363
  lines.push(`import { ${sortedUsedSchemas.join(", ")} } from '${options.schemaImportPath}'`);
19678
20364
  }
19679
20365
  lines.push("");
19680
- lines.push(`export function createRouter(service: ${serviceName}): Hono {`);
20366
+ for (const l of httpErrorClassLines()) lines.push(l);
20367
+ lines.push("");
20368
+ lines.push(`export function createRouter(service: ${serviceRef}): Hono {`);
19681
20369
  lines.push(" const app = new Hono()");
19682
20370
  lines.push("");
19683
20371
  for (const op of operations) {
19684
- lines.push(buildRouteHandler(op, " ", options?.schemaNames));
20372
+ lines.push(buildRouteHandler(op, " ", options?.schemaNames, ctx));
19685
20373
  lines.push("");
19686
20374
  }
19687
20375
  lines.push(" return app");
@@ -19711,9 +20399,14 @@ async function generateOne(cwd, config, label) {
19711
20399
  const prefix = label !== void 0 ? `[${label}] ` : "";
19712
20400
  console.log(`${prefix}Parsing spec: ${inputPath}`);
19713
20401
  const spec = await parseSpec(inputPath);
19714
- const generatedFiles = [generateService(spec)];
20402
+ const serviceOptions = config.context_type !== void 0 ? { contextType: config.context_type } : void 0;
20403
+ const generatedFiles = [generateService(spec, serviceOptions)];
19715
20404
  if (framework !== "none") {
19716
- generatedFiles.push(buildRouterFile(spec, framework));
20405
+ generatedFiles.push(
20406
+ buildRouterFile(spec, framework, {
20407
+ contextType: config.context_type
20408
+ })
20409
+ );
19717
20410
  }
19718
20411
  console.log(`${prefix}Writing output to: ${outputDir}`);
19719
20412
  await (0, import_promises2.mkdir)(outputDir, { recursive: true });
@@ -19746,7 +20439,8 @@ async function generateSchemaEnhancedRouter(cwd, config, spec, framework, output
19746
20439
  const schemaImportPathJs = schemaImportPath.replace(/\.ts$/, ".js");
19747
20440
  const routerFile = buildRouterFile(spec, framework, {
19748
20441
  schemaNames: exportedSchemas,
19749
- schemaImportPath: schemaImportPathJs
20442
+ schemaImportPath: schemaImportPathJs,
20443
+ contextType: config.context_type
19750
20444
  });
19751
20445
  const routerPath = (0, import_node_path2.join)(outputDir, routerFile.filename);
19752
20446
  await (0, import_promises2.writeFile)(routerPath, await formatTs(routerFile.content, routerPath), "utf-8");