orpc-fn 0.1.0 → 0.2.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 (141) hide show
  1. package/README.md +48 -32
  2. package/dist/assemble-procedure.d.ts +13 -0
  3. package/dist/assemble-procedure.d.ts.map +1 -0
  4. package/dist/assemble-procedure.js +29 -0
  5. package/dist/assemble-procedure.js.map +1 -0
  6. package/dist/bound-call.d.ts +1 -1
  7. package/dist/bound-call.d.ts.map +1 -1
  8. package/dist/bound-call.js.map +1 -1
  9. package/dist/client.d.ts +4 -2
  10. package/dist/client.d.ts.map +1 -1
  11. package/dist/client.js +4 -2
  12. package/dist/client.js.map +1 -1
  13. package/dist/compatibility.d.ts +16 -0
  14. package/dist/compatibility.d.ts.map +1 -0
  15. package/dist/compatibility.js +4 -0
  16. package/dist/compatibility.js.map +1 -0
  17. package/dist/create-fn.d.ts +30 -34
  18. package/dist/create-fn.d.ts.map +1 -1
  19. package/dist/create-fn.js +177 -121
  20. package/dist/create-fn.js.map +1 -1
  21. package/dist/expo.d.ts +11 -25
  22. package/dist/expo.d.ts.map +1 -1
  23. package/dist/expo.js +25 -30
  24. package/dist/expo.js.map +1 -1
  25. package/dist/hono.d.ts +12 -4
  26. package/dist/hono.d.ts.map +1 -1
  27. package/dist/hono.js +29 -12
  28. package/dist/hono.js.map +1 -1
  29. package/dist/index.d.ts +2 -2
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +2 -2
  32. package/dist/index.js.map +1 -1
  33. package/dist/json-schema.d.ts +2 -0
  34. package/dist/json-schema.d.ts.map +1 -1
  35. package/dist/json-schema.js +11 -0
  36. package/dist/json-schema.js.map +1 -1
  37. package/dist/live/codec.d.ts +6 -1
  38. package/dist/live/codec.d.ts.map +1 -1
  39. package/dist/live/codec.js +14 -3
  40. package/dist/live/codec.js.map +1 -1
  41. package/dist/live/fn-live.d.ts +63 -22
  42. package/dist/live/fn-live.d.ts.map +1 -1
  43. package/dist/live/fn-live.js +98 -16
  44. package/dist/live/fn-live.js.map +1 -1
  45. package/dist/live/index.d.ts +1 -1
  46. package/dist/live/index.d.ts.map +1 -1
  47. package/dist/live/index.js.map +1 -1
  48. package/dist/live/ioredis.d.ts +4 -1
  49. package/dist/live/ioredis.d.ts.map +1 -1
  50. package/dist/live/ioredis.js +21 -2
  51. package/dist/live/ioredis.js.map +1 -1
  52. package/dist/live/lifecycle.d.ts +7 -0
  53. package/dist/live/lifecycle.d.ts.map +1 -0
  54. package/dist/live/lifecycle.js +36 -0
  55. package/dist/live/lifecycle.js.map +1 -0
  56. package/dist/live/memory.d.ts.map +1 -1
  57. package/dist/live/memory.js +4 -0
  58. package/dist/live/memory.js.map +1 -1
  59. package/dist/live/pub-sub.d.ts +76 -12
  60. package/dist/live/pub-sub.d.ts.map +1 -1
  61. package/dist/live/pub-sub.js +429 -125
  62. package/dist/live/pub-sub.js.map +1 -1
  63. package/dist/live/redis-bun.d.ts +1 -1
  64. package/dist/live/redis-bun.d.ts.map +1 -1
  65. package/dist/live/redis-bun.js +3 -5
  66. package/dist/live/redis-bun.js.map +1 -1
  67. package/dist/live/redis.d.ts +2 -2
  68. package/dist/live/redis.d.ts.map +1 -1
  69. package/dist/live/redis.js +36 -9
  70. package/dist/live/redis.js.map +1 -1
  71. package/dist/live/transport.d.ts +5 -1
  72. package/dist/live/transport.d.ts.map +1 -1
  73. package/dist/live/transport.js.map +1 -1
  74. package/dist/logger.d.ts +2 -0
  75. package/dist/logger.d.ts.map +1 -1
  76. package/dist/logger.js +21 -0
  77. package/dist/logger.js.map +1 -1
  78. package/dist/mastra.d.ts.map +1 -1
  79. package/dist/mastra.js +16 -7
  80. package/dist/mastra.js.map +1 -1
  81. package/dist/mcp/sdk.d.ts +20 -0
  82. package/dist/mcp/sdk.d.ts.map +1 -0
  83. package/dist/mcp/sdk.js +82 -0
  84. package/dist/mcp/sdk.js.map +1 -0
  85. package/dist/mcp.d.ts +3 -2
  86. package/dist/mcp.d.ts.map +1 -1
  87. package/dist/mcp.js +22 -7
  88. package/dist/mcp.js.map +1 -1
  89. package/dist/meta.d.ts +15 -7
  90. package/dist/meta.d.ts.map +1 -1
  91. package/dist/meta.js +9 -2
  92. package/dist/meta.js.map +1 -1
  93. package/dist/otel.d.ts.map +1 -1
  94. package/dist/otel.js +77 -6
  95. package/dist/otel.js.map +1 -1
  96. package/dist/route-options.d.ts +22 -0
  97. package/dist/route-options.d.ts.map +1 -0
  98. package/dist/route-options.js +67 -0
  99. package/dist/route-options.js.map +1 -0
  100. package/dist/router.d.ts +5 -1
  101. package/dist/router.d.ts.map +1 -1
  102. package/dist/router.js +22 -0
  103. package/dist/router.js.map +1 -1
  104. package/dist/timing.d.ts +8 -0
  105. package/dist/timing.d.ts.map +1 -0
  106. package/dist/timing.js +4 -0
  107. package/dist/timing.js.map +1 -0
  108. package/dist/types.d.ts +86 -41
  109. package/dist/types.d.ts.map +1 -1
  110. package/dist/types.js +6 -2
  111. package/dist/types.js.map +1 -1
  112. package/package.json +15 -3
  113. package/src/assemble-procedure.ts +42 -0
  114. package/src/bound-call.ts +4 -3
  115. package/src/client.ts +9 -2
  116. package/src/compatibility.ts +39 -0
  117. package/src/create-fn.ts +297 -183
  118. package/src/expo.ts +49 -44
  119. package/src/hono.ts +36 -17
  120. package/src/index.ts +6 -2
  121. package/src/json-schema.ts +16 -0
  122. package/src/live/codec.ts +22 -3
  123. package/src/live/fn-live.ts +244 -69
  124. package/src/live/index.ts +2 -0
  125. package/src/live/ioredis.ts +22 -2
  126. package/src/live/lifecycle.ts +44 -0
  127. package/src/live/memory.ts +4 -0
  128. package/src/live/pub-sub.ts +608 -166
  129. package/src/live/redis-bun.ts +7 -5
  130. package/src/live/redis.ts +37 -11
  131. package/src/live/transport.ts +4 -1
  132. package/src/logger.ts +20 -0
  133. package/src/mastra.ts +15 -7
  134. package/src/mcp/sdk.ts +118 -0
  135. package/src/mcp.ts +48 -20
  136. package/src/meta.ts +40 -24
  137. package/src/otel.ts +77 -10
  138. package/src/route-options.ts +100 -0
  139. package/src/router.ts +39 -1
  140. package/src/timing.ts +5 -0
  141. package/src/types.ts +204 -62
@@ -0,0 +1,100 @@
1
+ import {
2
+ type AnySchema,
3
+ type ErrorMap,
4
+ getEventIteratorSchemaDetails
5
+ } from '@orpc/contract'
6
+ import { ROUTE_OPTION_KEYS } from './types.js'
7
+
8
+ type Resolver = (params: {
9
+ input: unknown
10
+ context: unknown
11
+ signal: AbortSignal | undefined
12
+ }) => unknown
13
+ function schemaOption(schema: unknown, name: string): AnySchema | undefined {
14
+ if (schema === undefined) return undefined
15
+ if (
16
+ typeof schema !== 'object' ||
17
+ schema === null ||
18
+ !('~standard' in schema) ||
19
+ typeof (schema as AnySchema)['~standard']?.validate !== 'function'
20
+ )
21
+ throw new TypeError(`orpc-fn: ${name} must be a Standard Schema`)
22
+ return schema as AnySchema
23
+ }
24
+
25
+ /** Runtime validation for JavaScript callers and spread route options. */
26
+ export function normalizeRoute(
27
+ options: Record<string, unknown>,
28
+ isGuard: (key: string) => boolean
29
+ ) {
30
+ const {
31
+ handler,
32
+ input: rawInput,
33
+ output: rawOutput,
34
+ name,
35
+ procedure,
36
+ meta: appMeta = {},
37
+ stream,
38
+ errors,
39
+ guardResolvers = {},
40
+ ...rest
41
+ } = options
42
+ if (typeof name !== 'string' || !name || typeof handler !== 'function')
43
+ throw new TypeError('orpc-fn: routes require a name and handler')
44
+ if (procedure !== undefined && typeof procedure !== 'string')
45
+ throw new TypeError('orpc-fn: procedure must be a configured key')
46
+ if (typeof appMeta !== 'object' || appMeta === null || Array.isArray(appMeta))
47
+ throw new TypeError('orpc-fn: meta must be an object')
48
+ if (stream !== undefined && typeof stream !== 'boolean')
49
+ throw new TypeError('orpc-fn: stream must be boolean')
50
+ if (
51
+ typeof guardResolvers !== 'object' ||
52
+ guardResolvers === null ||
53
+ Array.isArray(guardResolvers)
54
+ )
55
+ throw new TypeError('orpc-fn: guardResolvers must be an object')
56
+ const input = schemaOption(rawInput, 'input')
57
+ const output = schemaOption(rawOutput, 'output')
58
+ const route: Record<string, unknown> = {}
59
+ const guardChecks: Array<[string, unknown]> = []
60
+ for (const [option, value] of Object.entries(rest)) {
61
+ if ((ROUTE_OPTION_KEYS as readonly string[]).includes(option))
62
+ route[option] = value
63
+ else if (isGuard(option)) {
64
+ if (value !== undefined) guardChecks.push([option, value])
65
+ } else
66
+ throw new TypeError(
67
+ `orpc-fn: unknown route option "${option}"; put application metadata in meta`
68
+ )
69
+ }
70
+ const iterator = getEventIteratorSchemaDetails(output) !== undefined
71
+ if (stream === false && iterator)
72
+ throw new TypeError(
73
+ 'orpc-fn: an event iterator schema cannot declare stream: false'
74
+ )
75
+ for (const [guard, resolver] of Object.entries(guardResolvers)) {
76
+ if (!isGuard(guard))
77
+ throw new TypeError(`orpc-fn: unknown guard resolver "${guard}"`)
78
+ if (typeof resolver !== 'function')
79
+ throw new TypeError(
80
+ `orpc-fn: guard resolver "${guard}" must be a function`
81
+ )
82
+ if (guardChecks.some(([key]) => key === guard))
83
+ throw new TypeError(
84
+ `orpc-fn: guard "${guard}" has both a value and resolver`
85
+ )
86
+ }
87
+ return {
88
+ handler: handler as (params: Record<PropertyKey, unknown>) => unknown,
89
+ input,
90
+ output,
91
+ name,
92
+ procedure,
93
+ meta: appMeta as Record<string, unknown>,
94
+ stream: stream === true || iterator,
95
+ errors: errors as ErrorMap | undefined,
96
+ guardResolvers: guardResolvers as Record<string, Resolver>,
97
+ route,
98
+ guardChecks
99
+ }
100
+ }
package/src/router.ts CHANGED
@@ -1,4 +1,10 @@
1
- import type { AnyRouter } from '@orpc/server'
1
+ import {
2
+ type AnyRouter,
3
+ type AnyProcedure,
4
+ resolveContractProcedures,
5
+ traverseContractProcedures
6
+ } from '@orpc/server'
7
+ import { readFnMeta } from './meta.js'
2
8
 
3
9
  /**
4
10
  * Creates a router object from routes or nested routers.
@@ -13,3 +19,35 @@ import type { AnyRouter } from '@orpc/server'
13
19
  export function createRouter<T extends AnyRouter>(routes: T): Readonly<T> {
14
20
  return routes
15
21
  }
22
+
23
+ /** Serialize these paths to a frontend module; this helper belongs on the server. */
24
+ export function createStreamManifest(
25
+ router: AnyRouter
26
+ ): readonly (readonly string[])[] {
27
+ const paths: string[][] = []
28
+ const unresolved = traverseContractProcedures(
29
+ { router, path: [] },
30
+ ({ contract, path }) => {
31
+ if (readFnMeta(contract as AnyProcedure).stream) paths.push([...path])
32
+ }
33
+ )
34
+ if (unresolved.length)
35
+ throw new TypeError(
36
+ 'orpc-fn: lazy routers require createStreamManifestAsync'
37
+ )
38
+ return paths
39
+ }
40
+
41
+ /** Resolve lazy routers before generating the same serializable path manifest. */
42
+ export async function createStreamManifestAsync(
43
+ router: AnyRouter
44
+ ): Promise<readonly (readonly string[])[]> {
45
+ const paths: string[][] = []
46
+ await resolveContractProcedures(
47
+ { router, path: [] },
48
+ ({ contract, path }) => {
49
+ if (readFnMeta(contract as AnyProcedure).stream) paths.push([...path])
50
+ }
51
+ )
52
+ return paths
53
+ }
package/src/timing.ts ADDED
@@ -0,0 +1,5 @@
1
+ /** Internal bridge shared by the execution middleware and HTTP adapters. */
2
+ export const REQUEST_TIMING = Symbol('orpc-fn.requestTiming')
3
+ export type InvocationTiming = { procedureMs: number; calls: number }
4
+
5
+ export const RAW_INPUT = Symbol('orpc-fn.rawInput')
package/src/types.ts CHANGED
@@ -3,7 +3,7 @@ import type {
3
3
  ErrorMap,
4
4
  InferSchemaInput,
5
5
  InferSchemaOutput,
6
- Meta,
6
+ MergedErrorMap,
7
7
  Schema
8
8
  } from '@orpc/contract'
9
9
  import type {
@@ -12,7 +12,9 @@ import type {
12
12
  Procedure,
13
13
  Route
14
14
  } from '@orpc/server'
15
- import type { ZodType, z } from 'zod'
15
+ import type { createORPCErrorConstructorMap } from '@orpc/server'
16
+ import type { StoredFnMeta } from './meta.js'
17
+ import type { NativeErrors, NativeMeta } from './compatibility.js'
16
18
  import type { BoundCall } from './bound-call.js'
17
19
  import type { FnLogger } from './logger.js'
18
20
  import type { SpanLike } from './otel.js'
@@ -93,7 +95,7 @@ export const ROUTE_OPTION_KEYS = [
93
95
  ] as const satisfies readonly (keyof Route)[]
94
96
 
95
97
  /**
96
- * `fn()` option names owned by the route itself. Guard and meta keys must not
98
+ * `fn()` option names owned by the route itself. Guard keys must not
97
99
  * reuse them: such an option would be read as the route option and the guard
98
100
  * would never run.
99
101
  */
@@ -105,12 +107,16 @@ export const RESERVED_OPTION_KEYS = [
105
107
  'output',
106
108
  'handler',
107
109
  'live',
108
- 'operationId'
110
+ 'operationId',
111
+ 'meta',
112
+ 'stream',
113
+ 'errors',
114
+ 'guardResolvers'
109
115
  ] as const
110
116
 
111
117
  export type ReservedOptionKey = (typeof RESERVED_OPTION_KEYS)[number]
112
118
 
113
- /** @internal Builds one route from a `procedures` builder; shared by `fn` and `createPubSub`. */
119
+ /** @internal Builds one route from a selected native procedure builder. */
114
120
  export type BuildProcedure = (options: {
115
121
  name: string
116
122
  procedure: string | undefined
@@ -118,10 +124,14 @@ export type BuildProcedure = (options: {
118
124
  meta: Record<string, unknown>
119
125
  input?: AnySchema | undefined
120
126
  output?: AnySchema | undefined
127
+ stream?: boolean
128
+ errors?: ErrorMap
121
129
  handler: (options: {
122
130
  input: unknown
123
- context: Record<string, unknown>
131
+ context: Record<string | symbol, unknown>
124
132
  signal?: AbortSignal
133
+ lastEventId?: string
134
+ errors: ReturnType<typeof createORPCErrorConstructorMap>
125
135
  }) => unknown
126
136
  }) => AnyProcedure
127
137
 
@@ -134,11 +144,25 @@ export type RejectKeys<T, K extends PropertyKey, TMessage extends string> = {
134
144
  * Handler extras must have known keys: an index signature would claim every
135
145
  * handler param. Intersected into `createFn`'s options to reject it.
136
146
  */
137
- export type FiniteExtras<T> = string extends keyof T
138
- ? { extras: 'orpc-fn: extras need known keys, not an index signature' }
139
- : number extends keyof T
147
+ export type FiniteExtras<T> = T extends unknown
148
+ ? string extends keyof T
140
149
  ? { extras: 'orpc-fn: extras need known keys, not an index signature' }
141
- : unknown
150
+ : number extends keyof T
151
+ ? { extras: 'orpc-fn: extras need known keys, not an index signature' }
152
+ : Extract<
153
+ keyof T,
154
+ | 'input'
155
+ | 'context'
156
+ | 'call'
157
+ | 'signal'
158
+ | 'span'
159
+ | 'logger'
160
+ | 'errors'
161
+ | 'lastEventId'
162
+ > extends never
163
+ ? unknown
164
+ : { extras: 'orpc-fn: extras cannot shadow handler parameters' }
165
+ : never
142
166
 
143
167
  type ProcedureOf<TBuilder> = TBuilder extends {
144
168
  handler(handler: never): infer TProcedure
@@ -162,16 +186,8 @@ export type CurrentContextOf<TBuilder> =
162
186
  ? TContext
163
187
  : never
164
188
 
165
- type ErrorMapOf<TBuilder> =
166
- ProcedureOf<TBuilder> extends {
167
- '~orpc': { errorMap: infer T extends ErrorMap }
168
- }
169
- ? T
170
- : ErrorMap
171
- type MetaOf<TBuilder> =
172
- ProcedureOf<TBuilder> extends { '~orpc': { meta: infer T extends Meta } }
173
- ? T
174
- : Meta
189
+ export type ErrorMapOf<TBuilder> = NativeErrors<ProcedureOf<TBuilder>>
190
+ type MetaOf<TBuilder> = NativeMeta<ProcedureOf<TBuilder>>
175
191
 
176
192
  /**
177
193
  * Everything `createFn` inferred from its options. Route types read from it;
@@ -181,6 +197,7 @@ export type FnDefinition = {
181
197
  procedures: Record<string, BuilderLike>
182
198
  default: string
183
199
  extras: object
200
+ extrasByProcedure?: Partial<Record<string, object>>
184
201
  guards: object
185
202
  meta: object
186
203
  tag: string
@@ -218,7 +235,12 @@ export type GuardParams<TContext, TMeta> = {
218
235
  * `fn({...})`; the guard runs only on routes that set it. Throw (usually an
219
236
  * `ORPCError`) to refuse the call.
220
237
  */
221
- export type Guard<TValue, TParams> = (value: TValue, params: TParams) => unknown
238
+ /* biome-ignore-start lint/suspicious/noConfusingVoidType: union prevents accidental value returns while accepting assertion-style guards */
239
+ export type Guard<TValue, TParams> = (
240
+ value: TValue,
241
+ params: TParams
242
+ ) => MaybePromise<void | boolean>
243
+ /* biome-ignore-end lint/suspicious/noConfusingVoidType: assertion-style guards */
222
244
 
223
245
  export type GuardOptions<TGuards> = {
224
246
  [K in keyof TGuards]?: TGuards[K] extends (
@@ -229,13 +251,35 @@ export type GuardOptions<TGuards> = {
229
251
  : never
230
252
  }
231
253
 
254
+ /** Match object spread: scoped extras replace shared keys, preserving unions. */
255
+ type MergeExtras<TShared, TScoped> = TShared extends unknown
256
+ ? TScoped extends unknown
257
+ ? Omit<TShared, keyof TScoped> & TScoped
258
+ : never
259
+ : never
260
+
232
261
  export type HandlerParams<
233
262
  TDef extends FnDefinition,
234
263
  TInput,
235
- TKey extends ProcedureKey<TDef>
264
+ TKey extends ProcedureKey<TDef>,
265
+ TErrors extends ErrorMap = Record<never, never>
236
266
  > = DistributiveOmit<
237
- TDef['extras'],
238
- 'input' | 'context' | 'call' | 'signal' | 'span' | 'logger'
267
+ MergeExtras<
268
+ TDef['extras'],
269
+ TDef extends { extrasByProcedure: infer E }
270
+ ? TKey extends keyof E
271
+ ? E[TKey]
272
+ : object
273
+ : object
274
+ >,
275
+ | 'input'
276
+ | 'context'
277
+ | 'call'
278
+ | 'signal'
279
+ | 'span'
280
+ | 'logger'
281
+ | 'errors'
282
+ | 'lastEventId'
239
283
  > & {
240
284
  input: TInput
241
285
  context: FnContext<TDef, TKey>
@@ -246,6 +290,12 @@ export type HandlerParams<
246
290
  /** Active OpenTelemetry span, undefined when `createFn({ otel })` is not set. */
247
291
  span: TDef['span'] | undefined
248
292
  logger: TDef['logger']
293
+ lastEventId: string | undefined
294
+ errors: ReturnType<
295
+ typeof createORPCErrorConstructorMap<
296
+ MergedErrorMap<ErrorMapOf<TDef['procedures'][TKey]>, TErrors>
297
+ >
298
+ >
249
299
  }
250
300
 
251
301
  /** Route options shared by every `fn()` overload. */
@@ -260,11 +310,22 @@ export type RouteConfig<TTag extends string = string> = Pick<
260
310
 
261
311
  export type FnRouteOptions<
262
312
  TDef extends FnDefinition,
263
- TKey extends ProcedureKey<TDef>
313
+ TKey extends ProcedureKey<TDef>,
314
+ TInput = unknown,
315
+ TErrors extends ErrorMap = Record<never, never>
264
316
  > = RouteConfig<TDef['tag']> &
265
- GuardOptions<TDef['guards']> &
266
- TDef['meta'] &
267
- ProcedureOption<TDef, TKey>
317
+ GuardOptions<TDef['guards']> & {
318
+ meta?: TDef['meta']
319
+ stream?: boolean
320
+ errors?: TErrors
321
+ guardResolvers?: {
322
+ [K in keyof TDef['guards']]?: (params: {
323
+ input: TInput
324
+ context: FnContext<TDef, TKey>
325
+ signal: AbortSignal | undefined
326
+ }) => MaybePromise<GuardOptions<TDef['guards']>[K]>
327
+ }
328
+ } & ProcedureOption<TDef, TKey>
268
329
 
269
330
  /**
270
331
  * Which `createFn({ procedures })` builder a route uses. Optional when
@@ -281,97 +342,128 @@ export type FnProcedure<
281
342
  TDef extends FnDefinition,
282
343
  TKey extends ProcedureKey<TDef>,
283
344
  TInputSchema extends Schema<unknown, unknown>,
284
- TOutputSchema extends Schema<unknown, unknown>
345
+ TOutputSchema extends Schema<unknown, unknown>,
346
+ TErrors extends ErrorMap = Record<never, never>
285
347
  > = DecoratedProcedure<
286
348
  InitialContextOf<TDef['procedures'][TKey]>,
287
349
  FnContext<TDef, TKey>,
288
350
  TInputSchema,
289
351
  TOutputSchema,
290
- ErrorMapOf<TDef['procedures'][TKey]>,
291
- MetaOf<TDef['procedures'][TKey]>
352
+ MergedErrorMap<ErrorMapOf<TDef['procedures'][TKey]>, TErrors>,
353
+ MetaOf<TDef['procedures'][TKey]> & {
354
+ 'orpc-fn': StoredFnMeta<TDef['meta'], TKey>
355
+ }
292
356
  >
293
357
 
294
- type InputOf<T> = [T] extends [ZodType] ? z.output<T> : unknown
295
- type ReturnOf<TOut, TReturn> = [TOut] extends [ZodType]
296
- ? z.input<TOut>
358
+ type InputOf<T> = [T] extends [AnySchema] ? InferSchemaOutput<T> : unknown
359
+ type ReturnOf<TOut, TReturn> = [TOut] extends [AnySchema]
360
+ ? InferSchemaInput<TOut>
297
361
  : TReturn
298
362
 
299
363
  /** `fn()`: four overloads for input/output schema × inferred. */
300
364
  export interface Fn<TDef extends FnDefinition> {
301
365
  // 1. With input + output schema
302
366
  <
303
- TInput extends ZodType,
304
- TOut extends ZodType,
305
- TKey extends ProcedureKey<TDef> = TDef['default']
367
+ TInput extends AnySchema,
368
+ TOut extends AnySchema,
369
+ TKey extends ProcedureKey<TDef> = TDef['default'],
370
+ TErrors extends ErrorMap = Record<never, never>
306
371
  >(
307
372
  options: {
308
373
  input: TInput
309
374
  output: TOut
310
375
  // The output schema parses what the handler returns: its input type.
311
376
  handler: (
312
- params: HandlerParams<TDef, z.output<TInput>, NoInfer<TKey>>
313
- ) => MaybePromise<z.input<TOut>>
314
- } & FnRouteOptions<TDef, TKey>
315
- ): FnProcedure<TDef, TKey, TInput, TOut>
377
+ params: HandlerParams<
378
+ TDef,
379
+ InferSchemaOutput<TInput>,
380
+ NoInfer<TKey>,
381
+ TErrors
382
+ >
383
+ ) => MaybePromise<InferSchemaInput<TOut>>
384
+ } & FnRouteOptions<TDef, TKey, InferSchemaOutput<TInput>, TErrors>
385
+ ): FnProcedure<TDef, TKey, TInput, TOut, TErrors>
316
386
 
317
387
  // 2. With input, inferred output
318
388
  <
319
- TInput extends ZodType,
389
+ TInput extends AnySchema,
320
390
  TReturn,
321
- TKey extends ProcedureKey<TDef> = TDef['default']
391
+ TKey extends ProcedureKey<TDef> = TDef['default'],
392
+ TErrors extends ErrorMap = Record<never, never>
322
393
  >(
323
394
  options: {
324
395
  input: TInput
325
396
  output?: undefined
326
397
  handler: (
327
- params: HandlerParams<TDef, z.output<TInput>, NoInfer<TKey>>
398
+ params: HandlerParams<
399
+ TDef,
400
+ InferSchemaOutput<TInput>,
401
+ NoInfer<TKey>,
402
+ TErrors
403
+ >
328
404
  ) => MaybePromise<TReturn>
329
- } & FnRouteOptions<TDef, TKey>
330
- ): FnProcedure<TDef, TKey, TInput, Schema<TReturn, TReturn>>
405
+ } & FnRouteOptions<TDef, TKey, InferSchemaOutput<TInput>, TErrors>
406
+ ): FnProcedure<TDef, TKey, TInput, Schema<TReturn, TReturn>, TErrors>
331
407
 
332
408
  // 3. No input, with output schema
333
- <TOut extends ZodType, TKey extends ProcedureKey<TDef> = TDef['default']>(
409
+ <
410
+ TOut extends AnySchema,
411
+ TKey extends ProcedureKey<TDef> = TDef['default'],
412
+ TErrors extends ErrorMap = Record<never, never>
413
+ >(
334
414
  options: {
335
415
  input?: undefined
336
416
  output: TOut
337
417
  handler: (
338
- params: HandlerParams<TDef, unknown, NoInfer<TKey>>
339
- ) => MaybePromise<z.input<TOut>>
340
- } & FnRouteOptions<TDef, TKey>
341
- ): FnProcedure<TDef, TKey, Schema<unknown, unknown>, TOut>
418
+ params: HandlerParams<TDef, unknown, NoInfer<TKey>, TErrors>
419
+ ) => MaybePromise<InferSchemaInput<TOut>>
420
+ } & FnRouteOptions<TDef, TKey, unknown, TErrors>
421
+ ): FnProcedure<TDef, TKey, Schema<unknown, unknown>, TOut, TErrors>
342
422
 
343
423
  // 4. No input, inferred output
344
- <TReturn, TKey extends ProcedureKey<TDef> = TDef['default']>(
424
+ <
425
+ TReturn,
426
+ TKey extends ProcedureKey<TDef> = TDef['default'],
427
+ TErrors extends ErrorMap = Record<never, never>
428
+ >(
345
429
  options: {
346
430
  input?: undefined
347
431
  output?: undefined
348
432
  handler: (
349
- params: HandlerParams<TDef, unknown, NoInfer<TKey>>
433
+ params: HandlerParams<TDef, unknown, NoInfer<TKey>, TErrors>
350
434
  ) => MaybePromise<TReturn>
351
- } & FnRouteOptions<TDef, TKey>
352
- ): FnProcedure<TDef, TKey, Schema<unknown, unknown>, Schema<TReturn, TReturn>>
435
+ } & FnRouteOptions<TDef, TKey, unknown, TErrors>
436
+ ): FnProcedure<
437
+ TDef,
438
+ TKey,
439
+ Schema<unknown, unknown>,
440
+ Schema<TReturn, TReturn>,
441
+ TErrors
442
+ >
353
443
 
354
444
  // 5. Never matches valid code first; it only words the type errors, which
355
445
  // TypeScript reports against the last overload.
356
446
  <
357
447
  TReturn,
358
- TInput extends ZodType | undefined = undefined,
359
- TOut extends ZodType | undefined = undefined,
360
- TKey extends ProcedureKey<TDef> = TDef['default']
448
+ TInput extends AnySchema | undefined = undefined,
449
+ TOut extends AnySchema | undefined = undefined,
450
+ TKey extends ProcedureKey<TDef> = TDef['default'],
451
+ TErrors extends ErrorMap = Record<never, never>
361
452
  >(
362
453
  options: {
363
454
  input?: TInput
364
455
  output?: TOut
365
456
  // The output schema parses what the handler returns: its input type.
366
457
  handler: (
367
- params: HandlerParams<TDef, InputOf<TInput>, NoInfer<TKey>>
458
+ params: HandlerParams<TDef, InputOf<TInput>, NoInfer<TKey>, TErrors>
368
459
  ) => MaybePromise<ReturnOf<TOut, TReturn>>
369
- } & FnRouteOptions<TDef, TKey>
460
+ } & FnRouteOptions<TDef, TKey, InputOf<TInput>, TErrors>
370
461
  ): FnProcedure<
371
462
  TDef,
372
463
  TKey,
373
- [TInput] extends [ZodType] ? TInput : Schema<unknown, unknown>,
374
- [TOut] extends [ZodType] ? TOut : Schema<TReturn, TReturn>
464
+ [TInput] extends [AnySchema] ? TInput : Schema<unknown, unknown>,
465
+ [TOut] extends [AnySchema] ? TOut : Schema<TReturn, TReturn>,
466
+ TErrors
375
467
  >
376
468
  }
377
469
 
@@ -381,9 +473,59 @@ export type FnCompletedEvent<TContext = unknown, TSpan = SpanLike> = {
381
473
  procedure: string
382
474
  durationMs: number
383
475
  success: boolean
476
+ /** False when authentication or input validation prevented handler execution. */
477
+ handlerStarted: boolean
384
478
  error: unknown
385
479
  input: unknown
386
480
  context: TContext
387
481
  span: TSpan | undefined
388
482
  meta: Record<string, unknown>
389
483
  }
484
+
485
+ /** Factory hooks retain the builder key/context relationship. */
486
+ export type ProcedureHookParams<
487
+ TProcedures extends Record<string, BuilderLike>,
488
+ TExtra = object
489
+ > = {
490
+ [K in keyof TProcedures & string]: TExtra & {
491
+ procedure: K
492
+ context: CurrentContextOf<TProcedures[K]>
493
+ }
494
+ }[keyof TProcedures & string]
495
+
496
+ export type CompletedHookEvent<
497
+ TProcedures extends Record<string, BuilderLike>,
498
+ TSpan
499
+ > = {
500
+ [K in keyof TProcedures & string]: Omit<
501
+ FnCompletedEvent<unknown, TSpan>,
502
+ 'context' | 'procedure' | 'handlerStarted'
503
+ > & { procedure: K } & (
504
+ | { handlerStarted: true; context: CurrentContextOf<TProcedures[K]> }
505
+ | { handlerStarted: false; context: InitialContextOf<TProcedures[K]> }
506
+ )
507
+ }[keyof TProcedures & string]
508
+
509
+ export type FiniteScopedExtras<T> = {
510
+ [K in keyof T]: string extends keyof T[K]
511
+ ? K
512
+ : number extends keyof T[K]
513
+ ? K
514
+ : Extract<
515
+ keyof T[K],
516
+ | 'input'
517
+ | 'context'
518
+ | 'call'
519
+ | 'signal'
520
+ | 'span'
521
+ | 'logger'
522
+ | 'errors'
523
+ | 'lastEventId'
524
+ > extends never
525
+ ? never
526
+ : K
527
+ }[keyof T] extends never
528
+ ? unknown
529
+ : {
530
+ extrasByProcedure: 'orpc-fn: scoped extras need finite keys and cannot shadow handler parameters'
531
+ }