Skip to content

Commit 53096e7

Browse files
authored
feat(zod): better support zod@4.x.x (#760)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * None. * **Refactor** * Updated all Zod imports to namespace imports for consistency. * Simplified Zod schema method usage (e.g., `z.string().email()` to `z.email()`, `z.string().uuid()` to `z.uuid()`, `z.string().url()` to `z.url()`). * Enhanced file validation with explicit MIME types using `z.file().mime([...])`. * Separated OpenAPI schema example registration into an external JSON schema registry. * Switched to an experimental Zod-to-JSON schema converter imported from a new module path. * Relaxed error shape assertions in client tests for more flexible validation. * **Documentation** * Updated documentation and code examples to reflect new import styles and schema usage. * Adjusted example snippets to align with updated validation patterns and registry usage. * Removed or updated language annotations in code blocks for improved clarity. * **Chores** * Upgraded Zod dependency to version ^4.0.5 across all packages and playgrounds. * Removed unused dependencies and restructured TypeScript configuration files for better project setup. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent de7ca70 commit 53096e7

144 files changed

Lines changed: 647 additions & 812 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ This is a quick overview of how to use oRPC. For more details, please refer to t
6868
```ts
6969
import type { IncomingHttpHeaders } from 'node:http'
7070
import { ORPCError, os } from '@orpc/server'
71-
import { z } from 'zod'
71+
import * as z from 'zod'
7272

7373
const PlanetSchema = z.object({
7474
id: z.number().int().min(1),
@@ -177,7 +177,7 @@ This is a quick overview of how to use oRPC. For more details, please refer to t
177177

178178
```ts
179179
import { OpenAPIGenerator } from '@orpc/openapi'
180-
import { ZodToJsonSchemaConverter } from '@orpc/zod'
180+
import { experimental_ZodToJsonSchemaConverter as ZodToJsonSchemaConverter } from '@orpc/zod/zod4'
181181

182182
const generator = new OpenAPIGenerator({
183183
schemaConverters: [new ZodToJsonSchemaConverter()]

apps/content/docs/advanced/validation-errors.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ import { RPCHandler } from '@orpc/server/fetch'
1616
import { router } from './shared/planet'
1717
// ---cut---
1818
import { onError, ORPCError, ValidationError } from '@orpc/server'
19-
import { ZodError } from 'zod'
20-
import type { ZodIssue } from 'zod'
19+
import * as z from 'zod'
2120

2221
const handler = new RPCHandler(router, {
2322
clientInterceptors: [
@@ -28,11 +27,12 @@ const handler = new RPCHandler(router, {
2827
&& error.cause instanceof ValidationError
2928
) {
3029
// If you only use Zod you can safely cast to ZodIssue[]
31-
const zodError = new ZodError(error.cause.issues as ZodIssue[])
30+
const zodError = new z.ZodError(error.cause.issues as z.core.$ZodIssue[])
3231

3332
throw new ORPCError('INPUT_VALIDATION_FAILED', {
3433
status: 422,
35-
data: zodError.flatten(),
34+
message: z.prettifyError(zodError),
35+
data: z.flattenError(zodError),
3636
cause: error.cause,
3737
})
3838
}
@@ -54,9 +54,8 @@ const handler = new RPCHandler(router, {
5454
## Customizing with Middleware
5555

5656
```ts twoslash
57-
import { z, ZodError } from 'zod'
58-
import type { ZodIssue } from 'zod'
5957
import { onError, ORPCError, os, ValidationError } from '@orpc/server'
58+
import * as z from 'zod'
6059

6160
const base = os.use(onError((error) => {
6261
if (
@@ -65,11 +64,12 @@ const base = os.use(onError((error) => {
6564
&& error.cause instanceof ValidationError
6665
) {
6766
// If you only use Zod you can safely cast to ZodIssue[]
68-
const zodError = new ZodError(error.cause.issues as ZodIssue[])
67+
const zodError = new z.ZodError(error.cause.issues as z.core.$ZodIssue[])
6968

7069
throw new ORPCError('INPUT_VALIDATION_FAILED', {
7170
status: 422,
72-
data: zodError.flatten(),
71+
message: z.prettifyError(zodError),
72+
data: z.flattenError(zodError),
7373
cause: error.cause,
7474
})
7575
}
@@ -86,8 +86,8 @@ const base = os.use(onError((error) => {
8686
}))
8787

8888
const getting = base
89-
.input(z.object({ id: z.string().uuid() }))
90-
.output(z.object({ id: z.string().uuid(), name: z.string() }))
89+
.input(z.object({ id: z.uuid() }))
90+
.output(z.object({ id: z.uuid(), name: z.string() }))
9191
.handler(async ({ input, context }) => {
9292
return { id: input.id, name: 'name' }
9393
})
@@ -107,8 +107,7 @@ As explained in the [error handling guide](/docs/error-handling#combining-both-a
107107
import { RPCHandler } from '@orpc/server/fetch'
108108
// ---cut---
109109
import { onError, ORPCError, os, ValidationError } from '@orpc/server'
110-
import { z, ZodError } from 'zod'
111-
import type { ZodIssue } from 'zod'
110+
import * as z from 'zod'
112111

113112
const base = os.errors({
114113
INPUT_VALIDATION_FAILED: {
@@ -121,7 +120,7 @@ const base = os.errors({
121120
})
122121

123122
const example = base
124-
.input(z.object({ id: z.string().uuid() }))
123+
.input(z.object({ id: z.uuid() }))
125124
.handler(() => { /** do something */ })
126125

127126
const handler = new RPCHandler({ example }, {
@@ -133,11 +132,12 @@ const handler = new RPCHandler({ example }, {
133132
&& error.cause instanceof ValidationError
134133
) {
135134
// If you only use Zod you can safely cast to ZodIssue[]
136-
const zodError = new ZodError(error.cause.issues as ZodIssue[])
135+
const zodError = new z.ZodError(error.cause.issues as z.core.$ZodIssue[])
137136

138137
throw new ORPCError('INPUT_VALIDATION_FAILED', {
139138
status: 422,
140-
data: zodError.flatten(),
139+
message: z.prettifyError(zodError),
140+
data: z.flattenError(zodError),
141141
cause: error.cause,
142142
})
143143
}

apps/content/docs/client/error-handling.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ This guide explains how to handle type-safe errors in oRPC clients using [type-s
1111

1212
```ts twoslash
1313
import { os } from '@orpc/server'
14-
import { z } from 'zod'
14+
import * as z from 'zod'
1515
// ---cut---
1616
import { isDefinedError, safe } from '@orpc/client'
1717

apps/content/docs/client/event-iterator.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Simply iterate over it and await each event.
1212

1313
```ts twoslash
1414
import { ContractRouterClient, eventIterator, oc } from '@orpc/contract'
15-
import { z } from 'zod'
15+
import * as z from 'zod'
1616

1717
const contract = {
1818
streaming: oc.output(eventIterator(z.object({ message: z.string() })))

apps/content/docs/client/server-side.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Define your procedure and turn it into a callable procedure:
1717

1818
```ts twoslash
1919
import { os } from '@orpc/server'
20-
import { z } from 'zod'
20+
import * as z from 'zod'
2121

2222
const getProcedure = os
2323
.input(z.object({ id: z.string() }))
@@ -34,7 +34,7 @@ const result = await getProcedure({ id: '123' })
3434
Alternatively, call your procedure using the `call` helper:
3535

3636
```ts twoslash
37-
import { z } from 'zod'
37+
import * as z from 'zod'
3838
import { call, os } from '@orpc/server'
3939

4040
const getProcedure = os
@@ -51,7 +51,7 @@ const result = await call(getProcedure, { id: '123' }, {
5151
Create a [router](/docs/router) based client to access multiple procedures:
5252

5353
```ts twoslash
54-
import { z } from 'zod'
54+
import * as z from 'zod'
5555
// ---cut---
5656
import { createRouterClient, os } from '@orpc/server'
5757

@@ -70,7 +70,7 @@ const result = await client.ping()
7070
You can define a client context to pass additional information when calling procedures. This is useful for modifying procedure behavior dynamically.
7171

7272
```ts twoslash
73-
import { z } from 'zod'
73+
import * as z from 'zod'
7474
import { createRouterClient, os } from '@orpc/server'
7575
// ---cut---
7676
interface ClientContext {

apps/content/docs/contract-first/define-contract.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ deno install npm:@orpc/contract@latest
4040
A procedure contract in oRPC is similar to a standard [procedure](/docs/procedure) definition, but with extraneous APIs removed to better support contract-first development.
4141

4242
```ts twoslash
43-
import { z } from 'zod'
43+
import * as z from 'zod'
4444
// ---cut---
4545
import { oc } from '@orpc/contract'
4646

@@ -78,7 +78,7 @@ export const routerContract = {
7878
Below is a complete example demonstrating how to define a contract for a simple "Planet" service. This example extracted from our [Getting Started](/docs/getting-started) guide.
7979

8080
```ts twoslash
81-
import { z } from 'zod'
81+
import * as z from 'zod'
8282
import { oc } from '@orpc/contract'
8383
// ---cut---
8484
export const PlanetSchema = z.object({

apps/content/docs/error-handling.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ For a fully type‑safe error management experience, define your error types usi
5252

5353
```ts twoslash
5454
import { os } from '@orpc/server'
55-
import { z } from 'zod'
55+
import * as z from 'zod'
5656
// ---cut---
5757
const base = os.errors({ // <-- common errors
5858
RATE_LIMITED: {

apps/content/docs/file-upload-download.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ For files larger than 100 MB, we recommend using a dedicated upload solution or
1313

1414
## Validation
1515

16-
oRPC uses the standard [File](https://developer.mozilla.org/en-US/docs/Web/API/File) and [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) objects to handle file operations. To validate file uploads and downloads, you can use the `z.instanceof(File)` and `z.instanceof(Blob)` validators, or equivalent schemas in libraries like Valibot or Arktype.
16+
oRPC uses standard [File](https://developer.mozilla.org/en-US/docs/Web/API/File) and [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) objects for file operations.
1717

1818
```ts twoslash
1919
import { os } from '@orpc/server'
20-
import { z } from 'zod'
20+
import * as z from 'zod'
2121
// ---cut---
2222
const example = os
23-
.input(z.object({ file: z.instanceof(File) }))
23+
.input(z.object({ file: z.file() }))
2424
.output(z.object({ file: z.instanceof(File) }))
2525
.handler(async ({ input }) => {
2626
console.log(input.file.name)

apps/content/docs/getting-started.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ We'll use [Zod](https://github.com/colinhacks/zod) for schema validation (option
5050
```ts twoslash
5151
import type { IncomingHttpHeaders } from 'node:http'
5252
import { ORPCError, os } from '@orpc/server'
53-
import { z } from 'zod'
53+
import * as z from 'zod'
5454

5555
const PlanetSchema = z.object({
5656
id: z.number().int().min(1),

apps/content/docs/openapi/getting-started.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ This snippet is based on the [Getting Started](/docs/getting-started) guide. Ple
4848
```ts twoslash
4949
import type { IncomingHttpHeaders } from 'node:http'
5050
import { ORPCError, os } from '@orpc/server'
51-
import { z } from 'zod'
51+
import * as z from 'zod'
5252

5353
const PlanetSchema = z.object({
5454
id: z.number().int().min(1),
@@ -171,7 +171,9 @@ Just a small tweak makes your oRPC API OpenAPI-compliant!
171171

172172
```ts twoslash
173173
import { OpenAPIGenerator } from '@orpc/openapi'
174-
import { ZodToJsonSchemaConverter } from '@orpc/zod'
174+
import {
175+
experimental_ZodToJsonSchemaConverter as ZodToJsonSchemaConverter
176+
} from '@orpc/zod/zod4'
175177
import { router } from './shared/planet'
176178
177179
const generator = new OpenAPIGenerator({

0 commit comments

Comments
 (0)