@contractkit/plugin-typescript 0.33.2 → 0.34.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.
Files changed (39) hide show
  1. package/.turbo/turbo-build$colon$ci.log +4 -4
  2. package/.turbo/turbo-test$colon$ci.log +26 -25
  3. package/CHANGELOG.md +273 -0
  4. package/dist/codegen-contract.d.ts +22 -6
  5. package/dist/codegen-contract.d.ts.map +1 -1
  6. package/dist/codegen-mcp.d.ts.map +1 -1
  7. package/dist/codegen-operation.d.ts.map +1 -1
  8. package/dist/codegen-plain-types.d.ts.map +1 -1
  9. package/dist/codegen-revive.d.ts +38 -4
  10. package/dist/codegen-revive.d.ts.map +1 -1
  11. package/dist/codegen-sdk.d.ts +2 -0
  12. package/dist/codegen-sdk.d.ts.map +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +413 -202
  16. package/dist/index.js.map +1 -1
  17. package/dist/path-utils.d.ts +9 -0
  18. package/dist/path-utils.d.ts.map +1 -1
  19. package/dist/ts-render.d.ts +14 -0
  20. package/dist/ts-render.d.ts.map +1 -1
  21. package/package.json +3 -2
  22. package/src/codegen-contract.ts +84 -68
  23. package/src/codegen-mcp.ts +12 -11
  24. package/src/codegen-operation.ts +32 -19
  25. package/src/codegen-plain-types.ts +15 -8
  26. package/src/codegen-revive.ts +140 -20
  27. package/src/codegen-sdk.ts +322 -63
  28. package/src/index.ts +46 -4
  29. package/src/path-utils.ts +10 -0
  30. package/src/ts-render.ts +26 -0
  31. package/tests/codegen-contract.test.ts +87 -32
  32. package/tests/codegen-mcp.test.ts +31 -0
  33. package/tests/codegen-operation.test.ts +68 -8
  34. package/tests/codegen-plain-types.test.ts +13 -5
  35. package/tests/codegen-sdk.test.ts +265 -10
  36. package/tests/codegen-server.test.ts +43 -1
  37. package/tests/helpers.ts +7 -2
  38. package/tests/pipeline.test.ts +51 -4
  39. package/tests/ts-render.test.ts +29 -0
package/dist/index.js CHANGED
@@ -6,10 +6,11 @@ import { resolve as resolve2, join as join2, relative as relative7, dirname as d
6
6
  import { existsSync, readFileSync, writeFileSync, mkdirSync, rmSync, readdirSync, rmdirSync } from "fs";
7
7
 
8
8
  // src/codegen-contract.ts
9
- import { relative, dirname } from "path";
9
+ import { relative as relative2, dirname as dirname2 } from "path";
10
10
  import { collectTypeRefs, computeModelsWithOutput as ckComputeModelsWithOutput, collectExternalOutputRefs as ckCollectExternalOutputRefs } from "@contractkit/core";
11
11
 
12
12
  // src/ts-render.ts
13
+ import { dirname, relative } from "path";
13
14
  var JSON_VALUE_TYPE_DECL = "export type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };";
14
15
  function quoteKey(name) {
15
16
  return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : `'${name}'`;
@@ -23,6 +24,12 @@ function escapeSingleQuoted(s) {
23
24
  return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
24
25
  }
25
26
  __name(escapeSingleQuoted, "escapeSingleQuoted");
27
+ function sourceLink(label, outPath, sourceFile, line) {
28
+ const rel = outPath ? relative(dirname(outPath), sourceFile) : sourceFile;
29
+ const href = rel.startsWith(".") ? rel : `./${rel}`;
30
+ return `[${label}](${href}${line === void 0 ? "" : `#L${line}`})`;
31
+ }
32
+ __name(sourceLink, "sourceLink");
26
33
  function headerNameToProperty(name) {
27
34
  const parts = name.split(/[-_]/).filter(Boolean);
28
35
  return parts.map((p, i) => {
@@ -84,7 +91,9 @@ function renderTsScalar(name, target) {
84
91
  case "date":
85
92
  case "time":
86
93
  case "datetime":
94
+ return "DateTime";
87
95
  case "duration":
96
+ return "Duration";
88
97
  case "interval":
89
98
  return "string";
90
99
  case "null":
@@ -173,18 +182,66 @@ var DECIMAL_PRELUDE_LINES = [
173
182
  ];
174
183
 
175
184
  // src/codegen-revive.ts
176
- var DECIMAL_COERCE_DECL = [
177
- `const __dec = (v: unknown, path: string): Decimal => {`,
178
- ` if (typeof v !== 'string') {`,
179
- ` throw new TypeError(\`ContractKit: expected a decimal string at '\${path}', received \${typeof v} \u2014 decimals must be sent as quoted JSON strings.\`);`,
180
- ` }`,
181
- ` try {`,
182
- ` return new Decimal(v);`,
183
- ` } catch {`,
184
- ` throw new TypeError(\`ContractKit: '\${v}' at '\${path}' is not a valid decimal.\`);`,
185
- ` }`,
186
- `};`
187
- ];
185
+ var COERCE_DECLS = {
186
+ "__dec(": [
187
+ `const __dec = (v: unknown, path: string): Decimal => {`,
188
+ ` if (typeof v !== 'string') {`,
189
+ ` throw new TypeError(\`ContractKit: expected a decimal string at '\${path}', received \${typeof v} \u2014 decimals must be sent as quoted JSON strings.\`);`,
190
+ ` }`,
191
+ ` try {`,
192
+ ` return new Decimal(v);`,
193
+ ` } catch {`,
194
+ ` throw new TypeError(\`ContractKit: '\${v}' at '\${path}' is not a valid decimal.\`);`,
195
+ ` }`,
196
+ `};`
197
+ ],
198
+ "__dt(": [
199
+ `const __dt = (v: unknown, path: string): DateTime => {`,
200
+ ` if (typeof v !== 'string') {`,
201
+ ` throw new TypeError(\`ContractKit: expected an ISO 8601 string at '\${path}', received \${typeof v}.\`);`,
202
+ ` }`,
203
+ ` const d = DateTime.fromISO(v);`,
204
+ ` if (!d.isValid) throw new TypeError(\`ContractKit: '\${v}' at '\${path}' is not a valid ISO 8601 datetime.\`);`,
205
+ ` return d;`,
206
+ `};`
207
+ ],
208
+ "__dtf(": [
209
+ `const __dtf = (v: unknown, path: string, fmt: string): DateTime => {`,
210
+ ` if (typeof v !== 'string') {`,
211
+ ` throw new TypeError(\`ContractKit: expected a string at '\${path}' in format \${fmt}, received \${typeof v}.\`);`,
212
+ ` }`,
213
+ ` const d = DateTime.fromFormat(v, fmt);`,
214
+ ` if (!d.isValid) throw new TypeError(\`ContractKit: '\${v}' at '\${path}' does not match format \${fmt}.\`);`,
215
+ ` return d;`,
216
+ `};`
217
+ ],
218
+ "__dur(": [
219
+ `const __dur = (v: unknown, path: string): Duration => {`,
220
+ ` if (typeof v !== 'string') {`,
221
+ ` throw new TypeError(\`ContractKit: expected an ISO 8601 duration string at '\${path}', received \${typeof v}.\`);`,
222
+ ` }`,
223
+ ` const d = Duration.fromISO(v);`,
224
+ ` if (!d.isValid) throw new TypeError(\`ContractKit: '\${v}' at '\${path}' is not a valid ISO 8601 duration.\`);`,
225
+ ` return d;`,
226
+ `};`
227
+ ]
228
+ };
229
+ var DECIMAL_COERCE_DECL = COERCE_DECLS["__dec("];
230
+ function coerceDeclsFor(lines) {
231
+ const haystack = lines.join("\n");
232
+ return Object.entries(COERCE_DECLS).filter(([prefix]) => haystack.includes(prefix)).flatMap(([, decl]) => decl);
233
+ }
234
+ __name(coerceDeclsFor, "coerceDeclsFor");
235
+ function coerceLuxonImports(lines) {
236
+ const haystack = lines.join("\n");
237
+ const needed = /* @__PURE__ */ new Set();
238
+ if (haystack.includes("__dt(") || haystack.includes("__dtf(")) needed.add("DateTime");
239
+ if (haystack.includes("__dur(")) needed.add("Duration");
240
+ return [
241
+ ...needed
242
+ ].sort();
243
+ }
244
+ __name(coerceLuxonImports, "coerceLuxonImports");
188
245
  function reviveFnName(model, variant = "base") {
189
246
  return `revive${model}${variant === "output" ? "Output" : ""}`;
190
247
  }
@@ -195,10 +252,17 @@ function applyCase(name, caseTransform) {
195
252
  return name.charAt(0).toUpperCase() + name.slice(1);
196
253
  }
197
254
  __name(applyCase, "applyCase");
255
+ var DEFAULT_REVIVABLE_SCALARS = /* @__PURE__ */ new Set([
256
+ "decimal",
257
+ "date",
258
+ "time",
259
+ "datetime",
260
+ "duration"
261
+ ]);
198
262
  function typeReachesDecimal(type, opts) {
199
263
  switch (type.kind) {
200
264
  case "scalar":
201
- return type.name === "decimal";
265
+ return (opts.revivableScalars ?? DEFAULT_REVIVABLE_SCALARS).has(type.name);
202
266
  case "ref":
203
267
  return opts.modelsWithDecimal.has(type.name);
204
268
  case "array":
@@ -220,6 +284,34 @@ function typeReachesDecimal(type, opts) {
220
284
  }
221
285
  }
222
286
  __name(typeReachesDecimal, "typeReachesDecimal");
287
+ function scalarCoercion(type, slot, path, opts) {
288
+ if (!(opts.revivableScalars ?? DEFAULT_REVIVABLE_SCALARS).has(type.name)) return [];
289
+ switch (type.name) {
290
+ case "decimal":
291
+ return [
292
+ `${slot} = __dec(${slot}, '${path}');`
293
+ ];
294
+ case "datetime":
295
+ return [
296
+ `${slot} = __dt(${slot}, '${path}');`
297
+ ];
298
+ case "duration":
299
+ return [
300
+ `${slot} = __dur(${slot}, '${path}');`
301
+ ];
302
+ case "date":
303
+ return [
304
+ `${slot} = __dtf(${slot}, '${path}', '${type.format ?? "yyyy-MM-dd"}');`
305
+ ];
306
+ case "time":
307
+ return [
308
+ `${slot} = __dtf(${slot}, '${path}', '${type.format ?? "HH:mm:ss"}');`
309
+ ];
310
+ default:
311
+ return [];
312
+ }
313
+ }
314
+ __name(scalarCoercion, "scalarCoercion");
223
315
  var Scope = class Scope2 {
224
316
  static {
225
317
  __name(this, "Scope");
@@ -232,9 +324,7 @@ var Scope = class Scope2 {
232
324
  function emit(slot, type, path, opts, scope, variant) {
233
325
  switch (type.kind) {
234
326
  case "scalar":
235
- return type.name === "decimal" ? [
236
- `${slot} = __dec(${slot}, '${path}');`
237
- ] : [];
327
+ return scalarCoercion(type, slot, path, opts);
238
328
  case "ref":
239
329
  return opts.modelsWithDecimal.has(type.name) ? [
240
330
  `${reviveRefName(type.name, opts, variant)}(${slot} as never);`
@@ -386,7 +476,7 @@ function renderOne(model, opts, variant) {
386
476
  const body2 = emit("__v[0]", model.type, model.name, opts, scope, variant);
387
477
  if (body2.length === 0) return [];
388
478
  return [
389
- `/** Rehydrates every \`decimal\` in a ${typeName} from its wire string. Mutates and returns \`raw\`. */`,
479
+ `/** Rehydrates every wire-encoded scalar in a ${typeName} into its runtime type. Mutates and returns \`raw\`. */`,
390
480
  `export function ${fnName}(raw: ${typeName}): ${typeName} {`,
391
481
  ` const __v = [raw] as unknown[];`,
392
482
  ...body2.map((l) => ` ${l}`),
@@ -398,7 +488,7 @@ function renderOne(model, opts, variant) {
398
488
  const body = model.fields.flatMap((f) => typeReachesDecimal(f.type, opts) ? fieldStatements(obj, f, model.name, opts, scope, variant, model.outputCase) : []);
399
489
  if (body.length === 0) return [];
400
490
  return [
401
- `/** Rehydrates every \`decimal\` in a ${typeName} from its wire string. Mutates and returns \`raw\`. */`,
491
+ `/** Rehydrates every wire-encoded scalar in a ${typeName} into its runtime type. Mutates and returns \`raw\`. */`,
402
492
  `export function ${fnName}(raw: ${typeName}): ${typeName} {`,
403
493
  ` const ${obj} = raw as unknown as Record<string, unknown>;`,
404
494
  ...body.map((l) => ` ${l}`),
@@ -459,8 +549,7 @@ function generateComments(model, outPath) {
459
549
  if (model.description) {
460
550
  for (const l of escapeJsDocLines(model.description)) lines.push(` * ${l}`);
461
551
  }
462
- const relPath = outPath ? relative(dirname(outPath), model.loc.file) : model.loc.file;
463
- lines.push(` * generated from [${model.name}](file://./${relPath}#L${model.loc.line})`);
552
+ lines.push(` * generated from ${sourceLink(model.name, outPath, model.loc.file, model.loc.line)}`);
464
553
  lines.push("*/");
465
554
  return lines;
466
555
  }
@@ -510,7 +599,7 @@ function generateContract(root, context) {
510
599
  }
511
600
  lines.push("");
512
601
  if (needsBinary) {
513
- lines.push(`const _ZodBinary = z.custom<Buffer>((val) => Buffer.isBuffer(val), { error: 'Must be binary data' });`);
602
+ lines.push(context?.target === "server" ? `const _ZodBinary = z.custom<Buffer>((val) => Buffer.isBuffer(val), { error: 'Must be binary data' });` : `const _ZodBinary = z.custom<Blob>((val) => val instanceof Blob, { error: 'Must be binary data' });`);
514
603
  }
515
604
  if (needsDatetime) {
516
605
  lines.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' }));`);
@@ -526,7 +615,6 @@ function generateContract(root, context) {
526
615
  lines.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)]));`);
527
616
  }
528
617
  if (needsBinary || needsDatetime || needsInterval || needsDecimal || needsJson) lines.push("");
529
- const modelsWithWriteonly = new Set(root.models.filter((m) => m.fields.some((f) => f.visibility === "writeonly")).map((m) => m.name));
530
618
  const modelMap = new Map(root.models.map((m) => [
531
619
  m.name,
532
620
  m
@@ -538,7 +626,7 @@ function generateContract(root, context) {
538
626
  } : void 0;
539
627
  const bodyLines = [];
540
628
  for (const model of topoSortModels(root.models)) {
541
- bodyLines.push(...generateModel(model, context?.currentOutPath, allModelsWithInput, modelsWithWriteonly, modelMap, allModelsWithOutput));
629
+ bodyLines.push(...generateModel(model, context?.currentOutPath, allModelsWithInput, modelMap, allModelsWithOutput));
542
630
  if (reviveOpts) {
543
631
  const revivers = renderReviveFunctions(model, reviveOpts);
544
632
  if (revivers.length > 0) {
@@ -548,8 +636,9 @@ function generateContract(root, context) {
548
636
  }
549
637
  bodyLines.push("");
550
638
  }
551
- if (bodyLines.some((l) => l.includes("__dec("))) {
552
- lines.push(...DECIMAL_COERCE_DECL);
639
+ const coerceDecls = coerceDeclsFor(bodyLines);
640
+ if (coerceDecls.length > 0) {
641
+ lines.push(...coerceDecls);
553
642
  lines.push("");
554
643
  }
555
644
  lines.push(...bodyLines);
@@ -579,13 +668,13 @@ function flattenFormatChain(model, modelMap) {
579
668
  };
580
669
  }
581
670
  __name(flattenFormatChain, "flattenFormatChain");
582
- function generateModel(model, outPath, modelsWithInput, modelsWithWriteonly, modelMap, modelsWithOutput) {
671
+ function generateModel(model, outPath, modelsWithInput, modelMap, modelsWithOutput) {
583
672
  if (model.type) {
584
673
  return generateTypeAlias(model, outPath, modelsWithInput, modelsWithOutput);
585
674
  }
586
675
  const effective = modelMap ? flattenFormatChain(model, modelMap) : model;
587
676
  const needsInputSplit = effective.fields.some((f) => f.visibility !== "normal") || (modelsWithInput?.has(effective.name) ?? false);
588
- const lines = needsInputSplit ? generateThreeSchemaModel(effective, outPath, modelsWithInput, modelsWithWriteonly, modelMap) : generateSimpleModel(effective, outPath);
677
+ const lines = needsInputSplit ? generateThreeSchemaModel(effective, outPath, modelsWithInput, modelMap) : generateSimpleModel(effective, outPath);
589
678
  if (modelsWithOutput?.has(effective.name)) {
590
679
  lines.push(`export type ${effective.name}Output = z.output<typeof ${effective.name}>;`);
591
680
  }
@@ -674,26 +763,13 @@ function collectEffectiveWritableFieldNames(modelName, modelMap) {
674
763
  return result;
675
764
  }
676
765
  __name(collectEffectiveWritableFieldNames, "collectEffectiveWritableFieldNames");
677
- function generateThreeSchemaModel(model, outPath, modelsWithInput, modelsWithWriteonly, modelMap) {
766
+ function generateThreeSchemaModel(model, outPath, modelsWithInput, modelMap) {
678
767
  const lines = [];
679
768
  const name = model.name;
680
769
  lines.push(...generateComments(model, outPath));
681
770
  const wrapper = modeToWrapper(model.mode ?? "strict");
682
771
  const allFields = model.fields;
683
- const hasWriteonly = allFields.some((f) => f.visibility === "writeonly");
684
772
  const bases = model.bases ?? [];
685
- if (hasWriteonly) {
686
- const baseBody = renderFields(allFields, model.mode);
687
- if (bases.length > 0) {
688
- const { head, tail } = buildExtendChain(bases, (b) => modelsWithWriteonly?.has(b) ? `${b}Base` : b);
689
- lines.push(`const ${name}Base = ${head}${tail}.extend({`);
690
- } else {
691
- lines.push(`const ${name}Base = ${wrapper}({`);
692
- }
693
- lines.push(...baseBody.map((l) => ` ${l}`));
694
- lines.push(`});`);
695
- lines.push("");
696
- }
697
773
  const readFields = allFields.filter((f) => f.visibility !== "writeonly");
698
774
  const readBody = renderFields(readFields, model.mode);
699
775
  if (bases.length > 0) {
@@ -790,10 +866,7 @@ function renderFieldsAsSnakeCase(fields, defaultMode) {
790
866
  });
791
867
  }
792
868
  __name(renderFieldsAsSnakeCase, "renderFieldsAsSnakeCase");
793
- function renderField(field, defaultMode) {
794
- const lines = [];
795
- if (field.deprecated) lines.push("/** @deprecated */");
796
- let expr = renderType(field.type, void 0, defaultMode);
869
+ function applyFieldModifiers(expr, field) {
797
870
  if (field.nullable) expr += ".nullable()";
798
871
  if (field.default !== void 0) {
799
872
  const dv = typeof field.default === "string" ? `"${escapeString(field.default)}"` : String(field.default);
@@ -802,6 +875,14 @@ function renderField(field, defaultMode) {
802
875
  expr += ".optional()";
803
876
  }
804
877
  if (field.description) expr += `.describe("${escapeString(field.description)}")`;
878
+ return expr;
879
+ }
880
+ __name(applyFieldModifiers, "applyFieldModifiers");
881
+ function renderField(field, defaultMode) {
882
+ const lines = [];
883
+ if (field.deprecated) lines.push("/** @deprecated */");
884
+ let expr = renderType(field.type, void 0, defaultMode);
885
+ expr = applyFieldModifiers(expr, field);
805
886
  lines.push(`${quoteKey2(field.name)}: ${expr},`);
806
887
  return lines;
807
888
  }
@@ -855,6 +936,7 @@ function regexHasAnchor(source) {
855
936
  return backslashes % 2 === 0;
856
937
  }
857
938
  __name(regexHasAnchor, "regexHasAnchor");
939
+ var NUMERIC_PREPROCESS = `(v) => (typeof v === 'string' && v.trim() !== '' ? Number(v) : v)`;
858
940
  function renderScalar(s) {
859
941
  switch (s.name) {
860
942
  case "string": {
@@ -866,17 +948,12 @@ function renderScalar(s) {
866
948
  if (s.regex) e += `.regex(${renderRegexLiteral(s.regex)})`;
867
949
  return e;
868
950
  }
869
- case "number": {
870
- let e = "z.coerce.number()";
871
- if (s.min !== void 0) e += `.min(${s.min})`;
872
- if (s.max !== void 0) e += `.max(${s.max})`;
873
- return e;
874
- }
951
+ case "number":
875
952
  case "int": {
876
- let e = "z.coerce.number().int()";
877
- if (s.min !== void 0) e += `.min(${s.min})`;
878
- if (s.max !== void 0) e += `.max(${s.max})`;
879
- return e;
953
+ let inner = s.name === "int" ? "z.number().int()" : "z.number()";
954
+ if (s.min !== void 0) inner += `.min(${s.min})`;
955
+ if (s.max !== void 0) inner += `.max(${s.max})`;
956
+ return `z.preprocess(${NUMERIC_PREPROCESS}, ${inner})`;
880
957
  }
881
958
  case "bigint": {
882
959
  let inner = "z.bigint()";
@@ -1107,14 +1184,7 @@ function renderInputField(field, modelsWithInput, defaultMode) {
1107
1184
  const lines = [];
1108
1185
  if (field.deprecated) lines.push("/** @deprecated */");
1109
1186
  let expr = renderInputType(field.type, modelsWithInput, defaultMode);
1110
- if (field.nullable) expr += ".nullable()";
1111
- if (field.default !== void 0) {
1112
- const dv = typeof field.default === "string" ? `"${escapeString(field.default)}"` : String(field.default);
1113
- expr += `.default(${dv})`;
1114
- } else if (field.optional) {
1115
- expr += ".optional()";
1116
- }
1117
- if (field.description) expr += `.describe("${escapeString(field.description)}")`;
1187
+ expr = applyFieldModifiers(expr, field);
1118
1188
  lines.push(`${quoteKey2(field.name)}: ${expr},`);
1119
1189
  return lines;
1120
1190
  }
@@ -1167,14 +1237,7 @@ ${fieldLines}
1167
1237
  __name(renderQueryType, "renderQueryType");
1168
1238
  function renderQueryField(field, modelsWithInput, defaultMode) {
1169
1239
  let expr = field.type.kind === "array" ? renderQueryType(field.type, modelsWithInput, defaultMode) : modelsWithInput ? renderInputType(field.type, modelsWithInput, defaultMode) : renderType(field.type, void 0, defaultMode);
1170
- if (field.nullable) expr += ".nullable()";
1171
- if (field.default !== void 0) {
1172
- const dv = typeof field.default === "string" ? `"${escapeString(field.default)}"` : String(field.default);
1173
- expr += `.default(${dv})`;
1174
- } else if (field.optional) {
1175
- expr += ".optional()";
1176
- }
1177
- if (field.description) expr += `.describe("${escapeString(field.description)}")`;
1240
+ expr = applyFieldModifiers(expr, field);
1178
1241
  return `${quoteKey2(field.name)}: ${expr},`;
1179
1242
  }
1180
1243
  __name(renderQueryField, "renderQueryField");
@@ -1371,8 +1434,8 @@ function resolveImportPath(refName, context) {
1371
1434
  if (context) {
1372
1435
  const refOutPath = context.modelOutPaths.get(refName);
1373
1436
  if (refOutPath) {
1374
- const fromDir = dirname(context.currentOutPath);
1375
- let rel = relative(fromDir, refOutPath);
1437
+ const fromDir = dirname2(context.currentOutPath);
1438
+ let rel = relative2(fromDir, refOutPath);
1376
1439
  rel = rel.replace(/\.ts$/, ".js");
1377
1440
  if (!rel.startsWith(".")) rel = "./" + rel;
1378
1441
  return rel;
@@ -1388,8 +1451,8 @@ function pascalToDotCase(name) {
1388
1451
  __name(pascalToDotCase, "pascalToDotCase");
1389
1452
 
1390
1453
  // src/codegen-operation.ts
1391
- import { resolveModifiers, resolveSecurity, SECURITY_NONE, classifyContentType, emittedResponses } from "@contractkit/core";
1392
- import { basename, dirname as dirname2, relative as relative2 } from "path";
1454
+ import { resolveModifiers, resolveSecurity, SECURITY_NONE, classifyContentType, emittedResponses, PATH_PARAM_RE_G, toIdentifier } from "@contractkit/core";
1455
+ import { basename, dirname as dirname3, relative as relative3 } from "path";
1393
1456
  function bodyParserToken(contentType) {
1394
1457
  switch (classifyContentType(contentType)) {
1395
1458
  case "urlencoded":
@@ -1468,8 +1531,7 @@ function generateOp(root, options = {}) {
1468
1531
  const lines = [];
1469
1532
  lines.push("");
1470
1533
  lines.push("/**");
1471
- const relFile = options.outPath ? relative2(dirname2(options.outPath), root.file) : root.file;
1472
- lines.push(` * generated from [${basename(root.file)}](file://./${relFile})`);
1534
+ lines.push(` * generated from ${sourceLink(basename(root.file), options.outPath, root.file)}`);
1473
1535
  lines.push("*/");
1474
1536
  lines.push(`export const ${routerName} = ServerKitRouter();`);
1475
1537
  lines.push("");
@@ -1569,8 +1631,7 @@ function generateHandler(route, op, root, options) {
1569
1631
  if (desc) {
1570
1632
  for (const l of escapeJsDocLines(desc)) lines.push(` * ${l}`);
1571
1633
  }
1572
- const relFile = outPath ? relative2(dirname2(outPath), file) : file;
1573
- lines.push(` * from [${basename(file)}](file://./${relFile}#L${op.loc.line})`);
1634
+ lines.push(` * from ${sourceLink(basename(file), outPath, file, op.loc.line)}`);
1574
1635
  const effectiveSecurity = resolveSecurity(route, op, root);
1575
1636
  if (effectiveSecurity === SECURITY_NONE) {
1576
1637
  lines.push(` * anonymous access, no security required`);
@@ -1580,7 +1641,7 @@ function generateHandler(route, op, root, options) {
1580
1641
  if (mods.includes("deprecated")) lines.push(` * @deprecated`);
1581
1642
  lines.push("*/");
1582
1643
  const method = op.method;
1583
- const path = route.path.replace(/\{(\w+)\}/g, ":$1");
1644
+ const path = route.path.replace(PATH_PARAM_RE_G, (_m, name) => `:${toIdentifier(name)}`);
1584
1645
  const bodies = op.request?.bodies ?? [];
1585
1646
  const hasBody = bodies.length > 0;
1586
1647
  const isSingleMultipart = bodies.length === 1 && bodies[0].contentType === "multipart/form-data";
@@ -1679,7 +1740,7 @@ function generateSingleStatusResult(resp, op, className, call, options) {
1679
1740
  }
1680
1741
  }
1681
1742
  lines.push("");
1682
- lines.push(` ctx.status = ${resp?.statusCode ?? op.responses[0]?.statusCode ?? 200};`);
1743
+ lines.push(` ctx.status = ${resp?.statusCode ?? 204};`);
1683
1744
  lines.push(...headerSetLines(respHeaders, " "));
1684
1745
  if (bodies.length === 1) {
1685
1746
  lines.push(` ctx.type = '${bodies[0].contentType}';`);
@@ -1833,7 +1894,7 @@ function buildArgs(route, op) {
1833
1894
  const args = [];
1834
1895
  if (route.params) {
1835
1896
  if (route.params.kind === "params") {
1836
- args.push(...route.params.nodes.map((p) => p.name));
1897
+ args.push(...route.params.nodes.map((p) => toIdentifier(p.name)));
1837
1898
  } else {
1838
1899
  args.push("params");
1839
1900
  }
@@ -1963,18 +2024,17 @@ function generateParamValidation(source, ctxExpr, varName, mode, suffix = "", mo
1963
2024
  lines.push("");
1964
2025
  } else if (source.kind === "params") {
1965
2026
  if (source.nodes.length > 0) {
1966
- const lhs = varName === "params" ? `{ ${source.nodes.map((p) => p.name).join(", ")} }` : varName;
2027
+ const isPathParams = ctxExpr === "ctx.params";
2028
+ const bind = /* @__PURE__ */ __name((name) => isPathParams ? toIdentifier(name) : name, "bind");
2029
+ const lhs = varName === "params" ? `{ ${source.nodes.map((p) => bind(p.name)).join(", ")} }` : varName;
1967
2030
  lines.push(` const ${lhs} = await parseAndValidate(`);
1968
2031
  lines.push(` ${ctxExpr},`);
1969
2032
  lines.push(` ${modeToWrapper(mode)}({`);
1970
2033
  for (const param of source.nodes) {
1971
- const key = isValidIdentifier2(param.name) ? param.name : `'${param.name}'`;
1972
- if (isQuery && param.type.kind === "array") {
1973
- const inner = renderType(param.type);
1974
- lines.push(` ${key}: z.preprocess((v) => typeof v === 'string' ? v.split(',') : v, ${inner}),`);
1975
- } else {
1976
- lines.push(` ${key}: ${renderType(param.type)},`);
1977
- }
2034
+ const bound = bind(param.name);
2035
+ const key = isValidIdentifier2(bound) ? bound : `'${bound}'`;
2036
+ const base = isQuery ? renderQueryType(param.type, modelsWithInput) : renderInputType(param.type, modelsWithInput);
2037
+ lines.push(` ${key}: ${applyFieldModifiers(base, param)},`);
1978
2038
  }
1979
2039
  lines.push(` })${suffix},`);
1980
2040
  lines.push(` );`);
@@ -2004,9 +2064,9 @@ function generateTypeImports(types, opFile, options) {
2004
2064
  unresolved.push(type);
2005
2065
  }
2006
2066
  }
2007
- const fromDir = dirname2(outPath);
2067
+ const fromDir = dirname3(outPath);
2008
2068
  for (const [typeOutPath, names] of byFile) {
2009
- let rel = relative2(fromDir, typeOutPath);
2069
+ let rel = relative3(fromDir, typeOutPath);
2010
2070
  rel = rel.replace(/\.ts$/, ".js");
2011
2071
  if (!rel.startsWith(".")) rel = "./" + rel;
2012
2072
  lines.push(`import { ${names.sort().join(", ")} } from '${rel}';`);
@@ -2230,11 +2290,11 @@ function deriveTypeImportPath(file, template) {
2230
2290
  __name(deriveTypeImportPath, "deriveTypeImportPath");
2231
2291
 
2232
2292
  // src/index.ts
2233
- import { runIncrementalCodegen, parseIncrementalManifest, emptyIncrementalManifest, serializeIncrementalManifest, hashFingerprint, collectTransitiveModelRefs, computeModelsWithCaseTransform, computeModelsWithDecimal } from "@contractkit/core";
2293
+ import { runIncrementalCodegen, parseIncrementalManifest, emptyIncrementalManifest, serializeIncrementalManifest, hashFingerprint, collectTransitiveModelRefs, computeModelsWithCaseTransform, computeModelsWithScalar } from "@contractkit/core";
2234
2294
 
2235
2295
  // src/codegen-sdk.ts
2236
- import { resolveModifiers as resolveModifiers2, isJsonMime, classifyContentType as classifyContentType2, observableResponses, thrownResponses } from "@contractkit/core";
2237
- import { basename as basename2, dirname as dirname3, relative as relative3 } from "path";
2296
+ import { resolveModifiers as resolveModifiers2, isJsonMime, classifyContentType as classifyContentType2, observableResponses, thrownResponses, PATH_PARAM_RE_G as PATH_PARAM_RE_G2, toIdentifier as toIdentifier2 } from "@contractkit/core";
2297
+ import { basename as basename2, dirname as dirname4, relative as relative4 } from "path";
2238
2298
  function jsonOrFormSerialize(varName, contentType) {
2239
2299
  if (contentType === "application/x-www-form-urlencoded") {
2240
2300
  return `new URLSearchParams(${varName} as unknown as Record<string, string>).toString()`;
@@ -2304,7 +2364,6 @@ function generateSdk(root, options = {}) {
2304
2364
  const mods = resolveModifiers2(route, op);
2305
2365
  if (!includeInternal && mods.includes("internal")) continue;
2306
2366
  classBody.push("");
2307
- if (mods.includes("deprecated")) classBody.push(" /** @deprecated */");
2308
2367
  classBody.push(...generateMethod(route, op, root.file, options, inlineRevivers));
2309
2368
  }
2310
2369
  }
@@ -2312,19 +2371,29 @@ function generateSdk(root, options = {}) {
2312
2371
  ...inlineRevivers.values()
2313
2372
  ].flat();
2314
2373
  const decimalPrelude = decimalPreludeFor(inlineReviverDecls);
2315
- if (types.length > 0) {
2316
- lines.push(...generateTypeImports2(types, root.file, options, usedRevivers(classBody)));
2374
+ const errorAliases = generateErrorBodyAliases(root, options);
2375
+ const referenced = referencedTypes(types, [
2376
+ ...classBody,
2377
+ ...errorAliases,
2378
+ ...inlineReviverDecls
2379
+ ]);
2380
+ if (referenced.length > 0) {
2381
+ lines.push(...generateTypeImports2(referenced, root.file, options, usedRevivers(classBody)));
2317
2382
  }
2318
2383
  lines.push(...decimalPrelude.imports);
2384
+ const headerLuxon = headerLuxonImport(classBody, decimalPrelude.imports);
2385
+ if (headerLuxon) lines.push(headerLuxon);
2319
2386
  if (options.sdkOptionsPath && options.outPath) {
2320
- let rel = relative3(dirname3(options.outPath), options.sdkOptionsPath);
2387
+ let rel = relative4(dirname4(options.outPath), options.sdkOptionsPath);
2321
2388
  rel = rel.replace(/\.ts$/, ".js");
2322
2389
  if (!rel.startsWith(".")) rel = "./" + rel;
2323
2390
  const jsonImport = sdkNeedsJson(root, includeInternal) ? ", JsonValue" : "";
2324
2391
  lines.push(`import type { SdkFetch${jsonImport} } from '${rel}';`);
2325
2392
  const valueImports = [];
2326
2393
  if (sdkNeedsBigIntReplacer(root, includeInternal)) valueImports.push("bigIntReplacer");
2327
- if (sdkNeedsBigIntReviver(root, includeInternal)) valueImports.push("parseJson");
2394
+ if (sdkParsesJsonResponse(root, includeInternal)) {
2395
+ valueImports.push(sdkResponsesUseBigInt(root, options, includeInternal) ? "parseJsonWithBigInt as parseJson" : "parseJson");
2396
+ }
2328
2397
  if (sdkNeedsQueryString(root, includeInternal)) valueImports.push("buildQueryString");
2329
2398
  if (sdkNeedsReadContentType(root, includeInternal)) valueImports.push("readContentType");
2330
2399
  if (valueImports.length > 0) {
@@ -2401,14 +2470,13 @@ function generateSdk(root, options = {}) {
2401
2470
  lines.push("}");
2402
2471
  lines.push("");
2403
2472
  lines.push("export async function parseJson<T>(res: Response): Promise<T> {");
2404
- lines.push(" return JSON.parse(await res.text(), bigIntReviver) as T;");
2473
+ lines.push(sdkResponsesUseBigInt(root, options, includeInternal) ? " return JSON.parse(await res.text(), bigIntReviver) as T;" : " return JSON.parse(await res.text()) as T;");
2405
2474
  lines.push("}");
2406
2475
  }
2407
2476
  if (sdkNeedsJson(root, includeInternal) && !(options.sdkOptionsPath && options.outPath)) {
2408
2477
  lines.push(JSON_VALUE_TYPE_DECL);
2409
2478
  }
2410
2479
  lines.push("");
2411
- const errorAliases = generateErrorBodyAliases(root, options);
2412
2480
  if (errorAliases.length > 0) {
2413
2481
  lines.push(...errorAliases);
2414
2482
  lines.push("");
@@ -2422,8 +2490,7 @@ function generateSdk(root, options = {}) {
2422
2490
  lines.push(...decl);
2423
2491
  }
2424
2492
  lines.push("/**");
2425
- const relFile = options.outPath ? relative3(dirname3(options.outPath), root.file) : root.file;
2426
- lines.push(` * generated from [${basename2(root.file)}](file://./${relFile})`);
2493
+ lines.push(` * generated from ${sourceLink(basename2(root.file), options.outPath, root.file)}`);
2427
2494
  lines.push(" */");
2428
2495
  lines.push(`export class ${clientClassName} {`);
2429
2496
  lines.push(" constructor(private fetch: SdkFetch) {}");
@@ -2443,7 +2510,6 @@ function generateClientMethods(root, options) {
2443
2510
  const mods = resolveModifiers2(route, op);
2444
2511
  if (!includeInternal && mods.includes("internal")) continue;
2445
2512
  lines.push("");
2446
- if (mods.includes("deprecated")) lines.push(" /** @deprecated */");
2447
2513
  lines.push(...generateMethod(route, op, root.file, options, inlineRevivers));
2448
2514
  methodNames.push(deriveMethodName(op, route));
2449
2515
  }
@@ -2473,22 +2539,45 @@ function generateClientMethods(root, options) {
2473
2539
  }
2474
2540
  __name(generateClientMethods, "generateClientMethods");
2475
2541
  function decimalPreludeFor(declLines) {
2476
- if (!declLines.some((l) => l.includes("__dec("))) return {
2542
+ const decls = coerceDeclsFor(declLines);
2543
+ if (decls.length === 0) return {
2477
2544
  imports: [],
2478
2545
  decls: []
2479
2546
  };
2547
+ const imports = [];
2548
+ const preamble = [];
2549
+ if (declLines.some((l) => l.includes("__dec("))) {
2550
+ imports.push(DECIMAL_IMPORT);
2551
+ preamble.push(DECIMAL_CONFIG_LINE, "");
2552
+ }
2553
+ const luxon = coerceLuxonImports(declLines);
2554
+ if (luxon.length > 0) imports.push(`import { ${luxon.join(", ")} } from 'luxon';`);
2480
2555
  return {
2481
- imports: [
2482
- DECIMAL_IMPORT
2483
- ],
2556
+ imports,
2484
2557
  decls: [
2485
- DECIMAL_CONFIG_LINE,
2486
- "",
2487
- ...DECIMAL_COERCE_DECL
2558
+ ...preamble,
2559
+ ...decls
2488
2560
  ]
2489
2561
  };
2490
2562
  }
2491
2563
  __name(decimalPreludeFor, "decimalPreludeFor");
2564
+ function headerLuxonImport(methodLines, existingImports) {
2565
+ if (existingImports.some((l) => l.includes("from 'luxon'"))) return void 0;
2566
+ const needed = [
2567
+ "DateTime",
2568
+ "Duration"
2569
+ ].filter((c) => methodLines.some((l) => l.includes(`${c}.from`)));
2570
+ return needed.length > 0 ? `import { ${needed.join(", ")} } from 'luxon';` : void 0;
2571
+ }
2572
+ __name(headerLuxonImport, "headerLuxonImport");
2573
+ function referencedTypes(types, emitted) {
2574
+ const haystack = emitted.join("\n");
2575
+ return types.filter((t) => {
2576
+ const escaped = t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2577
+ return new RegExp(`(?<![A-Za-z0-9_$])${escaped}(?![A-Za-z0-9_$])`).test(haystack);
2578
+ });
2579
+ }
2580
+ __name(referencedTypes, "referencedTypes");
2492
2581
  function usedRevivers(lines) {
2493
2582
  const found = /* @__PURE__ */ new Set();
2494
2583
  for (const m of lines.join("\n").matchAll(/\brevive[A-Z]\w*/g)) found.add(m[0]);
@@ -2507,6 +2596,7 @@ function generateMethod(route, op, file, options, inlineRevivers) {
2507
2596
  } : void 0;
2508
2597
  const lines = [];
2509
2598
  const methodName = deriveMethodName(op, route);
2599
+ const where = `${op.method.toUpperCase()} ${route.path}`;
2510
2600
  const mRevive = hint(revive, `${methodName.charAt(0).toUpperCase()}${methodName.slice(1)}`);
2511
2601
  const httpMethod = op.method.toUpperCase();
2512
2602
  const { modelsWithInput, modelsWithOutput } = options;
@@ -2538,11 +2628,13 @@ function generateMethod(route, op, file, options, inlineRevivers) {
2538
2628
  const expectStatuses = observable.filter((r) => r.statusCode < 200 || r.statusCode >= 300).map((r) => r.statusCode);
2539
2629
  const desc = op.description ?? route.description;
2540
2630
  const errorBodyName = thrown.some((r) => r.bodies.length > 0) ? errorBodyTypeName(route, op) : void 0;
2541
- if (op.name || desc || errorBodyName) {
2631
+ const deprecated = resolveModifiers2(route, op).includes("deprecated");
2632
+ if (op.name || desc || errorBodyName || deprecated) {
2542
2633
  const tags = [];
2543
2634
  if (op.name) tags.push(`@name ${op.name}`);
2544
2635
  if (desc) tags.push(`@description ${desc}`);
2545
2636
  if (errorBodyName) tags.push(`@throws {SdkError<${errorBodyName}>} on ${thrown.map((r) => r.statusCode).join(", ")}`);
2637
+ if (deprecated) tags.push("@deprecated");
2546
2638
  const contentLines = tags.flatMap((t) => escapeJsDocLines(t));
2547
2639
  if (contentLines.length === 1) {
2548
2640
  lines.push(` /** ${contentLines[0]} */`);
@@ -2635,15 +2727,15 @@ function generateMethod(route, op, file, options, inlineRevivers) {
2635
2727
  lines.push(` switch (result.status) {`);
2636
2728
  for (const resp of rest) {
2637
2729
  lines.push(` case ${resp.statusCode}:`);
2638
- lines.push(...sdkReturnLines(resp, modelsWithOutput, " ", true, mRevive));
2730
+ lines.push(...sdkReturnLines(resp, modelsWithOutput, " ", true, mRevive, where));
2639
2731
  }
2640
2732
  lines.push(` default:`);
2641
- lines.push(...sdkReturnLines(fallback, modelsWithOutput, " ", true, mRevive));
2733
+ lines.push(...sdkReturnLines(fallback, modelsWithOutput, " ", true, mRevive, where));
2642
2734
  lines.push(` }`);
2643
2735
  } else if (primaryBodies.length > 1) {
2644
- lines.push(...sdkReturnLines(primaryResponse, modelsWithOutput, " ", false, mRevive));
2736
+ lines.push(...sdkReturnLines(primaryResponse, modelsWithOutput, " ", false, mRevive, where));
2645
2737
  } else if (hasRespHeaders) {
2646
- const headerEntries = sdkHeaderEntries(respHeaders);
2738
+ const headerEntries = sdkHeaderEntries(respHeaders, where);
2647
2739
  if (isVoid) {
2648
2740
  lines.push(` return { headers: { ${headerEntries} } };`);
2649
2741
  } else {
@@ -2687,6 +2779,7 @@ function reviveExprFor(bodyType, ctx) {
2687
2779
  if (!ctx || ctx.modelsWithDecimal.size === 0) return null;
2688
2780
  const opts = {
2689
2781
  modelsWithDecimal: ctx.modelsWithDecimal,
2782
+ revivableScalars: ctx.revivableScalars,
2690
2783
  modelsWithOutput: ctx.modelsWithOutput,
2691
2784
  modelMap: ctx.modelMap
2692
2785
  };
@@ -2722,8 +2815,48 @@ function renderSdkHeadersShape(headers, modelsWithOutput) {
2722
2815
  return `{ ${fields.join("; ")} }`;
2723
2816
  }
2724
2817
  __name(renderSdkHeadersShape, "renderSdkHeadersShape");
2725
- function sdkHeaderEntries(headers) {
2726
- return headers.map((h) => `${quoteKey(headerNameToProperty(h.name))}: result.headers.get('${h.name}') ?? undefined`).join(", ");
2818
+ function sdkHeaderEntry(h, where) {
2819
+ const raw = `result.headers.get('${h.name}')`;
2820
+ const key = quoteKey(headerNameToProperty(h.name));
2821
+ const scalar = h.type.kind === "scalar" ? h.type.name : void 0;
2822
+ const convert = /* @__PURE__ */ __name((expr) => `${key}: ${h.optional ? `${raw} === null ? undefined : ${expr(`${raw}!`)}` : expr(`${raw}!`)}`, "convert");
2823
+ switch (scalar) {
2824
+ case "string":
2825
+ case "email":
2826
+ case "url":
2827
+ case "uuid":
2828
+ case "interval":
2829
+ case "unknown":
2830
+ return `${key}: ${raw}${h.optional ? " ?? undefined" : "!"}`;
2831
+ // Temporals are Luxon objects since the SDK started reviving them, so the shape
2832
+ // `renderOutputTsType` produces says `DateTime` and the raw string no longer satisfies it.
2833
+ // The format for date and time comes from the contract, as it does in the reviver.
2834
+ case "datetime":
2835
+ return convert((v) => `DateTime.fromISO(${v})`);
2836
+ case "duration":
2837
+ return convert((v) => `Duration.fromISO(${v})`);
2838
+ case "date":
2839
+ return convert((v) => `DateTime.fromFormat(${v}, '${h.type.kind === "scalar" && h.type.format || "yyyy-MM-dd"}')`);
2840
+ case "time":
2841
+ return convert((v) => `DateTime.fromFormat(${v}, '${h.type.kind === "scalar" && h.type.format || "HH:mm:ss"}')`);
2842
+ case "number":
2843
+ case "int":
2844
+ return `${key}: ${h.optional ? `${raw} === null ? undefined : Number(${raw})` : `Number(${raw})`}`;
2845
+ case "boolean":
2846
+ return `${key}: ${h.optional ? `${raw} === null ? undefined : ${raw} === 'true'` : `${raw} === 'true'`}`;
2847
+ case "bigint":
2848
+ return convert((v) => `BigInt(${v})`);
2849
+ default:
2850
+ throw new Error(`Response header '${h.name}' on ${where} is declared as ${describeHeaderType(h.type)}, which cannot be read from an HTTP header. Header values arrive as strings \u2014 declare it as string, email, url, uuid, a date/time type, int, number, boolean or bigint.`);
2851
+ }
2852
+ }
2853
+ __name(sdkHeaderEntry, "sdkHeaderEntry");
2854
+ function describeHeaderType(type) {
2855
+ return type.kind === "scalar" ? `the '${type.name}' scalar` : type.kind === "ref" ? `the model '${type.name}'` : `${type.kind === "array" ? "an" : "a"} ${type.kind}`;
2856
+ }
2857
+ __name(describeHeaderType, "describeHeaderType");
2858
+ function sdkHeaderEntries(headers, where) {
2859
+ return headers.map((h) => sdkHeaderEntry(h, where)).join(", ");
2727
2860
  }
2728
2861
  __name(sdkHeaderEntries, "sdkHeaderEntries");
2729
2862
  function sdkResponseMembers(resp, modelsWithOutput, includeStatus) {
@@ -2763,14 +2896,14 @@ function sdkResponseMembers(resp, modelsWithOutput, includeStatus) {
2763
2896
  ].join("; ")} }`);
2764
2897
  }
2765
2898
  __name(sdkResponseMembers, "sdkResponseMembers");
2766
- function sdkReturnLines(resp, modelsWithOutput, indent, includeStatus, revive) {
2899
+ function sdkReturnLines(resp, modelsWithOutput, indent, includeStatus, revive, where) {
2767
2900
  const bodies = resp.bodies;
2768
2901
  const headers = resp.headers ?? [];
2769
2902
  const leading = includeStatus ? [
2770
2903
  `status: ${resp.statusCode}`
2771
2904
  ] : [];
2772
2905
  const trailing = headers.length > 0 ? [
2773
- `headers: { ${sdkHeaderEntries(headers)} }`
2906
+ `headers: { ${sdkHeaderEntries(headers, where)} }`
2774
2907
  ] : [];
2775
2908
  if (bodies.length === 0) {
2776
2909
  return [
@@ -2855,9 +2988,11 @@ function generateErrorBodyAliases(root, options) {
2855
2988
  return lines;
2856
2989
  }
2857
2990
  __name(generateErrorBodyAliases, "generateErrorBodyAliases");
2858
- function buildUrlExpression(path, _) {
2859
- return path.replace(/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g, (_match, name) => {
2860
- return `\${encodeURIComponent(${name})}`;
2991
+ function buildUrlExpression(path, params) {
2992
+ return path.replace(PATH_PARAM_RE_G2, (_m, name) => {
2993
+ if (!params || params.kind === "params") return `\${encodeURIComponent(${toIdentifier2(name)})}`;
2994
+ const access = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? `params.${name}` : `params[${JSON.stringify(name)}]`;
2995
+ return `\${encodeURIComponent(String(${access}))}`;
2861
2996
  });
2862
2997
  }
2863
2998
  __name(buildUrlExpression, "buildUrlExpression");
@@ -2867,7 +3002,7 @@ function buildMethodParams(route, op, modelsWithInput) {
2867
3002
  if (route.params.kind === "params") {
2868
3003
  for (const p of route.params.nodes) {
2869
3004
  params.push({
2870
- name: p.name,
3005
+ name: toIdentifier2(p.name),
2871
3006
  type: renderInputTsType(p.type, modelsWithInput),
2872
3007
  optional: false
2873
3008
  });
@@ -2951,55 +3086,44 @@ function buildMethodParams(route, op, modelsWithInput) {
2951
3086
  optional: false
2952
3087
  });
2953
3088
  }
2954
- if (op.query) {
2955
- if (op.query.kind === "params") {
2956
- const fields = op.query.nodes.map((p) => `${quoteKey(p.name)}?: ${renderInputTsType(p.type, modelsWithInput)}`).join("; ");
2957
- params.push({
2958
- name: "query",
2959
- type: `{ ${fields} }`,
2960
- optional: true
2961
- });
2962
- } else if (op.query.kind === "ref") {
2963
- const typeName = modelsWithInput?.has(op.query.name) ? `${op.query.name}Input` : op.query.name;
2964
- params.push({
2965
- name: "query",
2966
- type: typeName,
2967
- optional: true
2968
- });
2969
- } else {
2970
- params.push({
2971
- name: "query",
2972
- type: renderInputTsType(op.query.node, modelsWithInput),
2973
- optional: true
2974
- });
2975
- }
3089
+ if (op.query) params.push(inlineArgParam("query", op.query, modelsWithInput));
3090
+ if (op.headers) params.push(inlineArgParam("customHeaders", op.headers, modelsWithInput));
3091
+ return normaliseOptionalOrder(params);
3092
+ }
3093
+ __name(buildMethodParams, "buildMethodParams");
3094
+ function inlineArgParam(name, source, modelsWithInput) {
3095
+ if (source.kind === "params") {
3096
+ const isOptional = /* @__PURE__ */ __name((p) => Boolean(p.optional) || p.default !== void 0, "isOptional");
3097
+ const fields = source.nodes.map((p) => `${quoteKey(p.name)}${isOptional(p) ? "?" : ""}: ${renderInputTsType(p.type, modelsWithInput)}`).join("; ");
3098
+ return {
3099
+ name,
3100
+ type: `{ ${fields} }`,
3101
+ optional: source.nodes.every(isOptional)
3102
+ };
2976
3103
  }
2977
- if (op.headers) {
2978
- if (op.headers.kind === "params") {
2979
- const fields = op.headers.nodes.map((p) => `${quoteKey(p.name)}?: ${renderInputTsType(p.type, modelsWithInput)}`).join("; ");
2980
- params.push({
2981
- name: "customHeaders",
2982
- type: `{ ${fields} }`,
2983
- optional: true
2984
- });
2985
- } else if (op.headers.kind === "ref") {
2986
- const typeName = modelsWithInput?.has(op.headers.name) ? `${op.headers.name}Input` : op.headers.name;
2987
- params.push({
2988
- name: "customHeaders",
2989
- type: typeName,
2990
- optional: true
2991
- });
2992
- } else {
2993
- params.push({
2994
- name: "customHeaders",
2995
- type: renderInputTsType(op.headers.node, modelsWithInput),
2996
- optional: true
2997
- });
2998
- }
3104
+ if (source.kind === "ref") {
3105
+ const typeName = modelsWithInput?.has(source.name) ? `${source.name}Input` : source.name;
3106
+ return {
3107
+ name,
3108
+ type: typeName,
3109
+ optional: true
3110
+ };
2999
3111
  }
3000
- return params;
3112
+ return {
3113
+ name,
3114
+ type: renderInputTsType(source.node, modelsWithInput),
3115
+ optional: true
3116
+ };
3001
3117
  }
3002
- __name(buildMethodParams, "buildMethodParams");
3118
+ __name(inlineArgParam, "inlineArgParam");
3119
+ function normaliseOptionalOrder(params) {
3120
+ const lastRequired = params.reduce((last, p, i) => p.optional ? last : i, -1);
3121
+ return params.map((p, i) => i < lastRequired ? {
3122
+ ...p,
3123
+ optional: false
3124
+ } : p);
3125
+ }
3126
+ __name(normaliseOptionalOrder, "normaliseOptionalOrder");
3003
3127
  function deriveMethodName(op, route) {
3004
3128
  if (op.sdk) return op.sdk;
3005
3129
  if (op.name) return nameToMethodName(op.name);
@@ -3215,7 +3339,7 @@ function sdkNeedsBigIntReplacer(root, includeInternal = false) {
3215
3339
  return false;
3216
3340
  }
3217
3341
  __name(sdkNeedsBigIntReplacer, "sdkNeedsBigIntReplacer");
3218
- function sdkNeedsBigIntReviver(root, includeInternal = false) {
3342
+ function sdkParsesJsonResponse(root, includeInternal = false) {
3219
3343
  for (const route of root.routes) {
3220
3344
  for (const op of route.operations) {
3221
3345
  if (!includeInternal && resolveModifiers2(route, op).includes("internal")) continue;
@@ -3226,7 +3350,44 @@ function sdkNeedsBigIntReviver(root, includeInternal = false) {
3226
3350
  }
3227
3351
  return false;
3228
3352
  }
3229
- __name(sdkNeedsBigIntReviver, "sdkNeedsBigIntReviver");
3353
+ __name(sdkParsesJsonResponse, "sdkParsesJsonResponse");
3354
+ function sdkResponsesUseBigInt(root, options, includeInternal = false) {
3355
+ const tainted = options.modelsWithBigInt;
3356
+ const reachesBigInt = /* @__PURE__ */ __name((type) => {
3357
+ switch (type.kind) {
3358
+ case "ref":
3359
+ return tainted?.has(type.name) ?? false;
3360
+ case "array":
3361
+ return reachesBigInt(type.item);
3362
+ case "lazy":
3363
+ return reachesBigInt(type.inner);
3364
+ case "tuple":
3365
+ return type.items.some(reachesBigInt);
3366
+ case "record":
3367
+ return reachesBigInt(type.value);
3368
+ case "union":
3369
+ case "discriminatedUnion":
3370
+ case "intersection":
3371
+ return type.members.some(reachesBigInt);
3372
+ case "inlineObject":
3373
+ return type.fields.some((f) => reachesBigInt(f.type));
3374
+ default:
3375
+ return typeNeedsScalar(type, "bigint");
3376
+ }
3377
+ }, "reachesBigInt");
3378
+ for (const route of root.routes) {
3379
+ for (const op of route.operations) {
3380
+ if (!includeInternal && resolveModifiers2(route, op).includes("internal")) continue;
3381
+ for (const resp of op.responses) {
3382
+ for (const body of resp.bodies) {
3383
+ if (classifyContentType2(body.contentType) === "json" && reachesBigInt(body.bodyType)) return true;
3384
+ }
3385
+ }
3386
+ }
3387
+ }
3388
+ return false;
3389
+ }
3390
+ __name(sdkResponsesUseBigInt, "sdkResponsesUseBigInt");
3230
3391
  function sdkNeedsJson(root, includeInternal = false) {
3231
3392
  for (const route of root.routes) {
3232
3393
  for (const op of route.operations) {
@@ -3291,9 +3452,9 @@ function generateTypeImports2(types, opFile, options, revivers = []) {
3291
3452
  unresolved.push(type);
3292
3453
  }
3293
3454
  }
3294
- const fromDir = dirname3(outPath);
3455
+ const fromDir = dirname4(outPath);
3295
3456
  for (const [typeOutPath, names] of byFile) {
3296
- let rel = relative3(fromDir, typeOutPath);
3457
+ let rel = relative4(fromDir, typeOutPath);
3297
3458
  rel = rel.replace(/\.ts$/, ".js");
3298
3459
  if (!rel.startsWith(".")) rel = "./" + rel;
3299
3460
  lines.push(`import type { ${names.sort().join(", ")} } from '${rel}';`);
@@ -3413,7 +3574,20 @@ function generateSdkOptions() {
3413
3574
  " return qs ? `?${qs}` : '';",
3414
3575
  "}",
3415
3576
  "",
3577
+ "/**",
3578
+ " * Read a JSON response body.",
3579
+ " *",
3580
+ " * No reviver: `bigIntReviver` matches any string of the form `123n` anywhere in the",
3581
+ " * document, so a contract with no bigint field would still have a legitimate string like",
3582
+ ' * "123n" silently turned into a BigInt. Clients whose contracts do use bigint import',
3583
+ " * `parseJsonWithBigInt` under this name instead.",
3584
+ " */",
3416
3585
  "export async function parseJson<T>(res: Response): Promise<T> {",
3586
+ " return JSON.parse(await res.text()) as T;",
3587
+ "}",
3588
+ "",
3589
+ "/** `parseJson` for contracts that declare a bigint, applying the `123n` reviver. */",
3590
+ "export async function parseJsonWithBigInt<T>(res: Response): Promise<T> {",
3417
3591
  " return JSON.parse(await res.text(), bigIntReviver) as T;",
3418
3592
  "}",
3419
3593
  ""
@@ -3500,6 +3674,7 @@ function generateAreaClient(input) {
3500
3674
  const unresolvedTypes = /* @__PURE__ */ new Set();
3501
3675
  let needsJson = false;
3502
3676
  let needsBigIntReplacer = false;
3677
+ let needsParseJson = false;
3503
3678
  let needsBigIntReviver = false;
3504
3679
  let needsQueryString = false;
3505
3680
  let needsReadContentType = false;
@@ -3518,27 +3693,32 @@ function generateAreaClient(input) {
3518
3693
  for (const alias of generateErrorBodyAliases(inline.root, inline.codegenOptions)) collectedErrorAliases.add(alias);
3519
3694
  if (sdkNeedsJson(inline.root, includeInternal)) needsJson = true;
3520
3695
  if (sdkNeedsBigIntReplacer(inline.root, includeInternal)) needsBigIntReplacer = true;
3521
- if (sdkNeedsBigIntReviver(inline.root, includeInternal)) needsBigIntReviver = true;
3696
+ if (sdkParsesJsonResponse(inline.root, includeInternal)) needsParseJson = true;
3697
+ if (sdkResponsesUseBigInt(inline.root, inline.codegenOptions, includeInternal)) needsBigIntReviver = true;
3522
3698
  if (sdkNeedsQueryString(inline.root, includeInternal)) needsQueryString = true;
3523
3699
  if (sdkNeedsReadContentType(inline.root, includeInternal)) needsReadContentType = true;
3524
3700
  for (const reviver of usedRevivers(methodLines)) {
3525
3701
  const stem = reviver.replace(/^revive/, "");
3526
3702
  const modelOut = inline.codegenOptions.modelOutPaths?.get(stem) ?? inline.codegenOptions.modelOutPaths?.get(stem.replace(/Output$/, ""));
3527
3703
  if (!modelOut) continue;
3528
- let rel = relative3(dirname3(outPath), modelOut).replace(/\.ts$/, ".js");
3704
+ let rel = relative4(dirname4(outPath), modelOut).replace(/\.ts$/, ".js");
3529
3705
  if (!rel.startsWith(".")) rel = "./" + rel;
3530
3706
  const set = reviversByImportPath.get(rel) ?? /* @__PURE__ */ new Set();
3531
3707
  set.add(reviver);
3532
3708
  reviversByImportPath.set(rel, set);
3533
3709
  }
3534
- const typesForFile = collectTypes2(inline.root, inline.codegenOptions.modelsWithInput, inline.codegenOptions.modelsWithOutput, includeInternal);
3710
+ const typesForFile = referencedTypes(collectTypes2(inline.root, inline.codegenOptions.modelsWithInput, inline.codegenOptions.modelsWithOutput, includeInternal), [
3711
+ ...methodLines,
3712
+ ...generateErrorBodyAliases(inline.root, inline.codegenOptions),
3713
+ ...preludeLines
3714
+ ]);
3535
3715
  const { modelOutPaths } = inline.codegenOptions;
3536
3716
  if (modelOutPaths) {
3537
- const fromDir = dirname3(outPath);
3717
+ const fromDir = dirname4(outPath);
3538
3718
  for (const t of typesForFile) {
3539
3719
  const typeOutPath = modelOutPaths.get(t);
3540
3720
  if (typeOutPath) {
3541
- let rel = relative3(fromDir, typeOutPath).replace(/\.ts$/, ".js");
3721
+ let rel = relative4(fromDir, typeOutPath).replace(/\.ts$/, ".js");
3542
3722
  if (!rel.startsWith(".")) rel = "./" + rel;
3543
3723
  const set = typesByImportPath.get(rel) ?? /* @__PURE__ */ new Set();
3544
3724
  set.add(t);
@@ -3549,14 +3729,14 @@ function generateAreaClient(input) {
3549
3729
  }
3550
3730
  }
3551
3731
  }
3552
- let sdkOptionsRel = relative3(dirname3(outPath), sdkOptionsPath).replace(/\.ts$/, ".js");
3732
+ let sdkOptionsRel = relative4(dirname4(outPath), sdkOptionsPath).replace(/\.ts$/, ".js");
3553
3733
  if (!sdkOptionsRel.startsWith(".")) sdkOptionsRel = "./" + sdkOptionsRel;
3554
3734
  const lines = [];
3555
3735
  const jsonImport = needsJson ? ", JsonValue" : "";
3556
3736
  lines.push(`import type { SdkFetch${jsonImport} } from '${sdkOptionsRel}';`);
3557
3737
  const valueImports = [];
3558
3738
  if (needsBigIntReplacer) valueImports.push("bigIntReplacer");
3559
- if (needsBigIntReviver) valueImports.push("parseJson");
3739
+ if (needsParseJson) valueImports.push(needsBigIntReviver ? "parseJsonWithBigInt as parseJson" : "parseJson");
3560
3740
  if (needsQueryString) valueImports.push("buildQueryString");
3561
3741
  if (needsReadContentType) valueImports.push("readContentType");
3562
3742
  if (valueImports.length > 0) {
@@ -3580,6 +3760,8 @@ function generateAreaClient(input) {
3580
3760
  lines.push(`import type { ${t} } from './${pascalToDotCase(t)}.js';`);
3581
3761
  }
3582
3762
  if (areaNeedsDecimalImport) lines.push(DECIMAL_IMPORT);
3763
+ const areaHeaderLuxon = headerLuxonImport(collectedMethodLines, collectedRevivePrelude);
3764
+ if (areaHeaderLuxon) lines.push(areaHeaderLuxon);
3583
3765
  const importedClients = /* @__PURE__ */ new Set();
3584
3766
  for (const sc of subareaClients) {
3585
3767
  const key = `${sc.client.className}|${sc.client.importPath}`;
@@ -3655,7 +3837,6 @@ function generateSdkAggregator(input) {
3655
3837
  __name(generateSdkAggregator, "generateSdkAggregator");
3656
3838
 
3657
3839
  // src/codegen-plain-types.ts
3658
- import { relative as relative4, dirname as dirname4 } from "path";
3659
3840
  import { computeModelsWithOutput, collectExternalOutputRefs } from "@contractkit/core";
3660
3841
  function generatePlainTypes(root, context) {
3661
3842
  const target = context?.target ?? "client";
@@ -3684,6 +3865,10 @@ function generatePlainTypes(root, context) {
3684
3865
  ].sort();
3685
3866
  const needsDecimal = rootNeedsScalar(root, "decimal") || (context?.emitRevivers && context.modelsWithDecimal ? root.models.some((m) => context.modelsWithDecimal.has(m.name)) : false);
3686
3867
  if (needsDecimal) lines.push(DECIMAL_IMPORT);
3868
+ const luxonImports = [];
3869
+ if (rootNeedsScalar(root, "date") || rootNeedsScalar(root, "time") || rootNeedsScalar(root, "datetime")) luxonImports.push("DateTime");
3870
+ if (rootNeedsScalar(root, "duration")) luxonImports.push("Duration");
3871
+ if (luxonImports.length > 0) lines.push(`import { ${luxonImports.join(", ")} } from 'luxon';`);
3687
3872
  for (const ref of allExternalRefs) {
3688
3873
  const importPath = resolveImportPath(ref, context);
3689
3874
  lines.push(`import type { ${ref} } from '${importPath}';`);
@@ -3725,8 +3910,9 @@ function generatePlainTypes(root, context) {
3725
3910
  lines.push("");
3726
3911
  lines.push(DECIMAL_CONFIG_LINE);
3727
3912
  }
3728
- if (bodyLines.some((l) => l.includes("__dec("))) {
3729
- lines.push(...DECIMAL_COERCE_DECL);
3913
+ const coerceDecls = coerceDeclsFor(bodyLines);
3914
+ if (coerceDecls.length > 0) {
3915
+ lines.push(...coerceDecls);
3730
3916
  lines.push("");
3731
3917
  }
3732
3918
  lines.push(...bodyLines);
@@ -3772,8 +3958,7 @@ function generateComments2(model, outPath) {
3772
3958
  if (model.description) {
3773
3959
  for (const l of escapeJsDocLines(model.description)) lines.push(` * ${l}`);
3774
3960
  }
3775
- const relPath = outPath ? relative4(dirname4(outPath), model.loc.file) : model.loc.file;
3776
- lines.push(` * generated from [${model.name}](file://./${relPath}#L${model.loc.line})`);
3961
+ lines.push(` * generated from ${sourceLink(model.name, outPath, model.loc.file, model.loc.line)}`);
3777
3962
  lines.push(" */");
3778
3963
  return lines;
3779
3964
  }
@@ -3919,7 +4104,7 @@ function renderOutputField(field, outputCase, modelsWithOutput, target) {
3919
4104
  __name(renderOutputField, "renderOutputField");
3920
4105
 
3921
4106
  // src/codegen-mcp.ts
3922
- import { resolveModifiers as resolveModifiers3, emittedResponses as emittedResponses2 } from "@contractkit/core";
4107
+ import { resolveModifiers as resolveModifiers3, emittedResponses as emittedResponses2, toIdentifier as toIdentifier3 } from "@contractkit/core";
3923
4108
  import { basename as basename3, dirname as dirname5, relative as relative5 } from "path";
3924
4109
  function mcpConfig(op) {
3925
4110
  return op.mcp && typeof op.mcp === "object" ? op.mcp : void 0;
@@ -3976,7 +4161,7 @@ function buildArgsProps(route, op, modelsWithInput) {
3976
4161
  if (route.params.kind === "params") {
3977
4162
  for (const node of route.params.nodes) {
3978
4163
  props.push({
3979
- key: node.name,
4164
+ key: toIdentifier3(node.name),
3980
4165
  expr: renderInputType(node.type, modelsWithInput),
3981
4166
  optional: false
3982
4167
  });
@@ -4205,9 +4390,8 @@ function renderToolClass(plan, file, options) {
4205
4390
  const { route, op, toolName, className, argsConstName } = plan;
4206
4391
  const cfg = mcpConfig(op);
4207
4392
  const lines = [];
4208
- const relFile = options.outPath ? relative5(dirname5(options.outPath), file) : file;
4209
4393
  lines.push("/**");
4210
- lines.push(` * from [${basename3(file)}](file://./${relFile}#L${op.loc.line})`);
4394
+ lines.push(` * from ${sourceLink(basename3(file), options.outPath, file, op.loc.line)}`);
4211
4395
  lines.push(" */");
4212
4396
  lines.push("@Injectable()");
4213
4397
  lines.push(`export class ${className} implements McpToolHandler {`);
@@ -4301,9 +4485,8 @@ function generateMcpFile(root, options = {}) {
4301
4485
  imports.push(`import { ${svc} } from '${mod}';`);
4302
4486
  }
4303
4487
  imports.push(...schemaImportLines(collectSchemaIds(plans, options.modelsWithInput), options));
4304
- const relFile = options.outPath ? relative5(dirname5(options.outPath), root.file) : root.file;
4305
4488
  const header = `// Auto-generated MCP tools
4306
- // generated from [${basename3(root.file)}](file://./${relFile})`;
4489
+ // generated from ${sourceLink(basename3(root.file), options.outPath, root.file)}`;
4307
4490
  return `${header}
4308
4491
  ${imports.join("\n")}
4309
4492
 
@@ -4332,23 +4515,24 @@ function generateMcpAggregator(entries) {
4332
4515
  __name(generateMcpAggregator, "generateMcpAggregator");
4333
4516
  function generateMcpRouter(options = {}) {
4334
4517
  const path = options.path ?? "/mcp";
4335
- return `import { ServerKitRouter, requireSignature } from '@maroonedsoftware/koa';
4518
+ return `import { ServerKitRouter, bodyParserMiddleware, requireSignature } from '@maroonedsoftware/koa';
4336
4519
  import { McpDispatcher, createMcpRequestContext, MCP_AUTH_POLICY } from '@maroonedsoftware/mcp';
4337
4520
 
4338
4521
  /** Mount the MCP endpoint onto a ServerKit router. Call \`registerMcpTools(container)\` at startup. */
4339
4522
  export function mountMcp(router: ReturnType<typeof ServerKitRouter>): void {
4340
- router.post('${path}', requireSignature('mcp', { policy: MCP_AUTH_POLICY }), async (ctx) => {
4523
+ router.post('${path}', bodyParserMiddleware(['json']), requireSignature('mcp', { policy: MCP_AUTH_POLICY }), async (ctx) => {
4341
4524
  const dispatcher = ctx.container.get(McpDispatcher);
4342
4525
  const context = createMcpRequestContext({ requestId: ctx.requestId, logger: ctx.logger });
4343
4526
  if (dispatcher.sessionMode === 'stateful') {
4344
4527
  ctx.respond = false;
4345
4528
  await dispatcher.dispatchStateful(
4346
- { req: ctx.req, res: ctx.res, body: ctx.request.body, sessionId: ctx.get('mcp-session-id') },
4529
+ { req: ctx.req, res: ctx.res, body: ctx.parsedBody, sessionId: ctx.get('mcp-session-id') },
4347
4530
  context,
4348
4531
  );
4349
4532
  } else {
4350
- const response = await dispatcher.dispatch(JSON.parse(ctx.rawBody), context);
4533
+ const response = await dispatcher.dispatch(JSON.parse(String(ctx.rawBody)), context);
4351
4534
  if (response) ctx.body = response;
4535
+ else ctx.status = 202; // a notification \u2014 nothing to return
4352
4536
  }
4353
4537
  });
4354
4538
  }
@@ -4368,6 +4552,7 @@ function assertWithinBase(baseOutDir, outPath) {
4368
4552
  return outPath;
4369
4553
  }
4370
4554
  __name(assertWithinBase, "assertWithinBase");
4555
+ var TEMPLATE_VAR_RE_G = /\{(\w+)\}/g;
4371
4556
  function resolveTemplate(template, vars) {
4372
4557
  return template.replace(/\{(\w+)\}/g, (_, key) => vars[key] ?? `{${key}}`);
4373
4558
  }
@@ -4562,7 +4747,10 @@ function computePubliclyReachableTypes(opAsts, contractAsts, modelsWithInput, mo
4562
4747
  __name(computePubliclyReachableTypes, "computePubliclyReachableTypes");
4563
4748
 
4564
4749
  // src/index.ts
4565
- var TYPESCRIPT_CODEGEN_VERSION = "1";
4750
+ var BIGINT_SCALARS = /* @__PURE__ */ new Set([
4751
+ "bigint"
4752
+ ]);
4753
+ var TYPESCRIPT_CODEGEN_VERSION = "2";
4566
4754
  var CACHE_MANIFEST_FILENAME = "typescript-manifest.json";
4567
4755
  var plugin = {
4568
4756
  name: "typescript",
@@ -4607,11 +4795,19 @@ async function runTypescriptCodegen(inputs, ctx, config, rootDir) {
4607
4795
  fileExists: existsSync
4608
4796
  });
4609
4797
  deleteStalePaths(result.deletedPaths);
4798
+ const unresolved = /* @__PURE__ */ new Set();
4610
4799
  for (const { relativePath, content, ifAbsent } of result.filesToWrite) {
4800
+ for (const [, key] of relativePath.matchAll(TEMPLATE_VAR_RE_G)) unresolved.add(`${key}::${relativePath}`);
4611
4801
  ctx.emitFile(relativePath, content, ifAbsent ? {
4612
4802
  ifAbsent: true
4613
4803
  } : void 0);
4614
4804
  }
4805
+ for (const entry of [
4806
+ ...unresolved
4807
+ ].sort()) {
4808
+ const [key, outPath] = entry.split("::");
4809
+ ctx.warn?.(`Output path template variable {${key}} has no value, so '${outPath}' contains it literally. Declare it in the source file's 'options { keys { ${key}: ... } }' block, or remove it from the path template.`);
4810
+ }
4615
4811
  writeManifest(manifestPath, result.manifest);
4616
4812
  }
4617
4813
  __name(runTypescriptCodegen, "runTypescriptCodegen");
@@ -4817,7 +5013,8 @@ function collectSdkOutput(config, rootDir, inputs, units, globalFiles) {
4817
5013
  const subConfigKey = stableSubConfig(config);
4818
5014
  const modelsWithInput = inputs.modelsWithInput;
4819
5015
  const modelsWithOutput = inputs.modelsWithOutput;
4820
- const modelsWithDecimal = computeModelsWithDecimal(inputs.contractRoots.flatMap((r) => r.models));
5016
+ const modelsWithDecimal = computeModelsWithScalar(inputs.contractRoots.flatMap((r) => r.models), DEFAULT_REVIVABLE_SCALARS);
5017
+ const modelsWithBigInt = computeModelsWithScalar(inputs.contractRoots.flatMap((r) => r.models), BIGINT_SCALARS);
4821
5018
  const modelMap = buildModelMap(inputs.contractRoots);
4822
5019
  const allFiles = [
4823
5020
  ...inputs.contractRoots.map((r) => r.file),
@@ -4860,6 +5057,7 @@ function collectSdkOutput(config, rootDir, inputs, units, globalFiles) {
4860
5057
  // Not covered by `root`: adding a decimal to a model in a *different* .ck file changes
4861
5058
  // this file's revivers with no change to `root` or the config.
4862
5059
  modelsWithDecimal: sliceModelSet(refs, ownNames, modelsWithDecimal),
5060
+ modelsWithBigInt: sliceModelSet(refs, ownNames, modelsWithBigInt),
4863
5061
  sdkOptionsPath,
4864
5062
  sub: subConfigKey
4865
5063
  });
@@ -4875,7 +5073,10 @@ function collectSdkOutput(config, rootDir, inputs, units, globalFiles) {
4875
5073
  modelsWithInput,
4876
5074
  modelsWithOutput,
4877
5075
  modelsWithDecimal,
4878
- emitRevivers: true
5076
+ emitRevivers: true,
5077
+ // An SDK client runs in a browser as readily as in Node, and its scaffold
5078
+ // declares no `@types/node`.
5079
+ target: "client"
4879
5080
  });
4880
5081
  } else {
4881
5082
  let rel = relative7(dirname7(typeOutPath), sdkOptionsPath).replace(/\.ts$/, ".js");
@@ -4949,6 +5150,7 @@ function collectSdkOutput(config, rootDir, inputs, units, globalFiles) {
4949
5150
  modelsWithInput: sliceModelSet(refs, /* @__PURE__ */ new Set(), modelsWithInput),
4950
5151
  modelsWithOutput: sliceModelSet(refs, /* @__PURE__ */ new Set(), modelsWithOutput),
4951
5152
  modelsWithDecimal: sliceModelSet(refs, /* @__PURE__ */ new Set(), modelsWithDecimal),
5153
+ modelsWithBigInt: sliceModelSet(refs, /* @__PURE__ */ new Set(), modelsWithBigInt),
4952
5154
  sdkOptionsPath,
4953
5155
  className,
4954
5156
  includeInternal: config.includeInternal ?? false,
@@ -4968,6 +5170,7 @@ function collectSdkOutput(config, rootDir, inputs, units, globalFiles) {
4968
5170
  modelsWithInput,
4969
5171
  modelsWithOutput,
4970
5172
  modelsWithDecimal,
5173
+ modelsWithBigInt,
4971
5174
  modelMap,
4972
5175
  includeInternal: config.includeInternal,
4973
5176
  clientClassName: className
@@ -4994,6 +5197,7 @@ function collectSdkOutput(config, rootDir, inputs, units, globalFiles) {
4994
5197
  modelsWithInput: sliceModelSet(refs, /* @__PURE__ */ new Set(), modelsWithInput),
4995
5198
  modelsWithOutput: sliceModelSet(refs, /* @__PURE__ */ new Set(), modelsWithOutput),
4996
5199
  modelsWithDecimal: sliceModelSet(refs, /* @__PURE__ */ new Set(), modelsWithDecimal),
5200
+ modelsWithBigInt: sliceModelSet(refs, /* @__PURE__ */ new Set(), modelsWithBigInt),
4997
5201
  sdkOptionsPath,
4998
5202
  includeInternal: config.includeInternal ?? false,
4999
5203
  sub: subConfigKey
@@ -5012,6 +5216,7 @@ function collectSdkOutput(config, rootDir, inputs, units, globalFiles) {
5012
5216
  modelsWithInput,
5013
5217
  modelsWithOutput,
5014
5218
  modelsWithDecimal,
5219
+ modelsWithBigInt,
5015
5220
  modelMap,
5016
5221
  includeInternal: config.includeInternal
5017
5222
  })
@@ -5082,6 +5287,7 @@ function collectSdkOutput(config, rootDir, inputs, units, globalFiles) {
5082
5287
  modelsWithInput: sliceModelSet(allInlineRefs, /* @__PURE__ */ new Set(), modelsWithInput),
5083
5288
  modelsWithOutput: sliceModelSet(allInlineRefs, /* @__PURE__ */ new Set(), modelsWithOutput),
5084
5289
  modelsWithDecimal: sliceModelSet(allInlineRefs, /* @__PURE__ */ new Set(), modelsWithDecimal),
5290
+ modelsWithBigInt: sliceModelSet(allInlineRefs, /* @__PURE__ */ new Set(), modelsWithBigInt),
5085
5291
  sdkOptionsPath,
5086
5292
  includeInternal: config.includeInternal ?? false,
5087
5293
  sub: subConfigKey
@@ -5096,6 +5302,7 @@ function collectSdkOutput(config, rootDir, inputs, units, globalFiles) {
5096
5302
  modelsWithInput,
5097
5303
  modelsWithOutput,
5098
5304
  modelsWithDecimal,
5305
+ modelsWithBigInt,
5099
5306
  modelMap,
5100
5307
  includeInternal: config.includeInternal
5101
5308
  }
@@ -5237,11 +5444,15 @@ function collectZodOutput(config, rootDir, inputs, units) {
5237
5444
  render: /* @__PURE__ */ __name(() => [
5238
5445
  {
5239
5446
  relativePath: outPath,
5447
+ // Server-shaped, which is what this sub-generator has always emitted. The
5448
+ // standalone `zod:` output has no target option of its own; only the SDK's
5449
+ // schemas are client-shaped, and they pass their own target.
5240
5450
  content: generateContract(ast, {
5241
5451
  modelOutPaths,
5242
5452
  currentOutPath: outPath,
5243
5453
  modelsWithInput,
5244
- modelsWithOutput
5454
+ modelsWithOutput,
5455
+ target: "server"
5245
5456
  })
5246
5457
  }
5247
5458
  ], "render")