Skip to content

Commit e5e2613

Browse files
authored
feat(json-schema): smart coercion plugin for any standard schema (#748)
Closes: https://github.com/unnoq/orpc/issues/395 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Introduced the experimental Smart Coercion Plugin for automatic, deep type coercion based on JSON Schema, supporting native JavaScript types like BigInt, Date, RegExp, URL, Set, and Map. * Added detailed documentation for the Smart Coercion Plugin, including installation, setup, usage examples, and conversion rules. * Added new sidebar navigation entries for the Smart Coercion Plugin and legacy Zod Smart Coercion in the documentation. * **Improvements** * Enhanced JSON Schema conversion by annotating schemas with native type metadata (bigint, date, set, map, regexp, url). * Updated playgrounds and OpenAPI handler configurations to replace the deprecated ZodSmartCoercionPlugin with the new Smart Coercion Plugin. * Marked the ZodSmartCoercionPlugin as deprecated in favor of the new plugin. * **Bug Fixes** * Expanded test coverage for coercion logic, validating handling of diverse schema constructs and native types. * **Chores** * Added the new `@orpc/json-schema` package with source code, tests, documentation, and build configuration. * Updated dependencies and TypeScript project references in multiple packages and playgrounds to include `@orpc/json-schema`. * Added `.gitignore` and README files for the new package. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent bb23f2f commit e5e2613

39 files changed

Lines changed: 3208 additions & 2622 deletions

File tree

apps/content/.vitepress/config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,8 @@ export default withMermaid(defineConfig({
208208
collapsed: true,
209209
items: [
210210
{ text: 'OpenAPI Reference (Swagger)', link: '/docs/openapi/plugins/openapi-reference' },
211-
{ text: 'Zod Smart Coercion', link: '/docs/openapi/plugins/zod-smart-coercion' },
211+
{ text: 'Smart Coercion', link: '/docs/openapi/plugins/smart-coercion' },
212+
{ text: 'Zod Smart Coercion (old)', link: '/docs/openapi/plugins/zod-smart-coercion' },
212213
],
213214
},
214215
{

apps/content/docs/openapi/integrations/implement-contract-in-nest.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ oRPC is an ESM-only library. Therefore, your NestJS application must be configur
5454
```
5555

5656
2. **Node.js Environment**:
57-
5857
- **Node.js 22+**: Recommended, as it allows `require()` of ESM modules natively.
5958
- **Older Node.js versions**: Alternatively, use a bundler to compile ESM modules (including `@orpc/nest`) to CommonJS.
6059

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
---
2+
title: Smart Coercion Plugin
3+
description: Automatically converts input values to match schema types without manually defining coercion logic.
4+
---
5+
6+
# Smart Coercion Plugin
7+
8+
Automatically converts input values to match schema types without manually defining coercion logic.
9+
10+
::: warning
11+
This plugin improves developer experience but impacts performance. For high-performance applications or complex schemas, manually defining coercion in your schema validation is more efficient.
12+
:::
13+
14+
## Installation
15+
16+
::: code-group
17+
18+
```sh [npm]
19+
npm install @orpc/json-schema@latest
20+
```
21+
22+
```sh [yarn]
23+
yarn add @orpc/json-schema@latest
24+
```
25+
26+
```sh [pnpm]
27+
pnpm add @orpc/json-schema@latest
28+
```
29+
30+
```sh [bun]
31+
bun add @orpc/json-schema@latest
32+
```
33+
34+
```sh [deno]
35+
deno install npm:@orpc/json-schema@latest
36+
```
37+
38+
:::
39+
40+
## Setup
41+
42+
Configure the plugin with [JSON Schema Converters](/docs/openapi/openapi-specification#generating-specifications) for your validation libraries.
43+
44+
```ts
45+
import { OpenAPIHandler } from '@orpc/openapi/fetch'
46+
import {
47+
experimental_SmartCoercionPlugin as SmartCoercionPlugin
48+
} from '@orpc/json-schema'
49+
50+
const handler = new OpenAPIHandler(router, {
51+
plugins: [
52+
new SmartCoercionPlugin({
53+
schemaConverters: [
54+
new ZodToJsonSchemaConverter(),
55+
// Add other schema converters as needed
56+
],
57+
})
58+
]
59+
})
60+
```
61+
62+
## How It Works
63+
64+
The plugin converts values **safely** using these rules:
65+
66+
1. **Schema-guided:** Only converts when the schema says what type to use
67+
2. **Safe only:** Only converts values that make sense (like `'123'` to `123`)
68+
3. **Keep original:** If conversion is unsafe, keeps the original value
69+
4. **Smart unions:** Picks the best conversion for union types
70+
5. **Deep conversion:** Works inside nested objects and arrays
71+
72+
::: info
73+
JavaScript native types such as BigInt, Date, RegExp, URL, Set, and Map are not natively supported by JSON Schema. To enable correct coercion, oRPC relies on the `x-native-type` metadata in your schema:
74+
75+
- `x-native-type: 'bigint'` for BigInt
76+
- `x-native-type: 'date'` for Date
77+
- `x-native-type: 'regexp'` for RegExp
78+
- `x-native-type: 'url'` for URL
79+
- `x-native-type: 'set'` for Set
80+
- `x-native-type: 'map'` for Map
81+
82+
The built-in [JSON Schema Converters](/docs/openapi/openapi-specification#generating-specifications) handle these cases (except for some experimental converters). Since this approach is not part of the official JSON Schema specification, if you use a custom converter, you may need to add the appropriate `x-native-type` metadata to your schemas to ensure proper coercion.
83+
:::
84+
85+
## Conversion Rules
86+
87+
### String → Boolean
88+
89+
Support specific string values (case-insensitive):
90+
91+
- `'true'`, `'on'``true`
92+
- `'false'`, `'off'``false`
93+
94+
::: info
95+
HTML `<input type="checkbox">` elements submit `'on'` or `'off'` as values, so this conversion is especially useful for handling checkbox input in forms.
96+
:::
97+
98+
### String → Number
99+
100+
Support valid numeric strings:
101+
102+
- `'123'``123`
103+
- `'3.14'``3.14`
104+
105+
### String/Number → BigInt
106+
107+
Support valid numeric strings or numbers:
108+
109+
- `'12345678901234567890'``12345678901234567890n`
110+
- `12345678901234567890``12345678901234567890n`
111+
112+
### String → Date
113+
114+
Support ISO date/datetime strings:
115+
116+
- `'2023-10-01'``new Date('2023-10-01')`
117+
- `'2020-01-01T06:15'``new Date('2020-01-01T06:15')`
118+
- `'2020-01-01T06:15Z'``new Date('2020-01-01T06:15Z')`
119+
- `'2020-01-01T06:15:00Z'``new Date('2020-01-01T06:15:00Z')`
120+
- `'2020-01-01T06:15:00.123Z'``new Date('2020-01-01T06:15:00.123Z')`
121+
122+
### String → RegExp
123+
124+
Support valid regular expression strings:
125+
126+
- `'/^\\d+$/i'``new RegExp('^\\d+$', 'i')`
127+
- `'/abc/'``new RegExp('abc')`
128+
129+
### String → URL
130+
131+
Support valid URL strings:
132+
133+
- `'https://example.com'``new URL('https://example.com')`
134+
135+
### Array → Set
136+
137+
Support arrays of **unique values**:
138+
139+
- `['apple', 'banana']``new Set(['apple', 'banana'])`
140+
141+
### Array → Object
142+
143+
Converts arrays to objects with numeric keys:
144+
145+
- `['apple', 'banana']``{ 0: 'apple', 1: 'banana' }`
146+
147+
::: info
148+
This is particularly useful for [Bracket Notation](/docs/openapi/bracket-notation) when you need objects with numeric keys.
149+
:::
150+
151+
### Array → Map
152+
153+
Support arrays of key-value pairs:
154+
155+
- `[['key1', 'value1'], ['key2', 'value2']]``new Map([['key1', 'value1'], ['key2', 'value2']])`

packages/json-schema/.gitignore

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Hidden folders and files
2+
.*
3+
!.gitignore
4+
!.*.example
5+
6+
# Common generated folders
7+
logs/
8+
node_modules/
9+
out/
10+
dist/
11+
dist-ssr/
12+
build/
13+
coverage/
14+
temp/
15+
16+
# Common generated files
17+
*.log
18+
*.log.*
19+
*.tsbuildinfo
20+
*.vitest-temp.json
21+
vite.config.ts.timestamp-*
22+
vitest.config.ts.timestamp-*
23+
24+
# Common manual ignore files
25+
*.local
26+
*.pem

packages/json-schema/README.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<div align="center">
2+
<image align="center" src="https://orpc.unnoq.com/logo.webp" width=280 alt="oRPC logo" />
3+
</div>
4+
5+
<h1></h1>
6+
7+
<div align="center">
8+
<a href="https://codecov.io/gh/unnoq/orpc">
9+
<img alt="codecov" src="https://codecov.io/gh/unnoq/orpc/branch/main/graph/badge.svg">
10+
</a>
11+
<a href="https://www.npmjs.com/package/@orpc/json-schema">
12+
<img alt="weekly downloads" src="https://img.shields.io/npm/dw/%40orpc%2Fjson-schema?logo=npm" />
13+
</a>
14+
<a href="https://github.com/unnoq/orpc/blob/main/LICENSE">
15+
<img alt="MIT License" src="https://img.shields.io/github/license/unnoq/orpc?logo=open-source-initiative" />
16+
</a>
17+
<a href="https://discord.gg/TXEbwRBvQn">
18+
<img alt="Discord" src="https://img.shields.io/discord/1308966753044398161?color=7389D8&label&logo=discord&logoColor=ffffff" />
19+
</a>
20+
</div>
21+
22+
<h3 align="center">Typesafe APIs Made Simple 🪄</h3>
23+
24+
**oRPC is a powerful combination of RPC and OpenAPI**, makes it easy to build APIs that are end-to-end type-safe and adhere to OpenAPI standards
25+
26+
---
27+
28+
## Highlights
29+
30+
- **🔗 End-to-End Type Safety**: Ensure type-safe inputs, outputs, and errors from client to server.
31+
- **📘 First-Class OpenAPI**: Built-in support that fully adheres to the OpenAPI standard.
32+
- **📝 Contract-First Development**: Optionally define your API contract before implementation.
33+
- **⚙️ Framework Integrations**: Seamlessly integrate with TanStack Query (React, Vue, Solid, Svelte, Angular), Pinia Colada, and more.
34+
- **🚀 Server Actions**: Fully compatible with React Server Actions on Next.js, TanStack Start, and other platforms.
35+
- **🔠 Standard Schema Support**: Works out of the box with Zod, Valibot, ArkType, and other schema validators.
36+
- **🗃️ Native Types**: Supports native types like Date, File, Blob, BigInt, URL, and more.
37+
- **⏱️ Lazy Router**: Enhance cold start times with our lazy routing feature.
38+
- **📡 SSE & Streaming**: Enjoy full type-safe support for SSE and streaming.
39+
- **🌍 Multi-Runtime Support**: Fast and lightweight on Cloudflare, Deno, Bun, Node.js, and beyond.
40+
- **🔌 Extendability**: Easily extend functionality with plugins, middleware, and interceptors.
41+
- **🛡️ Reliability**: Well-tested, TypeScript-based, production-ready, and MIT licensed.
42+
43+
## Documentation
44+
45+
You can find the full documentation [here](https://orpc.unnoq.com).
46+
47+
## Packages
48+
49+
- [@orpc/contract](https://www.npmjs.com/package/@orpc/contract): Build your API contract.
50+
- [@orpc/server](https://www.npmjs.com/package/@orpc/server): Build your API or implement API contract.
51+
- [@orpc/client](https://www.npmjs.com/package/@orpc/client): Consume your API on the client with type-safety.
52+
- [@orpc/openapi](https://www.npmjs.com/package/@orpc/openapi): Generate OpenAPI specs and handle OpenAPI requests.
53+
- [@orpc/nest](https://www.npmjs.com/package/@orpc/nest): Deeply integrate oRPC with [NestJS](https://nestjs.com/).
54+
- [@orpc/react](https://www.npmjs.com/package/@orpc/react): Utilities for integrating oRPC with React and React Server Actions.
55+
- [@orpc/tanstack-query](https://www.npmjs.com/package/@orpc/tanstack-query): [TanStack Query](https://tanstack.com/query/latest) integration.
56+
- [@orpc/vue-colada](https://www.npmjs.com/package/@orpc/vue-colada): Integration with [Pinia Colada](https://pinia-colada.esm.dev/).
57+
- [@orpc/hey-api](https://www.npmjs.com/package/@orpc/hey-api): [Hey API](https://heyapi.dev/) integration.
58+
- [@orpc/zod](https://www.npmjs.com/package/@orpc/zod): More schemas that [Zod](https://zod.dev/) doesn't support yet.
59+
- [@orpc/valibot](https://www.npmjs.com/package/@orpc/valibot): OpenAPI spec generation from [Valibot](https://valibot.dev/).
60+
- [@orpc/arktype](https://www.npmjs.com/package/@orpc/arktype): OpenAPI spec generation from [ArkType](https://arktype.io/).
61+
62+
## `@orpc/json-schema`
63+
64+
Json Schema related utilities for oRPC.
65+
66+
## Sponsors
67+
68+
<p align="center">
69+
<a href="https://cdn.jsdelivr.net/gh/unnoq/unnoq/sponsors.svg">
70+
<img src='https://cdn.jsdelivr.net/gh/unnoq/unnoq/sponsors.svg'/>
71+
</a>
72+
</p>
73+
74+
## License
75+
76+
Distributed under the MIT License. See [LICENSE](https://github.com/unnoq/orpc/blob/main/LICENSE) for more information.

packages/json-schema/package.json

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"name": "@orpc/json-schema",
3+
"type": "module",
4+
"version": "0.0.0",
5+
"license": "MIT",
6+
"homepage": "https://orpc.unnoq.com",
7+
"repository": {
8+
"type": "git",
9+
"url": "git+https://github.com/unnoq/orpc.git",
10+
"directory": "packages/json-schema"
11+
},
12+
"keywords": [
13+
"unnoq",
14+
"orpc"
15+
],
16+
"publishConfig": {
17+
"exports": {
18+
".": {
19+
"types": "./dist/index.d.mts",
20+
"import": "./dist/index.mjs",
21+
"default": "./dist/index.mjs"
22+
}
23+
}
24+
},
25+
"exports": {
26+
".": "./src/index.ts"
27+
},
28+
"files": [
29+
"dist"
30+
],
31+
"scripts": {
32+
"build": "unbuild",
33+
"build:watch": "pnpm run build --watch",
34+
"type:check": "tsc -b"
35+
},
36+
"dependencies": {
37+
"@orpc/contract": "workspace:*",
38+
"@orpc/openapi": "workspace:*",
39+
"@orpc/server": "workspace:*",
40+
"@orpc/shared": "workspace:*",
41+
"json-schema-typed": "^8.0.1"
42+
},
43+
"devDependencies": {
44+
"zod": "^3.25.74"
45+
}
46+
}

0 commit comments

Comments
 (0)