@orpc/server 0.10.0-beta.1 → 0.11.0

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.
Files changed (64) hide show
  1. package/dist/{chunk-TDFYNRZV.js → chunk-CVLK2PBB.js} +0 -1
  2. package/dist/fetch.js +1 -2
  3. package/dist/index.js +1 -2
  4. package/dist/src/adapters/fetch.d.ts +0 -1
  5. package/dist/src/builder.d.ts +0 -1
  6. package/dist/src/index.d.ts +0 -1
  7. package/dist/src/middleware.d.ts +0 -1
  8. package/dist/src/procedure-builder.d.ts +0 -1
  9. package/dist/src/procedure-caller.d.ts +0 -1
  10. package/dist/src/procedure-implementer.d.ts +0 -1
  11. package/dist/src/procedure.d.ts +0 -1
  12. package/dist/src/router-builder.d.ts +0 -1
  13. package/dist/src/router-caller.d.ts +0 -1
  14. package/dist/src/router-implementer.d.ts +0 -1
  15. package/dist/src/router.d.ts +0 -1
  16. package/dist/src/types.d.ts +0 -1
  17. package/dist/src/utils.d.ts +0 -1
  18. package/package.json +12 -16
  19. package/dist/chunk-TDFYNRZV.js.map +0 -1
  20. package/dist/fetch.js.map +0 -1
  21. package/dist/index.js.map +0 -1
  22. package/dist/src/adapters/fetch.d.ts.map +0 -1
  23. package/dist/src/builder.d.ts.map +0 -1
  24. package/dist/src/index.d.ts.map +0 -1
  25. package/dist/src/middleware.d.ts.map +0 -1
  26. package/dist/src/procedure-builder.d.ts.map +0 -1
  27. package/dist/src/procedure-caller.d.ts.map +0 -1
  28. package/dist/src/procedure-implementer.d.ts.map +0 -1
  29. package/dist/src/procedure.d.ts.map +0 -1
  30. package/dist/src/router-builder.d.ts.map +0 -1
  31. package/dist/src/router-caller.d.ts.map +0 -1
  32. package/dist/src/router-implementer.d.ts.map +0 -1
  33. package/dist/src/router.d.ts.map +0 -1
  34. package/dist/src/types.d.ts.map +0 -1
  35. package/dist/src/utils.d.ts.map +0 -1
  36. package/dist/tsconfig.tsbuildinfo +0 -1
  37. package/src/adapters/fetch.test.ts +0 -629
  38. package/src/adapters/fetch.ts +0 -290
  39. package/src/builder.test.ts +0 -371
  40. package/src/builder.ts +0 -238
  41. package/src/index.ts +0 -16
  42. package/src/middleware.test.ts +0 -260
  43. package/src/middleware.ts +0 -136
  44. package/src/procedure-builder.test.ts +0 -223
  45. package/src/procedure-builder.ts +0 -158
  46. package/src/procedure-caller.test.ts +0 -171
  47. package/src/procedure-caller.ts +0 -138
  48. package/src/procedure-implementer.test.ts +0 -220
  49. package/src/procedure-implementer.ts +0 -102
  50. package/src/procedure.test.ts +0 -317
  51. package/src/procedure.ts +0 -237
  52. package/src/router-builder.test.ts +0 -106
  53. package/src/router-builder.ts +0 -122
  54. package/src/router-caller.test.ts +0 -126
  55. package/src/router-caller.ts +0 -64
  56. package/src/router-implementer.test.ts +0 -116
  57. package/src/router-implementer.ts +0 -113
  58. package/src/router.test-d.ts +0 -48
  59. package/src/router.test.ts +0 -142
  60. package/src/router.ts +0 -91
  61. package/src/types.test.ts +0 -18
  62. package/src/types.ts +0 -13
  63. package/src/utils.test.ts +0 -16
  64. package/src/utils.ts +0 -16
@@ -1,290 +0,0 @@
1
- /// <reference lib="dom" />
2
-
3
- import type {
4
- PartialOnUndefinedDeep,
5
- Promisable,
6
- Value,
7
- } from '@orpc/shared'
8
- import type { Router } from '../router'
9
- import {
10
- type HTTPPath,
11
- ORPC_HEADER,
12
- ORPC_HEADER_VALUE,
13
- standardizeHTTPPath,
14
- } from '@orpc/contract'
15
- import {
16
- get,
17
- isPlainObject,
18
- mapValues,
19
- trim,
20
- value,
21
- } from '@orpc/shared'
22
- import { ORPCError } from '@orpc/shared/error'
23
- import {
24
- OpenAPIDeserializer,
25
- OpenAPISerializer,
26
- ORPCDeserializer,
27
- ORPCSerializer,
28
- zodCoerce,
29
- } from '@orpc/transformer'
30
- import { LinearRouter } from 'hono/router/linear-router'
31
- import { RegExpRouter } from 'hono/router/reg-exp-router'
32
- import { isProcedure, type WELL_DEFINED_PROCEDURE } from '../procedure'
33
- import { createProcedureCaller } from '../procedure-caller'
34
-
35
- export interface FetchHandlerHooks {
36
- next: () => Promise<Response>
37
- response: (response: Response) => Response
38
- }
39
-
40
- export interface CreateFetchHandlerOptions<TRouter extends Router<any>> {
41
- router: TRouter
42
-
43
- /**
44
- * Hooks for executing logics on lifecycle events.
45
- */
46
- hooks?: (
47
- context: TRouter extends Router<infer UContext> ? UContext : never,
48
- hooks: FetchHandlerHooks,
49
- ) => Promisable<Response>
50
-
51
- /**
52
- * It will help improve the cold start time. But it will increase the performance.
53
- *
54
- * @default false
55
- */
56
- serverless?: boolean
57
- }
58
-
59
- export function createFetchHandler<TRouter extends Router<any>>(
60
- options: CreateFetchHandlerOptions<TRouter>,
61
- ): FetchHandler<TRouter> {
62
- const routing = options.serverless
63
- ? new LinearRouter<[string[], WELL_DEFINED_PROCEDURE]>()
64
- : new RegExpRouter<[string[], WELL_DEFINED_PROCEDURE]>()
65
-
66
- const addRouteRecursively = (router: Router<any>, basePath: string[]) => {
67
- for (const key in router) {
68
- const currentPath = [...basePath, key]
69
- const item = router[key] as WELL_DEFINED_PROCEDURE | Router<any>
70
-
71
- if (isProcedure(item)) {
72
- if (item.zz$p.contract.zz$cp.path) {
73
- const method = item.zz$p.contract.zz$cp.method ?? 'POST'
74
- const path = openAPIPathToRouterPath(item.zz$p.contract.zz$cp.path)
75
-
76
- routing.add(method, path, [currentPath, item])
77
- }
78
- }
79
- else {
80
- addRouteRecursively(item, currentPath)
81
- }
82
- }
83
- }
84
-
85
- addRouteRecursively(options.router, [])
86
-
87
- return async (requestOptions) => {
88
- const isORPCTransformer
89
- = requestOptions.request.headers.get(ORPC_HEADER) === ORPC_HEADER_VALUE
90
- const accept = requestOptions.request.headers.get('Accept') || undefined
91
-
92
- const serializer = isORPCTransformer
93
- ? new ORPCSerializer()
94
- : new OpenAPISerializer({ accept })
95
-
96
- const context = await value(requestOptions.context)
97
-
98
- const handler = async () => {
99
- const url = new URL(requestOptions.request.url)
100
- const pathname = `/${trim(url.pathname.replace(requestOptions.prefix ?? '', ''), '/')}`
101
-
102
- let path: string[] | undefined
103
- let procedure: WELL_DEFINED_PROCEDURE | undefined
104
- let params: Record<string, string> | undefined
105
-
106
- if (isORPCTransformer) {
107
- path = trim(pathname, '/').split('/').map(decodeURIComponent)
108
- const val = get(options.router, path)
109
-
110
- if (isProcedure(val)) {
111
- procedure = val
112
- }
113
- }
114
- else {
115
- const customMethod
116
- = requestOptions.request.method === 'POST'
117
- ? url.searchParams.get('method')?.toUpperCase()
118
- : undefined
119
- const method = customMethod || requestOptions.request.method
120
-
121
- const [matches, params_] = routing.match(method, pathname)
122
-
123
- const [match] = matches.sort((a, b) => {
124
- return Object.keys(a[1]).length - Object.keys(b[1]).length
125
- })
126
-
127
- if (match) {
128
- path = match[0][0]
129
- procedure = match[0][1]
130
-
131
- if (params_) {
132
- params = mapValues(
133
- (match as any)[1]!,
134
- v => params_[v as number]!,
135
- )
136
- }
137
- else {
138
- params = match[1] as Record<string, string>
139
- }
140
- }
141
-
142
- if (!path || !procedure) {
143
- path = trim(pathname, '/').split('/').map(decodeURIComponent)
144
-
145
- const val = get(options.router, path)
146
-
147
- if (isProcedure(val)) {
148
- procedure = val
149
- }
150
- }
151
- }
152
-
153
- if (!path || !procedure) {
154
- throw new ORPCError({ code: 'NOT_FOUND', message: 'Not found' })
155
- }
156
-
157
- const deserializer = isORPCTransformer
158
- ? new ORPCDeserializer()
159
- : new OpenAPIDeserializer({
160
- schema: procedure.zz$p.contract.zz$cp.InputSchema,
161
- })
162
-
163
- const input_ = await (async () => {
164
- try {
165
- return await deserializer.deserialize(requestOptions.request)
166
- }
167
- catch (e) {
168
- throw new ORPCError({
169
- code: 'BAD_REQUEST',
170
- message:
171
- 'Cannot parse request. Please check the request body and Content-Type header.',
172
- cause: e,
173
- })
174
- }
175
- })()
176
-
177
- const input = (() => {
178
- if (!params || Object.keys(params).length === 0) {
179
- return input_
180
- }
181
-
182
- const coercedParams = procedure.zz$p.contract.zz$cp.InputSchema
183
- ? (zodCoerce(
184
- procedure.zz$p.contract.zz$cp.InputSchema,
185
- { ...params },
186
- {
187
- bracketNotation: true,
188
- },
189
- ) as object)
190
- : params
191
-
192
- if (!isPlainObject(input_)) {
193
- return coercedParams
194
- }
195
-
196
- return {
197
- ...coercedParams,
198
- ...input_,
199
- }
200
- })()
201
-
202
- const caller = createProcedureCaller({
203
- context,
204
- procedure,
205
- path,
206
- })
207
-
208
- const output = await caller(input)
209
-
210
- const { body, headers } = serializer.serialize(output)
211
-
212
- return new Response(body, {
213
- status: 200,
214
- headers,
215
- })
216
- }
217
-
218
- try {
219
- return await options.hooks?.(context as any, {
220
- next: handler,
221
- response: response => response,
222
- }) ?? await handler()
223
- }
224
- catch (e) {
225
- const error = toORPCError(e)
226
-
227
- try {
228
- const { body, headers } = serializer.serialize(error.toJSON())
229
-
230
- return new Response(body, {
231
- status: error.status,
232
- headers,
233
- })
234
- }
235
- catch (e) {
236
- const error = toORPCError(e)
237
-
238
- // fallback to OpenAPI serializer (without accept) when expected serializer has failed
239
- const { body, headers } = new OpenAPISerializer().serialize(
240
- error.toJSON(),
241
- )
242
-
243
- return new Response(body, {
244
- status: error.status,
245
- headers,
246
- })
247
- }
248
- }
249
- }
250
- }
251
-
252
- function openAPIPathToRouterPath(path: HTTPPath): string {
253
- return standardizeHTTPPath(path).replace(/\{([^}]+)\}/g, ':$1')
254
- }
255
-
256
- export type FetchHandlerOptions<TRouter extends Router<any>> = {
257
- /**
258
- * The request need to be handled.
259
- */
260
- request: Request
261
-
262
- /**
263
- * Remove the prefix from the request path.
264
- *
265
- * @example /orpc
266
- * @example /api
267
- */
268
- prefix?: string
269
- } & PartialOnUndefinedDeep<{
270
- /**
271
- * The context used to handle the request.
272
- */
273
- context: Value<
274
- TRouter extends Router<infer UContext> ? UContext : never
275
- >
276
- }>
277
-
278
- export interface FetchHandler<TRouter extends Router<any>> {
279
- (options: FetchHandlerOptions<TRouter>): Promise<Response>
280
- }
281
-
282
- function toORPCError(e: unknown): ORPCError<any, any> {
283
- return e instanceof ORPCError
284
- ? e
285
- : new ORPCError({
286
- code: 'INTERNAL_SERVER_ERROR',
287
- message: 'Internal server error',
288
- cause: e,
289
- })
290
- }
@@ -1,371 +0,0 @@
1
- import type {
2
- Builder,
3
- DecoratedMiddleware,
4
- DecoratedProcedure,
5
- Meta,
6
- MiddlewareMeta,
7
- } from '.'
8
- import { oc } from '@orpc/contract'
9
- import { z } from 'zod'
10
- import {
11
- isProcedure,
12
- os,
13
- ProcedureBuilder,
14
- ProcedureImplementer,
15
- RouterImplementer,
16
- } from '.'
17
- import { RouterBuilder } from './router-builder'
18
-
19
- it('context method', () => {
20
- expectTypeOf<
21
- typeof os extends Builder<infer TContext, any> ? TContext : never
22
- >().toEqualTypeOf<undefined | Record<string, unknown>>()
23
-
24
- const os2 = os.context<{ foo: 'bar' }>()
25
-
26
- expectTypeOf<
27
- typeof os2 extends Builder<infer TContext, any> ? TContext : never
28
- >().toEqualTypeOf<{ foo: 'bar' }>()
29
-
30
- const os3 = os.context<{ foo: 'bar' }>().context()
31
-
32
- expectTypeOf<
33
- typeof os3 extends Builder<infer TContext, any> ? TContext : never
34
- >().toEqualTypeOf<{ foo: 'bar' }>()
35
- })
36
-
37
- describe('use middleware', () => {
38
- type Context = { auth: boolean }
39
-
40
- const osw = os.context<Context>()
41
-
42
- it('infer types', () => {
43
- osw.use((input, context, meta) => {
44
- expectTypeOf(input).toEqualTypeOf<unknown>()
45
- expectTypeOf(context).toEqualTypeOf<Context>()
46
- expectTypeOf(meta).toEqualTypeOf<MiddlewareMeta<unknown>>()
47
-
48
- return meta.next({})
49
- })
50
- })
51
-
52
- it('can map context', () => {
53
- osw
54
- .use((_, __, meta) => {
55
- return meta.next({ context: { userId: '1' } })
56
- })
57
- .use((_, context, meta) => {
58
- expectTypeOf(context).toMatchTypeOf<Context & { userId: string }>()
59
-
60
- return meta.next({})
61
- })
62
- })
63
-
64
- it('can map input', () => {
65
- osw
66
- // @ts-expect-error mismatch input
67
- .use((input: { postId: string }) => {})
68
- .use(
69
- (input: { postId: string }, _, meta) => {
70
- return meta.next({ context: { user: '1' } })
71
- },
72
- (input) => {
73
- expectTypeOf(input).toEqualTypeOf<unknown>()
74
- return { postId: '1' }
75
- },
76
- )
77
- .func((_, context) => {
78
- expectTypeOf(context).toMatchTypeOf<{ user: string }>()
79
- })
80
- })
81
- })
82
-
83
- describe('create middleware', () => {
84
- it('infer types', () => {
85
- const mid = os
86
- .context<{ auth: boolean }>()
87
- .middleware((input, context, meta) => {
88
- expectTypeOf(input).toEqualTypeOf<unknown>()
89
- expectTypeOf(context).toEqualTypeOf<{ auth: boolean }>()
90
- expectTypeOf(meta).toEqualTypeOf<MiddlewareMeta<any>>()
91
-
92
- return meta.next({ })
93
- })
94
-
95
- expectTypeOf(mid).toEqualTypeOf<
96
- DecoratedMiddleware<{ auth: boolean }, undefined, unknown, any>
97
- >()
98
- })
99
-
100
- it('map context', () => {
101
- const mid = os.context<{ auth: boolean }>().middleware((_, __, meta) => {
102
- return meta.next({ context: { userId: '1' } })
103
- })
104
-
105
- expectTypeOf(mid).toEqualTypeOf<
106
- DecoratedMiddleware<
107
- { auth: boolean },
108
- { userId: string },
109
- unknown,
110
- any
111
- >
112
- >()
113
- })
114
- })
115
-
116
- it('router method', () => {
117
- const pingContract = oc.input(z.string()).output(z.string())
118
- const userFindContract = oc
119
- .input(z.object({ id: z.string() }))
120
- .output(z.object({ name: z.string() }))
121
-
122
- const contract = oc.router({
123
- ping: pingContract,
124
- user: {
125
- find: userFindContract,
126
- },
127
-
128
- user2: oc.router({
129
- find: userFindContract,
130
- }),
131
-
132
- router: userFindContract,
133
- })
134
-
135
- const osw = os.contract(contract)
136
-
137
- expect(osw.ping).instanceOf(ProcedureImplementer)
138
- expect(osw.ping.zz$pi.contract).toEqual(pingContract)
139
-
140
- expect(osw.user).instanceOf(RouterImplementer)
141
-
142
- expect(osw.user.find).instanceOf(ProcedureImplementer)
143
- expect(osw.user.find.zz$pi.contract).toEqual(userFindContract)
144
-
145
- // Because of the router keyword is special, we can't use instanceof
146
- expect(osw.router.zz$pi.contract).toEqual(userFindContract)
147
- expect(
148
- osw.router.func(() => {
149
- return { name: '' }
150
- }),
151
- ).toSatisfy(isProcedure)
152
- })
153
-
154
- describe('define procedure builder', () => {
155
- const osw = os.context<{ auth: boolean }>()
156
- const schema1 = z.object({})
157
- const example1 = {}
158
- const schema2 = z.object({ a: z.string() })
159
- const example2 = { a: '' }
160
-
161
- it('input method', () => {
162
- const builder = osw.input(schema1, example1)
163
-
164
- expectTypeOf(builder).toEqualTypeOf<
165
- ProcedureBuilder<{ auth: boolean }, undefined, typeof schema1, undefined>
166
- >()
167
-
168
- expect(builder).instanceOf(ProcedureBuilder)
169
- expect(builder.zz$pb.middlewares).toBe(undefined)
170
- expect(builder.zz$pb).toMatchObject({
171
- contract: {
172
- zz$cp: {
173
- InputSchema: schema1,
174
- inputExample: example1,
175
- },
176
- },
177
- })
178
- })
179
-
180
- it('output method', () => {
181
- const builder = osw.output(schema2, example2)
182
-
183
- expectTypeOf(builder).toEqualTypeOf<
184
- ProcedureBuilder<{ auth: boolean }, undefined, undefined, typeof schema2>
185
- >()
186
-
187
- expect(builder).instanceOf(ProcedureBuilder)
188
- expect(builder.zz$pb.middlewares).toBe(undefined)
189
- expect(builder.zz$pb).toMatchObject({
190
- contract: {
191
- zz$cp: {
192
- OutputSchema: schema2,
193
- outputExample: example2,
194
- },
195
- },
196
- })
197
- })
198
-
199
- it('route method', () => {
200
- const builder = osw.route({
201
- method: 'GET',
202
- path: '/test',
203
- deprecated: true,
204
- description: 'des',
205
- summary: 'sum',
206
- tags: ['cccc'],
207
- })
208
-
209
- expectTypeOf(builder).toEqualTypeOf<
210
- ProcedureBuilder<{ auth: boolean }, undefined, undefined, undefined>
211
- >()
212
-
213
- expect(builder).instanceOf(ProcedureBuilder)
214
- expect(builder.zz$pb.middlewares).toBe(undefined)
215
- expect(builder.zz$pb).toMatchObject({
216
- contract: {
217
- zz$cp: {
218
- method: 'GET',
219
- path: '/test',
220
- deprecated: true,
221
- description: 'des',
222
- summary: 'sum',
223
- tags: ['cccc'],
224
- },
225
- },
226
- })
227
- })
228
-
229
- it('with middlewares', () => {
230
- const mid = os.middleware((_, __, meta) => {
231
- return meta.next({
232
- context: {
233
- userId: 'string',
234
- },
235
- })
236
- })
237
-
238
- const mid2 = os.middleware((_, __, meta) => {
239
- return meta.next({
240
- context: {
241
- mid2: true,
242
- },
243
- })
244
- })
245
-
246
- const osw = os.context<{ auth: boolean }>().use(mid).use(mid2)
247
-
248
- const builder1 = osw.input(schema1)
249
- const builder2 = osw.output(schema2)
250
- const builder3 = osw.route({ method: 'GET', path: '/test' })
251
-
252
- expectTypeOf(builder1).toEqualTypeOf<
253
- ProcedureBuilder<
254
- { auth: boolean },
255
- { userId: string } & { mid2: boolean },
256
- typeof schema1,
257
- undefined
258
- >
259
- >()
260
-
261
- expectTypeOf(builder2).toEqualTypeOf<
262
- ProcedureBuilder<
263
- { auth: boolean },
264
- { userId: string } & { mid2: boolean },
265
- undefined,
266
- typeof schema2
267
- >
268
- >()
269
-
270
- expectTypeOf(builder3).toEqualTypeOf<
271
- ProcedureBuilder<
272
- { auth: boolean },
273
- { userId: string } & { mid2: boolean },
274
- undefined,
275
- undefined
276
- >
277
- >()
278
-
279
- expect(builder1.zz$pb.middlewares).toEqual([mid, mid2])
280
- expect(builder2.zz$pb.middlewares).toEqual([mid, mid2])
281
- expect(builder3.zz$pb.middlewares).toEqual([mid, mid2])
282
- })
283
- })
284
-
285
- describe('handler method', () => {
286
- it('without middlewares', () => {
287
- const osw = os.context<{ auth: boolean }>()
288
-
289
- const procedure = osw.func((input, context, meta) => {
290
- expectTypeOf(input).toEqualTypeOf<unknown>()
291
- expectTypeOf(context).toEqualTypeOf<{ auth: boolean }>()
292
- expectTypeOf(meta).toEqualTypeOf<Meta>()
293
- })
294
-
295
- expectTypeOf(procedure).toEqualTypeOf<
296
- DecoratedProcedure<
297
- { auth: boolean },
298
- undefined,
299
- undefined,
300
- undefined,
301
- void
302
- >
303
- >()
304
-
305
- expect(isProcedure(procedure)).toBe(true)
306
- expect(procedure.zz$p.middlewares).toBe(undefined)
307
- })
308
-
309
- it('with middlewares', () => {
310
- const mid = os.middleware((_, __, meta) => {
311
- return meta.next({
312
- context: {
313
- userId: 'string',
314
- },
315
- })
316
- })
317
-
318
- const osw = os.context<{ auth: boolean }>().use(mid)
319
-
320
- const procedure = osw.func((input, context, meta) => {
321
- expectTypeOf(input).toEqualTypeOf<unknown>()
322
- expectTypeOf(context).toMatchTypeOf<{ auth: boolean }>()
323
- expectTypeOf(meta).toEqualTypeOf<Meta>()
324
- })
325
-
326
- expectTypeOf(procedure).toEqualTypeOf<
327
- DecoratedProcedure<
328
- { auth: boolean },
329
- { userId: string },
330
- undefined,
331
- undefined,
332
- void
333
- >
334
- >()
335
-
336
- expect(isProcedure(procedure)).toBe(true)
337
- expect(procedure.zz$p.middlewares).toEqual([mid])
338
- })
339
- })
340
-
341
- it('prefix', () => {
342
- const builder = os
343
- .context<{ auth: boolean }>()
344
- .use((_, __, meta) => {
345
- return meta.next({ context: { userId: '1' } })
346
- })
347
- .prefix('/api')
348
-
349
- expectTypeOf(builder).toEqualTypeOf<
350
- RouterBuilder<{ auth: boolean }, { userId: string }>
351
- >()
352
-
353
- expect(builder).instanceOf(RouterBuilder)
354
- expect(builder.zz$rb.prefix).toEqual('/api')
355
- })
356
-
357
- it('tags', () => {
358
- const builder = os
359
- .context<{ auth: boolean }>()
360
- .use((_, __, meta) => {
361
- return meta.next({ context: { userId: '1' } })
362
- })
363
- .tags('user', 'user2')
364
-
365
- expectTypeOf(builder).toEqualTypeOf<
366
- RouterBuilder<{ auth: boolean }, { userId: string }>
367
- >()
368
-
369
- expect(builder).instanceOf(RouterBuilder)
370
- expect(builder.zz$rb.tags).toEqual(['user', 'user2'])
371
- })