orpc-fn 0.7.0 → 0.9.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 (144) hide show
  1. package/README.md +66 -28
  2. package/dist/assemble-procedure.d.ts.map +1 -1
  3. package/dist/assemble-procedure.js +3 -1
  4. package/dist/assemble-procedure.js.map +1 -1
  5. package/dist/client.d.ts +13 -3
  6. package/dist/client.d.ts.map +1 -1
  7. package/dist/client.js +21 -1
  8. package/dist/client.js.map +1 -1
  9. package/dist/create-fn.d.ts +63 -5
  10. package/dist/create-fn.d.ts.map +1 -1
  11. package/dist/create-fn.js +45 -16
  12. package/dist/create-fn.js.map +1 -1
  13. package/dist/expected-client-error.d.ts +6 -0
  14. package/dist/expected-client-error.d.ts.map +1 -1
  15. package/dist/expected-client-error.js +16 -0
  16. package/dist/expected-client-error.js.map +1 -1
  17. package/dist/hono.d.ts +44 -22
  18. package/dist/hono.d.ts.map +1 -1
  19. package/dist/hono.js +51 -20
  20. package/dist/hono.js.map +1 -1
  21. package/dist/index.d.ts +1 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +0 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/issues.d.ts +12 -0
  26. package/dist/issues.d.ts.map +1 -0
  27. package/dist/issues.js +22 -0
  28. package/dist/issues.js.map +1 -0
  29. package/dist/json-schema.d.ts +3 -6
  30. package/dist/json-schema.d.ts.map +1 -1
  31. package/dist/json-schema.js +12 -7
  32. package/dist/json-schema.js.map +1 -1
  33. package/dist/live/codec.d.ts +2 -0
  34. package/dist/live/codec.d.ts.map +1 -1
  35. package/dist/live/codec.js +21 -16
  36. package/dist/live/codec.js.map +1 -1
  37. package/dist/live/fn-live.d.ts +6 -19
  38. package/dist/live/fn-live.d.ts.map +1 -1
  39. package/dist/live/fn-live.js +15 -23
  40. package/dist/live/fn-live.js.map +1 -1
  41. package/dist/live/index.d.ts +2 -2
  42. package/dist/live/index.d.ts.map +1 -1
  43. package/dist/live/index.js +1 -1
  44. package/dist/live/index.js.map +1 -1
  45. package/dist/live/memory.d.ts +11 -1
  46. package/dist/live/memory.d.ts.map +1 -1
  47. package/dist/live/memory.js +14 -1
  48. package/dist/live/memory.js.map +1 -1
  49. package/dist/live/pub-sub.d.ts +50 -8
  50. package/dist/live/pub-sub.d.ts.map +1 -1
  51. package/dist/live/pub-sub.js +79 -26
  52. package/dist/live/pub-sub.js.map +1 -1
  53. package/dist/live/redis-bun.d.ts +6 -1
  54. package/dist/live/redis-bun.d.ts.map +1 -1
  55. package/dist/live/redis-bun.js +10 -1
  56. package/dist/live/redis-bun.js.map +1 -1
  57. package/dist/live/scoped-change-bus.d.ts +3 -1
  58. package/dist/live/scoped-change-bus.d.ts.map +1 -1
  59. package/dist/live/scoped-change-bus.js +3 -0
  60. package/dist/live/scoped-change-bus.js.map +1 -1
  61. package/dist/mastra.d.ts +16 -3
  62. package/dist/mastra.d.ts.map +1 -1
  63. package/dist/mastra.js +15 -3
  64. package/dist/mastra.js.map +1 -1
  65. package/dist/mcp/core.d.ts +31 -14
  66. package/dist/mcp/core.d.ts.map +1 -1
  67. package/dist/mcp/core.js +39 -20
  68. package/dist/mcp/core.js.map +1 -1
  69. package/dist/mcp/sdk.d.ts +6 -2
  70. package/dist/mcp/sdk.d.ts.map +1 -1
  71. package/dist/mcp/sdk.js +9 -3
  72. package/dist/mcp/sdk.js.map +1 -1
  73. package/dist/mcp/server.d.ts +6 -2
  74. package/dist/mcp/server.d.ts.map +1 -1
  75. package/dist/mcp/server.js +9 -3
  76. package/dist/mcp/server.js.map +1 -1
  77. package/dist/mcp.d.ts +3 -7
  78. package/dist/mcp.d.ts.map +1 -1
  79. package/dist/mcp.js +4 -6
  80. package/dist/mcp.js.map +1 -1
  81. package/dist/meta.d.ts +14 -2
  82. package/dist/meta.d.ts.map +1 -1
  83. package/dist/meta.js +6 -2
  84. package/dist/meta.js.map +1 -1
  85. package/dist/otel.d.ts +16 -4
  86. package/dist/otel.d.ts.map +1 -1
  87. package/dist/otel.js +40 -10
  88. package/dist/otel.js.map +1 -1
  89. package/dist/react.d.ts +6 -1
  90. package/dist/react.d.ts.map +1 -1
  91. package/dist/react.js +5 -1
  92. package/dist/react.js.map +1 -1
  93. package/dist/route-options.d.ts.map +1 -1
  94. package/dist/route-options.js +4 -0
  95. package/dist/route-options.js.map +1 -1
  96. package/dist/stream-call.d.ts +15 -0
  97. package/dist/stream-call.d.ts.map +1 -0
  98. package/dist/stream-call.js +13 -0
  99. package/dist/stream-call.js.map +1 -0
  100. package/dist/timing.d.ts +2 -0
  101. package/dist/timing.d.ts.map +1 -1
  102. package/dist/timing.js +2 -0
  103. package/dist/timing.js.map +1 -1
  104. package/dist/tool-errors.d.ts +24 -0
  105. package/dist/tool-errors.d.ts.map +1 -0
  106. package/dist/tool-errors.js +36 -0
  107. package/dist/tool-errors.js.map +1 -0
  108. package/dist/types.d.ts +2 -0
  109. package/dist/types.d.ts.map +1 -1
  110. package/dist/types.js.map +1 -1
  111. package/dist/vue.d.ts +47 -4
  112. package/dist/vue.d.ts.map +1 -1
  113. package/dist/vue.js +39 -9
  114. package/dist/vue.js.map +1 -1
  115. package/package.json +2 -2
  116. package/src/assemble-procedure.ts +3 -1
  117. package/src/client.ts +26 -2
  118. package/src/create-fn.ts +149 -19
  119. package/src/expected-client-error.ts +19 -0
  120. package/src/hono.ts +120 -41
  121. package/src/index.ts +3 -2
  122. package/src/issues.ts +35 -0
  123. package/src/json-schema.ts +16 -10
  124. package/src/live/codec.ts +21 -19
  125. package/src/live/fn-live.ts +18 -36
  126. package/src/live/index.ts +2 -2
  127. package/src/live/memory.ts +22 -1
  128. package/src/live/pub-sub.ts +142 -37
  129. package/src/live/redis-bun.ts +23 -2
  130. package/src/live/scoped-change-bus.ts +10 -1
  131. package/src/mastra.ts +25 -6
  132. package/src/mcp/core.ts +76 -29
  133. package/src/mcp/sdk.ts +27 -2
  134. package/src/mcp/server.ts +27 -2
  135. package/src/mcp.ts +5 -16
  136. package/src/meta.ts +28 -4
  137. package/src/otel.ts +41 -9
  138. package/src/react.ts +10 -3
  139. package/src/route-options.ts +8 -0
  140. package/src/stream-call.ts +15 -0
  141. package/src/timing.ts +3 -0
  142. package/src/tool-errors.ts +52 -0
  143. package/src/types.ts +2 -0
  144. package/src/vue.ts +92 -19
package/README.md CHANGED
@@ -70,25 +70,41 @@ createFn({
70
70
  spanAttributes, // ({ context, name, procedure, meta }) => extra span attributes
71
71
  onCompleted, // (event) => extra attributes for the fn.completed log
72
72
  isExpectedError, // default: 4xx ORPCError or AbortError
73
- pubsub // { transport (or () => transport, created on first use), namespace?, ownsTransport?, maxQueueSize?, maxIngressSize?, maxReplaySize?, initializationTimeoutMs?, onDrop?, onMetric? } for fnLive / createPubSub
73
+ errorMessages, // 'all' (default) | 'unexpected': leave expected errors' text out of fn.completed
74
+ routePolicy, // ({ name, procedure, guards, dynamicGuards, meta, stream, tags }) => void; throw to reject a route
75
+ pubsub // { transport (or () => transport, created on first use), namespace?, ownsTransport?, safePublish?, maxStreamAgeMs?, maxQueueSize?, maxIngressSize?, maxReplaySize?, initializationTimeoutMs?, onDrop?, onMetric? } for fnLive / createPubSub
74
76
  })
75
77
  ```
76
78
 
77
- It returns `fn`, `fnLive`, `createPubSub`, `createPublisher`, `createRouter`, `readMeta`, `createCall`, `shutdown`, `drainPubSubSubscribers` and `activePubSubSubscriberCount`.
79
+ It returns `fn`, `fnLive`, `fnLivePatch`, `createPubSub`, `createPublisher`, `createRouter`, `readMeta`, `createCall`, `createTestCall`, `shutdown`, `drainPubSubSubscribers` and `activePubSubSubscriberCount`.
78
80
 
79
81
  Procedure builders must not set an input or output schema (`os.input(...)`); put schemas on routes. Guard keys cannot reuse route option names. Application metadata lives in a typed `meta` object and may share names with guards or route options. Unknown top-level route options and extras that shadow built-in handler params are rejected. Scoped extras replace matching shared keys, with handler types reflecting that precedence.
80
82
 
81
83
  **`fn(options)`** has four overloads (input and output schema, each given or inferred). Route options: `name` (required; operationId and span name), `procedure`, `path`, `method`, `summary`, `description`, `tags`, `deprecated`, `successStatus`, `successDescription`, `inputStructure`, `outputStructure`, configured guard keys, `guardResolvers`, `meta`, `stream` and native `errors` declarations. Core schemas use Standard Schema; Zod 4 remains the default for tool/live adapters. The handler receives `input`, `context`, `call`, `signal`, `span`, `logger`, native typed `errors`, `lastEventId` and your extras. With an output schema, the handler returns the schema's input type and callers receive its output type, so `output: z.string().transform(Number)` means the handler returns a string and callers get a number.
82
84
 
83
- **Guards** run inside the span, before the handler, and only on routes that set their option. Return `false` or throw an `ORPCError` to refuse; `true` and `undefined` allow. Static guards run sequentially in route declaration order, followed by resolved guards in resolver order. Unsupported return values fail types and runtime. For input-dependent checks use `guardResolvers: { permission: ({ input, context }) => requirement }`; these see parsed input. A guard cannot set both a static value and a resolver.
85
+ **Guards** run inside the span, before the handler, and only on routes that set their option. A route's guard values are stored in its meta: `readMeta(route).guards` returns them typed (`{ permission: 'deal.read' }`) and `dynamicGuards` names the ones a resolver decides, so a tool list or an audit reads the route's own declaration. To require a decision on every route, reject the rest at definition with `routePolicy`:
84
86
 
85
- **`call(procedure, input)`** calls another procedure with the caller's context and abort signal, inside a `call: <name>` child span. Input and output are typed from the callee. Calling a procedure whose context the caller's context does not satisfy is a type error. Outside a handler (seeders, scripts, tests), use `createCall(context, signal?)` from `createFn`.
87
+ ```ts
88
+ createFn({
89
+ guards: { permission },
90
+ routePolicy: ({ name, procedure, guards, dynamicGuards }) => {
91
+ if (procedure === 'protected' && !('permission' in guards) && !dynamicGuards.includes('permission'))
92
+ throw new Error(`${name}: declare permission (or permission: 'authenticated')`)
93
+ }
94
+ })
95
+ ```
96
+
97
+ It sees every route kind: `fn`, both `fnLive` routes and pub/sub subscriptions. Return `false` or throw an `ORPCError` to refuse; `true` and `undefined` allow. Static guards run sequentially in route declaration order, followed by resolved guards in resolver order. Unsupported return values fail types and runtime. For input-dependent checks use `guardResolvers: { permission: ({ input, context }) => requirement }`; these see parsed input. A guard cannot set both a static value and a resolver.
98
+
99
+ **`call(procedure, input)`** calls another procedure with the caller's context and abort signal, inside a `call: <name>` child span. Input and output are typed from the callee. Calling a procedure whose context the caller's context does not satisfy is a type error. Outside a handler (seeders, scripts, tests), use `createCall(context, signal?)` from `createFn`. A test calls a real route with `createTestCall(context, { extras: { db: fakeDb } })`: middleware, guards and schemas run as in production, and the given extras replace the configured ones for that call and the calls it makes. Mocking `fn` to return its options skips all of that.
86
100
 
87
101
  **Meta** is stored in oRPC's own procedure meta. It survives `os.router()`, prefixes and lazy routers. Declare the contract with `defineMeta<AppMeta>()` (no runtime defaults), then use `fn({ meta: { readOnly: true }, ... })`. Both `readMeta(procedure)` and `readFnMeta(procedure)` infer metadata from the passed procedure, including procedures from another factory.
88
102
 
103
+ **Errors.** `fn.completed` carries `error_type`, `error_code` (an `ORPCError`'s code) and `error_message`. An error `isExpectedError` accepts logs at `warn`, and its span keeps an unset status with no exception event, as OpenTelemetry asks of server spans; `fn.error_code` names it. Its message was written for the caller and may name what they asked for, so `errorMessages: 'unexpected'` leaves it out of the log too. `mountOrpc` and the tool adapters treat errors this factory classified as expected the same way without being told again. To turn the app's own error classes into `ORPCError`s, put one middleware on the base builder rather than a mapper in every route.
104
+
89
105
  Completion hooks discriminate by `procedure` and `handlerStarted`. Before handler context exists (auth/input failure), they receive the initial context. Logging, tracing, metrics and completion-hook failures preserve procedure results. Custom log fields cannot overwrite canonical completion fields. Stream completion runs once when the stream ends.
90
106
 
91
- Also exported: `defineMeta`, `createStreamManifest`, `createStreamManifestAsync`, `renderStreamManifest`, `isExpectedClientError`, `createBoundedEventQueue`, `createRouter`, `createBoundCall`, and the types `HandlerParams`, `RouteConfig`, `BoundCall`, `FnMeta`, `FnContext`, `FnCompletedEvent`, `ProcedureInput`, `ProcedureOutput`, `ProcedureContext`, `InitialContextOf`, `CurrentContextOf` and `GuardOptions`.
107
+ Also exported: `defineMeta`, `createStreamManifest`, `createStreamManifestAsync`, `renderStreamManifest`, `isExpectedClientError`, `createRouter`, `createBoundCall`, and the types `HandlerParams`, `RouteConfig`, `BoundCall`, `FnMeta`, `FnContext`, `FnCompletedEvent`, `ProcedureInput`, `ProcedureOutput`, `ProcedureContext`, `InitialContextOf`, `CurrentContextOf` and `GuardOptions`.
92
108
 
93
109
  ## `orpc-fn/live`: pub/sub and live queries
94
110
 
@@ -122,17 +138,19 @@ await listOrders.publish({ organizationId, orderId }) // after a write
122
138
 
123
139
  The `subscribe` route runs builder middleware and the same fn guards/extras/meta/completion policy, then `authFn` and subscribes to the channel, then streams the initial snapshot, then reloads when invalidated (once per coalescing batch). Queue bounds (`maxQueueSize`, `maxIngressSize`, `maxReplaySize`) can be overridden per pub/sub definition or live config. `maxBatchSize` bounds a live coalescing batch (default 1000). `pubsub.onMetric` reports typed queue depths, drops, successful reconnects and parsing failures; keep this synchronous callback fast. Startup waits for a successful broker subscription, bounded by `initializationTimeoutMs` (10000ms by default) and cancellation. A refused subscriber receives nothing, and events published while the snapshot loads are not lost. Snapshots go through the output schema, like the route's own result. Options:
124
140
 
125
- - `shouldUpdate` skips an event.
141
+ - `shouldUpdate` skips an event. It, `transformerFn` and `reauthorize` receive the same `context` object for every event of one subscription, so a `WeakMap` keyed on it holds per-subscription state.
126
142
  - `transformerFn` folds an event into initialized `previous`. It requires a `stateSchema` that validates parsed state. Patches returned by `fnLivePatch(state, emit)` additionally require `emitSchema`. These schemas should not transform already-parsed values. `rerun()` rechecks the current input through the procedure; it cannot change subscriber input.
143
+ - Without a transformer, a rerun whose snapshot serializes the same as the last one sent is not sent again (`skipUnchanged: false` sends it anyway).
127
144
  - `coalesceMs` performs one rerun for an invalidation batch. Reducers process every event but emit their final full state; ordered patch payloads remain ordered. Batches are bounded (1000 events by default in `streamLiveSnapshots`).
128
145
  - `authFn` and `useBacklog`/`backlogSize`/`backlogTtl` work as on `createPubSub`.
129
146
  - Live queries reload a full snapshot after overflow or reconnect. Raw pub/sub streams fail with `SERVICE_UNAVAILABLE` after detected loss, or emit an application-provided `overflowMarker` that must trigger recovery.
130
- - `reauthorize` can check access before each live update. Initial authorization covers idle streams; apps that need immediate revocation should close those streams.
131
- - `safePublish` logs publish errors instead of throwing them; it does not retry or guarantee delivery.
147
+ - `reauthorize` can check access before each live update. Initial authorization covers idle streams; `pubsub.maxStreamAgeMs` ends every route stream after that age (plus up to 10% jitter), and the client helpers reconnect with a freshly built context.
148
+ - `safePublish` logs publish errors at `warn` instead of throwing them; it does not retry or guarantee delivery. `pubsub.safePublish` sets the default for every definition.
149
+ - A route that only publishes needs no `createPublisher` beside it: `fnLive` returns `publish`.
132
150
 
133
- **`createPubSub`** returns a typed `subscribe` route plus `publish`, `publishMany` (one atomic round-trip across channels) and typed `getSubscriptionChannelName({ input, context })` / `getPublishChannelName(event)` methods. The shared partial-input `getChannelName` method is deprecated. Publishers pass the event schema's input type; subscribers, filters and channel resolvers get its output type. The raw event travels in oRPC's JSON format, so `Date`, `Map`, `Set` and `BigInt` survive, and each receiver parses it once. Options include `filterFn`, `authFn`, `mirrorChannel`, `overflowMarker`, `procedure`, configured guards and `meta`. Channels may be static or use `channel: { subscribe: ({ input, context }) => key, publish: (parsedEvent) => key }`. Carry publishing tenant keys in the event. A factory `namespace` prefixes channels, mirrors and backlog keys. Each pub/sub definition subscribes to each channel once and parses each payload once for all local subscribers. Each subscriber then runs its filter in arrival order as it reads; its bounded queue holds events before filtering, so `onDrop` counts unfiltered events. Abort and drain release a subscription at once, even one that is not being read. A lost subscription is retried with exponential backoff and jitter. `safePublish` logs a failed publish (`pubsub.publish_failed`) instead of throwing, as on `fnLive`. **`createPublisher`** is the publish-only half.
151
+ **`createPubSub`** returns a typed `subscribe` route plus `publish`, `publishMany` (one atomic round-trip across channels) and typed `getSubscriptionChannelName({ input, context })` / `getPublishChannelName(event)` methods. `authFn` is required: return false to refuse with FORBIDDEN, or pass `true` to say that anyone who passes the procedure builder may subscribe. It receives `{ input, context }` (`ctx` is a deprecated alias). The server reads a bus without its route: `for await (const event of await bus.listen(input, { signal }))` resolves once subscribed and skips guards and `authFn`, and `await bus.readBacklog(event)` returns the parsed backlog of the channel an event goes to. Publishers pass the event schema's input type; subscribers, filters and channel resolvers get its output type. The raw event travels in oRPC's JSON format, so `Date`, `Map`, `Set` and `BigInt` survive, and each receiver parses it once. Options include `filterFn`, `authFn`, `mirrorChannel`, `overflowMarker`, `procedure`, configured guards and `meta`. Channels may be static or use `channel: { subscribe: ({ input, context }) => key, publish: (parsedEvent) => key }`. Carry publishing tenant keys in the event. A factory `namespace` prefixes channels, mirrors and backlog keys. Each pub/sub definition subscribes to each channel once and parses each payload once for all local subscribers. Each subscriber then runs its filter in arrival order as it reads; its bounded queue holds events before filtering, so `onDrop` counts unfiltered events. Abort and drain release a subscription at once, even one that is not being read. A lost subscription is retried with exponential backoff and jitter. `safePublish` logs a failed publish (`pubsub.publish_failed`) instead of throwing, as on `fnLive`. **`createPublisher`** is the publish-only half; it also has `listen` and `readBacklog`.
134
152
 
135
- **`defineScopedChangeBus`** binds `createPubSub` to one scope rule, for buses that tell everyone in a scope (usually an organization) "this changed". Each bus names a channel `prefix`; events go to `${prefix}:${scope}`. Publishing is safe by default, since buses publish after committed writes. `inputSchema` defaults to `z.object({})`. `bus.channel` lets an `fnLive` share the bus's events, and `bus.channelFor({ organizationId })` returns a scope's full channel name, namespace included, e.g. for `backlogKey(...)`.
153
+ **`defineScopedChangeBus`** binds `createPubSub` to one scope rule, for buses that tell everyone in a scope (usually an organization) "this changed". Each bus names a channel `prefix`; events go to `${prefix}:${scope}`. Publishing is safe by default, since buses publish after committed writes. The scope is the access decision, so `authFn` defaults to `true`. `inputSchema` defaults to `z.object({})`. `bus.channel` lets an `fnLive` share the bus's events, and `bus.channelFor({ organizationId })` returns a scope's full channel name, namespace included, e.g. for `backlogKey(...)`.
136
154
 
137
155
  ```ts
138
156
  import { defineScopedChangeBus, invalidationStream } from 'orpc-fn/live'
@@ -167,7 +185,7 @@ export const subscribeChanges = fn({
167
185
 
168
186
  On the client, `useInvalidationStream` from `orpc-fn/vue` or `orpc-fn/react` consumes it; its `domains` map is typed from the route's domains.
169
187
 
170
- **Graceful shutdown:** `await shutdown()` stops new subscriptions/publication, cancels initialization/retries, ends existing streams and awaits cleanup. It closes a supplied transport only with `ownsTransport: true`, and a lazy `transport: () => …` only if it was ever created; it is never created during or after shutdown. A custom transport whose pending `subscribe` cannot be cancelled may resolve later; that late subscription is immediately released. `drainPubSubSubscribers()` remains a synchronous, nonterminal operation. Both are per factory.
188
+ **Graceful shutdown:** `await shutdown()` stops new subscriptions/publication, cancels initialization/retries, ends existing streams and awaits cleanup. It closes a supplied transport only with `ownsTransport: true`, and a lazy `transport: () => …` only if it was ever created; it is never created during or after shutdown. A custom transport whose pending `subscribe` cannot be cancelled may resolve later; that late subscription is immediately released. `drainPubSubSubscribers()` remains a synchronous, nonterminal operation; `await drainPubSubSubscribers({ overMs: 5000 })` spreads the closes over the window so clients do not reconnect in one burst. Both are per factory, and both end `listen` streams too.
171
189
 
172
190
  Recent event IDs deduplicate replay/live overlap; legacy payloads remain readable. Replay is bounded and TTL-limited, so it is not durable resume. A snapshot may already include a queued delta: reducers must use revisions or idempotence. See [the revision example](examples/adoption/revisions.ts). Parsing ingress, replay staging and subscriber queues are bounded; positive queue/TTL/time options are validated.
173
191
 
@@ -175,9 +193,9 @@ Recent event IDs deduplicate replay/live overlap; legacy payloads remain readabl
175
193
 
176
194
  | Subpath | Client | Notes |
177
195
  |---|---|---|
178
- | `orpc-fn/live/redis-bun` | Bun `RedisClient` | Dedicated subscriber via `duplicate()`; re-subscribes after Bun's reconnects |
196
+ | `orpc-fn/live/redis-bun` | Bun `RedisClient` | Dedicated subscriber via `duplicate()`, or pass `subscriber`; then the command client needs only `send` (a wrapper with timeouts fits). Re-subscribes after Bun's reconnects |
179
197
  | `orpc-fn/live/ioredis` | ioredis 5 | Dedicated subscriber via `duplicate()`; ioredis re-subscribes itself |
180
- | `orpc-fn/live/memory` | none | Tests and single-process dev |
198
+ | `orpc-fn/live/memory` | none | Tests and single-process dev. `published(channel?)` returns what was published, decoded; `clearPublished()` resets it |
181
199
 
182
200
  A subscriber connection that is lost, or cannot restore its channels after a reconnect, is closed and replaced; nothing it still emits is delivered. The Redis transports publish and maintain backlogs with one Lua script, sent by SHA, with a fallback to `EVAL` when Redis returns `NOSCRIPT`. Await `transport.close()` when the app owns its lifecycle.
183
201
 
@@ -190,7 +208,9 @@ meter.createObservableGauge('pubsub_subscribed_channels').addCallback((g) => g.o
190
208
  meter.createObservableGauge('pubsub_redis_subscriber_connected').addCallback((g) => g.observe(transport.stats().subscriberConnected ? 1 : 0))
191
209
  ``` The Redis Lua batching adapter supports standalone Redis; Redis Cluster requires compatible key slots and is not promised by this adapter.
192
210
 
193
- `streamLiveSnapshots`, `fnLivePatch` and `throwInitialSnapshotError` are exported for custom streams.
211
+ `streamLiveSnapshots`, `fnLivePatch` and `createBoundedEventQueue` are exported for custom streams.
212
+
213
+ In tests, return `memoryTransport()` from the lazy `pubsub.transport` and assert on `transport.published()` instead of mocking bus modules.
194
214
 
195
215
  ## `orpc-fn/mastra`
196
216
 
@@ -203,7 +223,7 @@ const tool = createMastraTool(getOrder, { requireApproval: true })
203
223
  // at run time: requestContext.set('orpcContext', context)
204
224
  ```
205
225
 
206
- Options: `id`, `description`, `requireApproval`, `allowMissingInputSchema`, `onExecuteFinish`, `contextKey` (default `'orpcContext'`). Execution goes through oRPC `call`, so middleware and guards apply.
226
+ Options: `id`, `description` (default: the route's description, else its summary), `requireApproval`, `allowMissingInputSchema`, `onExecuteFinish`, `contextKey` (default `'orpcContext'`), or `context: (requestContext) => …` to read the oRPC context yourself. A failed call throws an `Error` whose message the model reads: `CODE: message`, plus one `path: message` line per validation issue, for an expected error; a fixed sentence for anything else, which goes to `onError(error, { tool })`. `isExpectedError` and `formatError(error, { tool, expected })` change the rule and the text. The same options exist on `registerMcpTools`. Mastra writes plain request-context values into run snapshots and spans, so a context holding a session belongs in a slot that serializes to nothing (better-mastra's `defineRequestContextHolder`), read through `context`. Execution goes through oRPC `call`, so middleware and guards apply.
207
227
 
208
228
  The tool takes the procedure's raw input and returns its parsed output, and `InferToolInput`/`InferToolOutput`/`InferUITools` see exactly those types. Mastra validates the input (coercing date strings) but passes the raw value on, and the procedure parses that raw value: transforms/refinements must be pure: SDK validation and procedure execution may run them more than once, always on raw input. Agent tools accept objects and unions of object shapes. Other inputs and streams are rejected. Finish-hook failures preserve execution outcomes.
209
229
 
@@ -236,13 +256,17 @@ const mcp = new McpServer({ name: 'api', version: '1.0.0' }, { capabilities: { t
236
256
  registerMcpTools(mcp.server, router, { filter: hasTag('external'), context })
237
257
  ```
238
258
 
239
- The adapter installs the SDK list/call handlers on a low-level `Server` (also accessible as `McpServer.server`). Use one registration per server; these handlers own its tool list. It forwards cancellation, validates/coerces arguments, applies procedure authorization, and formats results as JSON/text MCP content. Override `formatResult` for images, structured content or other presentation. Tool selection is required. Object unions are supported; streams are rejected. Resolve lazy routers first with oRPC's `unlazyRouter`.
259
+ An app that builds a server per request lists and converts its tools once with `prepareMcpTools(router, { filter })` (from either entry) and registers the result with `registerPreparedMcpTools(server, prepared, { context })`. `prepared.names` lists the tool names without a server.
260
+
261
+ The adapter installs the SDK list/call handlers on a low-level `Server` (also accessible as `McpServer.server`). Use one registration per server; these handlers own its tool list. It forwards cancellation, validates/coerces arguments, applies procedure authorization, and formats results as JSON/text MCP content. Override `formatResult` for images, structured content or other presentation. A failed call answers as on `orpc-fn/mastra`: expected errors as `CODE: message` with the paths of rejected arguments, anything else as a fixed sentence reported to `onError`. Tool selection is required. Object unions are supported; streams are rejected. Resolve lazy routers first with oRPC's `unlazyRouter`.
240
262
 
241
263
  Sanitized tool-name collisions between distinct procedures throw; identical aliases are deduplicated. Override `name` to resolve deliberate collisions. Tool names are the sanitized `fn` names (`user.me` becomes `user-me`). Input schemas carry the same JSON Schema as your OpenAPI docs (dates become `string`/`date-time`), and validation turns them back into `Date`s. Validation returns the raw arguments, and `call(procedure, args, { context })` parses those: transforms/refinements must be pure: SDK validation and procedure execution may run them more than once, always on raw input. `readOnlyHint` defaults to GET routes; pass `readOnly` to change it. `hasTag(tag)` also works as an oRPC `OpenAPIHandler` filter.
242
264
 
243
265
  ## `orpc-fn/cli`
244
266
 
245
- Uses `hanzio/cli`, which orpc-fn depends on (`hanzio` ^3.3.0). Your `defineCli` must come from the same `hanzio` copy, so keep your own `hanzio` on a matching major.
267
+ Tool descriptions are the route's `description`, else its `summary`, for MCP, Mastra and `readFnMeta(...).description` alike.
268
+
269
+ XX, which orpc-fn depends on (`hanzio` ^3.4.0). Your `defineCli` must come from the same `hanzio` copy, so keep your own `hanzio` on a matching major.
246
270
 
247
271
  ```ts
248
272
  import { defineCli } from 'hanzio/cli'
@@ -268,16 +292,21 @@ await defineCli({
268
292
 
269
293
  ## `orpc-fn/hono`
270
294
 
271
- Optional peers: `hono`, `@orpc/openapi`, `@orpc/zod`, `@orpc/json-schema`.
295
+ Optional peers: `hono`; for `openapi` also `@orpc/openapi`, `@orpc/zod` and `@orpc/json-schema`, loaded on the first request to its prefix, so an RPC-only app does not install them.
272
296
 
273
297
  ```ts
274
- import { mountOrpc, normalizeExpoOrigin } from 'orpc-fn/hono'
298
+ import { hasTag, mountOrpc, normalizeExpoOrigin, timed } from 'orpc-fn/hono'
275
299
 
276
300
  mountOrpc(app, {
277
301
  router,
278
302
  rpcPrefix: '/rpc',
279
- openapi: { prefix: '/api', info: { title: 'API', version: '1.0.0' } },
280
- context: async (c, { headers, timing }) => ({ headers, timing, session: await getSession(headers) }),
303
+ // Only routes tagged 'external' get a REST endpoint and appear in the docs.
304
+ openapi: { prefix: '/api', filter: hasTag('external'), info: { title: 'API', version: '1.0.0' } },
305
+ context: async (c, { headers, timing }) => ({
306
+ headers,
307
+ timing,
308
+ session: await timed(timing, 'auth_ms', () => getSession(headers))
309
+ }),
281
310
  normalizeHeaders: normalizeExpoOrigin,
282
311
  onError: (error, request) => errorTracker.capture(error, request)
283
312
  })
@@ -289,11 +318,13 @@ Behaviour:
289
318
  - SSE responses get keep-alive comments (15 s) and anti-buffering headers.
290
319
  - RPC calls are batched (`BatchHandlerPlugin`).
291
320
  - JSON and batch responses are gzipped when the client accepts it (`compress: false` turns it off). A streaming batch sends each result as it finishes, so it is flushed after every result instead of buffered; Hono's `compress()` skips event streams entirely. Live event streams are never compressed. A response that already has a `content-encoding` is left alone.
292
- - Scalar docs are served at the OpenAPI prefix.
293
- - `onError` sees only unexpected errors.
294
- - `openapi.filter`, `openapi.smartCoercion` and `openapi.spec` (for security schemes) cover an external API with its own auth.
321
+ - `openapi.filter` is required and decides which routes get a REST endpoint and a place in the spec; `() => true` publishes all of them. Scalar docs are served at the OpenAPI prefix and the spec at `/spec.json`, unless `docs: false`.
322
+ - `onError` sees only unexpected errors: by default those `isExpectedClientError` rejects, which includes errors the route's `createFn({ isExpectedError })` classified as expected.
323
+ - `openapi.smartCoercion` and `openapi.spec` (for security schemes) cover an external API with its own auth.
324
+ - CORS belongs to Hono's `cors()` on the app, once. A cross-origin client that batches needs `x-orpc-batch` in `allowHeaders`.
325
+ - `timed(timing, key, work)` adds the duration of `work` to the request timing, which `Server-Timing` reports.
295
326
  - `markRequestArrival(request)` at the front door (`Bun.serve({ fetch })`, before routing) makes `queue_ms` the time until the procedure runs: in-process queueing no handler timer sees. Pass `queueMs` to measure it another way.
296
- - `context` builds the router's context and is checked against it: it is required when `{ headers, timing }` alone does not satisfy the router. It may return a `Response`, for example a 401.
327
+ - `context` builds the router's context and is checked against it: it is required when `{ headers, timing }` alone does not satisfy the router. It may return a `Response`, for example a 401. Any field the app's auth middleware trusts when it is already set (a `user`) must only come from here: `createCall`, `createMastraTool({ context })` and `registerMcpTools({ context })` take a ready-made context too, so build a context that acts for someone in one app function.
297
328
 
298
329
  ## `orpc-fn/client`
299
330
 
@@ -317,7 +348,9 @@ if (hasOrpcErrorCode(error, 'NOT_FOUND')) { /* … */ }
317
348
  toast(userFacingErrorMessage(error, t('errors.generic'))) // server text only when it opts in
318
349
  ```
319
350
 
320
- Route error messages are developer text, so `userFacingErrorMessage` shows your localized fallback unless the server opted in with `new ORPCError('BAD_REQUEST', { message, data: { userMessage } })`. `queryCacheSerializer` is a `serialize`/`deserialize` pair for TanStack Query persisters that round-trips `Date`s and the other types oRPC sends.
351
+ Route error messages are developer text, so `userFacingErrorMessage` shows your localized fallback unless the server opted in with `new ORPCError('BAD_REQUEST', { message, data: { userMessage } })`. `formatClientError(error, fallback)` does the same but lists the rejected fields of a failed input validation (`lines.0.sku: Required`) before falling back.
352
+
353
+ Streams cannot be batched. The Vue and React helpers mark the streams they open (`streamCall` as the client context), so `createRpcLink` keeps them out of batches without a stream manifest; pass `context: streamCall` when you iterate a stream by hand. The manifest (`streamPaths`) remains for streams opened without either. `queryCacheSerializer` is a `serialize`/`deserialize` pair for TanStack Query persisters that round-trips `Date`s and the other types oRPC sends.
321
354
 
322
355
  `createRpcLink` takes every `RPCLink` option. It also batches parallel calls into one request, matching `mountOrpc`'s server-side batching (`batch: { maxSize, exclude, groups }`, or `false` to turn it off). A batch is one request with one client context, so by default only calls without a client context are batched; pass `groups` to batch calls that share one. A streaming response cannot be batched, and the request doesn't say whether the response will stream, so pass `streamPaths` generated from the actual router with `createStreamManifest` (or `createStreamManifestAsync` for lazy routers). Serialize it to a frontend module; importing that data needs no server runtime. The `orpc-fn` CLI does that, resolving lazy routers, sorting paths and writing only on change:
323
356
 
@@ -366,7 +399,8 @@ declare module 'orpc-fn/vue' {
366
399
  ```ts
367
400
  const { useQuery, useMutation, invalidate } = useApiClient()
368
401
  const orders = useQuery((o) => o.order.list.queryOptions({ input: { search: search.value } }))
369
- const save = useMutation((o) => o.order.update.mutationOptions({ onSuccess: () => invalidate((o) => o.order.key()) }))
402
+ // `invalidates` runs after a success, before the options' own onSuccess.
403
+ const save = useMutation((o) => o.order.update.mutationOptions(), { invalidates: (o) => [o.order.key()] })
370
404
 
371
405
  // Form state: `inputData` is typed from the route; `initialInput` is checked against it.
372
406
  const { inputData: form, mutate } = useMutation((o) => o.order.update.mutationOptions(), {
@@ -385,6 +419,10 @@ const order = useLiveQuery(
385
419
  (o) => o.order.watch.call // the composable passes the input and an abort signal
386
420
  )
387
421
 
422
+ // A query as the props of a list or loading component.
423
+ const list = useQueryState(orders, { fallback: t('orders.loadFailed') })
424
+ // <ListStates v-bind="list" />: status, error, errorMessage, isRetrying, retry()
425
+
388
426
  // Once, in the signed-in shell: one stream of `{ domain }` events invalidates list queries.
389
427
  useInvalidationStream({}, (o) => o.realtime.subscribeChanges.call, {
390
428
  domains: { order: { debounceMs: 100, queryKeys: [orpc.order.key()] } },
@@ -392,7 +430,7 @@ useInvalidationStream({}, (o) => o.realtime.subscribeChanges.call, {
392
430
  })
393
431
  ```
394
432
 
395
- `useApiClient()` also returns the utils as `orpc`, for calls outside a composable such as `queryClient.fetchQuery(orpc.unit.list.queryOptions({ input }))`. `useOrpcSubscription` (also `useApiClient().useSubscription`) reconnects with jittered backoff and closes while the page is hidden (`respectVisibility: false` keeps it). `useLiveQuery` writes each stream event under the key it was opened for, so an event in flight from a previous input is dropped. A stream whose events are not the cached type (an `fnLive` patch route, or a `select`ed cache) does not compile without `applyEvent`, and the stream's input must match the query's unless you pass `subscriptionInput`. Use `mergeSeed` when the seed can resolve after stream events. For large live payloads such as chat timelines, pass vue-query's `shallow: true` in the query options so the data is not made deeply reactive; mutate it only through `setQueryData`. Live queries carry `meta.live`, so invalidation streams skip them. Leave `refetchType` at TanStack's `'active'`: `'all'` refetches every cached query of a domain on each event, including screens nobody has open, which refetch on their next visit anyway. `useSubscriptionInvalidates` treats a stream as a "something changed" ping, and `useQueryErrorMessage` is `userFacingErrorMessage` as a computed. A surface that talks to another API passes `orpc` to each composable instead of using `provideOrpc`.
433
+ `useApiClient()` also returns the utils as `orpc`, for calls outside a composable such as `queryClient.fetchQuery(orpc.unit.list.queryOptions({ input }))`. `useOrpcSubscription` (also `useApiClient().useSubscription`) reconnects with jittered backoff and closes while the page is hidden (`respectVisibility: false` keeps it). `useLiveQuery` writes each stream event under the key it was opened for, so an event in flight from a previous input is dropped. A stream whose events are not the cached type (an `fnLive` patch route, or a `select`ed cache) does not compile without `applyEvent`, and the stream's input must match the query's unless you pass `subscriptionInput`. Use `mergeSeed` when the seed can resolve after stream events. For large live payloads such as chat timelines, pass vue-query's `shallow: true` in the query options so the data is not made deeply reactive; mutate it only through `setQueryData`. Live queries carry `meta.live`, so invalidation streams skip them. Leave `refetchType` at TanStack's `'active'`: `'all'` refetches every cached query of a domain on each event, including screens nobody has open, which refetch on their next visit anyway. `useSubscriptionInvalidates` treats a stream as a "something changed" ping, and `useQueryErrorMessage` is `userFacingErrorMessage` as a computed. A surface that talks to another API passes `orpc` to each composable instead of using `provideOrpc`; `useOrpc({ optional: true })` returns null instead of throwing when nothing was provided. `useDocumentVisible()` is the visibility ref the streams pause on.
396
434
 
397
435
  `orpc-fn/vue/testing` runs composables in a real app without a DOM: `withOrpcApp(setup, { utils })`, `createTestStream()` for a subscription the test drives (`await stream.push(event)` resolves once it was handled), and `createOrpcMock({ fn: mock })` for a deep stand-in of the utils.
398
436
 
@@ -1 +1 @@
1
- {"version":3,"file":"assemble-procedure.d.ts","sourceRoot":"","sources":["../src/assemble-procedure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAEhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEhD,MAAM,MAAM,YAAY,GAAG;IAC1B,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,YAAY,CAAA;IACnD,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,YAAY,CAAA;IACjD,KAAK,CAAC,MAAM,EAAE,SAAS,GAAG,YAAY,CAAA;IACtC,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,YAAY,CAAA;IACvC,MAAM,CAAC,MAAM,EAAE,OAAO,gBAAgB,EAAE,QAAQ,GAAG,YAAY,CAAA;IAC/D,OAAO,CAAC,OAAO,EAAE,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,YAAY,CAAA;CACxE,CAAA;AAED,wBAAgB,wBAAwB,CACvC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,EACxC,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,GAC3D,cAAc,CAwBhB"}
1
+ {"version":3,"file":"assemble-procedure.d.ts","sourceRoot":"","sources":["../src/assemble-procedure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAEhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEhD,MAAM,MAAM,YAAY,GAAG;IAC1B,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,YAAY,CAAA;IACnD,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,YAAY,CAAA;IACjD,KAAK,CAAC,MAAM,EAAE,SAAS,GAAG,YAAY,CAAA;IACtC,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,YAAY,CAAA;IACvC,MAAM,CAAC,MAAM,EAAE,OAAO,gBAAgB,EAAE,QAAQ,GAAG,YAAY,CAAA;IAC/D,OAAO,CAAC,OAAO,EAAE,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,YAAY,CAAA;CACxE,CAAA;AAED,wBAAgB,wBAAwB,CACvC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,EACxC,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,GAC3D,cAAc,CA0BhB"}
@@ -9,7 +9,9 @@ export function createProcedureAssembler(procedures, resolveKey) {
9
9
  name: route.name,
10
10
  procedure: key,
11
11
  meta: route.meta,
12
- stream: route.stream ?? false
12
+ stream: route.stream ?? false,
13
+ guards: route.guards ?? {},
14
+ dynamicGuards: route.dynamicGuards ?? []
13
15
  };
14
16
  builder = builder
15
17
  .route({
@@ -1 +1 @@
1
- {"version":3,"file":"assemble-procedure.js","sourceRoot":"","sources":["../src/assemble-procedure.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAqB,MAAM,WAAW,CAAA;AAY1D,MAAM,UAAU,wBAAwB,CACvC,UAAwC,EACxC,UAA6D;IAE7D,OAAO,CAAC,KAAK,EAAE,EAAE;QAChB,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;QACnD,IAAI,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,CAAA;QAC7B,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,GAAG,GAAG,CAAC,CAAA;QACpE,MAAM,MAAM,GAAiB;YAC5B,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,SAAS,EAAE,GAAG;YACd,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,KAAK;SAC7B,CAAA;QACD,OAAO,GAAG,OAAO;aACf,KAAK,CAAC;YACN,WAAW,EAAE,KAAK,CAAC,IAAI;YACvB,GAAG,MAAM,CAAC,WAAW,CACpB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,CACtE;SACD,CAAC;aACD,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC,CAAA;QACjC,IAAI,KAAK,CAAC,MAAM;YAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;QACxD,IAAI,KAAK,CAAC,KAAK;YAAE,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QACrD,IAAI,KAAK,CAAC,MAAM;YAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;QACxD,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IACtC,CAAC,CAAA;AACF,CAAC","sourcesContent":["import type { AnySchema } from '@orpc/contract'\nimport type { AnyProcedure } from '@orpc/server'\nimport { FN_META_KEY, type StoredFnMeta } from './meta.js'\nimport type { BuildProcedure } from './types.js'\n\nexport type BuilderChain = {\n\troute(route: Record<string, unknown>): BuilderChain\n\tmeta(meta: Record<string, unknown>): BuilderChain\n\tinput(schema: AnySchema): BuilderChain\n\toutput(schema: AnySchema): BuilderChain\n\terrors(errors: import('@orpc/contract').ErrorMap): BuilderChain\n\thandler(handler: Parameters<BuildProcedure>[0]['handler']): AnyProcedure\n}\n\nexport function createProcedureAssembler(\n\tprocedures: Record<string, BuilderChain>,\n\tresolveKey: (key: string | undefined, name: string) => string\n): BuildProcedure {\n\treturn (route) => {\n\t\tconst key = resolveKey(route.procedure, route.name)\n\t\tlet builder = procedures[key]\n\t\tif (!builder) throw new Error(`orpc-fn: unknown procedure \"${key}\"`)\n\t\tconst stored: StoredFnMeta = {\n\t\t\tname: route.name,\n\t\t\tprocedure: key,\n\t\t\tmeta: route.meta,\n\t\t\tstream: route.stream ?? false\n\t\t}\n\t\tbuilder = builder\n\t\t\t.route({\n\t\t\t\toperationId: route.name,\n\t\t\t\t...Object.fromEntries(\n\t\t\t\t\tObject.entries(route.route).filter(([, value]) => value !== undefined)\n\t\t\t\t)\n\t\t\t})\n\t\t\t.meta({ [FN_META_KEY]: stored })\n\t\tif (route.errors) builder = builder.errors(route.errors)\n\t\tif (route.input) builder = builder.input(route.input)\n\t\tif (route.output) builder = builder.output(route.output)\n\t\treturn builder.handler(route.handler)\n\t}\n}\n"]}
1
+ {"version":3,"file":"assemble-procedure.js","sourceRoot":"","sources":["../src/assemble-procedure.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAqB,MAAM,WAAW,CAAA;AAY1D,MAAM,UAAU,wBAAwB,CACvC,UAAwC,EACxC,UAA6D;IAE7D,OAAO,CAAC,KAAK,EAAE,EAAE;QAChB,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;QACnD,IAAI,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,CAAA;QAC7B,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,GAAG,GAAG,CAAC,CAAA;QACpE,MAAM,MAAM,GAAiB;YAC5B,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,SAAS,EAAE,GAAG;YACd,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,KAAK;YAC7B,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,EAAE;YAC1B,aAAa,EAAE,KAAK,CAAC,aAAa,IAAI,EAAE;SACxC,CAAA;QACD,OAAO,GAAG,OAAO;aACf,KAAK,CAAC;YACN,WAAW,EAAE,KAAK,CAAC,IAAI;YACvB,GAAG,MAAM,CAAC,WAAW,CACpB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,CACtE;SACD,CAAC;aACD,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC,CAAA;QACjC,IAAI,KAAK,CAAC,MAAM;YAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;QACxD,IAAI,KAAK,CAAC,KAAK;YAAE,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QACrD,IAAI,KAAK,CAAC,MAAM;YAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;QACxD,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IACtC,CAAC,CAAA;AACF,CAAC","sourcesContent":["import type { AnySchema } from '@orpc/contract'\nimport type { AnyProcedure } from '@orpc/server'\nimport { FN_META_KEY, type StoredFnMeta } from './meta.js'\nimport type { BuildProcedure } from './types.js'\n\nexport type BuilderChain = {\n\troute(route: Record<string, unknown>): BuilderChain\n\tmeta(meta: Record<string, unknown>): BuilderChain\n\tinput(schema: AnySchema): BuilderChain\n\toutput(schema: AnySchema): BuilderChain\n\terrors(errors: import('@orpc/contract').ErrorMap): BuilderChain\n\thandler(handler: Parameters<BuildProcedure>[0]['handler']): AnyProcedure\n}\n\nexport function createProcedureAssembler(\n\tprocedures: Record<string, BuilderChain>,\n\tresolveKey: (key: string | undefined, name: string) => string\n): BuildProcedure {\n\treturn (route) => {\n\t\tconst key = resolveKey(route.procedure, route.name)\n\t\tlet builder = procedures[key]\n\t\tif (!builder) throw new Error(`orpc-fn: unknown procedure \"${key}\"`)\n\t\tconst stored: StoredFnMeta = {\n\t\t\tname: route.name,\n\t\t\tprocedure: key,\n\t\t\tmeta: route.meta,\n\t\t\tstream: route.stream ?? false,\n\t\t\tguards: route.guards ?? {},\n\t\t\tdynamicGuards: route.dynamicGuards ?? []\n\t\t}\n\t\tbuilder = builder\n\t\t\t.route({\n\t\t\t\toperationId: route.name,\n\t\t\t\t...Object.fromEntries(\n\t\t\t\t\tObject.entries(route.route).filter(([, value]) => value !== undefined)\n\t\t\t\t)\n\t\t\t})\n\t\t\t.meta({ [FN_META_KEY]: stored })\n\t\tif (route.errors) builder = builder.errors(route.errors)\n\t\tif (route.input) builder = builder.input(route.input)\n\t\tif (route.output) builder = builder.output(route.output)\n\t\treturn builder.handler(route.handler)\n\t}\n}\n"]}
package/dist/client.d.ts CHANGED
@@ -1,8 +1,15 @@
1
1
  import { type ClientContext, ORPCError, type ORPCErrorCode } from '@orpc/client';
2
2
  import { RPCLink, type RPCLinkOptions } from '@orpc/client/fetch';
3
3
  import { BatchLinkPlugin } from '@orpc/client/plugins';
4
+ export { STREAM_CALL, streamCall } from './stream-call.js';
4
5
  type BatchOptions<T extends ClientContext> = ConstructorParameters<typeof BatchLinkPlugin<T>>[0];
5
6
  export type CreateRpcLinkOptions<T extends ClientContext> = RPCLinkOptions<T> & {
7
+ /**
8
+ * Client-safe manifest generated by createStreamManifest(router). Calls
9
+ * opened by the framework helpers, or with `context: streamCall`, are
10
+ * recognised without it.
11
+ */
12
+ streamPaths?: readonly (readonly string[])[];
6
13
  /**
7
14
  * Collapse parallel calls into one HTTP request, matching `mountOrpc`'s
8
15
  * server-side batching. Default: true (25 calls). Pass false to turn off.
@@ -11,8 +18,6 @@ export type CreateRpcLinkOptions<T extends ClientContext> = RPCLinkOptions<T> &
11
18
  * default only calls without a client context are batched; pass
12
19
  * `groups` to batch calls that share a context (e.g. a token).
13
20
  */
14
- /** Client-safe manifest generated by createStreamManifest(router). */
15
- streamPaths?: readonly (readonly string[])[];
16
21
  batch?: boolean | {
17
22
  maxSize?: number;
18
23
  /** Calls that must not be batched. Default: {@link isSubscriptionPath}. */
@@ -58,6 +63,12 @@ export declare function hasOrpcErrorCode<TCode extends ORPCErrorCode>(error: unk
58
63
  export declare function userSafeServerMessage(error: unknown): string | null;
59
64
  /** User-facing text for a caught error: the opted-in server message, else your localized fallback. */
60
65
  export declare function userFacingErrorMessage(error: unknown, fallback: string): string;
66
+ /**
67
+ * Like {@link userFacingErrorMessage}, but a rejected input lists its
68
+ * fields: the server's `userMessage`, else one `path: message` line per
69
+ * validation issue of a BAD_REQUEST, else your fallback.
70
+ */
71
+ export declare function formatClientError(error: unknown, fallback: string): string;
61
72
  /**
62
73
  * `serialize`/`deserialize` for a TanStack Query persister. API payloads carry
63
74
  * `Date`s (and maybe `Map`s, `Set`s, `BigInt`s) that plain JSON returns as
@@ -71,5 +82,4 @@ export declare const queryCacheSerializer: {
71
82
  serialize(value: unknown): string;
72
83
  deserialize<T>(text: string): T;
73
84
  };
74
- export {};
75
85
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA;AAChF,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAGtD,KAAK,YAAY,CAAC,CAAC,SAAS,aAAa,IAAI,qBAAqB,CACjE,OAAO,eAAe,CAAC,CAAC,CAAC,CACzB,CAAC,CAAC,CAAC,CAAA;AAEJ,MAAM,MAAM,oBAAoB,CAAC,CAAC,SAAS,aAAa,IACvD,cAAc,CAAC,CAAC,CAAC,GAAG;IACnB;;;;;;;OAOG;IACH,sEAAsE;IACtE,WAAW,CAAC,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CAAA;IAC5C,KAAK,CAAC,EACH,OAAO,GACP;QACA,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,2EAA2E;QAC3E,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,OAAO,CAAA;QAC9C,MAAM,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAA;KACjC,CAAA;CACJ,CAAA;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,SAAU,SAAS,MAAM,EAAE,YACU,CAAA;AAEpE;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,aAAa,GAAG,aAAa,EACpE,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAC9B,OAAO,CAAC,CAAC,CAAC,CA2BZ;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,SAAS,aAAa,EAC3D,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,KAAK,GACT,KAAK,IAAI,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,CAEpC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAQnE;AAED,sGAAsG;AACtG,wBAAgB,sBAAsB,CACrC,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,GACd,MAAM,CAER;AAID;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB;IAChC,SAAS,QAAQ,OAAO,GAAG,MAAM;IAIjC,WAAW,CAAC,CAAC,QAAQ,MAAM,GAAG,CAAC;CAO/B,CAAA"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA;AAChF,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAKtD,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE1D,KAAK,YAAY,CAAC,CAAC,SAAS,aAAa,IAAI,qBAAqB,CACjE,OAAO,eAAe,CAAC,CAAC,CAAC,CACzB,CAAC,CAAC,CAAC,CAAA;AAEJ,MAAM,MAAM,oBAAoB,CAAC,CAAC,SAAS,aAAa,IACvD,cAAc,CAAC,CAAC,CAAC,GAAG;IACnB;;;;OAIG;IACH,WAAW,CAAC,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CAAA;IAC5C;;;;;;;OAOG;IACH,KAAK,CAAC,EACH,OAAO,GACP;QACA,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,2EAA2E;QAC3E,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,OAAO,CAAA;QAC9C,MAAM,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAA;KACjC,CAAA;CACJ,CAAA;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,SAAU,SAAS,MAAM,EAAE,YACU,CAAA;AAEpE;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,aAAa,GAAG,aAAa,EACpE,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAC9B,OAAO,CAAC,CAAC,CAAC,CA4BZ;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,SAAS,aAAa,EAC3D,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,KAAK,GACT,KAAK,IAAI,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,CAEpC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAQnE;AAED,sGAAsG;AACtG,wBAAgB,sBAAsB,CACrC,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,GACd,MAAM,CAER;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQ1E;AAID;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB;IAChC,SAAS,QAAQ,OAAO,GAAG,MAAM;IAIjC,WAAW,CAAC,CAAC,QAAQ,MAAM,GAAG,CAAC;CAO/B,CAAA"}
package/dist/client.js CHANGED
@@ -2,6 +2,9 @@ import { ORPCError } from '@orpc/client';
2
2
  import { RPCLink } from '@orpc/client/fetch';
3
3
  import { BatchLinkPlugin } from '@orpc/client/plugins';
4
4
  import { StandardRPCJsonSerializer } from '@orpc/client/standard';
5
+ import { formatIssue, issuesOf } from './issues.js';
6
+ import { isStreamCall } from './stream-call.js';
7
+ export { STREAM_CALL, streamCall } from './stream-call.js';
5
8
  /**
6
9
  * Streaming responses cannot be batched, and the request does not say a
7
10
  * response will stream - so streaming routes are recognised by name. The
@@ -34,7 +37,8 @@ export function createRpcLink(options) {
34
37
  // so the batch's empty context is exactly theirs.
35
38
  { condition: () => true, context: {} }
36
39
  ],
37
- exclude: ({ path, context }) => streams.has(JSON.stringify(path)) ||
40
+ exclude: ({ path, context }) => isStreamCall(context) ||
41
+ streams.has(JSON.stringify(path)) ||
38
42
  exclude(path) ||
39
43
  (groups === undefined && Object.keys(context).length > 0)
40
44
  }),
@@ -77,6 +81,22 @@ export function userSafeServerMessage(error) {
77
81
  export function userFacingErrorMessage(error, fallback) {
78
82
  return userSafeServerMessage(error) ?? fallback;
79
83
  }
84
+ /**
85
+ * Like {@link userFacingErrorMessage}, but a rejected input lists its
86
+ * fields: the server's `userMessage`, else one `path: message` line per
87
+ * validation issue of a BAD_REQUEST, else your fallback.
88
+ */
89
+ export function formatClientError(error, fallback) {
90
+ const safe = userSafeServerMessage(error);
91
+ if (safe)
92
+ return safe;
93
+ if (error instanceof ORPCError && error.code === 'BAD_REQUEST') {
94
+ const issues = issuesOf(error);
95
+ if (issues.length > 0)
96
+ return issues.map(formatIssue).join('\n');
97
+ }
98
+ return fallback;
99
+ }
80
100
  const cacheSerializer = new StandardRPCJsonSerializer();
81
101
  /**
82
102
  * `serialize`/`deserialize` for a TanStack Query persister. API payloads carry
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ClientContext, ORPCError, type ORPCErrorCode } from '@orpc/client'\nimport { RPCLink, type RPCLinkOptions } from '@orpc/client/fetch'\nimport { BatchLinkPlugin } from '@orpc/client/plugins'\nimport { StandardRPCJsonSerializer } from '@orpc/client/standard'\n\ntype BatchOptions<T extends ClientContext> = ConstructorParameters<\n\ttypeof BatchLinkPlugin<T>\n>[0]\n\nexport type CreateRpcLinkOptions<T extends ClientContext> =\n\tRPCLinkOptions<T> & {\n\t\t/**\n\t\t * Collapse parallel calls into one HTTP request, matching `mountOrpc`'s\n\t\t * server-side batching. Default: true (25 calls). Pass false to turn off.\n\t\t *\n\t\t * A batch is one HTTP request, so it carries one client context. By\n\t\t * default only calls without a client context are batched; pass\n\t\t * `groups` to batch calls that share a context (e.g. a token).\n\t\t */\n\t\t/** Client-safe manifest generated by createStreamManifest(router). */\n\t\tstreamPaths?: readonly (readonly string[])[]\n\t\tbatch?:\n\t\t\t| boolean\n\t\t\t| {\n\t\t\t\t\tmaxSize?: number\n\t\t\t\t\t/** Calls that must not be batched. Default: {@link isSubscriptionPath}. */\n\t\t\t\t\texclude?: (path: readonly string[]) => boolean\n\t\t\t\t\tgroups?: BatchOptions<T>['groups']\n\t\t\t }\n\t}\n\n/**\n * Streaming responses cannot be batched, and the request does not say a\n * response will stream - so streaming routes are recognised by name. The\n * default treats any path segment containing `subscribe` as one (the\n * convention `fnLive` routers use: `{ list, subscribe }`).\n */\nexport const isSubscriptionPath = (path: readonly string[]) =>\n\tpath.some((segment) => segment.toLowerCase().includes('subscribe'))\n\n/**\n * `RPCLink` with request batching: a page-load burst becomes one request\n * instead of many queued on the server's event loop.\n *\n * ```ts\n * const client: RouterClient<typeof router> = createORPCClient(createRpcLink({ url }))\n * export const orpc = createTanstackQueryUtils(client)\n * ```\n */\nexport function createRpcLink<T extends ClientContext = ClientContext>(\n\toptions: CreateRpcLinkOptions<T>\n): RPCLink<T> {\n\tconst { batch = true, plugins = [], streamPaths, ...rest } = options\n\tconst streams = new Set(streamPaths?.map((path) => JSON.stringify(path)))\n\tif (batch === false) return new RPCLink<T>({ ...rest, plugins })\n\tconst {\n\t\tmaxSize = 25,\n\t\texclude = isSubscriptionPath,\n\t\tgroups\n\t} = batch === true ? {} : batch\n\treturn new RPCLink<T>({\n\t\t...rest,\n\t\tplugins: [\n\t\t\tnew BatchLinkPlugin<T>({\n\t\t\t\tmaxSize,\n\t\t\t\tgroups: groups ?? [\n\t\t\t\t\t// Only context-free calls reach this group (see `exclude` below),\n\t\t\t\t\t// so the batch's empty context is exactly theirs.\n\t\t\t\t\t{ condition: () => true, context: {} as T }\n\t\t\t\t],\n\t\t\t\texclude: ({ path, context }) =>\n\t\t\t\t\tstreams.has(JSON.stringify(path)) ||\n\t\t\t\t\texclude(path) ||\n\t\t\t\t\t(groups === undefined && Object.keys(context).length > 0)\n\t\t\t}),\n\t\t\t...plugins\n\t\t]\n\t})\n}\n\n/**\n * Typed check for a route error on the client. The RPC link rebuilds thrown\n * route errors as `ORPCError`, so `instanceof` plus `code` is the contract.\n */\nexport function hasOrpcErrorCode<TCode extends ORPCErrorCode>(\n\terror: unknown,\n\tcode: TCode\n): error is ORPCError<TCode, unknown> {\n\treturn error instanceof ORPCError && error.code === code\n}\n\n/**\n * The server's message, only when the backend marked it user-safe by putting\n * a `userMessage` string on the error's `data`. Route error messages are\n * developer text (English, internal model names), so they are not shown by\n * default; they stay on the error for logs and error tracking.\n *\n * ```ts\n * throw new ORPCError('BAD_REQUEST', {\n * message: 'Deal has no pipeline stage', // logs, telemetry\n * data: { userMessage: 'Choose a stage first.' } // the user reads this\n * })\n * ```\n */\nexport function userSafeServerMessage(error: unknown): string | null {\n\tif (!(error instanceof ORPCError)) return null\n\tconst data: unknown = error.data\n\tif (typeof data !== 'object' || data === null) return null\n\tconst userMessage = (data as { userMessage?: unknown }).userMessage\n\treturn typeof userMessage === 'string' && userMessage.trim().length > 0\n\t\t? userMessage\n\t\t: null\n}\n\n/** User-facing text for a caught error: the opted-in server message, else your localized fallback. */\nexport function userFacingErrorMessage(\n\terror: unknown,\n\tfallback: string\n): string {\n\treturn userSafeServerMessage(error) ?? fallback\n}\n\nconst cacheSerializer = new StandardRPCJsonSerializer()\n\n/**\n * `serialize`/`deserialize` for a TanStack Query persister. API payloads carry\n * `Date`s (and maybe `Map`s, `Set`s, `BigInt`s) that plain JSON returns as\n * strings; oRPC's own serializer round-trips them.\n *\n * ```ts\n * createAsyncStoragePersister({ storage: AsyncStorage, ...queryCacheSerializer })\n * ```\n */\nexport const queryCacheSerializer = {\n\tserialize(value: unknown): string {\n\t\tconst [json, meta] = cacheSerializer.serialize(value)\n\t\treturn JSON.stringify({ json, meta })\n\t},\n\tdeserialize<T>(text: string): T {\n\t\tconst { json, meta } = JSON.parse(text) as {\n\t\t\tjson: unknown\n\t\t\tmeta: Parameters<typeof cacheSerializer.deserialize>[1]\n\t\t}\n\t\treturn cacheSerializer.deserialize(json, meta) as T\n\t}\n}\n"]}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ClientContext, ORPCError, type ORPCErrorCode } from '@orpc/client'\nimport { RPCLink, type RPCLinkOptions } from '@orpc/client/fetch'\nimport { BatchLinkPlugin } from '@orpc/client/plugins'\nimport { StandardRPCJsonSerializer } from '@orpc/client/standard'\nimport { formatIssue, issuesOf } from './issues.js'\nimport { isStreamCall } from './stream-call.js'\n\nexport { STREAM_CALL, streamCall } from './stream-call.js'\n\ntype BatchOptions<T extends ClientContext> = ConstructorParameters<\n\ttypeof BatchLinkPlugin<T>\n>[0]\n\nexport type CreateRpcLinkOptions<T extends ClientContext> =\n\tRPCLinkOptions<T> & {\n\t\t/**\n\t\t * Client-safe manifest generated by createStreamManifest(router). Calls\n\t\t * opened by the framework helpers, or with `context: streamCall`, are\n\t\t * recognised without it.\n\t\t */\n\t\tstreamPaths?: readonly (readonly string[])[]\n\t\t/**\n\t\t * Collapse parallel calls into one HTTP request, matching `mountOrpc`'s\n\t\t * server-side batching. Default: true (25 calls). Pass false to turn off.\n\t\t *\n\t\t * A batch is one HTTP request, so it carries one client context. By\n\t\t * default only calls without a client context are batched; pass\n\t\t * `groups` to batch calls that share a context (e.g. a token).\n\t\t */\n\t\tbatch?:\n\t\t\t| boolean\n\t\t\t| {\n\t\t\t\t\tmaxSize?: number\n\t\t\t\t\t/** Calls that must not be batched. Default: {@link isSubscriptionPath}. */\n\t\t\t\t\texclude?: (path: readonly string[]) => boolean\n\t\t\t\t\tgroups?: BatchOptions<T>['groups']\n\t\t\t }\n\t}\n\n/**\n * Streaming responses cannot be batched, and the request does not say a\n * response will stream - so streaming routes are recognised by name. The\n * default treats any path segment containing `subscribe` as one (the\n * convention `fnLive` routers use: `{ list, subscribe }`).\n */\nexport const isSubscriptionPath = (path: readonly string[]) =>\n\tpath.some((segment) => segment.toLowerCase().includes('subscribe'))\n\n/**\n * `RPCLink` with request batching: a page-load burst becomes one request\n * instead of many queued on the server's event loop.\n *\n * ```ts\n * const client: RouterClient<typeof router> = createORPCClient(createRpcLink({ url }))\n * export const orpc = createTanstackQueryUtils(client)\n * ```\n */\nexport function createRpcLink<T extends ClientContext = ClientContext>(\n\toptions: CreateRpcLinkOptions<T>\n): RPCLink<T> {\n\tconst { batch = true, plugins = [], streamPaths, ...rest } = options\n\tconst streams = new Set(streamPaths?.map((path) => JSON.stringify(path)))\n\tif (batch === false) return new RPCLink<T>({ ...rest, plugins })\n\tconst {\n\t\tmaxSize = 25,\n\t\texclude = isSubscriptionPath,\n\t\tgroups\n\t} = batch === true ? {} : batch\n\treturn new RPCLink<T>({\n\t\t...rest,\n\t\tplugins: [\n\t\t\tnew BatchLinkPlugin<T>({\n\t\t\t\tmaxSize,\n\t\t\t\tgroups: groups ?? [\n\t\t\t\t\t// Only context-free calls reach this group (see `exclude` below),\n\t\t\t\t\t// so the batch's empty context is exactly theirs.\n\t\t\t\t\t{ condition: () => true, context: {} as T }\n\t\t\t\t],\n\t\t\t\texclude: ({ path, context }) =>\n\t\t\t\t\tisStreamCall(context) ||\n\t\t\t\t\tstreams.has(JSON.stringify(path)) ||\n\t\t\t\t\texclude(path) ||\n\t\t\t\t\t(groups === undefined && Object.keys(context).length > 0)\n\t\t\t}),\n\t\t\t...plugins\n\t\t]\n\t})\n}\n\n/**\n * Typed check for a route error on the client. The RPC link rebuilds thrown\n * route errors as `ORPCError`, so `instanceof` plus `code` is the contract.\n */\nexport function hasOrpcErrorCode<TCode extends ORPCErrorCode>(\n\terror: unknown,\n\tcode: TCode\n): error is ORPCError<TCode, unknown> {\n\treturn error instanceof ORPCError && error.code === code\n}\n\n/**\n * The server's message, only when the backend marked it user-safe by putting\n * a `userMessage` string on the error's `data`. Route error messages are\n * developer text (English, internal model names), so they are not shown by\n * default; they stay on the error for logs and error tracking.\n *\n * ```ts\n * throw new ORPCError('BAD_REQUEST', {\n * message: 'Deal has no pipeline stage', // logs, telemetry\n * data: { userMessage: 'Choose a stage first.' } // the user reads this\n * })\n * ```\n */\nexport function userSafeServerMessage(error: unknown): string | null {\n\tif (!(error instanceof ORPCError)) return null\n\tconst data: unknown = error.data\n\tif (typeof data !== 'object' || data === null) return null\n\tconst userMessage = (data as { userMessage?: unknown }).userMessage\n\treturn typeof userMessage === 'string' && userMessage.trim().length > 0\n\t\t? userMessage\n\t\t: null\n}\n\n/** User-facing text for a caught error: the opted-in server message, else your localized fallback. */\nexport function userFacingErrorMessage(\n\terror: unknown,\n\tfallback: string\n): string {\n\treturn userSafeServerMessage(error) ?? fallback\n}\n\n/**\n * Like {@link userFacingErrorMessage}, but a rejected input lists its\n * fields: the server's `userMessage`, else one `path: message` line per\n * validation issue of a BAD_REQUEST, else your fallback.\n */\nexport function formatClientError(error: unknown, fallback: string): string {\n\tconst safe = userSafeServerMessage(error)\n\tif (safe) return safe\n\tif (error instanceof ORPCError && error.code === 'BAD_REQUEST') {\n\t\tconst issues = issuesOf(error)\n\t\tif (issues.length > 0) return issues.map(formatIssue).join('\\n')\n\t}\n\treturn fallback\n}\n\nconst cacheSerializer = new StandardRPCJsonSerializer()\n\n/**\n * `serialize`/`deserialize` for a TanStack Query persister. API payloads carry\n * `Date`s (and maybe `Map`s, `Set`s, `BigInt`s) that plain JSON returns as\n * strings; oRPC's own serializer round-trips them.\n *\n * ```ts\n * createAsyncStoragePersister({ storage: AsyncStorage, ...queryCacheSerializer })\n * ```\n */\nexport const queryCacheSerializer = {\n\tserialize(value: unknown): string {\n\t\tconst [json, meta] = cacheSerializer.serialize(value)\n\t\treturn JSON.stringify({ json, meta })\n\t},\n\tdeserialize<T>(text: string): T {\n\t\tconst { json, meta } = JSON.parse(text) as {\n\t\t\tjson: unknown\n\t\t\tmeta: Parameters<typeof cacheSerializer.deserialize>[1]\n\t\t}\n\t\treturn cacheSerializer.deserialize(json, meta) as T\n\t}\n}\n"]}
@@ -1,11 +1,24 @@
1
+ import { type AnyProcedure } from '@orpc/server';
1
2
  import { type BoundCall } from './bound-call.js';
2
- import { type FnLive } from './live/fn-live.js';
3
+ import { type FnLive, fnLivePatch } from './live/fn-live.js';
3
4
  import { type CreatePublisher, type CreatePubSub, type PubSubRuntimeOptions } from './live/pub-sub.js';
4
5
  import { type FnLogger, type LogAttributes } from './logger.js';
5
6
  import { readFnMeta } from './meta.js';
6
7
  import { type AttributeValue, type OtelApiLike, type SpanOf } from './otel.js';
7
8
  import { createRouter } from './router.js';
8
- import { type ProcedureHookParams, type CompletedHookEvent, type CurrentContextOf, type BuilderLike, type FiniteExtras, type ScopedExtrasGuard, type Fn, type FnDefinition, type Guard, type GuardParams, type MaybePromise, type RejectKeys, type ReservedOptionKey } from './types.js';
9
+ import { type ProcedureHookParams, type CompletedHookEvent, type CurrentContextOf, type BuilderLike, type FiniteExtras, type ScopedExtrasGuard, type Fn, type FnDefinition, type Guard, type GuardOptions, type GuardParams, type MaybePromise, type RejectKeys, type ReservedOptionKey } from './types.js';
10
+ /** What `routePolicy` sees of each route when it is defined. */
11
+ export type RoutePolicyParams<TKey extends string = string, TGuards = Record<string, unknown>, TMeta extends object = Record<string, unknown>, TTag extends string = string> = {
12
+ name: string;
13
+ procedure: TKey;
14
+ /** Guard values set on the route itself. */
15
+ guards: GuardOptions<TGuards>;
16
+ /** Guards the route decides per call through `guardResolvers`. */
17
+ dynamicGuards: readonly (keyof TGuards & string)[];
18
+ meta: Partial<TMeta>;
19
+ stream: boolean;
20
+ tags: readonly TTag[];
21
+ };
9
22
  export type CreateFnOptions<TProcedures extends Record<string, BuilderLike>, TDefault extends keyof TProcedures & string, TExtras extends object, TGuards, TMeta extends object, TTag extends string, TOtel extends OtelApiLike | undefined, TLogger extends FnLogger, TScoped extends Partial<Record<keyof TProcedures, object>> = Record<never, never>> = {
10
23
  /** Named oRPC builders, e.g. `{ public: os.$context<Ctx>(), protected: authed }`. */
11
24
  procedures: TProcedures;
@@ -51,8 +64,24 @@ export type CreateFnOptions<TProcedures extends Record<string, BuilderLike>, TDe
51
64
  }>) => Record<string, AttributeValue | undefined>;
52
65
  /** Called after the procedure (or stream) settles; returned attributes join the `fn.completed` log. */
53
66
  onCompleted?: (event: CompletedHookEvent<TProcedures, SpanOf<TOtel>>) => LogAttributes | undefined | void;
54
- /** Errors that are the caller's fault, logged at `warn`. Default: 4xx `ORPCError` or `AbortError`. */
67
+ /**
68
+ * Errors that are the caller's fault: logged at `warn`, and their span keeps
69
+ * an unset status. `mountOrpc` and the tool adapters follow it for errors
70
+ * a route threw. Default: 4xx `ORPCError` or `AbortError`.
71
+ */
55
72
  isExpectedError?: (error: unknown) => boolean;
73
+ /**
74
+ * `'unexpected'` leaves the message of expected errors out of `fn.completed`
75
+ * (it was written for the caller and may name what they asked for);
76
+ * `error_code` and `error_type` stay. Default: `'all'`.
77
+ */
78
+ errorMessages?: 'all' | 'unexpected';
79
+ /**
80
+ * Sees every route (`fn`, `fnLive`, pub/sub subscriptions) when it is
81
+ * defined; throw to reject it, e.g. a protected route with no permission:
82
+ * `if (procedure === 'protected' && !('permission' in guards)) throw ...`.
83
+ */
84
+ routePolicy?: (route: RoutePolicyParams<keyof TProcedures & string, NoInfer<TGuards>, NoInfer<TMeta>, NoInfer<TTag>>) => void;
56
85
  /** Needed for `fnLive` and `createPubSub`. */
57
86
  pubsub?: PubSubRuntimeOptions;
58
87
  };
@@ -67,21 +96,50 @@ type Definition<TProcedures extends Record<string, BuilderLike>, TDefault extend
67
96
  span: SpanOf<TOtel>;
68
97
  logger: TLogger;
69
98
  };
99
+ /** Extras a test call may replace: any shared or scoped extra. */
100
+ export type TestCallExtras<TDef extends FnDefinition> = Partial<TDef['extras']> & (TDef extends {
101
+ extrasByProcedure: infer E;
102
+ } ? [keyof E] extends [never] ? object : Partial<NonNullable<E[keyof E]>> : object);
70
103
  export type FnFactory<TDef extends FnDefinition> = {
71
104
  fn: Fn<TDef>;
72
105
  fnLive: FnLive<TDef>;
106
+ /** Same as `fnLivePatch` from `orpc-fn/live`. */
107
+ fnLivePatch: typeof fnLivePatch;
73
108
  createPubSub: CreatePubSub<TDef>;
74
109
  createPublisher: CreatePublisher;
75
110
  createRouter: typeof createRouter;
76
- readMeta: typeof readFnMeta;
111
+ /** `readFnMeta`, with guard values typed by this factory's guards. */
112
+ readMeta: <T extends AnyProcedure>(procedure: T) => Omit<ReturnType<typeof readFnMeta<T>>, 'guards' | 'dynamicGuards'> & {
113
+ guards: GuardOptions<TDef['guards']>;
114
+ dynamicGuards: readonly (keyof TDef['guards'] & string)[];
115
+ };
77
116
  /**
78
117
  * A `call` outside any handler (seeders, scripts, tests), traced with this
79
118
  * instance's OpenTelemetry.
80
119
  */
81
120
  createCall: <TContext>(context: TContext, signal?: AbortSignal) => BoundCall<TContext>;
121
+ /**
122
+ * `createCall` for tests: middleware, guards and schemas run as in
123
+ * production, and `extras` replace the configured ones (`{ db: fakeDb }`)
124
+ * for this call and the calls it makes.
125
+ */
126
+ createTestCall: <TContext>(context: TContext, options?: {
127
+ extras?: TestCallExtras<TDef>;
128
+ signal?: AbortSignal;
129
+ }) => BoundCall<TContext>;
82
130
  /** Terminal, idempotent shutdown: close streams and await owned resources. */
83
131
  shutdown: () => Promise<void>;
84
- drainPubSubSubscribers: () => number;
132
+ /**
133
+ * End every open subscription and return how many were closed. With
134
+ * `overMs`, spread the closes evenly over that window so clients do not
135
+ * reconnect in one burst; the promise resolves after the last one.
136
+ */
137
+ drainPubSubSubscribers: {
138
+ (): number;
139
+ (options: {
140
+ overMs: number;
141
+ }): Promise<number>;
142
+ };
85
143
  activePubSubSubscriberCount: () => number;
86
144
  };
87
145
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"create-fn.d.ts","sourceRoot":"","sources":["../src/create-fn.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"create-fn.d.ts","sourceRoot":"","sources":["../src/create-fn.ts"],"names":[],"mappings":"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"}