Skip to content

Commit bf72c41

Browse files
committed
fix(docs): teach composition, not .extend — and stop depending on which FieldSchema declaration you get
CI rejected the first fix: `FieldSchema.in.extend(…)` passed locally and failed with "Property 'in' does not exist on type 'ZodObject<…>'" on the runner. The two builds emit DIFFERENT declarations for the same source — locally z.ZodPipe<z.ZodObject<…>>, in CI a plain z.ZodObject. The runtime is unambiguous: after `rm -rf dist && pnpm build`, FieldSchema is a `bound ZodPipe` with `.extend === undefined` and `.in` a ZodObject. So CI's declaration contradicts the value it describes — `.extend()` type-checks there and throws at runtime. That is a real spec-side divergence (likely inference instability in the DTS bundling of these very large zod types) and deserves its own investigation; it is not something this docs fix should paper over. So the checked example now uses only `.parse()`: parse with FieldSchema, validate the additions alongside it. Correct under either declaration, verified to run, and better advice anyway — composition keeps the transform, which `.extend()` discards. `.in.extend()` stays in prose as the merged-schema route with that caveat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ajwvrmd1hDC9RBofYBhGuR
1 parent 2325706 commit bf72c41

2 files changed

Lines changed: 34 additions & 19 deletions

File tree

.changeset/docs-fieldschema-extend-rot.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,21 @@ time, which makes it a **`ZodPipe`, not a `ZodObject`** — `.extend` is `undefi
2020
on it (verified against the built package). Anyone following the FAQ got a
2121
`not a function` throw. The example also used `z` without importing it.
2222

23-
Rewritten to `FieldSchema.in.extend({ … })``.in` is the input object the pipe
24-
wraps — verified to parse, and marked so the gate holds it. It also now names why
25-
(the same applies to `ObjectSchema` / `ActionSchema`) and warns that the extended
26-
schema no longer runs the transform.
23+
Rewritten to **compose** — parse with `FieldSchema`, validate your additions
24+
alongside it — verified to both type-check and run. `.in.extend()` is mentioned in
25+
prose as the merged-schema route, with the caveat that it skips the transform.
26+
27+
**A divergence worth knowing about, found while fixing this.** The first fix used
28+
`FieldSchema.in.extend(…)` in the checked block. It passed locally and failed in
29+
CI with `Property 'in' does not exist on type 'ZodObject<…>'` — the two builds
30+
emit **different declarations for the same source**: locally
31+
`z.ZodPipe<z.ZodObject<…>>`, in CI a plain `z.ZodObject`. The runtime is
32+
unambiguous (`bound ZodPipe`, `.extend === undefined`, verified after a clean
33+
`rm -rf dist && pnpm build`), so **CI's declaration contradicts the value it
34+
describes**`.extend()` type-checks there and throws at runtime. Probably
35+
inference instability in the DTS bundling of these very large zod types; worth its
36+
own investigation. The example now uses only `.parse()`, so it is correct under
37+
either declaration and the doc is not hostage to which one you get.
2738

2839
**The sweep's method is recorded in the gate's docstring**, because two traps make
2940
a naive pass report a confident "nothing found":

content/docs/deployment/troubleshooting.mdx

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -359,30 +359,34 @@ Yes. All schemas work with plain JavaScript. You lose compile-time type checking
359359

360360
### How do I extend a built-in schema?
361361

362-
Use Zod's `.extend()` — but reach the **object** first. Several built-in schemas
363-
(`FieldSchema`, `ObjectSchema`, `ActionSchema`, …) carry a `.transform()` that
364-
lowers author-facing sugar at parse time, which makes them a `ZodPipe` rather
365-
than a `ZodObject`. `.extend()` does not exist on a pipe; `.in` is the input
366-
object it wraps:
362+
**Not with `.extend()` on the schema itself.** Several built-in schemas —
363+
`FieldSchema`, `ObjectSchema`, `ActionSchema` — carry a `.transform()` that lowers
364+
author-facing sugar at parse time, which makes the exported value a **`ZodPipe`,
365+
not a `ZodObject`**. `FieldSchema.extend` is `undefined`, so calling it throws
366+
`is not a function`.
367+
368+
Compose instead: parse with the built-in schema, and validate your additions
369+
alongside it. This keeps the transform running, which `.extend()` would discard.
367370

368371
{/* os:check */}
369372
```typescript
370373
import { z } from 'zod';
371374
import { FieldSchema } from '@objectstack/spec/data';
372375

373-
// `.in` — the ZodObject the pipe wraps. Calling `.extend()` on FieldSchema
374-
// itself throws "not a function": it is a ZodPipe.
375-
const CustomFieldSchema = FieldSchema.in.extend({
376-
customProperty: z.string().optional(),
377-
});
376+
const CustomProps = z.object({ customProperty: z.string().optional() });
377+
378+
function parseCustomField(input: unknown) {
379+
return { ...FieldSchema.parse(input), ...CustomProps.parse(input) };
380+
}
378381

379-
CustomFieldSchema.parse({ name: 'code', type: 'text', customProperty: 'x' });
382+
parseCustomField({ name: 'code', type: 'text', customProperty: 'x' });
380383
```
381384

382-
Note the extended schema no longer runs the transform, so author-facing sugar
383-
that the pipe would have lowered stays raw. When you only need to *validate* an
384-
extra key alongside the real parse, prefer parsing with the built-in schema and
385-
checking your own additions separately.
385+
If you genuinely need one merged schema object, `FieldSchema.in` is the
386+
`ZodObject` the pipe wraps, so `FieldSchema.in.extend({ … })` builds one — but
387+
the result **skips the transform**, so author-facing sugar the pipe would have
388+
lowered stays raw. Prefer the composition above unless you specifically want the
389+
untransformed shape.
386390

387391
### Where are the JSON Schemas for IDE autocomplete?
388392

0 commit comments

Comments
 (0)