@orpc/server 0.1.0 → 0.1.2

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