@contractkit/plugin-typescript 0.28.2 → 0.30.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/index.js CHANGED
@@ -31,41 +31,41 @@ function headerNameToProperty(name) {
31
31
  }).join("");
32
32
  }
33
33
  __name(headerNameToProperty, "headerNameToProperty");
34
- function renderTsType(type) {
34
+ function renderTsType(type, target = "client") {
35
35
  switch (type.kind) {
36
36
  case "scalar":
37
- return renderTsScalar(type.name);
37
+ return renderTsScalar(type.name, target);
38
38
  case "array": {
39
- const inner = renderTsType(type.item);
39
+ const inner = renderTsType(type.item, target);
40
40
  const needsParens = type.item.kind === "union" || type.item.kind === "discriminatedUnion" || type.item.kind === "intersection" || type.item.kind === "enum";
41
41
  return needsParens ? `(${inner})[]` : `${inner}[]`;
42
42
  }
43
43
  case "tuple":
44
- return `[${type.items.map(renderTsType).join(", ")}]`;
44
+ return `[${type.items.map((i) => renderTsType(i, target)).join(", ")}]`;
45
45
  case "record":
46
- return `Record<${renderTsType(type.key)}, ${renderTsType(type.value)}>`;
46
+ return `Record<${renderTsType(type.key, target)}, ${renderTsType(type.value, target)}>`;
47
47
  case "enum":
48
48
  return type.values.map((v) => `'${escapeSingleQuoted(v)}'`).join(" | ");
49
49
  case "literal":
50
50
  return typeof type.value === "string" ? `'${escapeSingleQuoted(type.value)}'` : String(type.value);
51
51
  case "union":
52
- return type.members.map(renderTsType).join(" | ");
52
+ return type.members.map((m) => renderTsType(m, target)).join(" | ");
53
53
  case "discriminatedUnion":
54
- return type.members.map(renderTsType).join(" | ");
54
+ return type.members.map((m) => renderTsType(m, target)).join(" | ");
55
55
  case "intersection":
56
- return type.members.map(renderTsType).join(" & ");
56
+ return type.members.map((m) => renderTsType(m, target)).join(" & ");
57
57
  case "ref":
58
58
  return type.name;
59
59
  case "lazy":
60
- return renderTsType(type.inner);
60
+ return renderTsType(type.inner, target);
61
61
  case "inlineObject":
62
- return renderTsInlineObject(type.fields);
62
+ return renderTsInlineObject(type.fields, target);
63
63
  default:
64
64
  return "unknown";
65
65
  }
66
66
  }
67
67
  __name(renderTsType, "renderTsType");
68
- function renderTsScalar(name) {
68
+ function renderTsScalar(name, target) {
69
69
  switch (name) {
70
70
  case "string":
71
71
  case "email":
@@ -92,7 +92,7 @@ function renderTsScalar(name) {
92
92
  case "object":
93
93
  return "Record<string, unknown>";
94
94
  case "binary":
95
- return "Blob";
95
+ return target === "server" ? "Buffer" : "Blob";
96
96
  case "json":
97
97
  return "JsonValue";
98
98
  default: {
@@ -102,61 +102,61 @@ function renderTsScalar(name) {
102
102
  }
103
103
  }
104
104
  __name(renderTsScalar, "renderTsScalar");
105
- function renderTsInlineObject(fields) {
105
+ function renderTsInlineObject(fields, target) {
106
106
  const entries = fields.map((f) => {
107
107
  const opt = f.optional ? "?" : "";
108
- return `${quoteKey(f.name)}${opt}: ${renderTsType(f.type)}`;
108
+ return `${quoteKey(f.name)}${opt}: ${renderTsType(f.type, target)}`;
109
109
  });
110
110
  return `{ ${entries.join("; ")} }`;
111
111
  }
112
112
  __name(renderTsInlineObject, "renderTsInlineObject");
113
- function renderInputTsType(type, modelsWithInput) {
114
- if (!modelsWithInput || modelsWithInput.size === 0) return renderTsType(type);
113
+ function renderInputTsType(type, modelsWithInput, target = "client") {
114
+ if (!modelsWithInput || modelsWithInput.size === 0) return renderTsType(type, target);
115
115
  switch (type.kind) {
116
116
  case "ref":
117
117
  return modelsWithInput.has(type.name) ? `${type.name}Input` : type.name;
118
118
  case "array": {
119
- const inner = renderInputTsType(type.item, modelsWithInput);
119
+ const inner = renderInputTsType(type.item, modelsWithInput, target);
120
120
  const needsParens = type.item.kind === "union" || type.item.kind === "discriminatedUnion" || type.item.kind === "intersection" || type.item.kind === "enum";
121
121
  return needsParens ? `(${inner})[]` : `${inner}[]`;
122
122
  }
123
123
  case "intersection":
124
- return type.members.map((m) => renderInputTsType(m, modelsWithInput)).join(" & ");
124
+ return type.members.map((m) => renderInputTsType(m, modelsWithInput, target)).join(" & ");
125
125
  case "union":
126
- return type.members.map((m) => renderInputTsType(m, modelsWithInput)).join(" | ");
126
+ return type.members.map((m) => renderInputTsType(m, modelsWithInput, target)).join(" | ");
127
127
  case "discriminatedUnion":
128
- return type.members.map((m) => renderInputTsType(m, modelsWithInput)).join(" | ");
128
+ return type.members.map((m) => renderInputTsType(m, modelsWithInput, target)).join(" | ");
129
129
  case "inlineObject":
130
- return `{ ${type.fields.map((f) => `${quoteKey(f.name)}${f.optional ? "?" : ""}: ${renderInputTsType(f.type, modelsWithInput)}`).join("; ")} }`;
130
+ return `{ ${type.fields.map((f) => `${quoteKey(f.name)}${f.optional ? "?" : ""}: ${renderInputTsType(f.type, modelsWithInput, target)}`).join("; ")} }`;
131
131
  case "lazy":
132
- return renderInputTsType(type.inner, modelsWithInput);
132
+ return renderInputTsType(type.inner, modelsWithInput, target);
133
133
  default:
134
- return renderTsType(type);
134
+ return renderTsType(type, target);
135
135
  }
136
136
  }
137
137
  __name(renderInputTsType, "renderInputTsType");
138
- function renderOutputTsType(type, modelsWithOutput) {
139
- if (!modelsWithOutput || modelsWithOutput.size === 0) return renderTsType(type);
138
+ function renderOutputTsType(type, modelsWithOutput, target = "client") {
139
+ if (!modelsWithOutput || modelsWithOutput.size === 0) return renderTsType(type, target);
140
140
  switch (type.kind) {
141
141
  case "ref":
142
142
  return modelsWithOutput.has(type.name) ? `${type.name}Output` : type.name;
143
143
  case "array": {
144
- const inner = renderOutputTsType(type.item, modelsWithOutput);
144
+ const inner = renderOutputTsType(type.item, modelsWithOutput, target);
145
145
  const needsParens = type.item.kind === "union" || type.item.kind === "discriminatedUnion" || type.item.kind === "intersection" || type.item.kind === "enum";
146
146
  return needsParens ? `(${inner})[]` : `${inner}[]`;
147
147
  }
148
148
  case "intersection":
149
- return type.members.map((m) => renderOutputTsType(m, modelsWithOutput)).join(" & ");
149
+ return type.members.map((m) => renderOutputTsType(m, modelsWithOutput, target)).join(" & ");
150
150
  case "union":
151
- return type.members.map((m) => renderOutputTsType(m, modelsWithOutput)).join(" | ");
151
+ return type.members.map((m) => renderOutputTsType(m, modelsWithOutput, target)).join(" | ");
152
152
  case "discriminatedUnion":
153
- return type.members.map((m) => renderOutputTsType(m, modelsWithOutput)).join(" | ");
153
+ return type.members.map((m) => renderOutputTsType(m, modelsWithOutput, target)).join(" | ");
154
154
  case "inlineObject":
155
- return `{ ${type.fields.map((f) => `${quoteKey(f.name)}${f.optional ? "?" : ""}: ${renderOutputTsType(f.type, modelsWithOutput)}`).join("; ")} }`;
155
+ return `{ ${type.fields.map((f) => `${quoteKey(f.name)}${f.optional ? "?" : ""}: ${renderOutputTsType(f.type, modelsWithOutput, target)}`).join("; ")} }`;
156
156
  case "lazy":
157
- return renderOutputTsType(type.inner, modelsWithOutput);
157
+ return renderOutputTsType(type.inner, modelsWithOutput, target);
158
158
  default:
159
- return renderTsType(type);
159
+ return renderTsType(type, target);
160
160
  }
161
161
  }
162
162
  __name(renderOutputTsType, "renderOutputTsType");
@@ -1182,33 +1182,6 @@ function generateOp(root, options = {}) {
1182
1182
  const types = collectTypes(root, options.modelsWithInput, options.modelsWithOutput);
1183
1183
  const services = collectServices(root);
1184
1184
  const routerName = deriveRouterName(root.file);
1185
- const needsParseAndValidate = routeNeedsValidation(root);
1186
- const body = [];
1187
- const needsSignature = fileNeedsSignature(root);
1188
- const needsPolicy = fileNeedsPolicy(root);
1189
- const koaImports = [
1190
- "ServerKitRouter",
1191
- "bodyParserMiddleware"
1192
- ];
1193
- if (needsPolicy) koaImports.push("requirePolicy");
1194
- if (needsSignature) koaImports.push("requireSignature");
1195
- body.push(`import { ${koaImports.join(", ")} } from '@maroonedsoftware/koa';`);
1196
- for (const svc of services) {
1197
- const modulePath = root.services?.[svc] ?? root.meta[svc] ?? deriveModulePath(svc, options.servicePathTemplate);
1198
- body.push(`import { ${svc} } from '${modulePath}';`);
1199
- }
1200
- if (types.length > 0) {
1201
- body.push(...generateTypeImports(types, root.file, options));
1202
- }
1203
- if (opNeedsDateTime(root)) {
1204
- body.push(`import { DateTime } from 'luxon';`);
1205
- }
1206
- if (needsParseAndValidate) {
1207
- body.push(`import { parseAndValidate } from '@maroonedsoftware/zod';`);
1208
- }
1209
- if (fileUsesMultipart(root)) {
1210
- body.push(`import { MultipartBody } from '@maroonedsoftware/multipart';`);
1211
- }
1212
1185
  const helpers = [];
1213
1186
  if (opNeedsScalar(root, "binary")) {
1214
1187
  helpers.push(`const _ZodBinary = z.custom<Buffer>((val) => Buffer.isBuffer(val), { error: 'Must be binary data' });`);
@@ -1216,6 +1189,9 @@ function generateOp(root, options = {}) {
1216
1189
  if (opNeedsScalar(root, "datetime")) {
1217
1190
  helpers.push(`const _ZodDatetime = z.preprocess((val) => typeof val === 'string' ? DateTime.fromISO(val) : val, z.custom<DateTime>((val) => val instanceof DateTime && val.isValid, { message: 'Must be in ISO 8601 format' }));`);
1218
1191
  }
1192
+ if (opNeedsScalar(root, "interval")) {
1193
+ helpers.push(`const _ZodInterval = z.preprocess((val) => typeof val === 'string' ? Interval.fromISO(val) : val, z.custom<Interval>((val) => val instanceof Interval && val.isValid, { message: 'Must be an ISO 8601 interval' })).transform(val => val.toISO()!);`);
1194
+ }
1219
1195
  if (opNeedsScalar(root, "json")) {
1220
1196
  helpers.push(`type _JsonValue = string | number | boolean | null | _JsonValue[] | { [key: string]: _JsonValue };`);
1221
1197
  helpers.push(`const _ZodJson: z.ZodType<_JsonValue> = z.lazy(() => z.union([z.string(), z.number(), z.boolean(), z.null(), z.array(_ZodJson), z.record(z.string(), _ZodJson)]));`);
@@ -1236,6 +1212,45 @@ function generateOp(root, options = {}) {
1236
1212
  lines.push("");
1237
1213
  }
1238
1214
  }
1215
+ const generated = [
1216
+ ...helpers.length ? [
1217
+ "",
1218
+ ...helpers
1219
+ ] : [],
1220
+ ...lines
1221
+ ].join("\n");
1222
+ const uses = /* @__PURE__ */ __name((symbol) => new RegExp(`\\b${symbol}\\b`).test(generated), "uses");
1223
+ const body = [];
1224
+ const koaImports = [
1225
+ "ServerKitRouter",
1226
+ "bodyParserMiddleware",
1227
+ "requirePolicy",
1228
+ "requireSignature"
1229
+ ].filter(uses);
1230
+ if (koaImports.length > 0) {
1231
+ body.push(`import { ${koaImports.join(", ")} } from '@maroonedsoftware/koa';`);
1232
+ }
1233
+ for (const svc of services) {
1234
+ const modulePath = root.services?.[svc] ?? root.meta[svc] ?? deriveModulePath(svc, options.servicePathTemplate);
1235
+ body.push(`import { ${svc} } from '${modulePath}';`);
1236
+ }
1237
+ if (types.length > 0) {
1238
+ body.push(...generateTypeImports(types, root.file, options));
1239
+ }
1240
+ const luxonImports = [
1241
+ "DateTime",
1242
+ "Duration",
1243
+ "Interval"
1244
+ ].filter(uses);
1245
+ if (luxonImports.length > 0) {
1246
+ body.push(`import { ${luxonImports.join(", ")} } from 'luxon';`);
1247
+ }
1248
+ if (uses("parseAndValidate")) {
1249
+ body.push(`import { parseAndValidate } from '@maroonedsoftware/zod';`);
1250
+ }
1251
+ if (uses("MultipartBody")) {
1252
+ body.push(`import { MultipartBody } from '@maroonedsoftware/multipart';`);
1253
+ }
1239
1254
  const allContent = [
1240
1255
  ...body,
1241
1256
  ...helpers.length ? [
@@ -1325,7 +1340,7 @@ function generateHandler(route, op, root, options) {
1325
1340
  const serviceParts = inferService(op, route, file);
1326
1341
  const respHeaders = primaryResponse2?.headers ?? [];
1327
1342
  const hasRespHeaders = respHeaders.length > 0;
1328
- const headersAnnotation = hasRespHeaders ? `{ ${respHeaders.map((h) => `${quoteKey(headerNameToProperty(h.name))}${h.optional ? "?" : ""}: ${renderOutputTsType(h.type, options.modelsWithOutput)}`).join("; ")} }` : "";
1343
+ const headersAnnotation = hasRespHeaders ? `{ ${respHeaders.map((h) => `${quoteKey(headerNameToProperty(h.name))}${h.optional ? "?" : ""}: ${renderOutputTsType(h.type, options.modelsWithOutput, "server")}`).join("; ")} }` : "";
1329
1344
  if (primaryResponse2?.bodyType) {
1330
1345
  const { annotation, prelude } = formatTypeAnnotation(primaryResponse2.bodyType, options.modelsWithOutput);
1331
1346
  if (prelude) {
@@ -1419,6 +1434,45 @@ function buildArgs(route, op) {
1419
1434
  return args.join(", ");
1420
1435
  }
1421
1436
  __name(buildArgs, "buildArgs");
1437
+ function serverTsScalar(name) {
1438
+ switch (name) {
1439
+ case "string":
1440
+ case "email":
1441
+ case "url":
1442
+ case "uuid":
1443
+ return "string";
1444
+ case "number":
1445
+ case "int":
1446
+ return "number";
1447
+ case "bigint":
1448
+ return "bigint";
1449
+ case "boolean":
1450
+ return "boolean";
1451
+ case "date":
1452
+ case "time":
1453
+ case "datetime":
1454
+ return "DateTime";
1455
+ case "duration":
1456
+ return "Duration";
1457
+ case "interval":
1458
+ return "string";
1459
+ case "binary":
1460
+ return "Buffer";
1461
+ case "json":
1462
+ return "_JsonValue";
1463
+ case "object":
1464
+ return "Record<string, unknown>";
1465
+ case "null":
1466
+ return "null";
1467
+ case "unknown":
1468
+ return "unknown";
1469
+ default: {
1470
+ const _exhaustive = name;
1471
+ throw new Error(`plugin-typescript: unmapped scalar '${String(_exhaustive)}' \u2014 add a case`);
1472
+ }
1473
+ }
1474
+ }
1475
+ __name(serverTsScalar, "serverTsScalar");
1422
1476
  function formatTypeAnnotation(bodyType, modelsWithOutput) {
1423
1477
  if (bodyType.kind === "array") {
1424
1478
  const inner = formatTypeAnnotation(bodyType.item, modelsWithOutput);
@@ -1434,7 +1488,7 @@ function formatTypeAnnotation(bodyType, modelsWithOutput) {
1434
1488
  };
1435
1489
  }
1436
1490
  if (bodyType.kind === "scalar") return {
1437
- annotation: bodyType.name
1491
+ annotation: serverTsScalar(bodyType.name)
1438
1492
  };
1439
1493
  const schema = renderType(bodyType);
1440
1494
  return {
@@ -1670,17 +1724,6 @@ function collectTypeNodeRefs(type, out) {
1670
1724
  }
1671
1725
  }
1672
1726
  __name(collectTypeNodeRefs, "collectTypeNodeRefs");
1673
- function paramSourceNeedsDateTime(source) {
1674
- if (!source) return false;
1675
- if (source.kind === "ref") return false;
1676
- if (source.kind === "params") return source.nodes.some((p) => typeNeedsDateTime(p.type));
1677
- return typeNeedsDateTime(source.node);
1678
- }
1679
- __name(paramSourceNeedsDateTime, "paramSourceNeedsDateTime");
1680
- function opNeedsDateTime(root) {
1681
- return root.routes.some((route) => paramSourceNeedsDateTime(route.params) || route.operations.some((op) => !!op.request?.bodies.some((b) => typeNeedsDateTime(b.bodyType)) || op.responses.some((r) => r.bodyType && typeNeedsDateTime(r.bodyType)) || paramSourceNeedsDateTime(op.query) || paramSourceNeedsDateTime(op.headers)));
1682
- }
1683
- __name(opNeedsDateTime, "opNeedsDateTime");
1684
1727
  function paramSourceNeedsScalar(source, name) {
1685
1728
  if (!source) return false;
1686
1729
  if (source.kind === "ref") return false;
@@ -1709,29 +1752,6 @@ function collectServices(root) {
1709
1752
  ].sort();
1710
1753
  }
1711
1754
  __name(collectServices, "collectServices");
1712
- function hasParamSource(source) {
1713
- if (!source) return false;
1714
- if (source.kind === "ref") return true;
1715
- if (source.kind === "params") return source.nodes.length > 0;
1716
- return true;
1717
- }
1718
- __name(hasParamSource, "hasParamSource");
1719
- function routeNeedsValidation(root) {
1720
- return root.routes.some((r) => hasParamSource(r.params) || r.operations.some((op) => !!op.request || hasParamSource(op.query) || hasParamSource(op.headers)));
1721
- }
1722
- __name(routeNeedsValidation, "routeNeedsValidation");
1723
- function fileNeedsPolicy(root) {
1724
- return root.routes.some((route) => route.operations.some((op) => resolveSecurity(route, op, root) !== SECURITY_NONE));
1725
- }
1726
- __name(fileNeedsPolicy, "fileNeedsPolicy");
1727
- function fileNeedsSignature(root) {
1728
- return root.routes.some((route) => route.operations.some((op) => !!op.signature));
1729
- }
1730
- __name(fileNeedsSignature, "fileNeedsSignature");
1731
- function fileUsesMultipart(root) {
1732
- return root.routes.some((route) => route.operations.some((op) => (op.request?.bodies ?? []).some((b) => b.contentType === "multipart/form-data")));
1733
- }
1734
- __name(fileUsesMultipart, "fileUsesMultipart");
1735
1755
  function isValidIdentifier2(name) {
1736
1756
  return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
1737
1757
  }
@@ -2807,6 +2827,7 @@ __name(generateSdkAggregator, "generateSdkAggregator");
2807
2827
  import { relative as relative4, dirname as dirname4 } from "path";
2808
2828
  import { computeModelsWithOutput, collectExternalOutputRefs } from "@contractkit/core";
2809
2829
  function generatePlainTypes(root, context) {
2830
+ const target = context?.target ?? "client";
2810
2831
  const externalRefs = collectExternalRefs(root);
2811
2832
  const lines = [];
2812
2833
  const externalModelsWithInput = context?.modelsWithInput ?? /* @__PURE__ */ new Set();
@@ -2848,21 +2869,21 @@ function generatePlainTypes(root, context) {
2848
2869
  m
2849
2870
  ]));
2850
2871
  for (const model of topoSortModels(root.models)) {
2851
- lines.push(...generateModel2(model, context?.currentOutPath, allModelsWithInput, allModelsWithOutput, modelMap));
2872
+ lines.push(...generateModel2(model, target, context?.currentOutPath, allModelsWithInput, allModelsWithOutput, modelMap));
2852
2873
  lines.push("");
2853
2874
  }
2854
2875
  return lines.join("\n");
2855
2876
  }
2856
2877
  __name(generatePlainTypes, "generatePlainTypes");
2857
- function generateModel2(model, outPath, modelsWithInput, modelsWithOutput, modelMap) {
2878
+ function generateModel2(model, target, outPath, modelsWithInput, modelsWithOutput, modelMap) {
2858
2879
  if (model.type) {
2859
- return generateTypeAlias2(model, outPath, modelsWithInput, modelsWithOutput);
2880
+ return generateTypeAlias2(model, target, outPath, modelsWithInput, modelsWithOutput);
2860
2881
  }
2861
2882
  const needsInputSplit = model.fields.some((f) => f.visibility !== "normal") || (modelsWithInput?.has(model.name) ?? false);
2862
- const lines = needsInputSplit ? generateVisibilityModel(model, outPath, modelsWithInput, modelMap) : generateSimpleModel2(model, outPath, modelMap);
2883
+ const lines = needsInputSplit ? generateVisibilityModel(model, target, outPath, modelsWithInput, modelMap) : generateSimpleModel2(model, target, outPath, modelMap);
2863
2884
  if (modelsWithOutput?.has(model.name)) {
2864
2885
  lines.push("");
2865
- lines.push(...generateOutputModel(model, modelsWithOutput));
2886
+ lines.push(...generateOutputModel(model, target, modelsWithOutput));
2866
2887
  }
2867
2888
  return lines;
2868
2889
  }
@@ -2899,15 +2920,15 @@ function generateComments2(model, outPath) {
2899
2920
  return lines;
2900
2921
  }
2901
2922
  __name(generateComments2, "generateComments");
2902
- function generateTypeAlias2(model, outPath, modelsWithInput, modelsWithOutput) {
2923
+ function generateTypeAlias2(model, target, outPath, modelsWithInput, modelsWithOutput) {
2903
2924
  const lines = [];
2904
2925
  lines.push(...generateComments2(model, outPath));
2905
- lines.push(`export type ${model.name} = ${renderTsType(model.type)};`);
2926
+ lines.push(`export type ${model.name} = ${renderTsType(model.type, target)};`);
2906
2927
  if (modelsWithInput?.has(model.name)) {
2907
- lines.push(`export type ${model.name}Input = ${renderInputTsType(model.type, modelsWithInput)};`);
2928
+ lines.push(`export type ${model.name}Input = ${renderInputTsType(model.type, modelsWithInput, target)};`);
2908
2929
  }
2909
2930
  if (modelsWithOutput?.has(model.name)) {
2910
- lines.push(`export type ${model.name}Output = ${renderOutputTsType(model.type, modelsWithOutput)};`);
2931
+ lines.push(`export type ${model.name}Output = ${renderOutputTsType(model.type, modelsWithOutput, target)};`);
2911
2932
  }
2912
2933
  return lines;
2913
2934
  }
@@ -2920,20 +2941,20 @@ function buildExtendsClause(bases, overrideNames, baseNameResolver) {
2920
2941
  return ` extends ${wrapped.join(", ")}`;
2921
2942
  }
2922
2943
  __name(buildExtendsClause, "buildExtendsClause");
2923
- function generateSimpleModel2(model, outPath, modelMap) {
2944
+ function generateSimpleModel2(model, target, outPath, modelMap) {
2924
2945
  const lines = [];
2925
2946
  lines.push(...generateComments2(model, outPath));
2926
2947
  const bases = model.bases ?? [];
2927
2948
  const overrideNames = computeOverrideNames(model, modelMap);
2928
2949
  lines.push(`export interface ${model.name}${buildExtendsClause(bases, overrideNames, (b) => b)} {`);
2929
2950
  for (const field of model.fields) {
2930
- lines.push(` ${renderField2(field)}`);
2951
+ lines.push(` ${renderField2(field, target)}`);
2931
2952
  }
2932
2953
  lines.push("}");
2933
2954
  return lines;
2934
2955
  }
2935
2956
  __name(generateSimpleModel2, "generateSimpleModel");
2936
- function generateVisibilityModel(model, outPath, modelsWithInput, modelMap) {
2957
+ function generateVisibilityModel(model, target, outPath, modelsWithInput, modelMap) {
2937
2958
  const lines = [];
2938
2959
  lines.push(...generateComments2(model, outPath));
2939
2960
  const bases = model.bases ?? [];
@@ -2941,7 +2962,7 @@ function generateVisibilityModel(model, outPath, modelsWithInput, modelMap) {
2941
2962
  const readFields = model.fields.filter((f) => f.visibility !== "writeonly");
2942
2963
  lines.push(`export interface ${model.name}${buildExtendsClause(bases, overrideNames, (b) => b)} {`);
2943
2964
  for (const field of readFields) {
2944
- lines.push(` ${renderField2(field)}`);
2965
+ lines.push(` ${renderField2(field, target)}`);
2945
2966
  }
2946
2967
  lines.push("}");
2947
2968
  lines.push("");
@@ -2949,7 +2970,7 @@ function generateVisibilityModel(model, outPath, modelsWithInput, modelMap) {
2949
2970
  const inputResolver = /* @__PURE__ */ __name((b) => modelsWithInput?.has(b) ? `${b}Input` : b, "inputResolver");
2950
2971
  lines.push(`export interface ${model.name}Input${buildExtendsClause(bases, overrideNames, inputResolver)} {`);
2951
2972
  for (const field of writeFields) {
2952
- lines.push(` ${modelsWithInput ? renderInputField2(field, modelsWithInput) : renderField2(field)}`);
2973
+ lines.push(` ${modelsWithInput ? renderInputField2(field, modelsWithInput, target) : renderField2(field, target)}`);
2953
2974
  }
2954
2975
  lines.push("}");
2955
2976
  return lines;
@@ -2969,9 +2990,9 @@ ${body}
2969
2990
  ${line}`;
2970
2991
  }
2971
2992
  __name(withFieldJsDoc, "withFieldJsDoc");
2972
- function renderField2(field) {
2993
+ function renderField2(field, target) {
2973
2994
  const opt = field.optional || field.default !== void 0 ? "?" : "";
2974
- let typeStr = renderTsType(field.type);
2995
+ let typeStr = renderTsType(field.type, target);
2975
2996
  if (field.nullable) typeStr += " | null";
2976
2997
  const line = `${quoteKey(field.name)}${opt}: ${typeStr};`;
2977
2998
  const jsdocParts = [];
@@ -2980,9 +3001,9 @@ function renderField2(field) {
2980
3001
  return withFieldJsDoc(jsdocParts, line);
2981
3002
  }
2982
3003
  __name(renderField2, "renderField");
2983
- function renderInputField2(field, modelsWithInput) {
3004
+ function renderInputField2(field, modelsWithInput, target) {
2984
3005
  const opt = field.optional || field.default !== void 0 ? "?" : "";
2985
- let typeStr = renderInputTsType(field.type, modelsWithInput);
3006
+ let typeStr = renderInputTsType(field.type, modelsWithInput, target);
2986
3007
  if (field.nullable) typeStr += " | null";
2987
3008
  const line = `${quoteKey(field.name)}${opt}: ${typeStr};`;
2988
3009
  const jsdocParts = [];
@@ -3005,7 +3026,7 @@ function applyOutputCase(name, c) {
3005
3026
  return camelToPascal2(name);
3006
3027
  }
3007
3028
  __name(applyOutputCase, "applyOutputCase");
3008
- function generateOutputModel(model, modelsWithOutput) {
3029
+ function generateOutputModel(model, target, modelsWithOutput) {
3009
3030
  const lines = [];
3010
3031
  const outputCase = model.outputCase && model.outputCase !== "camel" ? model.outputCase : void 0;
3011
3032
  const readFields = model.fields.filter((f) => f.visibility !== "writeonly");
@@ -3013,23 +3034,23 @@ function generateOutputModel(model, modelsWithOutput) {
3013
3034
  const baseExt = model.bases?.[0] && modelsWithOutput.has(model.bases?.[0]) ? ` extends ${model.bases?.[0]}Output` : model.bases?.[0] ? ` extends ${model.bases?.[0]}` : "";
3014
3035
  lines.push(`export interface ${model.name}Output${baseExt} {`);
3015
3036
  for (const field of readFields) {
3016
- lines.push(` ${renderOutputField(field, model.outputCase, modelsWithOutput)}`);
3037
+ lines.push(` ${renderOutputField(field, model.outputCase, modelsWithOutput, target)}`);
3017
3038
  }
3018
3039
  lines.push("}");
3019
3040
  return lines;
3020
3041
  }
3021
3042
  lines.push(`export interface ${model.name}Output {`);
3022
3043
  for (const field of readFields) {
3023
- lines.push(` ${renderOutputField(field, outputCase, modelsWithOutput)}`);
3044
+ lines.push(` ${renderOutputField(field, outputCase, modelsWithOutput, target)}`);
3024
3045
  }
3025
3046
  lines.push("}");
3026
3047
  return lines;
3027
3048
  }
3028
3049
  __name(generateOutputModel, "generateOutputModel");
3029
- function renderOutputField(field, outputCase, modelsWithOutput) {
3050
+ function renderOutputField(field, outputCase, modelsWithOutput, target) {
3030
3051
  const opt = field.optional || field.default !== void 0 ? "?" : "";
3031
3052
  const key = applyOutputCase(field.name, outputCase);
3032
- let typeStr = renderOutputTsType(field.type, modelsWithOutput);
3053
+ let typeStr = renderOutputTsType(field.type, modelsWithOutput, target);
3033
3054
  if (field.nullable) typeStr += " | null";
3034
3055
  const line = `${quoteKey(key)}${opt}: ${typeStr};`;
3035
3056
  const jsdocParts = [];
@@ -3856,7 +3877,9 @@ function collectServerOutput(config, rootDir, inputs, units) {
3856
3877
  modelOutPaths: serverModelOutPaths,
3857
3878
  currentOutPath: typeOutPath,
3858
3879
  modelsWithInput,
3859
- modelsWithOutput
3880
+ modelsWithOutput,
3881
+ // These types are consumed by Koa handlers, so `binary` is a Buffer, not a Blob.
3882
+ target: "server"
3860
3883
  };
3861
3884
  const content = config.zod ? generateContract(ast, renderCtx) : generatePlainTypes(ast, renderCtx);
3862
3885
  return [
@@ -4368,7 +4391,8 @@ function collectTypesOutput(config, rootDir, inputs, units) {
4368
4391
  modelOutPaths,
4369
4392
  currentOutPath: outPath,
4370
4393
  modelsWithInput,
4371
- modelsWithOutput
4394
+ modelsWithOutput,
4395
+ target: config.target
4372
4396
  })
4373
4397
  }
4374
4398
  ], "render")