@orpc/json-schema 2.0.0-beta.2 → 2.0.0-beta.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,6 +7,9 @@
7
7
  <a href="https://www.npmjs.com/package/@orpc/json-schema">
8
8
  <img alt="weekly downloads" src="https://img.shields.io/npm/dw/%40orpc%2Fjson-schema?logo=npm" />
9
9
  </a>
10
+ <a href="https://app.codspeed.io/middleapi/orpc?utm_source=badge">
11
+ <img alt="CodSpeed" src="https://img.shields.io/endpoint?url=https://codspeed.io/badge.json" />
12
+ </a>
10
13
  <a href="https://github.com/middleapi/orpc/blob/main/LICENSE">
11
14
  <img alt="MIT License" src="https://img.shields.io/github/license/middleapi/orpc?logo=open-source-initiative" />
12
15
  </a>
@@ -26,10 +29,10 @@ You can read the documentation [here](https://orpc.dev).
26
29
 
27
30
  **Core**
28
31
 
29
- - [@orpc/contract](https://www.npmjs.com/package/@orpc/contract): Define type-safe contracts for your API.
30
- - [@orpc/server](https://www.npmjs.com/package/@orpc/server): Implement your API or contract on the server.
31
- - [@orpc/client](https://www.npmjs.com/package/@orpc/client): Consume your API from the client with full type safety.
32
- - [@orpc/openapi](https://www.npmjs.com/package/@orpc/openapi): Bring OpenAPI compatibility to your APIs.
32
+ - [@orpc/contract](https://www.npmjs.com/package/@orpc/contract): Define API contract as the single source of truth.
33
+ - [@orpc/server](https://www.npmjs.com/package/@orpc/server): Build APIs or implement contracts.
34
+ - [@orpc/client](https://www.npmjs.com/package/@orpc/client): Consume APIs with end-to-end type safety.
35
+ - [@orpc/openapi](https://www.npmjs.com/package/@orpc/openapi): Add OpenAPI compatibility to APIs.
33
36
 
34
37
  **Schema validation**
35
38
 
@@ -37,23 +40,30 @@ You can read the documentation [here](https://orpc.dev).
37
40
  - [@orpc/valibot](https://www.npmjs.com/package/@orpc/valibot): Integrate with [Valibot](https://valibot.dev/).
38
41
  - [@orpc/arktype](https://www.npmjs.com/package/@orpc/arktype): Integrate with [ArkType](https://arktype.io/).
39
42
 
43
+ **Built-in features**
44
+
45
+ - [@orpc/publisher](https://www.npmjs.com/package/@orpc/publisher): Pub/Sub with memory, Redis, and Upstash adapters.
46
+ - [@orpc/ratelimit](https://www.npmjs.com/package/@orpc/ratelimit): Rate limiting with memory, Redis, and Upstash adapters.
47
+ - [@orpc/json-schema](https://www.npmjs.com/package/@orpc/json-schema): Smart coercion for OpenAPI requests.
48
+
40
49
  **Framework & ecosystem integrations**
41
50
 
42
- - [@orpc/next](https://www.npmjs.com/package/@orpc/react): Use oRPC with [Next.js Server Functions](https://nextjs.org/docs/app/getting-started/mutating-data).
51
+ - [@orpc/next](https://www.npmjs.com/package/@orpc/next): Integrate with [Next.js Server Functions](https://nextjs.org/docs/app/getting-started/mutating-data).
52
+ - [@orpc/ai-sdk](https://www.npmjs.com/package/@orpc/ai-sdk): Turn contracts and procedures into [AI SDK](https://ai-sdk.dev/) tools.
43
53
  - [@orpc/tanstack-query](https://www.npmjs.com/package/@orpc/tanstack-query): Integrate with [TanStack Query](https://tanstack.com/query/latest).
54
+ - [@orpc/pinia-colada](https://www.npmjs.com/package/@orpc/pinia-colada): Integrate with [Pinia Colada](https://pinia-colada.esm.dev/).
55
+ - [@orpc/swr](https://www.npmjs.com/package/@orpc/swr): Integrate with [SWR](https://swr.vercel.app/).
44
56
  - [@orpc/experimental-effect](https://www.npmjs.com/package/@orpc/experimental-effect): Integrate with [Effect](https://effect.website/).
45
-
46
- **Built-in features**
47
-
48
- - [@orpc/publisher](https://www.npmjs.com/package/@orpc/publisher): Pub/Sub.
49
- - [@orpc/ratelimit](https://www.npmjs.com/package/@orpc/ratelimit): Rate limiting.
50
- - [@orpc/json-schema](https://www.npmjs.com/package/@orpc/json-schema): Smart coercion for OpenAPI requests.
57
+ - [@orpc/nest](https://www.npmjs.com/package/@orpc/nest): Implement your contract with [NestJS](https://nestjs.com/).
58
+ - [@orpc/bun](https://www.npmjs.com/package/@orpc/bun): Adapters for [Bun's Redis](https://bun.sh/).
59
+ - [@orpc/cloudflare](https://www.npmjs.com/package/@orpc/cloudflare): Adapters for [Cloudflare's RateLimit and Durable Objects](https://developers.cloudflare.com/workers/).
60
+ - [@orpc/trpc](https://www.npmjs.com/package/@orpc/trpc): Reuse existing [tRPC](https://trpc.io/) routers within oRPC.
51
61
 
52
62
  **Observability**
53
63
 
54
- - [@orpc/opentelemetry](https://www.npmjs.com/package/@orpc/opentelemetry): Instrument your API with [OpenTelemetry](https://opentelemetry.io/).
55
- - [@orpc/pino](https://www.npmjs.com/package/@orpc/pino): Log with [Pino](https://getpino.io/).
56
- - [@orpc/evlog](https://www.npmjs.com/package/@orpc/evlog): Log with [Evlog](https://evlog.dev/).
64
+ - [@orpc/opentelemetry](https://www.npmjs.com/package/@orpc/opentelemetry): Integrate with [OpenTelemetry](https://opentelemetry.io/) for distributed tracing.
65
+ - [@orpc/pino](https://www.npmjs.com/package/@orpc/pino): Integrate with [Pino](https://getpino.io/) for logging.
66
+ - [@orpc/evlog](https://www.npmjs.com/package/@orpc/evlog): Integrate with [Evlog](https://evlog.dev/) for logging.
57
67
 
58
68
  ## Sponsors
59
69
 
@@ -99,12 +109,10 @@ Like what we build over at [middleapi](https://github.com/middleapi)? You can he
99
109
  <td align="center"><a href="https://github.com/valerii15298?ref=orpc" target="_blank" rel="noopener" title="Valerii Petryniak"><img src="https://avatars.githubusercontent.com/u/44531564?u=88ac74d9bacd20401518441907acad21063cd397&amp;v=4" width="139" alt="Valerii Petryniak"/><br />Valerii Petryniak</a></td>
100
110
  <td align="center"><a href="https://github.com/letstri?ref=orpc" target="_blank" rel="noopener" title="Valerii Strilets"><img src="https://avatars.githubusercontent.com/u/13253748?u=c7b10399ccc8f8081e24db94ec32cd9858e86ac3&amp;v=4" width="139" alt="Valerii Strilets"/><br />Valerii Strilets</a></td>
101
111
  <td align="center"><a href="https://github.com/K-Mistele?ref=orpc" target="_blank" rel="noopener" title="Kyle Mistele"><img src="https://avatars.githubusercontent.com/u/18430555?u=3afebeb81de666e35aaac3ed46f14159d7603ffb&amp;v=4" width="139" alt="Kyle Mistele"/><br />Kyle Mistele</a></td>
102
- <td align="center"><a href="https://github.com/andrewpeters9?ref=orpc" target="_blank" rel="noopener" title="Andrew Peters"><img src="https://avatars.githubusercontent.com/u/36251325?v=4" width="139" alt="Andrew Peters"/><br />Andrew Peters</a></td>
103
112
  <td align="center"><a href="https://github.com/R44VC0RP?ref=orpc" target="_blank" rel="noopener" title="Ryan Vogel"><img src="https://avatars.githubusercontent.com/u/89211796?u=1857347b9787d8d8a7ea5bfc333f96be92d5a683&amp;v=4" width="139" alt="Ryan Vogel"/><br />Ryan Vogel</a></td>
113
+ <td align="center"><a href="https://github.com/christ12938?ref=orpc" target="_blank" rel="noopener" title="christ12938"><img src="https://avatars.githubusercontent.com/u/25758598?v=4" width="139" alt="christ12938"/><br />christ12938</a></td>
104
114
  </tr>
105
115
  <tr>
106
- <td align="center"><a href="https://github.com/christ12938?ref=orpc" target="_blank" rel="noopener" title="christ12938"><img src="https://avatars.githubusercontent.com/u/25758598?v=4" width="139" alt="christ12938"/><br />christ12938</a></td>
107
- <td align="center"><a href="https://github.com/peter-adam-dy?ref=orpc" target="_blank" rel="noopener" title="Peter Adam"><img src="https://avatars.githubusercontent.com/u/132129459?u=4f3dbbb3b443990b56acb7d6a5d11ed2c555f6db&amp;v=4" width="139" alt="Peter Adam"/><br />Peter Adam</a></td>
108
116
  <td align="center"><a href="https://github.com/Ryanjso?ref=orpc" target="_blank" rel="noopener" title="Ryan Soderberg"><img src="https://avatars.githubusercontent.com/u/39172778?u=5ed913c31d57e7221b75784abcad48c7ebddde27&amp;v=4" width="139" alt="Ryan Soderberg"/><br />Ryan Soderberg</a></td>
109
117
  <td align="center"><a href="https://github.com/itigoore01?ref=orpc" target="_blank" rel="noopener" title="shota"><img src="https://avatars.githubusercontent.com/u/11831107?u=c976a6dc7e055eb026304c46c99100ed22b0c8e0&amp;v=4" width="139" alt="shota"/><br />shota</a></td>
110
118
  </tr>
@@ -115,25 +123,25 @@ Like what we build over at [middleapi](https://github.com/middleapi)? You can he
115
123
  <table>
116
124
  <tr>
117
125
  <td align="center"><a href="https://github.com/rhinodavid?ref=orpc" target="_blank" rel="noopener" title="David Walsh"><img src="https://avatars.githubusercontent.com/u/5778036?u=b5521f07d2f88c3db2a0dae62b5f2f8357214af0&amp;v=4" width="119" alt="David Walsh"/><br />David Walsh</a></td>
126
+ <td align="center"><a href="https://github.com/Nic13Gamer?ref=orpc" target="_blank" rel="noopener" title="Nicholas"><img src="https://avatars.githubusercontent.com/u/54724556?u=56a7ab430ce7a80d648ab6eba051d454a818ed0b&amp;v=4" width="119" alt="Nicholas"/><br />Nicholas</a></td>
118
127
  <td align="center"><a href="https://github.com/Robbe95?ref=orpc" target="_blank" rel="noopener" title="Robbe Vaes"><img src="https://avatars.githubusercontent.com/u/44748019?u=e0232402c045ad4eac7cbd217f1f47e083103b89&amp;v=4" width="119" alt="Robbe Vaes"/><br />Robbe Vaes</a></td>
119
128
  <td align="center"><a href="https://github.com/aidansunbury?ref=orpc" target="_blank" rel="noopener" title="Aidan Sunbury"><img src="https://avatars.githubusercontent.com/u/64103161?v=4" width="119" alt="Aidan Sunbury"/><br />Aidan Sunbury</a></td>
120
129
  <td align="center"><a href="https://github.com/soonoo?ref=orpc" target="_blank" rel="noopener" title="soonoo"><img src="https://avatars.githubusercontent.com/u/5436405?u=5d0b4aa955c87e30e6bda7f0cccae5402da99528&amp;v=4" width="119" alt="soonoo"/><br />soonoo</a></td>
121
130
  <td align="center"><a href="https://github.com/kporten?ref=orpc" target="_blank" rel="noopener" title="Kevin Porten"><img src="https://avatars.githubusercontent.com/u/1839345?u=dc2263d5cfe0d927ce1a0be04a1d55dd6b55405c&amp;v=4" width="119" alt="Kevin Porten"/><br />Kevin Porten</a></td>
122
131
  <td align="center"><a href="https://github.com/pumpkinlink?ref=orpc" target="_blank" rel="noopener" title="Denis"><img src="https://avatars.githubusercontent.com/u/11864620?u=5f47bbe6c65d0f6f5cf011021490238e4b0593d0&amp;v=4" width="119" alt="Denis"/><br />Denis</a></td>
123
- <td align="center"><a href="https://github.com/christopher-kapic?ref=orpc" target="_blank" rel="noopener" title="Christopher Kapic"><img src="https://avatars.githubusercontent.com/u/59740769?u=e7ad4b72b5bf6c9eb1644c26dbf3332a8f987377&amp;v=4" width="119" alt="Christopher Kapic"/><br />Christopher Kapic</a></td>
124
132
  </tr>
125
133
  <tr>
134
+ <td align="center"><a href="https://github.com/christopher-kapic?ref=orpc" target="_blank" rel="noopener" title="Christopher Kapic"><img src="https://avatars.githubusercontent.com/u/59740769?u=e7ad4b72b5bf6c9eb1644c26dbf3332a8f987377&amp;v=4" width="119" alt="Christopher Kapic"/><br />Christopher Kapic</a></td>
126
135
  <td align="center"><a href="https://github.com/thomasballinger?ref=orpc" target="_blank" rel="noopener" title="Tom Ballinger"><img src="https://avatars.githubusercontent.com/u/458879?u=4b045ac75d721b6ac2b42a74d7d37f61f0414031&amp;v=4" width="119" alt="Tom Ballinger"/><br />Tom Ballinger</a></td>
127
136
  <td align="center"><a href="https://github.com/SSam0419?ref=orpc" target="_blank" rel="noopener" title="Sam"><img src="https://avatars.githubusercontent.com/u/102863520?u=3c89611f549d5070be232eb4532f690c8f2e7a65&amp;v=4" width="119" alt="Sam"/><br />Sam</a></td>
128
137
  <td align="center"><a href="https://github.com/Titoine?ref=orpc" target="_blank" rel="noopener" title="Titoine"><img src="https://avatars.githubusercontent.com/u/3514286?u=1bb1e86b0c99c8a1121372e56d51a177eea12191&amp;v=4" width="119" alt="Titoine"/><br />Titoine</a></td>
129
138
  <td align="center"><a href="https://github.com/Mnigos?ref=orpc" target="_blank" rel="noopener" title="Igor Makowski"><img src="https://avatars.githubusercontent.com/u/56691628?u=ee8c879478f7c151b9156aef6c74243fa3e247a8&amp;v=4" width="119" alt="Igor Makowski"/><br />Igor Makowski</a></td>
130
139
  <td align="center"><a href="https://github.com/hanayashiki?ref=orpc" target="_blank" rel="noopener" title="hanayashiki"><img src="https://avatars.githubusercontent.com/u/26056783?u=06c3b9205a16fd41a871e82da1cc2a09306d53f5&amp;v=4" width="119" alt="hanayashiki"/><br />hanayashiki</a></td>
131
140
  <td align="center"><a href="https://github.com/ldub?ref=orpc" target="_blank" rel="noopener" title="Lev Dubinets"><img src="https://avatars.githubusercontent.com/u/3114081?u=f547f5d5012cab54851f1b1ad72d10e537f78fc2&amp;v=4" width="119" alt="Lev Dubinets"/><br />Lev Dubinets</a></td>
132
- <td align="center"><a href="https://github.com/mr-kelly?ref=orpc" target="_blank" rel="noopener" title="Kelly Peilin Chan"><img src="https://avatars.githubusercontent.com/u/520852?u=6b0f7105f694e7b5cacf410a3f04c7044b469dc8&amp;v=4" width="119" alt="Kelly Peilin Chan"/><br />Kelly Peilin Chan</a></td>
133
141
  </tr>
134
142
  <tr>
143
+ <td align="center"><a href="https://github.com/mr-kelly?ref=orpc" target="_blank" rel="noopener" title="Kelly Peilin Chan"><img src="https://avatars.githubusercontent.com/u/520852?u=6b0f7105f694e7b5cacf410a3f04c7044b469dc8&amp;v=4" width="119" alt="Kelly Peilin Chan"/><br />Kelly Peilin Chan</a></td>
135
144
  <td align="center"><a href="https://github.com/piscis?ref=orpc" target="_blank" rel="noopener" title="Alex"><img src="https://avatars.githubusercontent.com/u/326163?u=b245f368bd940cf51d08c0b6bf55f8257f359437&amp;v=4" width="119" alt="Alex"/><br />Alex</a></td>
136
- <td align="center"><a href="https://github.com/nattstack?ref=orpc" target="_blank" rel="noopener" title="nattstack"><img src="https://avatars.githubusercontent.com/u/31426677?u=fa9dbb8b3e66eb0ea3c88db5dc07f31c8c5418fe&amp;v=4" width="119" alt="nattstack"/><br />nattstack</a></td>
137
145
  <td align="center"><a href="https://github.com/finom?ref=orpc" target="_blank" rel="noopener" title="Andrey Gubanov"><img src="https://avatars.githubusercontent.com/u/1082083?u=29e91400dbd4a9c217048a8f59562c4f740498e6&amp;v=4" width="119" alt="Andrey Gubanov"/><br />Andrey Gubanov</a></td>
138
146
  </tr>
139
147
  </table>
@@ -150,7 +158,7 @@ Like what we build over at [middleapi](https://github.com/middleapi)? You can he
150
158
  <a href="https://github.com/theoludwig?ref=orpc" target="_blank" rel="noopener" title="Théo LUDWIG"><img src="https://avatars.githubusercontent.com/u/25207499?u=a6a9653725a2f574c07893748806668e0598cdbe&amp;v=4" width="32" height="32" alt="Théo LUDWIG" /></a>
151
159
  <a href="https://github.com/abhay-ramesh?ref=orpc" target="_blank" rel="noopener" title="Abhay Ramesh"><img src="https://avatars.githubusercontent.com/u/66196314?u=c5c2b0327b26606c2efcfaf17046ab18c3d25c57&amp;v=4" width="32" height="32" alt="Abhay Ramesh" /></a>
152
160
  <a href="https://github.com/shr-ink?ref=orpc" target="_blank" rel="noopener" title="shr.ink oü"><img src="https://avatars.githubusercontent.com/u/139700438?v=4" width="32" height="32" alt="shr.ink oü" /></a>
153
- <a href="https://github.com/johngerome?ref=orpc" target="_blank" rel="noopener" title="0x4e32"><img src="https://avatars.githubusercontent.com/u/2002000?u=24e8dd943cfc862aa284d858a023532c75071ade&amp;v=4" width="32" height="32" alt="0x4e32" /></a>
161
+ <a href="https://github.com/johngerome?ref=orpc" target="_blank" rel="noopener" title="0x4e32"><img src="https://avatars.githubusercontent.com/u/2002000?u=505e54608466ab53754f702973687b04c6424c1f&amp;v=4" width="32" height="32" alt="0x4e32" /></a>
154
162
  <a href="https://github.com/yzuyr?ref=orpc" target="_blank" rel="noopener" title="Ryuz"><img src="https://avatars.githubusercontent.com/u/196539378?u=d38374588d219b6748b16406982f6559411466d4&amp;v=4" width="32" height="32" alt="Ryuz" /></a>
155
163
  <a href="https://github.com/happyboy2022?ref=orpc" target="_blank" rel="noopener" title="happyboy"><img src="https://avatars.githubusercontent.com/u/103669586?u=65b49c4b893ed3703909fbb3a7a22313f3f9c121&amp;v=4" width="32" height="32" alt="happyboy" /></a>
156
164
  <a href="https://github.com/YiCChi?ref=orpc" target="_blank" rel="noopener" title="yicchi"><img src="https://avatars.githubusercontent.com/u/86967274?u=6c2756f09fe15dd94d572f560e979cd157982852&amp;v=4" width="32" height="32" alt="yicchi" /></a>
@@ -164,7 +172,9 @@ Like what we build over at [middleapi](https://github.com/middleapi)? You can he
164
172
  <a href="https://github.com/nelsonlaidev?ref=orpc" target="_blank" rel="noopener" title="Nelson Lai"><img src="https://avatars.githubusercontent.com/u/75498339?u=2fc0e0b95dd184c5ffb744df977cb15a18b60672&amp;v=4" width="32" height="32" alt="Nelson Lai" /></a>
165
173
  <a href="https://github.com/nguyenlc1993?ref=orpc" target="_blank" rel="noopener" title="Lê Cao Nguyên"><img src="https://avatars.githubusercontent.com/u/13871971?u=83c8b69d9e35b589c4e1f066cc113b1d9461386f&amp;v=4" width="32" height="32" alt="Lê Cao Nguyên" /></a>
166
174
  <a href="https://github.com/wobsoriano?ref=orpc" target="_blank" rel="noopener" title="Robert Soriano"><img src="https://avatars.githubusercontent.com/u/13049130?u=6d72104182e7c9ed25934815313fb69107332111&amp;v=4" width="32" height="32" alt="Robert Soriano" /></a>
175
+ <a href="https://github.com/andrewpeters9?ref=orpc" target="_blank" rel="noopener" title="Andrew Peters"><img src="https://avatars.githubusercontent.com/u/36251325?v=4" width="32" height="32" alt="Andrew Peters" /></a>
167
176
  <a href="https://github.com/SKostyukovich?ref=orpc" target="_blank" rel="noopener" title="SKostyukovich"><img src="https://avatars.githubusercontent.com/u/10700067?v=4" width="32" height="32" alt="SKostyukovich" /></a>
177
+ <a href="https://github.com/peter-adam-dy?ref=orpc" target="_blank" rel="noopener" title="Peter Adam"><img src="https://avatars.githubusercontent.com/u/132129459?u=4f3dbbb3b443990b56acb7d6a5d11ed2c555f6db&amp;v=4" width="32" height="32" alt="Peter Adam" /></a>
168
178
  <a href="https://github.com/FabworksHQ?ref=orpc" target="_blank" rel="noopener" title="Fabworks"><img src="https://avatars.githubusercontent.com/u/160179500?v=4" width="32" height="32" alt="Fabworks" /></a>
169
179
  <a href="https://github.com/NovakAnton?ref=orpc" target="_blank" rel="noopener" title="Novak Antonijevic"><img src="https://avatars.githubusercontent.com/u/157126729?u=ae49fa22292d55c0434ff0ca008206155b18663b&amp;v=4" width="32" height="32" alt="Novak Antonijevic" /></a>
170
180
  <a href="https://github.com/laduniestu?ref=orpc" target="_blank" rel="noopener" title="Laduni Estu Syalwa"><img src="https://avatars.githubusercontent.com/u/44757637?u=a2fc1ea8f7d827a96721176f79d30592d1c48059&amp;v=4" width="32" height="32" alt="Laduni Estu Syalwa" /></a>
@@ -172,6 +182,7 @@ Like what we build over at [middleapi](https://github.com/middleapi)? You can he
172
182
  <a href="https://github.com/illarionvk?ref=orpc" target="_blank" rel="noopener" title="Illarion Koperski"><img src="https://avatars.githubusercontent.com/u/5012724?u=7cfa13652f7ac5fb3c56d880e3eb3fbe40c3ea34&amp;v=4" width="32" height="32" alt="Illarion Koperski" /></a>
173
183
  <a href="https://github.com/steelbrain?ref=orpc" target="_blank" rel="noopener" title="Anees Iqbal"><img src="https://avatars.githubusercontent.com/u/4278113?u=22b80b5399eed68ac76cd58b02961b0481f1db11&amp;v=4" width="32" height="32" alt="Anees Iqbal" /></a>
174
184
  <a href="https://github.com/Scrumplex?ref=orpc" target="_blank" rel="noopener" title="Sefa Eyeoglu"><img src="https://avatars.githubusercontent.com/u/11587657?u=ab503582165c0bbff0cca47ce31c9450bb1553c9&amp;v=4" width="32" height="32" alt="Sefa Eyeoglu" /></a>
185
+ <a href="https://github.com/nattstack?ref=orpc" target="_blank" rel="noopener" title="natt"><img src="https://avatars.githubusercontent.com/u/31426677?u=fa9dbb8b3e66eb0ea3c88db5dc07f31c8c5418fe&amp;v=4" width="32" height="32" alt="natt" /></a>
175
186
  <a href="https://github.com/ChromeGG?ref=orpc" target="_blank" rel="noopener" title="Adam Tkaczyk"><img src="https://avatars.githubusercontent.com/u/39050595?u=a58ca6042a6950e94e6e92442db76ef584279bc0&amp;v=4" width="32" height="32" alt="Adam Tkaczyk" /></a>
176
187
  <a href="https://github.com/plancraft?ref=orpc" target="_blank" rel="noopener" title="plancraft"><img src="https://avatars.githubusercontent.com/u/46482287?v=4" width="32" height="32" alt="plancraft" /></a>
177
188
  </p>
package/dist/index.d.mts CHANGED
@@ -2,14 +2,13 @@ import * as Draft2020 from 'json-schema-typed/draft-2020-12';
2
2
  export { Format as JsonSchemaFormat, TypeName as JsonSchemaType } from 'json-schema-typed/draft-2020-12';
3
3
  import { AnySchema, RouterContract } from '@orpc/contract';
4
4
  export { AnySchema, Schema } from '@orpc/contract';
5
- import { Promisable } from '@orpc/shared';
6
5
  import { Context } from '@orpc/server';
7
6
  import { StandardHandlerPlugin, StandardHandlerOptions } from '@orpc/server/standard';
8
7
  import { ClientContext } from '@orpc/client';
9
8
  import { StandardLinkPlugin, StandardLinkOptions } from '@orpc/client/standard';
10
9
  import { StandardJSONSchemaV1 } from '@standard-schema/spec';
11
10
 
12
- type JsonSchema = Draft2020.JSONSchema;
11
+ type JsonSchema<Value = any> = Draft2020.JSONSchema<Value>;
13
12
  type JsonSchemaKeywords = typeof Draft2020.keywords[number];
14
13
  declare enum JsonSchemaXNativeType {
15
14
  BigInt = "bigint",
@@ -30,6 +29,7 @@ declare class JsonSchemaCoercer {
30
29
  * and exclusively use absolute JSON pointers for `$ref` values.
31
30
  */
32
31
 
32
+ declare function isJsonPrimitiveSchema(schema: JsonSchema): schema is JsonSchema & object;
33
33
  type JsonFileSchema = JsonSchema & object & {
34
34
  type: 'string';
35
35
  contentMediaType?: string;
@@ -70,10 +70,6 @@ declare function ensureJsonSchemaObject(schema: JsonSchema): Exclude<JsonSchema,
70
70
  * Combines multiple schemas under the requested composition keyword, promoting branch `$defs` to the root.
71
71
  */
72
72
  declare function combineJsonSchemasWithComposition(keyword: 'allOf' | 'anyOf' | 'oneOf', schemas: JsonSchema[]): JsonSchema;
73
- /**
74
- * Returns true when every branch in the schema describes a primitive value.
75
- */
76
- declare function isJsonPrimitiveSchema(schema: JsonSchema): boolean;
77
73
  type JsonObjectSchemaEntry = [name: string, schema: JsonSchema, optional: boolean];
78
74
  /**
79
75
  * Combines object property entries back into a single object schema.
@@ -98,16 +94,16 @@ interface JsonSchemaConverter {
98
94
  /**
99
95
  * Determines whether this converter can handle the given schema.
100
96
  */
101
- condition(schema: AnySchema | undefined, direction: JsonSchemaConverterDirection): Promisable<boolean>;
97
+ condition(schema: AnySchema | undefined, direction: JsonSchemaConverterDirection): boolean;
102
98
  /**
103
99
  * Converts an ORPC schema to a JSON Schema representation.
104
100
  */
105
- convert(schema: AnySchema | undefined, direction: JsonSchemaConverterDirection): Promisable<[jsonSchema: JsonSchema, optional: boolean]>;
101
+ convert(schema: AnySchema | undefined, direction: JsonSchemaConverterDirection): [jsonSchema: JsonSchema, optional: boolean];
106
102
  }
107
103
  declare class DelegatingJsonSchemaConverter implements Pick<JsonSchemaConverter, 'convert'> {
108
104
  private readonly converters;
109
105
  constructor(converters?: JsonSchemaConverter[]);
110
- convert(schema: AnySchema | undefined, direction: JsonSchemaConverterDirection): Promise<[jsonSchema: JsonSchema, optional: boolean]>;
106
+ convert(schema: AnySchema | undefined, direction: JsonSchemaConverterDirection): [jsonSchema: JsonSchema, optional: boolean];
111
107
  }
112
108
 
113
109
  /**
@@ -127,6 +123,11 @@ declare function encodeJsonPointerSegment(segment: string): string;
127
123
  * https://datatracker.ietf.org/doc/html/rfc6901
128
124
  */
129
125
  declare function decodeJsonPointerSegment(segment: string): string;
126
+ /**
127
+ * Visits every `$ref` in a schema, traversing the same keywords as {@link mapJsonSchemaRefs}.
128
+ * Shared or cyclic object instances are visited once.
129
+ */
130
+ declare function visitJsonSchemaRefs(value: JsonSchema, visit: (ref: string) => void, schemaLevel?: boolean, seen?: Set<object>): void;
130
131
  declare function mapJsonSchemaRefs(value: JsonSchema, map: (ref: string, path: Array<string | number>) => string, schemaLevel?: boolean, path?: Array<string | number>): JsonSchema;
131
132
  /**
132
133
  * Rewrites recursive root `#` refs by moving the schema body into `$defs`.
@@ -186,5 +187,5 @@ declare class StandardJsonSchemaConverter implements JsonSchemaConverter {
186
187
  convertInternal(schema: StandardJSONSchemaV1 & AnySchema, direction: JsonSchemaConverterDirection): [jsonSchema: JsonSchema, optional: boolean];
187
188
  }
188
189
 
189
- export { DelegatingJsonSchemaConverter, JsonSchemaCoercer, JsonSchemaXNativeType, SmartCoercionHandlerPlugin, SmartCoercionLinkPlugin, StandardJsonSchemaConverter, combineJsonObjectSchemaEntries, combineJsonSchemasWithComposition, decodeJsonPointerSegment, deduplicateJsonSchemas, encodeJsonPointerSegment, ensureJsonSchemaObject, extractJsonObjectSchemaEntries, flattenJsonUnionSchema, hoistRecursiveRefToDef, isJsonArraySchema, isJsonFileSchema, isJsonObjectSchema, isJsonPrimitiveSchema, isUnconstrainedSchema, mapJsonSchemaRefs, matchArrayableJsonSchema, resolveJsonSchemaRootLocalRef };
190
+ export { DelegatingJsonSchemaConverter, JsonSchemaCoercer, JsonSchemaXNativeType, SmartCoercionHandlerPlugin, SmartCoercionLinkPlugin, StandardJsonSchemaConverter, combineJsonObjectSchemaEntries, combineJsonSchemasWithComposition, decodeJsonPointerSegment, deduplicateJsonSchemas, encodeJsonPointerSegment, ensureJsonSchemaObject, extractJsonObjectSchemaEntries, flattenJsonUnionSchema, hoistRecursiveRefToDef, isJsonArraySchema, isJsonFileSchema, isJsonObjectSchema, isJsonPrimitiveSchema, isUnconstrainedSchema, mapJsonSchemaRefs, matchArrayableJsonSchema, resolveJsonSchemaRootLocalRef, visitJsonSchemaRefs };
190
191
  export type { JsonArraySchema, JsonFileSchema, JsonObjectSchema, JsonObjectSchemaEntry, JsonSchema, JsonSchemaConverter, JsonSchemaConverterDirection, JsonSchemaKeywords, SmartCoercionHandlerPluginOptions, SmartCoercionLinkPluginOptions };
package/dist/index.d.ts CHANGED
@@ -2,14 +2,13 @@ import * as Draft2020 from 'json-schema-typed/draft-2020-12';
2
2
  export { Format as JsonSchemaFormat, TypeName as JsonSchemaType } from 'json-schema-typed/draft-2020-12';
3
3
  import { AnySchema, RouterContract } from '@orpc/contract';
4
4
  export { AnySchema, Schema } from '@orpc/contract';
5
- import { Promisable } from '@orpc/shared';
6
5
  import { Context } from '@orpc/server';
7
6
  import { StandardHandlerPlugin, StandardHandlerOptions } from '@orpc/server/standard';
8
7
  import { ClientContext } from '@orpc/client';
9
8
  import { StandardLinkPlugin, StandardLinkOptions } from '@orpc/client/standard';
10
9
  import { StandardJSONSchemaV1 } from '@standard-schema/spec';
11
10
 
12
- type JsonSchema = Draft2020.JSONSchema;
11
+ type JsonSchema<Value = any> = Draft2020.JSONSchema<Value>;
13
12
  type JsonSchemaKeywords = typeof Draft2020.keywords[number];
14
13
  declare enum JsonSchemaXNativeType {
15
14
  BigInt = "bigint",
@@ -30,6 +29,7 @@ declare class JsonSchemaCoercer {
30
29
  * and exclusively use absolute JSON pointers for `$ref` values.
31
30
  */
32
31
 
32
+ declare function isJsonPrimitiveSchema(schema: JsonSchema): schema is JsonSchema & object;
33
33
  type JsonFileSchema = JsonSchema & object & {
34
34
  type: 'string';
35
35
  contentMediaType?: string;
@@ -70,10 +70,6 @@ declare function ensureJsonSchemaObject(schema: JsonSchema): Exclude<JsonSchema,
70
70
  * Combines multiple schemas under the requested composition keyword, promoting branch `$defs` to the root.
71
71
  */
72
72
  declare function combineJsonSchemasWithComposition(keyword: 'allOf' | 'anyOf' | 'oneOf', schemas: JsonSchema[]): JsonSchema;
73
- /**
74
- * Returns true when every branch in the schema describes a primitive value.
75
- */
76
- declare function isJsonPrimitiveSchema(schema: JsonSchema): boolean;
77
73
  type JsonObjectSchemaEntry = [name: string, schema: JsonSchema, optional: boolean];
78
74
  /**
79
75
  * Combines object property entries back into a single object schema.
@@ -98,16 +94,16 @@ interface JsonSchemaConverter {
98
94
  /**
99
95
  * Determines whether this converter can handle the given schema.
100
96
  */
101
- condition(schema: AnySchema | undefined, direction: JsonSchemaConverterDirection): Promisable<boolean>;
97
+ condition(schema: AnySchema | undefined, direction: JsonSchemaConverterDirection): boolean;
102
98
  /**
103
99
  * Converts an ORPC schema to a JSON Schema representation.
104
100
  */
105
- convert(schema: AnySchema | undefined, direction: JsonSchemaConverterDirection): Promisable<[jsonSchema: JsonSchema, optional: boolean]>;
101
+ convert(schema: AnySchema | undefined, direction: JsonSchemaConverterDirection): [jsonSchema: JsonSchema, optional: boolean];
106
102
  }
107
103
  declare class DelegatingJsonSchemaConverter implements Pick<JsonSchemaConverter, 'convert'> {
108
104
  private readonly converters;
109
105
  constructor(converters?: JsonSchemaConverter[]);
110
- convert(schema: AnySchema | undefined, direction: JsonSchemaConverterDirection): Promise<[jsonSchema: JsonSchema, optional: boolean]>;
106
+ convert(schema: AnySchema | undefined, direction: JsonSchemaConverterDirection): [jsonSchema: JsonSchema, optional: boolean];
111
107
  }
112
108
 
113
109
  /**
@@ -127,6 +123,11 @@ declare function encodeJsonPointerSegment(segment: string): string;
127
123
  * https://datatracker.ietf.org/doc/html/rfc6901
128
124
  */
129
125
  declare function decodeJsonPointerSegment(segment: string): string;
126
+ /**
127
+ * Visits every `$ref` in a schema, traversing the same keywords as {@link mapJsonSchemaRefs}.
128
+ * Shared or cyclic object instances are visited once.
129
+ */
130
+ declare function visitJsonSchemaRefs(value: JsonSchema, visit: (ref: string) => void, schemaLevel?: boolean, seen?: Set<object>): void;
130
131
  declare function mapJsonSchemaRefs(value: JsonSchema, map: (ref: string, path: Array<string | number>) => string, schemaLevel?: boolean, path?: Array<string | number>): JsonSchema;
131
132
  /**
132
133
  * Rewrites recursive root `#` refs by moving the schema body into `$defs`.
@@ -186,5 +187,5 @@ declare class StandardJsonSchemaConverter implements JsonSchemaConverter {
186
187
  convertInternal(schema: StandardJSONSchemaV1 & AnySchema, direction: JsonSchemaConverterDirection): [jsonSchema: JsonSchema, optional: boolean];
187
188
  }
188
189
 
189
- export { DelegatingJsonSchemaConverter, JsonSchemaCoercer, JsonSchemaXNativeType, SmartCoercionHandlerPlugin, SmartCoercionLinkPlugin, StandardJsonSchemaConverter, combineJsonObjectSchemaEntries, combineJsonSchemasWithComposition, decodeJsonPointerSegment, deduplicateJsonSchemas, encodeJsonPointerSegment, ensureJsonSchemaObject, extractJsonObjectSchemaEntries, flattenJsonUnionSchema, hoistRecursiveRefToDef, isJsonArraySchema, isJsonFileSchema, isJsonObjectSchema, isJsonPrimitiveSchema, isUnconstrainedSchema, mapJsonSchemaRefs, matchArrayableJsonSchema, resolveJsonSchemaRootLocalRef };
190
+ export { DelegatingJsonSchemaConverter, JsonSchemaCoercer, JsonSchemaXNativeType, SmartCoercionHandlerPlugin, SmartCoercionLinkPlugin, StandardJsonSchemaConverter, combineJsonObjectSchemaEntries, combineJsonSchemasWithComposition, decodeJsonPointerSegment, deduplicateJsonSchemas, encodeJsonPointerSegment, ensureJsonSchemaObject, extractJsonObjectSchemaEntries, flattenJsonUnionSchema, hoistRecursiveRefToDef, isJsonArraySchema, isJsonFileSchema, isJsonObjectSchema, isJsonPrimitiveSchema, isUnconstrainedSchema, mapJsonSchemaRefs, matchArrayableJsonSchema, resolveJsonSchemaRootLocalRef, visitJsonSchemaRefs };
190
191
  export type { JsonArraySchema, JsonFileSchema, JsonObjectSchema, JsonObjectSchemaEntry, JsonSchema, JsonSchemaConverter, JsonSchemaConverterDirection, JsonSchemaKeywords, SmartCoercionHandlerPluginOptions, SmartCoercionLinkPluginOptions };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { get, isPlainObject, toArray, tryOrUndefined, isDeepEqual, omit, isTypescriptObject } from '@orpc/shared';
1
+ import { get, isPlainObject, tryOrUndefined, toArray, isDeepEqual, omit, isTypescriptObject } from '@orpc/shared';
2
2
  export { Format as JsonSchemaFormat, TypeName as JsonSchemaType } from 'json-schema-typed/draft-2020-12';
3
3
  import { ORPCError, cloneORPCError } from '@orpc/client';
4
4
  import { getProcedureContractOrThrow } from '@orpc/contract';
@@ -59,6 +59,27 @@ function encodeJsonPointerSegment(segment) {
59
59
  function decodeJsonPointerSegment(segment) {
60
60
  return segment.replaceAll("~1", "/").replaceAll("~0", "~");
61
61
  }
62
+ function visitJsonSchemaRefs(value, visit, schemaLevel = true, seen = /* @__PURE__ */ new Set()) {
63
+ if (!value || typeof value !== "object" || seen.has(value)) {
64
+ return;
65
+ }
66
+ seen.add(value);
67
+ if (Array.isArray(value)) {
68
+ for (const item of value) {
69
+ visitJsonSchemaRefs(item, visit, schemaLevel, seen);
70
+ }
71
+ return;
72
+ }
73
+ for (const [key, val] of Object.entries(value)) {
74
+ if (key === "$ref" && typeof val === "string") {
75
+ visit(val);
76
+ } else if (!schemaLevel) {
77
+ visitJsonSchemaRefs(val, visit, true, seen);
78
+ } else if (JSON_SCHEMA_LOGIC_KEYWORDS.has(key) || JSON_SCHEMA_RECORD_KEYWORDS.has(key)) {
79
+ visitJsonSchemaRefs(val, visit, !JSON_SCHEMA_RECORD_KEYWORDS.has(key), seen);
80
+ }
81
+ }
82
+ }
62
83
  function mapJsonSchemaRefs(value, map, schemaLevel = true, path = []) {
63
84
  if (!value || typeof value !== "object") {
64
85
  return value;
@@ -148,7 +169,12 @@ var JsonSchemaXNativeType = /* @__PURE__ */ ((JsonSchemaXNativeType2) => {
148
169
  return JsonSchemaXNativeType2;
149
170
  })(JsonSchemaXNativeType || {});
150
171
 
151
- const FLEXIBLE_DATE_FORMAT_REGEX = /^[^-]+-[^-]+-[^-]+$/;
172
+ const UNSATISFIED = 0;
173
+ const LOOSELY_SATISFIED = 1;
174
+ const SATISFIED = 2;
175
+ function minSatisfaction(a, b) {
176
+ return a < b ? a : b;
177
+ }
152
178
  class JsonSchemaCoercer {
153
179
  coerce([schema, optional], value) {
154
180
  if (optional && value === void 0) {
@@ -157,25 +183,29 @@ class JsonSchemaCoercer {
157
183
  const [, coerced] = this.coerceInternal(schema, schema, value);
158
184
  return coerced;
159
185
  }
160
- coerceInternal(rootSchema, schema, value) {
186
+ coerceInternal(rootSchema, schema, value, appliedRefs) {
161
187
  if (typeof schema === "boolean") {
162
- return [schema, value];
188
+ return [schema ? SATISFIED : UNSATISFIED, value];
163
189
  }
164
190
  if (Array.isArray(schema.type)) {
165
191
  return this.coerceInternal(
166
192
  rootSchema,
167
193
  { anyOf: schema.type.map((type) => ({ ...schema, type })) },
168
- value
194
+ value,
195
+ appliedRefs
169
196
  );
170
197
  }
171
198
  let coerced = value;
172
- let satisfied = true;
199
+ let satisfied = SATISFIED;
173
200
  if (typeof schema.$ref === "string") {
174
201
  const resolved = schema.$ref.startsWith("#/") ? get(rootSchema, schema.$ref.slice("#/".length).split("/").map(decodeJsonPointerSegment)) : schema.$ref === "#" ? rootSchema : void 0;
175
- if (resolved !== void 0) {
176
- const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, resolved, coerced);
202
+ if (resolved !== void 0 && !appliedRefs?.has(resolved)) {
203
+ appliedRefs ??= /* @__PURE__ */ new Set();
204
+ appliedRefs.add(resolved);
205
+ const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, resolved, coerced, appliedRefs);
206
+ appliedRefs.delete(resolved);
177
207
  coerced = subCoerced;
178
- satisfied = subSatisfied;
208
+ satisfied = minSatisfaction(satisfied, subSatisfied);
179
209
  }
180
210
  }
181
211
  const enumValues = schema.const !== void 0 ? [schema.const] : schema.enum;
@@ -189,24 +219,24 @@ class JsonSchemaCoercer {
189
219
  if (enumValues.includes(booleanValue)) {
190
220
  coerced = booleanValue;
191
221
  } else {
192
- satisfied = false;
222
+ satisfied = UNSATISFIED;
193
223
  }
194
224
  }
195
225
  } else {
196
- satisfied = false;
226
+ satisfied = UNSATISFIED;
197
227
  }
198
228
  }
199
229
  if (schema.type) {
200
230
  switch (schema.type) {
201
231
  case "null": {
202
232
  if (coerced !== null) {
203
- satisfied = false;
233
+ satisfied = UNSATISFIED;
204
234
  }
205
235
  break;
206
236
  }
207
237
  case "string": {
208
238
  if (typeof coerced !== "string") {
209
- satisfied = false;
239
+ satisfied = UNSATISFIED;
210
240
  }
211
241
  break;
212
242
  }
@@ -215,7 +245,7 @@ class JsonSchemaCoercer {
215
245
  coerced = stringToNumber(coerced);
216
246
  }
217
247
  if (typeof coerced !== "number") {
218
- satisfied = false;
248
+ satisfied = UNSATISFIED;
219
249
  }
220
250
  break;
221
251
  }
@@ -224,7 +254,7 @@ class JsonSchemaCoercer {
224
254
  coerced = stringToInteger(coerced);
225
255
  }
226
256
  if (typeof coerced !== "number" || !Number.isInteger(coerced)) {
227
- satisfied = false;
257
+ satisfied = UNSATISFIED;
228
258
  }
229
259
  break;
230
260
  }
@@ -233,7 +263,7 @@ class JsonSchemaCoercer {
233
263
  coerced = stringToBoolean(coerced);
234
264
  }
235
265
  if (typeof coerced !== "boolean") {
236
- satisfied = false;
266
+ satisfied = UNSATISFIED;
237
267
  }
238
268
  break;
239
269
  }
@@ -245,26 +275,24 @@ class JsonSchemaCoercer {
245
275
  const coercedItems = coerced.map((item, i) => {
246
276
  const subSchema = prefixItemSchemas[i] ?? itemSchema;
247
277
  if (subSchema === void 0) {
248
- satisfied = false;
278
+ satisfied = minSatisfaction(satisfied, LOOSELY_SATISFIED);
249
279
  return item;
250
280
  }
251
281
  const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, item);
252
- if (!subSatisfied) {
253
- satisfied = false;
254
- }
282
+ satisfied = minSatisfaction(satisfied, subSatisfied);
255
283
  if (subCoerced !== item) {
256
284
  shouldUseCoercedItems = true;
257
285
  }
258
286
  return subCoerced;
259
287
  });
260
288
  if (coercedItems.length < prefixItemSchemas.length) {
261
- satisfied = false;
289
+ satisfied = UNSATISFIED;
262
290
  }
263
291
  if (shouldUseCoercedItems) {
264
292
  coerced = coercedItems;
265
293
  }
266
294
  } else {
267
- satisfied = false;
295
+ satisfied = UNSATISFIED;
268
296
  }
269
297
  break;
270
298
  }
@@ -274,35 +302,36 @@ class JsonSchemaCoercer {
274
302
  }
275
303
  if (isPlainObject(coerced)) {
276
304
  let shouldUseCoercedItems = false;
277
- const coercedItems = {};
278
- const patternProperties = Object.entries(schema.patternProperties ?? {}).map(([key, value2]) => [new RegExp(key), value2]);
305
+ const coercedItems = { ...coerced };
306
+ const patternProperties = Object.entries(schema.patternProperties ?? {}).flatMap(([key, value2]) => {
307
+ const pattern = tryOrUndefined(() => new RegExp(key));
308
+ return pattern ? [[pattern, value2]] : [];
309
+ });
310
+ const propertySchemas = schema.properties;
279
311
  for (const key in coerced) {
280
312
  const value2 = coerced[key];
281
- const subSchema = schema.properties?.[key] ?? patternProperties.find(([pattern]) => pattern.test(key))?.[1] ?? schema.additionalProperties;
282
- if (value2 === void 0 && !schema.required?.includes(key)) {
283
- coercedItems[key] = value2;
284
- } else if (subSchema === void 0) {
285
- coercedItems[key] = value2;
286
- satisfied = false;
287
- } else {
288
- const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, value2);
289
- coercedItems[key] = subCoerced;
290
- if (!subSatisfied) {
291
- satisfied = false;
292
- }
293
- if (subCoerced !== value2) {
294
- shouldUseCoercedItems = true;
313
+ const subSchema = (propertySchemas !== void 0 && Object.hasOwn(propertySchemas, key) ? propertySchemas[key] : void 0) ?? patternProperties.find(([pattern]) => pattern.test(key))?.[1] ?? schema.additionalProperties;
314
+ if (value2 !== void 0 || schema.required?.includes(key)) {
315
+ if (subSchema === void 0) {
316
+ satisfied = minSatisfaction(satisfied, LOOSELY_SATISFIED);
317
+ } else {
318
+ const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, value2);
319
+ coercedItems[key] = subCoerced;
320
+ satisfied = minSatisfaction(satisfied, subSatisfied);
321
+ if (subCoerced !== value2) {
322
+ shouldUseCoercedItems = true;
323
+ }
295
324
  }
296
325
  }
297
326
  }
298
327
  if (schema.required?.some((key) => !Object.hasOwn(coercedItems, key))) {
299
- satisfied = false;
328
+ satisfied = UNSATISFIED;
300
329
  }
301
330
  if (shouldUseCoercedItems) {
302
331
  coerced = coercedItems;
303
332
  }
304
333
  } else {
305
- satisfied = false;
334
+ satisfied = UNSATISFIED;
306
335
  }
307
336
  break;
308
337
  }
@@ -315,7 +344,7 @@ class JsonSchemaCoercer {
315
344
  coerced = stringToDate(coerced);
316
345
  }
317
346
  if (!(coerced instanceof Date)) {
318
- satisfied = false;
347
+ satisfied = UNSATISFIED;
319
348
  }
320
349
  break;
321
350
  }
@@ -329,7 +358,7 @@ class JsonSchemaCoercer {
329
358
  break;
330
359
  }
331
360
  if (typeof coerced !== "bigint") {
332
- satisfied = false;
361
+ satisfied = UNSATISFIED;
333
362
  }
334
363
  break;
335
364
  }
@@ -338,7 +367,7 @@ class JsonSchemaCoercer {
338
367
  coerced = stringToRegExp(coerced);
339
368
  }
340
369
  if (!(coerced instanceof RegExp)) {
341
- satisfied = false;
370
+ satisfied = UNSATISFIED;
342
371
  }
343
372
  break;
344
373
  }
@@ -347,7 +376,7 @@ class JsonSchemaCoercer {
347
376
  coerced = stringToURL(coerced);
348
377
  }
349
378
  if (!(coerced instanceof URL)) {
350
- satisfied = false;
379
+ satisfied = UNSATISFIED;
351
380
  }
352
381
  break;
353
382
  }
@@ -356,7 +385,7 @@ class JsonSchemaCoercer {
356
385
  coerced = arrayToSet(coerced);
357
386
  }
358
387
  if (!(coerced instanceof Set)) {
359
- satisfied = false;
388
+ satisfied = UNSATISFIED;
360
389
  }
361
390
  break;
362
391
  }
@@ -365,7 +394,7 @@ class JsonSchemaCoercer {
365
394
  coerced = arrayToMap(coerced);
366
395
  }
367
396
  if (!(coerced instanceof Map)) {
368
- satisfied = false;
397
+ satisfied = UNSATISFIED;
369
398
  }
370
399
  break;
371
400
  }
@@ -373,54 +402,78 @@ class JsonSchemaCoercer {
373
402
  }
374
403
  if (schema.allOf) {
375
404
  for (const subSchema of schema.allOf) {
376
- const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, coerced);
405
+ const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, coerced, appliedRefs);
377
406
  coerced = subCoerced;
378
- if (!subSatisfied) {
379
- satisfied = false;
380
- }
407
+ satisfied = minSatisfaction(satisfied, subSatisfied);
381
408
  }
382
409
  }
383
410
  for (const key of ["anyOf", "oneOf"]) {
384
411
  if (schema[key]) {
385
- let bestOptions;
412
+ let best;
386
413
  for (const subSchema of schema[key]) {
387
- const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, coerced);
388
- if (subSatisfied) {
389
- if (!bestOptions || subCoerced === coerced) {
390
- bestOptions = { coerced: subCoerced, satisfied: subSatisfied };
391
- }
392
- if (subCoerced === coerced) {
393
- break;
394
- }
414
+ const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, coerced, appliedRefs);
415
+ if (subSatisfied === UNSATISFIED) {
416
+ continue;
395
417
  }
418
+ if (subCoerced === coerced) {
419
+ best = { satisfied: subSatisfied, value: subCoerced };
420
+ break;
421
+ }
422
+ if (!best || subSatisfied > best.satisfied) {
423
+ best = { satisfied: subSatisfied, value: subCoerced };
424
+ }
425
+ }
426
+ if (best) {
427
+ coerced = best.value;
428
+ satisfied = minSatisfaction(satisfied, best.satisfied);
429
+ } else {
430
+ satisfied = UNSATISFIED;
396
431
  }
397
- coerced = bestOptions ? bestOptions.coerced : coerced;
398
- satisfied = bestOptions ? bestOptions.satisfied : false;
399
432
  }
400
433
  }
401
434
  if (typeof schema.not !== "undefined") {
402
- const [notSatisfied] = this.coerceInternal(rootSchema, schema.not, coerced);
403
- if (notSatisfied) {
404
- satisfied = false;
435
+ const [notSatisfied] = this.coerceInternal(rootSchema, schema.not, coerced, appliedRefs);
436
+ if (notSatisfied !== UNSATISFIED) {
437
+ satisfied = UNSATISFIED;
405
438
  }
406
439
  }
407
440
  return [satisfied, coerced];
408
441
  }
409
442
  }
443
+ const NUMBER_PATTERN = /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/;
410
444
  function stringToNumber(value) {
411
- const num = Number.parseFloat(value);
412
- if (Number.isNaN(num) || num !== Number(value)) {
445
+ if (!NUMBER_PATTERN.test(value)) {
446
+ return value;
447
+ }
448
+ const num = Number(value);
449
+ if (num < Number.MIN_SAFE_INTEGER || num > Number.MAX_SAFE_INTEGER) {
413
450
  return value;
414
451
  }
415
452
  return num;
416
453
  }
454
+ const INTEGER_PATTERN = /^-?(?:0|[1-9]\d*)$/;
417
455
  function stringToInteger(value) {
418
- const num = Number.parseInt(value);
419
- if (Number.isNaN(num) || num !== Number(value)) {
456
+ if (!INTEGER_PATTERN.test(value)) {
457
+ return value;
458
+ }
459
+ const num = Number(value);
460
+ if (!Number.isSafeInteger(num)) {
420
461
  return value;
421
462
  }
422
463
  return num;
423
464
  }
465
+ function stringToBigInt(value) {
466
+ if (!INTEGER_PATTERN.test(value)) {
467
+ return value;
468
+ }
469
+ return BigInt(value);
470
+ }
471
+ function numberToBigInt(value) {
472
+ if (!Number.isInteger(value)) {
473
+ return value;
474
+ }
475
+ return BigInt(value);
476
+ }
424
477
  function stringToBoolean(value) {
425
478
  const lower = value.toLowerCase();
426
479
  if (lower === "false" || lower === "off") {
@@ -431,21 +484,20 @@ function stringToBoolean(value) {
431
484
  }
432
485
  return value;
433
486
  }
434
- function stringToBigInt(value) {
435
- return tryOrUndefined(() => BigInt(value)) ?? value;
436
- }
437
- function numberToBigInt(value) {
438
- return tryOrUndefined(() => BigInt(value)) ?? value;
439
- }
487
+ const DATE_TIME_PATTERN = /^[+-]?\d{4,6}-\d{1,2}-\d{1,2}(?:[T ].*)?$/;
440
488
  function stringToDate(value) {
489
+ if (!DATE_TIME_PATTERN.test(value)) {
490
+ return value;
491
+ }
441
492
  const date = new Date(value);
442
- if (Number.isNaN(date.getTime()) || !FLEXIBLE_DATE_FORMAT_REGEX.test(value)) {
493
+ if (Number.isNaN(date.getTime())) {
443
494
  return value;
444
495
  }
445
496
  return date;
446
497
  }
498
+ const REGEXP_PATTERN = /^\/([\s\S]*)\/([a-z]*)$/;
447
499
  function stringToRegExp(value) {
448
- const match = value.match(/^\/(.*)\/([a-z]*)$/);
500
+ const match = value.match(REGEXP_PATTERN);
449
501
  if (match) {
450
502
  const [, pattern, flags] = match;
451
503
  return tryOrUndefined(() => new RegExp(pattern, flags)) ?? value;
@@ -473,6 +525,21 @@ function arrayToMap(value) {
473
525
  return result;
474
526
  }
475
527
 
528
+ function isJsonPrimitiveSchema(schema) {
529
+ if (typeof schema === "boolean") {
530
+ return false;
531
+ }
532
+ if (typeof schema.type === "string" && JSON_SCHEMA_PRIMITIVE_TYPES.has(schema.type)) {
533
+ return true;
534
+ }
535
+ if (schema.const !== void 0) {
536
+ return true;
537
+ }
538
+ if (schema.enum !== void 0) {
539
+ return true;
540
+ }
541
+ return false;
542
+ }
476
543
  function isJsonFileSchema(schema) {
477
544
  return typeof schema !== "boolean" && schema.type === "string" && (typeof schema.contentMediaType === "string" || schema.format === "binary" || schema.contentEncoding === "binary");
478
545
  }
@@ -568,23 +635,6 @@ function combineJsonSchemasWithComposition(keyword, schemas) {
568
635
  }
569
636
  return result;
570
637
  }
571
- function isJsonPrimitiveSchema(schema) {
572
- return flattenJsonUnionSchema(schema).every((s) => {
573
- if (typeof s === "boolean") {
574
- return false;
575
- }
576
- if (typeof s.type === "string" && JSON_SCHEMA_PRIMITIVE_TYPES.has(s.type)) {
577
- return true;
578
- }
579
- if (s.const !== void 0) {
580
- return true;
581
- }
582
- if (s.enum !== void 0) {
583
- return true;
584
- }
585
- return false;
586
- });
587
- }
588
638
  function combineJsonObjectSchemaEntries(entries) {
589
639
  const properties = {};
590
640
  const required = [];
@@ -846,13 +896,14 @@ class DelegatingJsonSchemaConverter {
846
896
  constructor(converters = []) {
847
897
  this.converters = converters;
848
898
  }
849
- async convert(schema, direction) {
899
+ convert(schema, direction) {
850
900
  for (const converter of this.converters) {
851
- if (await converter.condition(schema, direction)) {
901
+ if (converter.condition(schema, direction)) {
852
902
  return converter.convert(schema, direction);
853
903
  }
854
904
  }
855
- const optional = !(await schema?.["~standard"].validate(void 0))?.issues?.length;
905
+ const result = schema?.["~standard"].validate(void 0);
906
+ const optional = result instanceof Promise ? false : !result?.issues?.length;
856
907
  if (schema && "jsonSchema" in schema["~standard"] && schema["~standard"].jsonSchema) {
857
908
  try {
858
909
  return [
@@ -909,23 +960,23 @@ class SmartCoercionHandlerPlugin {
909
960
  return {
910
961
  ...options,
911
962
  clientInterceptors: [
912
- async ({ next, input, ...interceptorOptions }) => {
963
+ ({ next, input, ...interceptorOptions }) => {
913
964
  const inputSchemas = interceptorOptions.procedure["~orpc"].inputSchemas;
914
965
  if (!inputSchemas) {
915
966
  return next();
916
967
  }
917
- const coercedInput = await this.coerceValue(inputSchemas, input);
968
+ const coercedInput = this.coerceValue(inputSchemas, input);
918
969
  return next({ ...interceptorOptions, input: coercedInput });
919
970
  },
920
971
  ...toArray(options.clientInterceptors)
921
972
  ]
922
973
  };
923
974
  }
924
- async coerceValue(schemas, value) {
975
+ coerceValue(schemas, value) {
925
976
  for (const schema of schemas) {
926
977
  let converted = this.cache.get(schema);
927
978
  if (!converted) {
928
- converted = await this.converter.convert(schema, "input");
979
+ converted = this.converter.convert(schema, "input");
929
980
  this.cache.set(schema, converted);
930
981
  }
931
982
  value = this.coercer.coerce(converted, value);
@@ -964,7 +1015,7 @@ class SmartCoercionLinkPlugin {
964
1015
  if (!outputSchemas) {
965
1016
  return output;
966
1017
  }
967
- const coercedOutput = await this.coerceValue(outputSchemas, output);
1018
+ const coercedOutput = this.coerceValue(outputSchemas, output);
968
1019
  return coercedOutput;
969
1020
  } catch (error) {
970
1021
  if (!(error instanceof ORPCError) || !error.defined) {
@@ -976,18 +1027,18 @@ class SmartCoercionLinkPlugin {
976
1027
  throw error;
977
1028
  }
978
1029
  const cloned = cloneORPCError(error);
979
- cloned.data = await this.coerceValue([dataSchema], cloned.data);
1030
+ cloned.data = this.coerceValue([dataSchema], cloned.data);
980
1031
  throw cloned;
981
1032
  }
982
1033
  }
983
1034
  ]
984
1035
  };
985
1036
  }
986
- async coerceValue(schemas, value) {
1037
+ coerceValue(schemas, value) {
987
1038
  for (const schema of schemas) {
988
1039
  let converted = this.cache.get(schema);
989
1040
  if (!converted) {
990
- converted = await this.converter.convert(schema, "output");
1041
+ converted = this.converter.convert(schema, "output");
991
1042
  this.cache.set(schema, converted);
992
1043
  }
993
1044
  value = this.coercer.coerce(converted, value);
@@ -996,4 +1047,4 @@ class SmartCoercionLinkPlugin {
996
1047
  }
997
1048
  }
998
1049
 
999
- export { DelegatingJsonSchemaConverter, JsonSchemaCoercer, JsonSchemaXNativeType, SmartCoercionHandlerPlugin, SmartCoercionLinkPlugin, StandardJsonSchemaConverter, combineJsonObjectSchemaEntries, combineJsonSchemasWithComposition, decodeJsonPointerSegment, deduplicateJsonSchemas, encodeJsonPointerSegment, ensureJsonSchemaObject, extractJsonObjectSchemaEntries, flattenJsonUnionSchema, hoistRecursiveRefToDef, isJsonArraySchema, isJsonFileSchema, isJsonObjectSchema, isJsonPrimitiveSchema, isUnconstrainedSchema, mapJsonSchemaRefs, matchArrayableJsonSchema, resolveJsonSchemaRootLocalRef };
1050
+ export { DelegatingJsonSchemaConverter, JsonSchemaCoercer, JsonSchemaXNativeType, SmartCoercionHandlerPlugin, SmartCoercionLinkPlugin, StandardJsonSchemaConverter, combineJsonObjectSchemaEntries, combineJsonSchemasWithComposition, decodeJsonPointerSegment, deduplicateJsonSchemas, encodeJsonPointerSegment, ensureJsonSchemaObject, extractJsonObjectSchemaEntries, flattenJsonUnionSchema, hoistRecursiveRefToDef, isJsonArraySchema, isJsonFileSchema, isJsonObjectSchema, isJsonPrimitiveSchema, isUnconstrainedSchema, mapJsonSchemaRefs, matchArrayableJsonSchema, resolveJsonSchemaRootLocalRef, visitJsonSchemaRefs };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@orpc/json-schema",
3
3
  "type": "module",
4
- "version": "2.0.0-beta.2",
4
+ "version": "2.0.0-beta.21",
5
5
  "license": "MIT",
6
6
  "homepage": "https://orpc.dev",
7
7
  "repository": {
@@ -27,10 +27,10 @@
27
27
  "dependencies": {
28
28
  "@standard-schema/spec": "^1.1.0",
29
29
  "json-schema-typed": "^8.0.2",
30
- "@orpc/server": "2.0.0-beta.2",
31
- "@orpc/contract": "2.0.0-beta.2",
32
- "@orpc/client": "2.0.0-beta.2",
33
- "@orpc/shared": "2.0.0-beta.2"
30
+ "@orpc/client": "2.0.0-beta.21",
31
+ "@orpc/contract": "2.0.0-beta.21",
32
+ "@orpc/server": "2.0.0-beta.21",
33
+ "@orpc/shared": "2.0.0-beta.21"
34
34
  },
35
35
  "devDependencies": {
36
36
  "zod": "^4.4.3"