Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ This is a quick overview of how to use oRPC. For more details, please refer to t
```ts
import type { IncomingHttpHeaders } from 'node:http'
import { ORPCError, os } from '@orpc/server'
import { z } from 'zod'
import * as z from 'zod'

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

```ts
import { OpenAPIGenerator } from '@orpc/openapi'
import { ZodToJsonSchemaConverter } from '@orpc/zod'
import { experimental_ZodToJsonSchemaConverter as ZodToJsonSchemaConverter } from '@orpc/zod/zod4'

const generator = new OpenAPIGenerator({
schemaConverters: [new ZodToJsonSchemaConverter()]
Expand Down
30 changes: 15 additions & 15 deletions apps/content/docs/advanced/validation-errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ import { RPCHandler } from '@orpc/server/fetch'
import { router } from './shared/planet'
// ---cut---
import { onError, ORPCError, ValidationError } from '@orpc/server'
import { ZodError } from 'zod'
import type { ZodIssue } from 'zod'
import * as z from 'zod'

const handler = new RPCHandler(router, {
clientInterceptors: [
Expand All @@ -28,11 +27,12 @@ const handler = new RPCHandler(router, {
&& error.cause instanceof ValidationError
) {
// If you only use Zod you can safely cast to ZodIssue[]
const zodError = new ZodError(error.cause.issues as ZodIssue[])
const zodError = new z.ZodError(error.cause.issues as z.core.$ZodIssue[])
Comment thread
dinwwwh marked this conversation as resolved.

throw new ORPCError('INPUT_VALIDATION_FAILED', {
status: 422,
data: zodError.flatten(),
message: z.prettifyError(zodError),
data: z.flattenError(zodError),
cause: error.cause,
})
}
Expand All @@ -54,9 +54,8 @@ const handler = new RPCHandler(router, {
## Customizing with Middleware

```ts twoslash
import { z, ZodError } from 'zod'
import type { ZodIssue } from 'zod'
import { onError, ORPCError, os, ValidationError } from '@orpc/server'
import * as z from 'zod'

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

throw new ORPCError('INPUT_VALIDATION_FAILED', {
status: 422,
data: zodError.flatten(),
message: z.prettifyError(zodError),
data: z.flattenError(zodError),
cause: error.cause,
})
}
Expand All @@ -86,8 +86,8 @@ const base = os.use(onError((error) => {
}))

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

const base = os.errors({
INPUT_VALIDATION_FAILED: {
Expand All @@ -121,7 +120,7 @@ const base = os.errors({
})

const example = base
.input(z.object({ id: z.string().uuid() }))
.input(z.object({ id: z.uuid() }))
.handler(() => { /** do something */ })

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

throw new ORPCError('INPUT_VALIDATION_FAILED', {
status: 422,
data: zodError.flatten(),
message: z.prettifyError(zodError),
data: z.flattenError(zodError),
cause: error.cause,
})
}
Expand Down
2 changes: 1 addition & 1 deletion apps/content/docs/client/error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This guide explains how to handle type-safe errors in oRPC clients using [type-s

```ts twoslash
import { os } from '@orpc/server'
import { z } from 'zod'
import * as z from 'zod'
// ---cut---
import { isDefinedError, safe } from '@orpc/client'

Expand Down
2 changes: 1 addition & 1 deletion apps/content/docs/client/event-iterator.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Simply iterate over it and await each event.

```ts twoslash
import { ContractRouterClient, eventIterator, oc } from '@orpc/contract'
import { z } from 'zod'
import * as z from 'zod'

const contract = {
streaming: oc.output(eventIterator(z.object({ message: z.string() })))
Expand Down
8 changes: 4 additions & 4 deletions apps/content/docs/client/server-side.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Define your procedure and turn it into a callable procedure:

```ts twoslash
import { os } from '@orpc/server'
import { z } from 'zod'
import * as z from 'zod'

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

```ts twoslash
import { z } from 'zod'
import * as z from 'zod'
import { call, os } from '@orpc/server'

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

```ts twoslash
import { z } from 'zod'
import * as z from 'zod'
// ---cut---
import { createRouterClient, os } from '@orpc/server'

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

```ts twoslash
import { z } from 'zod'
import * as z from 'zod'
import { createRouterClient, os } from '@orpc/server'
// ---cut---
interface ClientContext {
Expand Down
4 changes: 2 additions & 2 deletions apps/content/docs/contract-first/define-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ deno install npm:@orpc/contract@latest
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.

```ts twoslash
import { z } from 'zod'
import * as z from 'zod'
// ---cut---
import { oc } from '@orpc/contract'

Expand Down Expand Up @@ -78,7 +78,7 @@ export const routerContract = {
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.

```ts twoslash
import { z } from 'zod'
import * as z from 'zod'
import { oc } from '@orpc/contract'
// ---cut---
export const PlanetSchema = z.object({
Expand Down
2 changes: 1 addition & 1 deletion apps/content/docs/error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ For a fully type‑safe error management experience, define your error types usi

```ts twoslash
import { os } from '@orpc/server'
import { z } from 'zod'
import * as z from 'zod'
// ---cut---
const base = os.errors({ // <-- common errors
RATE_LIMITED: {
Expand Down
6 changes: 3 additions & 3 deletions apps/content/docs/file-upload-download.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ For files larger than 100 MB, we recommend using a dedicated upload solution or

## Validation

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.
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.

```ts twoslash
import { os } from '@orpc/server'
import { z } from 'zod'
import * as z from 'zod'
// ---cut---
const example = os
.input(z.object({ file: z.instanceof(File) }))
.input(z.object({ file: z.file() }))
.output(z.object({ file: z.instanceof(File) }))
.handler(async ({ input }) => {
console.log(input.file.name)
Expand Down
2 changes: 1 addition & 1 deletion apps/content/docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ We'll use [Zod](https://github.com/colinhacks/zod) for schema validation (option
```ts twoslash
import type { IncomingHttpHeaders } from 'node:http'
import { ORPCError, os } from '@orpc/server'
import { z } from 'zod'
import * as z from 'zod'

const PlanetSchema = z.object({
id: z.number().int().min(1),
Expand Down
6 changes: 4 additions & 2 deletions apps/content/docs/openapi/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ This snippet is based on the [Getting Started](/docs/getting-started) guide. Ple
```ts twoslash
import type { IncomingHttpHeaders } from 'node:http'
import { ORPCError, os } from '@orpc/server'
import { z } from 'zod'
import * as z from 'zod'

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

```ts twoslash
import { OpenAPIGenerator } from '@orpc/openapi'
import { ZodToJsonSchemaConverter } from '@orpc/zod'
import {
experimental_ZodToJsonSchemaConverter as ZodToJsonSchemaConverter
} from '@orpc/zod/zod4'
import { router } from './shared/planet'

const generator = new OpenAPIGenerator({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Before implementation, define your oRPC contract. This process is consistent wit
```ts
import { populateContractRouterPaths } from '@orpc/nest'
import { oc } from '@orpc/contract'
import { z } from 'zod'
import * as z from 'zod'

export const PlanetSchema = z.object({
id: z.number().int().min(1),
Expand Down
14 changes: 7 additions & 7 deletions apps/content/docs/openapi/openapi-specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ Define reusable schema components that can be referenced across your OpenAPI spe
const UserSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
email: z.email(),
})

const PetSchema = z.object({
Expand Down Expand Up @@ -248,8 +248,8 @@ Zod v4 includes a native `File` schema. oRPC will detect it automatically - no e
import * as z from 'zod'

const InputSchema = z.object({
file: oz.file(),
image: oz.file().mime(['image/png', 'image/jpeg']),
file: z.file(),
image: z.file().mime(['image/png', 'image/jpeg']),
})
```

Expand Down Expand Up @@ -301,8 +301,8 @@ JSON_SCHEMA_OUTPUT_REGISTRY.add(InputSchema, {

In the [File Upload/Download](/docs/file-upload-download) guide, `z.instanceof` is used to describe file/blob schemas. However, this method prevents oRPC from recognizing file/blob schema. Instead, use the enhanced file schema approach:

```ts twoslash
import { z } from 'zod'
```ts
import { z } from 'zod/v3'
import { oz } from '@orpc/zod'

const InputSchema = z.object({
Expand All @@ -316,8 +316,8 @@ const InputSchema = z.object({

If Zod alone does not cover your JSON Schema requirements, you can extend or override the generated schema:

```ts twoslash
import { z } from 'zod'
```ts
import { z } from 'zod/v3'
import { oz } from '@orpc/zod'

const InputSchema = oz.openapi(
Expand Down
4 changes: 3 additions & 1 deletion apps/content/docs/openapi/plugins/openapi-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ This plugin relies on the [OpenAPI Generator](/docs/openapi/openapi-specificatio
## Setup

```ts
import { ZodToJsonSchemaConverter } from '@orpc/zod'
import {
experimental_ZodToJsonSchemaConverter as ZodToJsonSchemaConverter
} from '@orpc/zod/zod4'
import { OpenAPIReferencePlugin } from '@orpc/openapi/plugins'

const handler = new OpenAPIHandler(router, {
Expand Down
2 changes: 1 addition & 1 deletion apps/content/docs/plugins/hibernation.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ import {
} from '@orpc/server/hibernation'
import { onError, os } from '@orpc/server'
import { DurableObject } from 'cloudflare:workers'
import { z } from 'zod'
import * as z from 'zod'

const base = os.$context<{
handler: RPCHandler<any>
Expand Down
6 changes: 3 additions & 3 deletions apps/content/docs/server-action.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Define your procedure with `.actionable` for Server Action support.

```ts twoslash
import { onError, onSuccess, os } from '@orpc/server'
import { z } from 'zod'
import * as z from 'zod'
// ---cut---
'use server'

Expand Down Expand Up @@ -70,7 +70,7 @@ The `.actionable` modifier supports type-safe error handling with a JSON-like er

```ts twoslash
import { os } from '@orpc/server'
import { z } from 'zod'
import * as z from 'zod'

export const someAction = os
.input(z.object({ name: z.string() }))
Expand Down Expand Up @@ -137,7 +137,7 @@ The `useServerAction` hook simplifies invoking server actions in React.
```tsx twoslash
import * as React from 'react'
import { os } from '@orpc/server'
import { z } from 'zod'
import * as z from 'zod'

export const someAction = os
.input(z.object({ name: z.string() }))
Expand Down
2 changes: 1 addition & 1 deletion apps/content/examples/openai-streaming.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ This example shows how to integrate oRPC with the OpenAI Streaming API to build
import { createORPCClient } from '@orpc/client'
import { RPCLink } from '@orpc/client/fetch'
import { os, RouterClient } from '@orpc/server'
import { z } from 'zod'
import * as z from 'zod'
// ---cut---
import OpenAI from 'openai'

Expand Down
3 changes: 2 additions & 1 deletion apps/content/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"vitepress-plugin-llms": "^1.7.0",
"vitepress-plugin-mermaid": "^2.0.17",
"vitepress-plugin-shiki-twoslash": "^0.0.6",
"vue": "^3.5.17"
"vue": "^3.5.17",
"zod": "^4.0.5"
}
}
2 changes: 1 addition & 1 deletion apps/content/shared/planet.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { z } from 'zod'
import * as z from 'zod'
import { oc } from '@orpc/contract'
import type { RouterClient } from '@orpc/server'
import { implement } from '@orpc/server'
Expand Down
2 changes: 1 addition & 1 deletion packages/arktype/src/converter.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type } from 'arktype'
import { z } from 'zod'
import * as z from 'zod'
import { experimental_ArkTypeToJsonSchemaConverter as ArkTypeToJsonSchemaConverter } from './converter'

it('arkTypeToJsonSchemaConverter.convert', async () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,6 @@
"@orpc/standard-server-peer": "workspace:*"
},
"devDependencies": {
"zod": "^3.25.76"
"zod": "^4.0.5"
}
}
9 changes: 3 additions & 6 deletions packages/client/tests/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,10 @@ describe('e2e', () => {
expect(error3).toBeInstanceOf(ORPCError)
expect((error3 as any).code).toEqual('BAD_REQUEST')
expect((error3 as any).data).toEqual({
issues: [{
code: 'invalid_type',
expected: 'string',
message: 'Required',
issues: [expect.objectContaining({
message: expect.any(String),
path: ['title'],
received: 'undefined',
}],
})],
})
})

Expand Down
Loading