@orpc/server 0.0.0-unsafe-pr-2-20241118033608

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