@codewithagents/openapi-server 1.8.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -18953,35 +18953,131 @@ function getQueryParams(operation, spec) {
18953
18953
  const resolved = resolveParam(p, spec);
18954
18954
  if (resolved === void 0 || resolved.in !== "query") continue;
18955
18955
  const schema = resolved.schema;
18956
- result.push({
18956
+ const resolvedStyle = resolved.style;
18957
+ const resolvedExplode = resolved.explode;
18958
+ const param = {
18957
18959
  name: normalizeParamName(resolved.name),
18960
+ rawName: resolved.name,
18958
18961
  tsType: schemaToTsType(schema),
18959
18962
  required: resolved.required === true
18960
- });
18963
+ };
18964
+ if (resolvedStyle === "deepObject" && schema !== void 0 && !isRef3(schema)) {
18965
+ const s = schema;
18966
+ if (s.type === "object" && s.properties !== void 0) {
18967
+ param.isDeepObject = true;
18968
+ param.deepObjectProperties = Object.entries(s.properties).map(([key, propSchema]) => ({
18969
+ key,
18970
+ tsType: schemaToTsType(propSchema)
18971
+ }));
18972
+ }
18973
+ }
18974
+ if (!param.isDeepObject && schema !== void 0 && !isRef3(schema) && schema.type === "array" && resolvedExplode === false) {
18975
+ if (resolvedStyle === "spaceDelimited") {
18976
+ param.delimiterStyle = "ssv";
18977
+ } else if (resolvedStyle === "pipeDelimited") {
18978
+ param.delimiterStyle = "psv";
18979
+ } else {
18980
+ param.delimiterStyle = "csv";
18981
+ }
18982
+ }
18983
+ if (schema !== void 0 && !isRef3(schema)) {
18984
+ const s = schema;
18985
+ if (Array.isArray(s.enum)) param.enum = s.enum;
18986
+ if (typeof s.minimum === "number") param.minimum = s.minimum;
18987
+ if (typeof s.maximum === "number") param.maximum = s.maximum;
18988
+ if (typeof s.exclusiveMinimum === "number") param.exclusiveMinimum = s.exclusiveMinimum;
18989
+ if (typeof s.exclusiveMaximum === "number") param.exclusiveMaximum = s.exclusiveMaximum;
18990
+ if (typeof s.minLength === "number") param.minLength = s.minLength;
18991
+ if (typeof s.maxLength === "number") param.maxLength = s.maxLength;
18992
+ if (typeof s.pattern === "string") param.pattern = s.pattern;
18993
+ }
18994
+ result.push(param);
18961
18995
  }
18962
18996
  return result;
18963
18997
  }
18964
18998
  function getBodyInfo(operation) {
18965
18999
  const requestBody = operation.requestBody;
18966
19000
  if (requestBody === void 0) return void 0;
18967
- if (isRef3(requestBody)) return { typeName: void 0 };
19001
+ if (isRef3(requestBody)) {
19002
+ return { typeName: void 0, contentType: "application/json", isSynthesized: false };
19003
+ }
18968
19004
  const rb = requestBody;
18969
19005
  const content = rb.content;
18970
- if (content === void 0) return { typeName: void 0 };
19006
+ if (content === void 0) {
19007
+ return { typeName: void 0, contentType: "application/json", isSynthesized: false };
19008
+ }
18971
19009
  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) };
19010
+ if (jsonContent !== void 0 && jsonContent.schema !== void 0) {
19011
+ const schema = jsonContent.schema;
19012
+ if (isRef3(schema)) {
19013
+ return {
19014
+ typeName: refToName(schema.$ref),
19015
+ contentType: "application/json",
19016
+ isSynthesized: false
19017
+ };
19018
+ }
19019
+ const operationId = operation.operationId;
19020
+ if (operationId !== void 0 && operationId.length > 0) {
19021
+ return { typeName: toTypeName(operationId), contentType: "application/json", isSynthesized: true };
19022
+ }
19023
+ return { typeName: void 0, contentType: "application/json", isSynthesized: false };
18976
19024
  }
18977
- return { typeName: void 0 };
19025
+ const formContent = content["application/x-www-form-urlencoded"];
19026
+ if (formContent !== void 0) {
19027
+ const schema = formContent.schema;
19028
+ if (schema !== void 0 && isRef3(schema)) {
19029
+ return {
19030
+ typeName: refToName(schema.$ref),
19031
+ contentType: "application/x-www-form-urlencoded",
19032
+ isSynthesized: false
19033
+ };
19034
+ }
19035
+ const operationId = operation.operationId;
19036
+ if (operationId !== void 0 && operationId.length > 0) {
19037
+ return {
19038
+ typeName: toTypeName(operationId),
19039
+ contentType: "application/x-www-form-urlencoded",
19040
+ isSynthesized: true
19041
+ };
19042
+ }
19043
+ return { typeName: void 0, contentType: "application/x-www-form-urlencoded", isSynthesized: false };
19044
+ }
19045
+ const multipartContent = content["multipart/form-data"];
19046
+ if (multipartContent !== void 0) {
19047
+ const schema = multipartContent.schema;
19048
+ if (schema !== void 0 && isRef3(schema)) {
19049
+ return {
19050
+ typeName: refToName(schema.$ref),
19051
+ contentType: "multipart/form-data",
19052
+ isSynthesized: false
19053
+ };
19054
+ }
19055
+ const operationId = operation.operationId;
19056
+ if (operationId !== void 0 && operationId.length > 0) {
19057
+ return {
19058
+ typeName: toTypeName(operationId),
19059
+ contentType: "multipart/form-data",
19060
+ isSynthesized: true
19061
+ };
19062
+ }
19063
+ return { typeName: void 0, contentType: "multipart/form-data", isSynthesized: false };
19064
+ }
19065
+ return { typeName: void 0, contentType: "application/json", isSynthesized: false };
18978
19066
  }
18979
19067
 
18980
19068
  // src/plugins/service.ts
19069
+ function collectContentfulTwoxxCodes(responses) {
19070
+ return Object.keys(responses).filter((k) => /^2\d\d$/.test(k) && k !== "204").sort();
19071
+ }
18981
19072
  function getReturnInfo(operation) {
18982
19073
  const responses = operation.responses;
18983
19074
  if (responses === void 0) return { typeName: void 0, isArray: false, isVoid: true };
18984
- for (const code of ["200", "201"]) {
19075
+ const contentfulCodes = collectContentfulTwoxxCodes(responses);
19076
+ const isMultiStatus = contentfulCodes.length > 1;
19077
+ const twoxxCodes = ["200", "201", ...Object.keys(responses).filter(
19078
+ (k) => /^2\d\d$/.test(k) && k !== "200" && k !== "201" && k !== "204"
19079
+ )];
19080
+ for (const code of twoxxCodes) {
18985
19081
  const response = responses[code];
18986
19082
  if (response === void 0) continue;
18987
19083
  if (isRef3(response)) continue;
@@ -18989,28 +19085,37 @@ function getReturnInfo(operation) {
18989
19085
  const content = resp.content;
18990
19086
  if (content === void 0) continue;
18991
19087
  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)) {
19088
+ if (jsonContent !== void 0 && jsonContent.schema !== void 0) {
19089
+ const schema = jsonContent.schema;
19090
+ if (isRef3(schema)) {
19005
19091
  return {
19006
- typeName: refToName(items.$ref),
19007
- isArray: true,
19008
- isVoid: false
19092
+ typeName: refToName(schema.$ref),
19093
+ isArray: false,
19094
+ isVoid: false,
19095
+ isMultiStatus
19009
19096
  };
19010
19097
  }
19011
- return { typeName: void 0, isArray: true, isVoid: false };
19098
+ const s = schema;
19099
+ if (s.type === "array") {
19100
+ const items = s.items;
19101
+ if (items !== void 0 && isRef3(items)) {
19102
+ return {
19103
+ typeName: refToName(items.$ref),
19104
+ isArray: true,
19105
+ isVoid: false,
19106
+ isMultiStatus
19107
+ };
19108
+ }
19109
+ return { typeName: void 0, isArray: true, isVoid: false, isMultiStatus };
19110
+ }
19111
+ return { typeName: void 0, isArray: false, isVoid: false, isMultiStatus };
19112
+ }
19113
+ if (content["text/plain"] !== void 0) {
19114
+ return { typeName: void 0, isArray: false, isVoid: false, primitiveType: "string" };
19115
+ }
19116
+ if (content["application/octet-stream"] !== void 0) {
19117
+ return { typeName: void 0, isArray: false, isVoid: false, primitiveType: "Uint8Array" };
19012
19118
  }
19013
- return { typeName: void 0, isArray: false, isVoid: false };
19014
19119
  }
19015
19120
  if (responses["204"] !== void 0) {
19016
19121
  return { typeName: void 0, isArray: false, isVoid: true };
@@ -19019,6 +19124,16 @@ function getReturnInfo(operation) {
19019
19124
  }
19020
19125
  function buildReturnType(info) {
19021
19126
  if (info.isVoid) return "Promise<void>";
19127
+ if (info.primitiveType !== void 0) return `Promise<${info.primitiveType}>`;
19128
+ if (info.isMultiStatus === true) {
19129
+ let bodyType;
19130
+ if (info.typeName !== void 0) {
19131
+ bodyType = info.isArray ? `${info.typeName}[]` : info.typeName;
19132
+ } else {
19133
+ bodyType = info.isArray ? "unknown[]" : "unknown";
19134
+ }
19135
+ return `Promise<{ status: number; body: ${bodyType} }>`;
19136
+ }
19022
19137
  if (info.typeName !== void 0) {
19023
19138
  return info.isArray ? `Promise<${info.typeName}[]>` : `Promise<${info.typeName}>`;
19024
19139
  }
@@ -19056,7 +19171,7 @@ function buildMethodSignature(op) {
19056
19171
  args.push(`${sanitizeOperationId2(p)}: string`);
19057
19172
  }
19058
19173
  if (op.bodyInfo !== void 0) {
19059
- const typeName = op.bodyInfo.typeName ?? "unknown";
19174
+ const typeName = op.bodyInfo.typeName !== void 0 && !op.bodyInfo.isSynthesized ? op.bodyInfo.typeName : "unknown";
19060
19175
  args.push(`body: ${typeName}`);
19061
19176
  }
19062
19177
  if (op.queryParams.length > 0) {
@@ -19074,7 +19189,7 @@ function generateService(spec) {
19074
19189
  const operations = collectOperations(spec);
19075
19190
  const importTypes = /* @__PURE__ */ new Set();
19076
19191
  for (const op of operations) {
19077
- if (op.bodyInfo?.typeName !== void 0) {
19192
+ if (op.bodyInfo?.typeName !== void 0 && !op.bodyInfo.isSynthesized) {
19078
19193
  importTypes.add(op.bodyInfo.typeName);
19079
19194
  }
19080
19195
  if (op.returnInfo.typeName !== void 0) {
@@ -19124,6 +19239,21 @@ function formatToZodModifier(format) {
19124
19239
  function pathParamZodExpr(schema) {
19125
19240
  if (schema === void 0 || isRef3(schema)) return void 0;
19126
19241
  const s = schema;
19242
+ if (s.type === "integer" || s.type === "number") {
19243
+ const hasMin = typeof s.minimum === "number";
19244
+ const hasMax = typeof s.maximum === "number";
19245
+ const hasExcMin = typeof s.exclusiveMinimum === "number";
19246
+ const hasExcMax = typeof s.exclusiveMaximum === "number";
19247
+ if (hasMin || hasMax || hasExcMin || hasExcMax) {
19248
+ let expr = "z.coerce.number()";
19249
+ if (hasMin) expr += `.min(${s.minimum})`;
19250
+ if (hasMax) expr += `.max(${s.maximum})`;
19251
+ if (hasExcMin) expr += `.gt(${s.exclusiveMinimum})`;
19252
+ if (hasExcMax) expr += `.lt(${s.exclusiveMaximum})`;
19253
+ return expr;
19254
+ }
19255
+ return void 0;
19256
+ }
19127
19257
  if (s.type !== "string") return void 0;
19128
19258
  const format = s.format;
19129
19259
  if (format === void 0) return void 0;
@@ -19131,23 +19261,64 @@ function pathParamZodExpr(schema) {
19131
19261
  if (modifier === "") return void 0;
19132
19262
  return `z.string()${modifier}`;
19133
19263
  }
19134
- function paramZodExpr(tsType, required, schema) {
19264
+ function queryParamDelimitedZodBase(_param) {
19265
+ return "z.array(z.string())";
19266
+ }
19267
+ function queryParamDeepObjectZodBase(param) {
19268
+ const propFields = (param.deepObjectProperties ?? []).map((p) => {
19269
+ const coerced = p.tsType === "number" ? "z.coerce.number()" : "z.string()";
19270
+ return `${p.key}: ${coerced}.optional()`;
19271
+ });
19272
+ return `z.object({ ${propFields.join(", ")} })`;
19273
+ }
19274
+ function queryParamNumberZodBase(param) {
19275
+ let base = "z.number()";
19276
+ if (param.minimum !== void 0) base += `.min(${param.minimum})`;
19277
+ if (param.maximum !== void 0) base += `.max(${param.maximum})`;
19278
+ if (param.exclusiveMinimum !== void 0) base += `.gt(${param.exclusiveMinimum})`;
19279
+ if (param.exclusiveMaximum !== void 0) base += `.lt(${param.exclusiveMaximum})`;
19280
+ return base;
19281
+ }
19282
+ function queryParamStringZodBase(param) {
19135
19283
  let base;
19136
- if (tsType === "number") {
19137
- base = "z.number()";
19138
- } else if (tsType === "boolean") {
19284
+ if (param.enum !== void 0 && param.enum.length > 0) {
19285
+ const members = param.enum.map((v) => JSON.stringify(v)).join(", ");
19286
+ base = `z.enum([${members}])`;
19287
+ } else {
19288
+ base = "z.string()";
19289
+ }
19290
+ if (param.minLength !== void 0) base += `.min(${param.minLength})`;
19291
+ if (param.maxLength !== void 0) base += `.max(${param.maxLength})`;
19292
+ if (param.pattern !== void 0) base += `.regex(/${param.pattern}/)`;
19293
+ return base;
19294
+ }
19295
+ function queryParamZodExpr(param) {
19296
+ let base;
19297
+ if (param.delimiterStyle !== void 0) {
19298
+ base = queryParamDelimitedZodBase(param);
19299
+ } else if (param.isDeepObject === true && param.deepObjectProperties !== void 0) {
19300
+ base = queryParamDeepObjectZodBase(param);
19301
+ } else if (param.tsType === "number") {
19302
+ base = queryParamNumberZodBase(param);
19303
+ } else if (param.tsType === "boolean") {
19139
19304
  base = "z.boolean()";
19140
19305
  } 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
- }
19306
+ base = queryParamStringZodBase(param);
19307
+ }
19308
+ return param.required ? base : `${base}.optional()`;
19309
+ }
19310
+ function headerParamZodExpr(param) {
19311
+ let base;
19312
+ if (param.enum !== void 0 && param.enum.length > 0) {
19313
+ const members = param.enum.map((v) => JSON.stringify(v)).join(", ");
19314
+ base = `z.enum([${members}])`;
19315
+ } else {
19316
+ base = "z.string()";
19149
19317
  }
19150
- return required ? base : `${base}.optional()`;
19318
+ if (param.minLength !== void 0) base += `.min(${param.minLength})`;
19319
+ if (param.maxLength !== void 0) base += `.max(${param.maxLength})`;
19320
+ if (param.pattern !== void 0) base += `.regex(/${param.pattern}/)`;
19321
+ return param.required ? base : `${base}.optional()`;
19151
19322
  }
19152
19323
  function getPathParamValidations(operation, spec, rawPathParamNames) {
19153
19324
  const parameters = operation.parameters;
@@ -19174,21 +19345,51 @@ function getHeaderParams(operation, spec) {
19174
19345
  for (const p of parameters) {
19175
19346
  const resolved = resolveParam(p, spec);
19176
19347
  if (resolved === void 0 || resolved.in !== "header") continue;
19177
- result.push({
19348
+ const param = {
19178
19349
  rawName: resolved.name,
19179
19350
  required: resolved.required === true
19180
- });
19351
+ };
19352
+ const schema = resolved.schema;
19353
+ if (schema !== void 0 && !isRef3(schema)) {
19354
+ const s = schema;
19355
+ if (Array.isArray(s.enum)) param.enum = s.enum;
19356
+ if (typeof s.minLength === "number") param.minLength = s.minLength;
19357
+ if (typeof s.maxLength === "number") param.maxLength = s.maxLength;
19358
+ if (typeof s.pattern === "string") param.pattern = s.pattern;
19359
+ }
19360
+ result.push(param);
19181
19361
  }
19182
19362
  return result;
19183
19363
  }
19364
+ function queryParamHasConstraints(q) {
19365
+ const constraintFields = [
19366
+ q.enum,
19367
+ q.minimum,
19368
+ q.maximum,
19369
+ q.exclusiveMinimum,
19370
+ q.exclusiveMaximum,
19371
+ q.minLength,
19372
+ q.maxLength,
19373
+ q.pattern,
19374
+ q.delimiterStyle
19375
+ ];
19376
+ return constraintFields.some((f) => f !== void 0) || q.isDeepObject === true;
19377
+ }
19184
19378
  function queryParamsNeedValidation(queryParams) {
19185
- return queryParams.some((q) => q.required || q.tsType !== "string");
19379
+ return queryParams.some(
19380
+ (q) => q.required || q.tsType !== "string" || queryParamHasConstraints(q)
19381
+ );
19382
+ }
19383
+ function delimiterChar(style) {
19384
+ if (style === "ssv") return " ";
19385
+ if (style === "psv") return "|";
19386
+ return ",";
19186
19387
  }
19187
19388
  function emitQueryValidation(lines, queryParams, indent) {
19188
19389
  const inner = `${indent} `;
19189
19390
  const fieldIndent = `${indent} `;
19190
19391
  const fields = queryParams.map((q) => {
19191
- const expr = paramZodExpr(q.tsType, q.required);
19392
+ const expr = queryParamZodExpr(q);
19192
19393
  return `${fieldIndent}${q.name}: ${expr}`;
19193
19394
  }).join(",\n");
19194
19395
  lines.push(`${inner}// Validate query parameters: returns 422 with Zod issues on failure`);
@@ -19227,7 +19428,7 @@ function emitHeaderValidation(lines, headerParams, indent, framework) {
19227
19428
  const fieldIndent = `${indent} `;
19228
19429
  const schemaFields = headerParams.map((h) => {
19229
19430
  const key = JSON.stringify(h.rawName);
19230
- const expr = h.required ? "z.string()" : "z.string().optional()";
19431
+ const expr = headerParamZodExpr(h);
19231
19432
  return `${fieldIndent}${key}: ${expr}`;
19232
19433
  }).join(",\n");
19233
19434
  const rawFields = headerParams.map((h) => {
@@ -19255,18 +19456,63 @@ function response200IsVoid(resp) {
19255
19456
  const content = r.content;
19256
19457
  return content === void 0 || Object.keys(content).length === 0;
19257
19458
  }
19459
+ function detectResponseContentType(resp) {
19460
+ if (isRef3(resp)) return "application/json";
19461
+ const r = resp;
19462
+ const content = r.content;
19463
+ if (content === void 0) return "application/json";
19464
+ if ("text/plain" in content) return "text/plain";
19465
+ if ("application/octet-stream" in content) return "application/octet-stream";
19466
+ return "application/json";
19467
+ }
19258
19468
  function getResponseStatus(operation, httpMethod) {
19259
19469
  const responses = operation.responses;
19260
19470
  if (responses === void 0) {
19261
- return httpMethod === "delete" ? { status: 204, isVoid: true } : { status: 200, isVoid: false };
19471
+ return httpMethod === "delete" ? { status: 204, isVoid: true, responseContentType: "application/json" } : { status: 200, isVoid: false, responseContentType: "application/json" };
19472
+ }
19473
+ const contentfulTwoxxKeys = Object.keys(responses).filter((k) => /^2\d\d$/.test(k) && k !== "204").sort();
19474
+ if (contentfulTwoxxKeys.length > 1) {
19475
+ return {
19476
+ status: 200,
19477
+ isVoid: false,
19478
+ responseContentType: "application/json",
19479
+ isMultiStatus: true
19480
+ };
19481
+ }
19482
+ if (responses["201"] !== void 0) {
19483
+ return {
19484
+ status: 201,
19485
+ isVoid: false,
19486
+ responseContentType: detectResponseContentType(responses["201"])
19487
+ };
19488
+ }
19489
+ if (responses["204"] !== void 0) {
19490
+ return { status: 204, isVoid: true, responseContentType: "application/json" };
19262
19491
  }
19263
- if (responses["201"] !== void 0) return { status: 201, isVoid: false };
19264
- if (responses["204"] !== void 0) return { status: 204, isVoid: true };
19265
19492
  if (responses["200"] !== void 0) {
19266
- if (response200IsVoid(responses["200"])) return { status: 204, isVoid: true };
19267
- return { status: 200, isVoid: false };
19493
+ if (response200IsVoid(responses["200"])) {
19494
+ return { status: 204, isVoid: true, responseContentType: "application/json" };
19495
+ }
19496
+ return {
19497
+ status: 200,
19498
+ isVoid: false,
19499
+ responseContentType: detectResponseContentType(responses["200"])
19500
+ };
19268
19501
  }
19269
- return httpMethod === "delete" ? { status: 204, isVoid: true } : { status: 200, isVoid: false };
19502
+ const twoxxKeys = Object.keys(responses).filter(
19503
+ (k) => /^2\d\d$/.test(k) && k !== "200" && k !== "201" && k !== "204"
19504
+ );
19505
+ if (twoxxKeys.length === 1) {
19506
+ const code = parseInt(twoxxKeys[0], 10);
19507
+ const resp = responses[twoxxKeys[0]];
19508
+ const isVoid = isRef3(resp) ? false : (() => {
19509
+ const r = resp;
19510
+ const content = r.content;
19511
+ return content === void 0 || Object.keys(content).length === 0;
19512
+ })();
19513
+ return { status: code, isVoid, responseContentType: detectResponseContentType(resp) };
19514
+ }
19515
+ return httpMethod === "delete" ? { status: 204, isVoid: true, responseContentType: "application/json" } : { status: 200, isVoid: false, responseContentType: "application/json" };
19270
19516
  }
19271
19517
  function collectOperations2(spec) {
19272
19518
  const paths = spec.paths;
@@ -19302,7 +19548,9 @@ function collectOperations2(spec) {
19302
19548
  function collectSortedBodyTypes(operations) {
19303
19549
  const bodyTypes = /* @__PURE__ */ new Set();
19304
19550
  for (const op of operations) {
19305
- if (op.bodyInfo?.typeName !== void 0) bodyTypes.add(op.bodyInfo.typeName);
19551
+ if (op.bodyInfo?.typeName !== void 0 && !op.bodyInfo.isSynthesized) {
19552
+ bodyTypes.add(op.bodyInfo.typeName);
19553
+ }
19306
19554
  }
19307
19555
  return Array.from(bodyTypes).sort();
19308
19556
  }
@@ -19334,7 +19582,27 @@ function buildRouteHandler(op, indent, schemaNames) {
19334
19582
  lines.push(`${indent} }`);
19335
19583
  }
19336
19584
  if (op.queryParams.length > 0) {
19585
+ const deepObjectParams = op.queryParams.filter((q) => q.isDeepObject === true);
19586
+ if (deepObjectParams.length > 0) {
19587
+ lines.push(`${indent} const _dq = c.req.queries()`);
19588
+ for (const q of deepObjectParams) {
19589
+ const prefixLen = q.rawName.length + 1;
19590
+ const bracketPrefix = q.rawName + "[";
19591
+ lines.push(`${indent} const ${q.name} = Object.fromEntries(`);
19592
+ lines.push(
19593
+ `${indent} Object.entries(_dq).filter(([k]) => k.startsWith('${bracketPrefix}') && k.endsWith(']')).map(([k, vs]) => [k.slice(${prefixLen}, -1), vs[0]])`
19594
+ );
19595
+ lines.push(`${indent} )`);
19596
+ }
19597
+ }
19337
19598
  const fields = op.queryParams.map((q) => {
19599
+ if (q.isDeepObject === true) {
19600
+ return ` ${q.name}`;
19601
+ }
19602
+ if (q.delimiterStyle !== void 0) {
19603
+ const delim = JSON.stringify(delimiterChar(q.delimiterStyle));
19604
+ return ` ${q.name}: c.req.query('${q.rawName}') !== undefined ? c.req.query('${q.rawName}')!.split(${delim}) : undefined`;
19605
+ }
19338
19606
  if (q.tsType === "number") {
19339
19607
  return ` ${q.name}: c.req.query('${q.name}') !== undefined ? Number(c.req.query('${q.name}')) : undefined`;
19340
19608
  }
@@ -19362,8 +19630,32 @@ function buildRouteHandler(op, indent, schemaNames) {
19362
19630
  }
19363
19631
  let bodyVarName = "body";
19364
19632
  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}()`);
19633
+ const typeDecl = op.bodyInfo.typeName !== void 0 && !op.bodyInfo.isSynthesized ? op.bodyInfo.typeName : "unknown";
19634
+ if (op.bodyInfo.contentType === "application/x-www-form-urlencoded") {
19635
+ lines.push(`${indent} const _ct = c.req.header('content-type') ?? ''`);
19636
+ lines.push(
19637
+ `${indent} if (!_ct.toLowerCase().startsWith('application/x-www-form-urlencoded')) {`
19638
+ );
19639
+ lines.push(`${indent} return c.json({ error: 'Unsupported Media Type' }, 415)`);
19640
+ lines.push(`${indent} }`);
19641
+ lines.push(`${indent} const body: unknown = await c.req.parseBody()`);
19642
+ } else if (op.bodyInfo.contentType === "multipart/form-data") {
19643
+ lines.push(
19644
+ `${indent} // multipart/form-data: parseBody({ all: true }) collects repeated keys into arrays.`
19645
+ );
19646
+ lines.push(`${indent} const body: unknown = await c.req.parseBody({ all: true })`);
19647
+ } else {
19648
+ lines.push(`${indent} const _ct = c.req.header('content-type') ?? ''`);
19649
+ lines.push(`${indent} if (!_ct.toLowerCase().startsWith('application/json')) {`);
19650
+ lines.push(`${indent} return c.json({ error: 'Unsupported Media Type' }, 415)`);
19651
+ lines.push(`${indent} }`);
19652
+ lines.push(`${indent} let body: ${typeDecl}`);
19653
+ lines.push(`${indent} try {`);
19654
+ lines.push(`${indent} body = JSON.parse(await c.req.text()) as ${typeDecl}`);
19655
+ lines.push(`${indent} } catch {`);
19656
+ lines.push(`${indent} return c.json({ error: 'Invalid JSON body' }, 400)`);
19657
+ lines.push(`${indent} }`);
19658
+ }
19367
19659
  const schemaName = op.bodyInfo.typeName !== void 0 ? `${op.bodyInfo.typeName}Schema` : void 0;
19368
19660
  if (schemaName !== void 0 && schemaNames !== void 0 && schemaNames.has(schemaName)) {
19369
19661
  lines.push(`${indent} // Validate request body: returns 422 with Zod issues on failure`);
@@ -19388,14 +19680,44 @@ function buildRouteHandler(op, indent, schemaNames) {
19388
19680
  serviceArgs.push("params");
19389
19681
  }
19390
19682
  const serviceCall = `service.${op.methodName}(${serviceArgs.join(", ")})`;
19683
+ lines.push(`${indent} try {`);
19391
19684
  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)`);
19685
+ lines.push(`${indent} await ${serviceCall}`);
19686
+ lines.push(`${indent} return new Response(null, { status: ${op.responseStatus.status} })`);
19687
+ } else if (op.responseStatus.isMultiStatus === true) {
19688
+ lines.push(`${indent} const _envelope = await ${serviceCall}`);
19689
+ lines.push(`${indent} return c.json(_envelope.body, _envelope.status as any)`);
19690
+ } else if (op.responseStatus.responseContentType === "text/plain") {
19691
+ if (op.responseStatus.status === 200) {
19692
+ lines.push(`${indent} return c.text(await ${serviceCall})`);
19693
+ } else {
19694
+ lines.push(`${indent} return c.text(await ${serviceCall}, ${op.responseStatus.status})`);
19695
+ }
19696
+ } else if (op.responseStatus.responseContentType === "application/octet-stream") {
19697
+ if (op.responseStatus.status === 200) {
19698
+ lines.push(`${indent} const _result = await ${serviceCall}`);
19699
+ lines.push(
19700
+ `${indent} return new Response(_result, { headers: { 'content-type': 'application/octet-stream' } })`
19701
+ );
19702
+ } else {
19703
+ lines.push(`${indent} const _result = await ${serviceCall}`);
19704
+ lines.push(
19705
+ `${indent} return new Response(_result, { status: ${op.responseStatus.status}, headers: { 'content-type': 'application/octet-stream' } })`
19706
+ );
19707
+ }
19708
+ } else if (op.responseStatus.status === 200) {
19709
+ lines.push(`${indent} return c.json(await ${serviceCall})`);
19396
19710
  } else {
19397
- lines.push(`${indent} return c.json(await ${serviceCall})`);
19711
+ lines.push(`${indent} return c.json(await ${serviceCall}, ${op.responseStatus.status})`);
19398
19712
  }
19713
+ lines.push(`${indent} } catch (err) {`);
19714
+ lines.push(`${indent} if (err instanceof HttpError) {`);
19715
+ lines.push(
19716
+ `${indent} return new Response(JSON.stringify({ error: err.message }), { status: err.status, headers: { 'content-type': 'application/json' } })`
19717
+ );
19718
+ lines.push(`${indent} }`);
19719
+ lines.push(`${indent} throw err`);
19720
+ lines.push(`${indent} }`);
19399
19721
  lines.push(`${indent}})`);
19400
19722
  return lines.join("\n");
19401
19723
  }
@@ -19414,6 +19736,13 @@ function buildExpressRouteHandler(op, indent, schemaNames) {
19414
19736
  }
19415
19737
  if (op.queryParams.length > 0) {
19416
19738
  const fields = op.queryParams.map((q) => {
19739
+ if (q.isDeepObject === true) {
19740
+ return ` ${q.name}: (req.query['${q.rawName}'] ?? {}) as Record<string, string | undefined>`;
19741
+ }
19742
+ if (q.delimiterStyle !== void 0) {
19743
+ const delim = JSON.stringify(delimiterChar(q.delimiterStyle));
19744
+ return ` ${q.name}: typeof req.query['${q.rawName}'] === 'string' ? (req.query['${q.rawName}'] as string).split(${delim}) : undefined`;
19745
+ }
19417
19746
  if (q.tsType === "number") {
19418
19747
  return ` ${q.name}: Number(req.query['${q.name}'] as string)`;
19419
19748
  }
@@ -19444,21 +19773,28 @@ function buildExpressRouteHandler(op, indent, schemaNames) {
19444
19773
  }
19445
19774
  let bodyVarName = "body";
19446
19775
  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) {`);
19776
+ if (op.bodyInfo.contentType === "multipart/form-data") {
19453
19777
  lines.push(
19454
- `${indent} return void res.status(422).json({ error: 'Invalid request body', issues: parseResult.error.issues })`
19778
+ `${indent} // multipart/form-data: assumes multer middleware has populated req.files + req.body.`
19455
19779
  );
19456
- lines.push(`${indent} }`);
19457
- lines.push(`${indent} const validatedBody = parseResult.data`);
19458
- bodyVarName = "validatedBody";
19780
+ lines.push(`${indent} const body = { ...req.body, ...(req as any).files } as unknown`);
19459
19781
  } else {
19460
- const typeAnnotation = op.bodyInfo.typeName !== void 0 ? ` as ${op.bodyInfo.typeName}` : "";
19461
- lines.push(`${indent} const body = req.body${typeAnnotation}`);
19782
+ const schemaName = op.bodyInfo.typeName !== void 0 ? `${op.bodyInfo.typeName}Schema` : void 0;
19783
+ const useZod = schemaName !== void 0 && schemaNames !== void 0 && schemaNames.has(schemaName);
19784
+ if (useZod) {
19785
+ lines.push(`${indent} // Validate request body: returns 422 with Zod issues on failure`);
19786
+ lines.push(`${indent} const parseResult = ${schemaName}.safeParse(req.body)`);
19787
+ lines.push(`${indent} if (!parseResult.success) {`);
19788
+ lines.push(
19789
+ `${indent} return void res.status(422).json({ error: 'Invalid request body', issues: parseResult.error.issues })`
19790
+ );
19791
+ lines.push(`${indent} }`);
19792
+ lines.push(`${indent} const validatedBody = parseResult.data`);
19793
+ bodyVarName = "validatedBody";
19794
+ } else {
19795
+ const typeAnnotation = op.bodyInfo.typeName !== void 0 && !op.bodyInfo.isSynthesized ? ` as ${op.bodyInfo.typeName}` : "";
19796
+ lines.push(`${indent} const body = req.body${typeAnnotation}`);
19797
+ }
19462
19798
  }
19463
19799
  }
19464
19800
  const serviceArgs = [];
@@ -19472,14 +19808,42 @@ function buildExpressRouteHandler(op, indent, schemaNames) {
19472
19808
  serviceArgs.push("params");
19473
19809
  }
19474
19810
  const serviceCall = `service.${op.methodName}(${serviceArgs.join(", ")})`;
19811
+ lines.push(`${indent} try {`);
19475
19812
  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})`);
19813
+ lines.push(`${indent} await ${serviceCall}`);
19814
+ lines.push(`${indent} res.status(${op.responseStatus.status}).end()`);
19815
+ } else if (op.responseStatus.isMultiStatus === true) {
19816
+ lines.push(`${indent} const _envelope = await ${serviceCall}`);
19817
+ lines.push(`${indent} res.status(_envelope.status).json(_envelope.body)`);
19818
+ } else if (op.responseStatus.responseContentType === "text/plain") {
19819
+ if (op.responseStatus.status === 200) {
19820
+ lines.push(`${indent} res.type('text/plain').send(await ${serviceCall})`);
19821
+ } else {
19822
+ lines.push(
19823
+ `${indent} res.status(${op.responseStatus.status}).type('text/plain').send(await ${serviceCall})`
19824
+ );
19825
+ }
19826
+ } else if (op.responseStatus.responseContentType === "application/octet-stream") {
19827
+ if (op.responseStatus.status === 200) {
19828
+ lines.push(
19829
+ `${indent} res.setHeader('Content-Type', 'application/octet-stream').send(Buffer.from(await ${serviceCall}))`
19830
+ );
19831
+ } else {
19832
+ lines.push(
19833
+ `${indent} res.status(${op.responseStatus.status}).setHeader('Content-Type', 'application/octet-stream').send(Buffer.from(await ${serviceCall}))`
19834
+ );
19835
+ }
19836
+ } else if (op.responseStatus.status === 200) {
19837
+ lines.push(`${indent} res.json(await ${serviceCall})`);
19480
19838
  } else {
19481
- lines.push(`${indent} res.json(await ${serviceCall})`);
19482
- }
19839
+ lines.push(`${indent} res.status(${op.responseStatus.status}).json(await ${serviceCall})`);
19840
+ }
19841
+ lines.push(`${indent} } catch (err) {`);
19842
+ lines.push(`${indent} if (err instanceof HttpError) {`);
19843
+ lines.push(`${indent} return void res.status(err.status).json({ error: err.message })`);
19844
+ lines.push(`${indent} }`);
19845
+ lines.push(`${indent} throw err`);
19846
+ lines.push(`${indent} }`);
19483
19847
  lines.push(`${indent}})`);
19484
19848
  return lines.join("\n");
19485
19849
  }
@@ -19487,14 +19851,23 @@ function buildFastifyRouteHandler(op, indent, schemaNames) {
19487
19851
  const lines = [];
19488
19852
  const genericParts = [];
19489
19853
  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) {
19854
+ const hasDeepOrDelimited = op.queryParams.some(
19855
+ (q) => q.isDeepObject === true || q.delimiterStyle !== void 0
19856
+ );
19857
+ let querystringType;
19858
+ if (hasDeepOrDelimited) {
19859
+ querystringType = "Record<string, string | string[] | undefined>";
19860
+ } else {
19861
+ const queryFields = op.queryParams.map((q) => {
19862
+ if (q.tsType === "number") return `${q.name}?: number`;
19863
+ if (q.tsType === "boolean") return `${q.name}?: boolean`;
19864
+ return `${q.name}?: string`;
19865
+ }).join("; ");
19866
+ querystringType = `{ ${queryFields} }`;
19867
+ }
19868
+ genericParts.push(`Querystring: ${querystringType}`);
19869
+ }
19870
+ if (op.bodyInfo !== void 0 && op.bodyInfo.typeName !== void 0 && !op.bodyInfo.isSynthesized) {
19498
19871
  genericParts.push(`Body: ${op.bodyInfo.typeName}`);
19499
19872
  } else if (op.bodyInfo !== void 0) {
19500
19873
  genericParts.push("Body: unknown");
@@ -19517,7 +19890,36 @@ function buildFastifyRouteHandler(op, indent, schemaNames) {
19517
19890
  lines.push(`${indent} }`);
19518
19891
  }
19519
19892
  if (op.queryParams.length > 0) {
19520
- const fields = op.queryParams.map((q) => ` ${q.name}: req.query.${q.name}`).join(",\n");
19893
+ const deepObjectParams = op.queryParams.filter((q) => q.isDeepObject === true);
19894
+ const hasDeepOrDelimited = op.queryParams.some(
19895
+ (q) => q.isDeepObject === true || q.delimiterStyle !== void 0
19896
+ );
19897
+ if (hasDeepOrDelimited) {
19898
+ lines.push(
19899
+ `${indent} const _dq = req.query as unknown as Record<string, string | undefined>`
19900
+ );
19901
+ }
19902
+ if (deepObjectParams.length > 0) {
19903
+ for (const q of deepObjectParams) {
19904
+ const prefixLen = q.rawName.length + 1;
19905
+ const bracketPrefix = q.rawName + "[";
19906
+ lines.push(`${indent} const ${q.name} = Object.fromEntries(`);
19907
+ lines.push(
19908
+ `${indent} Object.entries(_dq).filter(([k]) => k.startsWith('${bracketPrefix}') && k.endsWith(']')).map(([k, v]) => [k.slice(${prefixLen}, -1), v])`
19909
+ );
19910
+ lines.push(`${indent} )`);
19911
+ }
19912
+ }
19913
+ const fields = op.queryParams.map((q) => {
19914
+ if (q.isDeepObject === true) {
19915
+ return ` ${q.name}`;
19916
+ }
19917
+ if (q.delimiterStyle !== void 0) {
19918
+ const delim = JSON.stringify(delimiterChar(q.delimiterStyle));
19919
+ return ` ${q.name}: typeof _dq['${q.rawName}'] === 'string' ? _dq['${q.rawName}']!.split(${delim}) : undefined`;
19920
+ }
19921
+ return hasDeepOrDelimited ? ` ${q.name}: _dq['${q.rawName}']` : ` ${q.name}: req.query.${q.name}`;
19922
+ }).join(",\n");
19521
19923
  lines.push(`${indent} const params = {`);
19522
19924
  lines.push(fields);
19523
19925
  lines.push(`${indent} }`);
@@ -19542,17 +19944,23 @@ function buildFastifyRouteHandler(op, indent, schemaNames) {
19542
19944
  }
19543
19945
  let bodyVarName = "req.body";
19544
19946
  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) {`);
19947
+ if (op.bodyInfo.contentType === "multipart/form-data") {
19551
19948
  lines.push(
19552
- `${indent} return reply.status(422).send({ error: 'Invalid request body', issues: parseResult.error.issues })`
19949
+ `${indent} // multipart/form-data: assumes @fastify/multipart plugin has populated req.body.`
19553
19950
  );
19554
- lines.push(`${indent} }`);
19555
- bodyVarName = "parseResult.data";
19951
+ } else {
19952
+ const schemaName = op.bodyInfo.typeName !== void 0 ? `${op.bodyInfo.typeName}Schema` : void 0;
19953
+ const useZod = schemaName !== void 0 && schemaNames !== void 0 && schemaNames.has(schemaName);
19954
+ if (useZod) {
19955
+ lines.push(`${indent} // Validate request body: returns 422 with Zod issues on failure`);
19956
+ lines.push(`${indent} const parseResult = ${schemaName}.safeParse(req.body)`);
19957
+ lines.push(`${indent} if (!parseResult.success) {`);
19958
+ lines.push(
19959
+ `${indent} return reply.status(422).send({ error: 'Invalid request body', issues: parseResult.error.issues })`
19960
+ );
19961
+ lines.push(`${indent} }`);
19962
+ bodyVarName = "parseResult.data";
19963
+ }
19556
19964
  }
19557
19965
  }
19558
19966
  const serviceArgs = [];
@@ -19566,18 +19974,50 @@ function buildFastifyRouteHandler(op, indent, schemaNames) {
19566
19974
  serviceArgs.push("params");
19567
19975
  }
19568
19976
  const serviceCall = `service.${op.methodName}(${serviceArgs.join(", ")})`;
19977
+ lines.push(`${indent} try {`);
19569
19978
  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}`);
19979
+ lines.push(`${indent} await ${serviceCall}`);
19980
+ lines.push(`${indent} reply.status(${op.responseStatus.status}).send()`);
19981
+ } else if (op.responseStatus.isMultiStatus === true) {
19982
+ lines.push(`${indent} const _envelope = await ${serviceCall}`);
19983
+ lines.push(`${indent} return reply.status(_envelope.status).send(_envelope.body)`);
19984
+ } else if (op.responseStatus.responseContentType === "text/plain") {
19985
+ if (op.responseStatus.status === 200) {
19986
+ lines.push(`${indent} return reply.type('text/plain').send(await ${serviceCall})`);
19987
+ } else {
19988
+ lines.push(`${indent} return reply.status(${op.responseStatus.status}).type('text/plain').send(await ${serviceCall})`);
19989
+ }
19990
+ } else if (op.responseStatus.responseContentType === "application/octet-stream") {
19991
+ if (op.responseStatus.status === 200) {
19992
+ lines.push(`${indent} return reply.type('application/octet-stream').send(Buffer.from(await ${serviceCall}))`);
19993
+ } else {
19994
+ lines.push(`${indent} return reply.status(${op.responseStatus.status}).type('application/octet-stream').send(Buffer.from(await ${serviceCall}))`);
19995
+ }
19996
+ } else if (op.responseStatus.status === 200) {
19997
+ lines.push(`${indent} return ${serviceCall}`);
19575
19998
  } else {
19576
- lines.push(`${indent} return ${serviceCall}`);
19577
- }
19999
+ lines.push(`${indent} reply.status(${op.responseStatus.status})`);
20000
+ lines.push(`${indent} return ${serviceCall}`);
20001
+ }
20002
+ lines.push(`${indent} } catch (err) {`);
20003
+ lines.push(`${indent} if (err instanceof HttpError) {`);
20004
+ lines.push(`${indent} return reply.status(err.status).send({ error: err.message })`);
20005
+ lines.push(`${indent} }`);
20006
+ lines.push(`${indent} throw err`);
20007
+ lines.push(`${indent} }`);
19578
20008
  lines.push(`${indent}})`);
19579
20009
  return lines.join("\n");
19580
20010
  }
20011
+ function httpErrorClassLines() {
20012
+ return [
20013
+ "export class HttpError extends Error {",
20014
+ " constructor(public readonly status: number, message: string) {",
20015
+ " super(message)",
20016
+ " this.name = 'HttpError'",
20017
+ " }",
20018
+ "}"
20019
+ ];
20020
+ }
19581
20021
  function operationsNeedZodForParams(operations) {
19582
20022
  for (const op of operations) {
19583
20023
  if (op.pathParamValidations.length > 0) return true;
@@ -19610,6 +20050,8 @@ function generateExpressRouter(spec, options) {
19610
20050
  lines.push(`import { ${sortedUsedSchemas.join(", ")} } from '${options.schemaImportPath}'`);
19611
20051
  }
19612
20052
  lines.push("");
20053
+ for (const l of httpErrorClassLines()) lines.push(l);
20054
+ lines.push("");
19613
20055
  lines.push(`export function createRouter(service: ${serviceName}): Router {`);
19614
20056
  lines.push(" const router = Router()");
19615
20057
  lines.push("");
@@ -19645,6 +20087,8 @@ function generateFastifyRouter(spec, options) {
19645
20087
  lines.push(`import { ${sortedUsedSchemas.join(", ")} } from '${options.schemaImportPath}'`);
19646
20088
  }
19647
20089
  lines.push("");
20090
+ for (const l of httpErrorClassLines()) lines.push(l);
20091
+ lines.push("");
19648
20092
  lines.push(`export function createRouter(app: FastifyInstance, service: ${serviceName}): void {`);
19649
20093
  for (const op of operations) {
19650
20094
  lines.push("");
@@ -19677,6 +20121,8 @@ function generateRouter(spec, options) {
19677
20121
  lines.push(`import { ${sortedUsedSchemas.join(", ")} } from '${options.schemaImportPath}'`);
19678
20122
  }
19679
20123
  lines.push("");
20124
+ for (const l of httpErrorClassLines()) lines.push(l);
20125
+ lines.push("");
19680
20126
  lines.push(`export function createRouter(service: ${serviceName}): Hono {`);
19681
20127
  lines.push(" const app = new Hono()");
19682
20128
  lines.push("");