Skip to content

Commit 9867281

Browse files
os-zhuangclaude
andauthored
fix(types): zod example teaches the Zod 4 .issues accessor, and examples/ is type-checked (#2919) (#2939)
`ZodError.errors` was removed in Zod 4 (the repo is on 4.4.3). `packages/types/examples/zod-validation-example.ts` read `.errors` in seven places, so every `console.error` printed `undefined` and the last one — `invalidButtonResult.error.errors.length` — threw `TypeError: Cannot read properties of undefined (reading 'length')`, killing the example before its summary. Same bug and same cause as the `objectui validate` fix in #2919. `src/zod/README.md` documented the same dead accessor plus a Zod 3 issue shape; both corrected against what 4.4.3 emits (`invalid_value` + `values`, and "Invalid option: expected one of ..."). The example was invisible to CI, so the swap alone would let this rot again. `packages/types` type-checks a project whose `include` is `["src/**/*"]`, so `examples/` sat outside it — the `"examples"` entry in `exclude` was belt-and-braces, and deleting it alone would have changed nothing. Examples cannot join that project either: it is the package build (`tsc` -> `dist`) with `rootDir: "./src"`, `composite` and `declaration`. Added an emit-free `tsconfig.examples.json` and chained it from `type-check`. The example now imports `../src/zod/index.zod` instead of `../dist/zod/index.zod.js`, matching its three sibling example files, so the check needs no prior build. Verified the gate has teeth: restoring `.errors` fails with seven `TS2339: Property 'errors' does not exist on type 'ZodError<...>'`. Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
1 parent 059f7ba commit 9867281

5 files changed

Lines changed: 80 additions & 12 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
"@object-ui/types": patch
3+
---
4+
5+
fix(types): zod-validation example and zod README teach the Zod 4 `.issues` accessor, and `examples/` is now type-checked
6+
7+
`ZodError.errors` was removed in Zod 4 (the repo is on 4.4.3). The
8+
`packages/types/examples/zod-validation-example.ts` documentation example read
9+
`.errors` in seven places, so every `console.error` printed `undefined` and the
10+
last one — `invalidButtonResult.error.errors.length` — threw
11+
`TypeError: Cannot read properties of undefined (reading 'length')`, killing the
12+
example before its summary. Same bug, same cause as the `objectui validate` fix
13+
in #2919; now reads `.issues`.
14+
15+
`src/zod/README.md` documented the same dead accessor plus a Zod 3 issue shape
16+
(`code: 'invalid_enum_value'`, `"Invalid enum value. Expected …"`). Both were
17+
corrected against what 4.4.3 actually emits: `code: 'invalid_value'` with a
18+
`values` array and `'Invalid option: expected one of …'`.
19+
20+
**The example was invisible to CI, so the swap alone would let this rot again.**
21+
`packages/types` type-checks with `tsc --noEmit` over a project whose `include`
22+
is `["src/**/*"]``examples/` was outside it (the `"examples"` entry in
23+
`exclude` was belt-and-braces; deleting it alone would have changed nothing).
24+
Examples cannot simply join that project either: it is the package build
25+
(`tsc``dist`) with `rootDir: "./src"`, `composite` and `declaration`, so
26+
example files are both outside `rootDir` and would emit into `dist`.
27+
28+
Added `packages/types/tsconfig.examples.json` — an emit-free project covering
29+
`examples/**/*.ts` — and chained it: `"type-check": "tsc --noEmit && tsc -p
30+
tsconfig.examples.json"`. The example also now imports from `../src/zod/index.zod`
31+
rather than `../dist/zod/index.zod.js`, matching its three sibling example files
32+
(`dashboard.ts`, `login-form.ts`, `rest-data-source.ts`, all on `../src/index`)
33+
so the check needs no prior build.
34+
35+
Verified the gate has teeth rather than trusting the green: restoring `.errors`
36+
makes `tsc -p tsconfig.examples.json` fail with seven
37+
`TS2339: Property 'errors' does not exist on type 'ZodError<…>'`. The example
38+
also runs clean end-to-end again, printing `Expected validation errors: 2`
39+
where it previously threw.
40+
41+
No runtime or published-type change: `examples/` is not in the package's `files`.

packages/types/examples/zod-validation-example.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@ import {
1919
CardSchema,
2020
DataTableSchema,
2121
KanbanSchema,
22-
} from '../dist/zod/index.zod.js';
22+
} from '../src/zod/index.zod';
23+
24+
// The failure accessor below is `error.issues`. Zod 4 removed the `.errors`
25+
// alias, so `.errors` reads `undefined` and any `.length`/`.forEach` on it
26+
// throws — see the same bug fixed in `objectui validate` (#2919).
2327

2428
// Example 1: Validate a Button component
2529
const buttonExample = {
@@ -33,7 +37,7 @@ const buttonExample = {
3337
const buttonResult = ButtonSchema.safeParse(buttonExample);
3438
console.log('Button validation:', buttonResult.success ? 'PASSED ✓' : 'FAILED ✗');
3539
if (!buttonResult.success) {
36-
console.error('Button errors:', buttonResult.error.errors);
40+
console.error('Button errors:', buttonResult.error.issues);
3741
}
3842

3943
// Example 2: Validate an Input component
@@ -49,7 +53,7 @@ const inputExample = {
4953
const inputResult = InputSchema.safeParse(inputExample);
5054
console.log('Input validation:', inputResult.success ? 'PASSED ✓' : 'FAILED ✗');
5155
if (!inputResult.success) {
52-
console.error('Input errors:', inputResult.error.errors);
56+
console.error('Input errors:', inputResult.error.issues);
5357
}
5458

5559
// Example 3: Validate a Form component
@@ -77,7 +81,7 @@ const formExample = {
7781
const formResult = FormSchema.safeParse(formExample);
7882
console.log('Form validation:', formResult.success ? 'PASSED ✓' : 'FAILED ✗');
7983
if (!formResult.success) {
80-
console.error('Form errors:', formResult.error.errors);
84+
console.error('Form errors:', formResult.error.issues);
8185
}
8286

8387
// Example 4: Validate a Card component
@@ -97,7 +101,7 @@ const cardExample = {
97101
const cardResult = CardSchema.safeParse(cardExample);
98102
console.log('Card validation:', cardResult.success ? 'PASSED ✓' : 'FAILED ✗');
99103
if (!cardResult.success) {
100-
console.error('Card errors:', cardResult.error.errors);
104+
console.error('Card errors:', cardResult.error.issues);
101105
}
102106

103107
// Example 5: Validate a DataTable component
@@ -124,7 +128,7 @@ const dataTableExample = {
124128
const dataTableResult = DataTableSchema.safeParse(dataTableExample);
125129
console.log('DataTable validation:', dataTableResult.success ? 'PASSED ✓' : 'FAILED ✗');
126130
if (!dataTableResult.success) {
127-
console.error('DataTable errors:', dataTableResult.error.errors);
131+
console.error('DataTable errors:', dataTableResult.error.issues);
128132
}
129133

130134
// Example 6: Validate a Kanban component
@@ -160,7 +164,7 @@ const kanbanExample = {
160164
const kanbanResult = KanbanSchema.safeParse(kanbanExample);
161165
console.log('Kanban validation:', kanbanResult.success ? 'PASSED ✓' : 'FAILED ✗');
162166
if (!kanbanResult.success) {
163-
console.error('Kanban errors:', kanbanResult.error.errors);
167+
console.error('Kanban errors:', kanbanResult.error.issues);
164168
}
165169

166170
// Example 7: Test validation errors
@@ -175,7 +179,7 @@ const invalidButton = {
175179
const invalidButtonResult = ButtonSchema.safeParse(invalidButton);
176180
console.log('Invalid button validation:', invalidButtonResult.success ? 'PASSED (unexpected)' : 'FAILED (expected) ✓');
177181
if (!invalidButtonResult.success) {
178-
console.log('Expected validation errors:', invalidButtonResult.error.errors.length);
182+
console.log('Expected validation errors:', invalidButtonResult.error.issues.length);
179183
}
180184

181185
// Summary

packages/types/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
"scripts": {
6565
"build": "tsc",
6666
"clean": "rm -rf dist",
67-
"type-check": "tsc --noEmit",
67+
"type-check": "tsc --noEmit && tsc -p tsconfig.examples.json",
6868
"lint": "eslint ."
6969
},
7070
"keywords": [

packages/types/src/zod/README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,14 +243,18 @@ const result = ButtonSchema.safeParse({
243243
variant: 'invalid-variant'
244244
});
245245

246-
// result.error.errors:
246+
// result.error.issues:
247247
// [
248248
// {
249-
// code: 'invalid_enum_value',
249+
// code: 'invalid_value',
250+
// values: ['default', 'secondary', ...],
250251
// path: ['variant'],
251-
// message: "Invalid enum value. Expected 'default' | 'secondary' | ...",
252+
// message: 'Invalid option: expected one of "default"|"secondary"|...',
252253
// }
253254
// ]
255+
//
256+
// Note: the accessor is `.issues`. Zod 4 removed the `.errors` alias, so
257+
// `.errors` reads `undefined` rather than throwing at the access itself.
254258
```
255259

256260
### Nested Validation
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
// `examples/` is documentation that consumers copy from, so it has to compile
3+
// against the same sources it teaches. It cannot live in tsconfig.json: that
4+
// project is the package build (`tsc` -> dist) with "rootDir": "./src",
5+
// "composite" and "declaration", so example files are both outside rootDir and
6+
// would emit into dist. Note that deleting "examples" from that file's
7+
// "exclude" would change nothing on its own — its "include" is already scoped
8+
// to "src/**/*". Hence a separate, emit-free project, chained from the
9+
// package's `type-check` script.
10+
"extends": "../../tsconfig.json",
11+
"compilerOptions": {
12+
"noEmit": true,
13+
"lib": ["ES2020", "DOM"],
14+
// Same reason as tsconfig.json: drop the root tsconfig's source-tree paths
15+
// so @objectstack/spec resolves through the workspace dependency.
16+
"paths": {}
17+
},
18+
"include": ["examples/**/*.ts"]
19+
}

0 commit comments

Comments
 (0)