Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@contractkit/plugin-typescript

Package Overview
Dependencies
Maintainers
1
Versions
21
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@contractkit/plugin-typescript - npm Package Compare versions

Comparing version
0.23.1
to
0.24.0
+6
-6
.turbo/turbo-build$colon$ci.log
> @contractkit/plugin-typescript@0.23.1 build:ci /home/runner/work/ContractKit/ContractKit/packages/plugin-typescript
> @contractkit/plugin-typescript@0.24.0 build:ci /home/runner/work/ContractKit/ContractKit/packages/plugin-typescript
> eslint --max-warnings=0 && pnpm run build
> @contractkit/plugin-typescript@0.23.1 build /home/runner/work/ContractKit/ContractKit/packages/plugin-typescript
> @contractkit/plugin-typescript@0.24.0 build /home/runner/work/ContractKit/ContractKit/packages/plugin-typescript
> tsup src/index.ts --format esm --sourcemap --dts && tsc --emitDeclarationOnly --declaration

@@ -14,7 +14,7 @@

ESM Build start
ESM dist/index.js 149.66 KB
ESM dist/index.js.map 326.35 KB
ESM ⚡️ Build success in 367ms
ESM dist/index.js 149.94 KB
ESM dist/index.js.map 327.56 KB
ESM ⚡️ Build success in 339ms
DTS Build start
DTS ⚡️ Build success in 6065ms
DTS ⚡️ Build success in 4724ms
DTS dist/index.d.ts 1.82 KB
> @contractkit/plugin-typescript@0.23.1 test:ci /home/runner/work/ContractKit/ContractKit/packages/plugin-typescript
> @contractkit/plugin-typescript@0.24.0 test:ci /home/runner/work/ContractKit/ContractKit/packages/plugin-typescript
> vitest run --coverage

@@ -9,13 +9,13 @@

✓ tests/codegen-contract.test.ts (125 tests) 78ms
✓ tests/codegen-sdk.test.ts (132 tests) 128ms
✓ tests/codegen-operation.test.ts (88 tests) 109ms
✓ tests/codegen-plain-types.test.ts (61 tests) 62ms
✓ tests/codegen-server.test.ts (19 tests) 88ms
✓ tests/pipeline.test.ts (25 tests) 277ms
✓ tests/codegen-operation.test.ts (88 tests) 104ms
✓ tests/codegen-contract.test.ts (127 tests) 116ms
✓ tests/codegen-sdk.test.ts (132 tests) 134ms
✓ tests/codegen-plain-types.test.ts (61 tests) 34ms
✓ tests/pipeline.test.ts (25 tests) 149ms
✓ tests/codegen-server.test.ts (19 tests) 31ms
 Test Files  6 passed (6)
 Tests  450 passed (450)
 Start at  14:28:27
 Duration  6.25s (transform 3.59s, setup 0ms, import 11.84s, tests 741ms, environment 3ms)
 Tests  452 passed (452)
 Start at  15:00:19
 Duration  5.63s (transform 3.00s, setup 0ms, import 10.57s, tests 567ms, environment 4ms)

@@ -26,5 +26,5 @@  % Coverage report from v8

-------------------|---------|----------|---------|---------|-------------------
All files | 79.97 | 75.13 | 83.5 | 82.58 |
src | 79.75 | 74.81 | 83.06 | 82.34 |
...n-contract.ts | 87.85 | 82.2 | 90.08 | 88.98 | ...1070,1075-1076
All files | 80 | 75.28 | 83.5 | 82.61 |
src | 79.79 | 74.96 | 83.06 | 82.37 |
...n-contract.ts | 87.92 | 82.7 | 90.08 | 89.06 | ...1085,1090-1091
...-operation.ts | 78.13 | 74.26 | 77.77 | 79.69 | ...69-680,685-686

@@ -31,0 +31,0 @@ ...lain-types.ts | 89.08 | 78.37 | 96.66 | 92.3 | ...31,243,249,301

# @contractkit/contractkit-plugin-typescript
## 0.24.0
### Minor Changes
- 27521cc: Preserve the optional-field modality through `format(input=...)` / `format(output=...)` transforms. Optional fields are now emitted with a conditional spread (`...(data.x !== undefined ? { k: data.x } : {})` for output, `... != null` for input) so the inferred `z.input` / `z.output` type widens the property to `k?: T` instead of required-nullable `k: T | undefined`. Consumer code that constructs values with `...(x ? { k: x } : {})` is now assignable to the schema's inferred type. Runtime wire output is unchanged.
## 0.23.1

@@ -4,0 +10,0 @@

@@ -26,5 +26,5 @@

<div class='fl pad1y space-right2'>
<span class="strong">79.97% </span>
<span class="strong">80% </span>
<span class="quiet">Statements</span>
<span class='fraction'>2029/2537</span>
<span class='fraction'>2033/2541</span>
</div>

@@ -34,5 +34,5 @@

<div class='fl pad1y space-right2'>
<span class="strong">75.13% </span>
<span class="strong">75.28% </span>
<span class="quiet">Branches</span>
<span class='fraction'>1230/1637</span>
<span class='fraction'>1234/1639</span>
</div>

@@ -49,5 +49,5 @@

<div class='fl pad1y space-right2'>
<span class="strong">82.58% </span>
<span class="strong">82.61% </span>
<span class="quiet">Lines</span>
<span class='fraction'>1778/2153</span>
<span class='fraction'>1782/2157</span>
</div>

@@ -67,3 +67,3 @@

</div>
<div class='status-line medium'></div>
<div class='status-line high'></div>
<div class="pad1">

@@ -87,13 +87,13 @@ <table class="coverage-summary">

<td class="file medium" data-value="src"><a href="src/index.html">src</a></td>
<td data-value="79.75" class="pic medium">
<td data-value="79.79" class="pic medium">
<div class="chart"><div class="cover-fill" style="width: 79%"></div><div class="cover-empty" style="width: 21%"></div></div>
</td>
<td data-value="79.75" class="pct medium">79.75%</td>
<td data-value="2485" class="abs medium">1982/2485</td>
<td data-value="74.81" class="pct medium">74.81%</td>
<td data-value="1604" class="abs medium">1200/1604</td>
<td data-value="79.79" class="pct medium">79.79%</td>
<td data-value="2489" class="abs medium">1986/2489</td>
<td data-value="74.96" class="pct medium">74.96%</td>
<td data-value="1606" class="abs medium">1204/1606</td>
<td data-value="83.06" class="pct high">83.06%</td>
<td data-value="372" class="abs high">309/372</td>
<td data-value="82.34" class="pct high">82.34%</td>
<td data-value="2107" class="abs high">1735/2107</td>
<td data-value="82.37" class="pct high">82.37%</td>
<td data-value="2111" class="abs high">1739/2111</td>
</tr>

@@ -124,3 +124,3 @@

<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-12T14:28:33.558Z
at 2026-05-12T15:00:24.685Z
</div>

@@ -127,0 +127,0 @@ <script src="prettify.js"></script>

@@ -985,3 +985,3 @@

<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-12T14:28:33.558Z
at 2026-05-12T15:00:24.685Z
</div>

@@ -988,0 +988,0 @@ <script src="../prettify.js"></script>

@@ -26,5 +26,5 @@

<div class='fl pad1y space-right2'>
<span class="strong">79.75% </span>
<span class="strong">79.79% </span>
<span class="quiet">Statements</span>
<span class='fraction'>1982/2485</span>
<span class='fraction'>1986/2489</span>
</div>

@@ -34,5 +34,5 @@

<div class='fl pad1y space-right2'>
<span class="strong">74.81% </span>
<span class="strong">74.96% </span>
<span class="quiet">Branches</span>
<span class='fraction'>1200/1604</span>
<span class='fraction'>1204/1606</span>
</div>

@@ -49,5 +49,5 @@

<div class='fl pad1y space-right2'>
<span class="strong">82.34% </span>
<span class="strong">82.37% </span>
<span class="quiet">Lines</span>
<span class='fraction'>1735/2107</span>
<span class='fraction'>1739/2111</span>
</div>

@@ -86,13 +86,13 @@

<td class="file high" data-value="codegen-contract.ts"><a href="codegen-contract.ts.html">codegen-contract.ts</a></td>
<td data-value="87.85" class="pic high">
<td data-value="87.92" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 87%"></div><div class="cover-empty" style="width: 13%"></div></div>
</td>
<td data-value="87.85" class="pct high">87.85%</td>
<td data-value="675" class="abs high">593/675</td>
<td data-value="82.2" class="pct high">82.2%</td>
<td data-value="472" class="abs high">388/472</td>
<td data-value="87.92" class="pct high">87.92%</td>
<td data-value="679" class="abs high">597/679</td>
<td data-value="82.7" class="pct high">82.7%</td>
<td data-value="474" class="abs high">392/474</td>
<td data-value="90.08" class="pct high">90.08%</td>
<td data-value="121" class="abs high">109/121</td>
<td data-value="88.98" class="pct high">88.98%</td>
<td data-value="554" class="abs high">493/554</td>
<td data-value="89.06" class="pct high">89.06%</td>
<td data-value="558" class="abs high">497/558</td>
</tr>

@@ -198,3 +198,3 @@

<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-12T14:28:33.558Z
at 2026-05-12T15:00:24.685Z
</div>

@@ -201,0 +201,0 @@ <script src="../prettify.js"></script>

@@ -733,3 +733,3 @@

<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-12T14:28:33.558Z
at 2026-05-12T15:00:24.685Z
</div>

@@ -736,0 +736,0 @@ <script src="../prettify.js"></script>

@@ -580,3 +580,3 @@

<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-12T14:28:33.558Z
at 2026-05-12T15:00:24.685Z
</div>

@@ -583,0 +583,0 @@ <script src="../prettify.js"></script>

@@ -344,7 +344,7 @@

<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">895x</span>
<span class="cline-any cline-yes">903x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">320x</span>
<span class="cline-any cline-yes">325x</span>
<span class="cline-any cline-neutral">&nbsp;</span>

@@ -385,3 +385,3 @@ <span class="cline-any cline-neutral">&nbsp;</span>

<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-neutral">&nbsp;</span>

@@ -394,3 +394,3 @@ <span class="cline-any cline-neutral">&nbsp;</span>

<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">259x</span>
<span class="cline-any cline-yes">265x</span>
<span class="cline-any cline-neutral">&nbsp;</span>

@@ -407,3 +407,3 @@ <span class="cline-any cline-neutral">&nbsp;</span>

<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">179x</span>
<span class="cline-any cline-yes">181x</span>
<span class="cline-any cline-neutral">&nbsp;</span>

@@ -418,3 +418,3 @@ <span class="cline-any cline-neutral">&nbsp;</span>

<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">138x</span>
<span class="cline-any cline-yes">140x</span>
<span class="cline-any cline-neutral">&nbsp;</span>

@@ -819,3 +819,3 @@ <span class="cline-any cline-neutral">&nbsp;</span>

<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-12T14:28:33.558Z
at 2026-05-12T15:00:24.685Z
</div>

@@ -822,0 +822,0 @@ <script src="../prettify.js"></script>

@@ -104,3 +104,3 @@

<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-12T14:28:33.558Z
at 2026-05-12T15:00:24.685Z
</div>

@@ -107,0 +107,0 @@ <script src="../prettify.js"></script>

@@ -1,1 +0,1 @@

{"version":3,"file":"codegen-contract.d.ts","sourceRoot":"","sources":["../src/codegen-contract.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACR,gBAAgB,EAChB,SAAS,EAET,gBAAgB,EAWhB,UAAU,EACb,MAAM,mBAAmB,CAAC;AAO3B;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAStD;AAID,4GAA4G;AAC5G,MAAM,WAAW,sBAAsB;IACnC,sDAAsD;IACtD,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,8DAA8D;IAC9D,cAAc,EAAE,MAAM,CAAC;IACvB,qFAAqF;IACrF,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC/B,6GAA6G;IAC7G,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAChC;AAID;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,uBAAuB,GAAE,GAAG,CAAC,MAAM,CAAa,GAAG,GAAG,CAAC,MAAM,CAAC,CAsCzH;AAkBD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,MAAM,CAkEjG;AAuUD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,gBAAgB,EAAE,kBAAkB,CAAC,EAAE,OAAO,GAAG,QAAQ,EAAE,WAAW,CAAC,EAAE,UAAU,GAAG,MAAM,CA6B5H;AA0ND;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,EAAE,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC,EAAE,UAAU,GAAG,MAAM,CAuDvH;AA2BD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,EAAE,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC,EAAE,UAAU,GAAG,MAAM,CAsCvH;AA0CD,oFAAoF;AACpF,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAuB7E;AAED,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAE7E;AAED,4FAA4F;AAC5F,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAiBjE;AAED,mGAAmG;AACnG,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,EAAE,CAcpE;AAED,0EAA0E;AAC1E,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,gBAAgB,EAAE,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CA4BvG;AAmCD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CA6D/D;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,MAAM,CAgB3F;AAED,gGAAgG;AAChG,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD"}
{"version":3,"file":"codegen-contract.d.ts","sourceRoot":"","sources":["../src/codegen-contract.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACR,gBAAgB,EAChB,SAAS,EAET,gBAAgB,EAWhB,UAAU,EACb,MAAM,mBAAmB,CAAC;AAO3B;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAStD;AAID,4GAA4G;AAC5G,MAAM,WAAW,sBAAsB;IACnC,sDAAsD;IACtD,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,8DAA8D;IAC9D,cAAc,EAAE,MAAM,CAAC;IACvB,qFAAqF;IACrF,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC/B,6GAA6G;IAC7G,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAChC;AAID;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,uBAAuB,GAAE,GAAG,CAAC,MAAM,CAAa,GAAG,GAAG,CAAC,MAAM,CAAC,CAsCzH;AAkBD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,MAAM,CAkEjG;AAgVD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,gBAAgB,EAAE,kBAAkB,CAAC,EAAE,OAAO,GAAG,QAAQ,EAAE,WAAW,CAAC,EAAE,UAAU,GAAG,MAAM,CA6B5H;AAgOD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,EAAE,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC,EAAE,UAAU,GAAG,MAAM,CAuDvH;AA2BD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,EAAE,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC,EAAE,UAAU,GAAG,MAAM,CAsCvH;AA0CD,oFAAoF;AACpF,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAuB7E;AAED,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAE7E;AAED,4FAA4F;AAC5F,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAiBjE;AAED,mGAAmG;AACnG,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,EAAE,CAcpE;AAED,0EAA0E;AAC1E,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,gBAAgB,EAAE,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CA4BvG;AAmCD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CA6D/D;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,MAAM,CAgB3F;AAED,gGAAgG;AAChG,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD"}
{
"name": "@contractkit/plugin-typescript",
"version": "0.23.1",
"version": "0.24.0",
"description": "ContractKit built-in plugin: TypeScript codegen (SDK clients, Koa routers, Zod schemas, plain types)",

@@ -32,4 +32,4 @@ "author": {

"devDependencies": {
"@repo/config-typescript": "0.1.0",
"@repo/config-eslint": "0.3.1"
"@repo/config-eslint": "0.3.1",
"@repo/config-typescript": "0.1.0"
},

@@ -36,0 +36,0 @@ "scripts": {

@@ -303,4 +303,13 @@ import { relative, dirname } from 'node:path';

const outputKey = applyCase(field.name, outputCase);
const val = field.optional ? `data.${inputKey} ?? undefined` : `data.${inputKey}`;
lines.push(` ${quoteKey(outputKey)}: ${val},`);
if (field.optional) {
// Conditional spread keeps the field optional (`k?: T`) in the inferred
// z.output / z.input type, instead of widening to required-nullable (`k: T | undefined`).
// Consumer code built with `...(x ? { k: x } : {})` is only assignable to the optional form.
// When inputCase is set, the input schema uses `.nullish()` so the guard must reject both
// null and undefined; otherwise `.optional()` only allows undefined.
const guard = hasInputTransform ? `data.${inputKey} != null` : `data.${inputKey} !== undefined`;
lines.push(` ...(${guard} ? { ${quoteKey(outputKey)}: data.${inputKey} } : {}),`);
} else {
lines.push(` ${quoteKey(outputKey)}: data.${inputKey},`);
}
}

@@ -743,5 +752,9 @@ lines.push(`}));`);

const snakeKey = camelToSnake(f.name);
// Optional fields use .nullish() on input; coerce null → undefined in output
const val = f.optional ? `data.${snakeKey} ?? undefined` : `data.${snakeKey}`;
return ` ${quoteKey(f.name)}: ${val},`;
// Optional fields use .nullish() on input. Conditional spread (instead of `?? undefined`)
// keeps the key optional in the inferred output type (`k?: T`) rather than widening to
// required-nullable (`k: T | undefined`).
if (f.optional) {
return ` ...(data.${snakeKey} != null ? { ${quoteKey(f.name)}: data.${snakeKey} } : {}),`;
}
return ` ${quoteKey(f.name)}: data.${snakeKey},`;
})

@@ -757,4 +770,6 @@ .join('\n');

const pascalKey = camelToPascal(f.name);
const val = f.optional ? `data.${pascalKey} ?? undefined` : `data.${pascalKey}`;
return ` ${quoteKey(f.name)}: ${val},`;
if (f.optional) {
return ` ...(data.${pascalKey} != null ? { ${quoteKey(f.name)}: data.${pascalKey} } : {}),`;
}
return ` ${quoteKey(f.name)}: data.${pascalKey},`;
})

@@ -761,0 +776,0 @@ .join('\n');

@@ -1063,3 +1063,3 @@ import { generateContract, renderType } from '../src/codegen-contract.js';

expect(output).toContain('grantType: data.grant_type');
expect(output).toContain('clientId: data.client_id ?? undefined');
expect(output).toContain('clientId: data.client_id,');
expect(output).toContain('clientSecret: data.client_secret');

@@ -1069,3 +1069,6 @@ expect(output).toContain('export type Child = z.output<typeof Child>');

it('input=snake: optional fields coerce null → undefined in the transform', () => {
it('input=snake: optional fields use conditional spread guarded by `!= null`', () => {
// .nullish() on input accepts null and undefined; conditional spread keeps the field
// optional in the inferred output type (`k?: T`) instead of widening to required-nullable
// (`k: T | undefined`). The `!= null` guard omits the key for both null and undefined.
const root = contractRoot([

@@ -1081,5 +1084,38 @@ model(

expect(output).toContain('firstName: data.first_name,');
expect(output).toContain('clientId: data.client_id ?? undefined,');
expect(output).toContain('...(data.client_id != null ? { clientId: data.client_id } : {}),');
expect(output).not.toContain('?? undefined');
});
it('output=snake: optional fields use conditional spread guarded by `!== undefined`', () => {
// With only outputCase set, input is `.optional()` (undefined-only). Conditional spread keeps
// the field optional in the inferred z.input type so consumers building values with
// `...(x ? { k: x } : {})` are assignable.
const root = contractRoot([
model('Token', [field('accessToken', scalarType('string')), field('refreshToken', scalarType('string'), { optional: true })], {
outputCase: 'snake',
}),
]);
const output = generateContract(root);
expect(output).toContain('refreshToken: z.string().optional()');
expect(output).toContain('access_token: data.accessToken,');
expect(output).toContain('...(data.refreshToken !== undefined ? { refresh_token: data.refreshToken } : {}),');
expect(output).not.toContain('?? undefined');
});
it('input=pascal: inline-object optional fields use conditional spread guarded by `!= null`', () => {
const dataType = inlineObjectType([
field('id', scalarType('uuid')),
field('amount', scalarType('number'), { optional: true }),
]);
const root = contractRoot([
model('Webhook', [field('event', scalarType('string')), field('data', dataType)], { inputCase: 'pascal' }),
]);
const output = generateContract(root);
// Inline object emits its own transform inside the Webhook input shape.
expect(output).toContain('Amount: z.coerce.number().nullish()');
expect(output).toContain('id: data.Id,');
expect(output).toContain('...(data.Amount != null ? { amount: data.Amount } : {}),');
expect(output).not.toContain('?? undefined');
});
});
});

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display