@orpc/server 0.1.0 → 0.1.2

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 (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