orpc-fn 0.4.0 → 0.5.1

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 (93) hide show
  1. package/README.md +146 -4
  2. package/dist/cli.js +37 -48
  3. package/dist/cli.js.map +1 -1
  4. package/dist/client.d.ts +29 -0
  5. package/dist/client.d.ts.map +1 -1
  6. package/dist/client.js +49 -0
  7. package/dist/client.js.map +1 -1
  8. package/dist/create-fn.d.ts.map +1 -1
  9. package/dist/create-fn.js +0 -1
  10. package/dist/create-fn.js.map +1 -1
  11. package/dist/hono.d.ts +12 -1
  12. package/dist/hono.d.ts.map +1 -1
  13. package/dist/hono.js +15 -1
  14. package/dist/hono.js.map +1 -1
  15. package/dist/live/fn-live.d.ts +1 -6
  16. package/dist/live/fn-live.d.ts.map +1 -1
  17. package/dist/live/fn-live.js +2 -18
  18. package/dist/live/fn-live.js.map +1 -1
  19. package/dist/live/index.d.ts +3 -1
  20. package/dist/live/index.d.ts.map +1 -1
  21. package/dist/live/index.js +2 -0
  22. package/dist/live/index.js.map +1 -1
  23. package/dist/live/invalidation-stream.d.ts +52 -0
  24. package/dist/live/invalidation-stream.d.ts.map +1 -0
  25. package/dist/live/invalidation-stream.js +98 -0
  26. package/dist/live/invalidation-stream.js.map +1 -0
  27. package/dist/live/ioredis.d.ts +1 -0
  28. package/dist/live/ioredis.d.ts.map +1 -1
  29. package/dist/live/ioredis.js +1 -0
  30. package/dist/live/ioredis.js.map +1 -1
  31. package/dist/live/memory.d.ts +2 -1
  32. package/dist/live/memory.d.ts.map +1 -1
  33. package/dist/live/memory.js +6 -1
  34. package/dist/live/memory.js.map +1 -1
  35. package/dist/live/pub-sub.d.ts +5 -0
  36. package/dist/live/pub-sub.d.ts.map +1 -1
  37. package/dist/live/pub-sub.js +16 -3
  38. package/dist/live/pub-sub.js.map +1 -1
  39. package/dist/live/redis-bun.d.ts +3 -0
  40. package/dist/live/redis-bun.d.ts.map +1 -1
  41. package/dist/live/redis-bun.js +1 -0
  42. package/dist/live/redis-bun.js.map +1 -1
  43. package/dist/live/redis.d.ts +4 -1
  44. package/dist/live/redis.d.ts.map +1 -1
  45. package/dist/live/redis.js +14 -2
  46. package/dist/live/redis.js.map +1 -1
  47. package/dist/live/scoped-change-bus.d.ts +45 -0
  48. package/dist/live/scoped-change-bus.d.ts.map +1 -0
  49. package/dist/live/scoped-change-bus.js +38 -0
  50. package/dist/live/scoped-change-bus.js.map +1 -0
  51. package/dist/live/transport.d.ts +11 -0
  52. package/dist/live/transport.d.ts.map +1 -1
  53. package/dist/live/transport.js.map +1 -1
  54. package/dist/procedure-cli.d.ts +25 -0
  55. package/dist/procedure-cli.d.ts.map +1 -0
  56. package/dist/procedure-cli.js +67 -0
  57. package/dist/procedure-cli.js.map +1 -0
  58. package/dist/react.d.ts +116 -0
  59. package/dist/react.d.ts.map +1 -0
  60. package/dist/react.js +156 -0
  61. package/dist/react.js.map +1 -0
  62. package/dist/realtime.d.ts +114 -0
  63. package/dist/realtime.d.ts.map +1 -0
  64. package/dist/realtime.js +316 -0
  65. package/dist/realtime.js.map +1 -0
  66. package/dist/vue/testing.d.ts +67 -0
  67. package/dist/vue/testing.d.ts.map +1 -0
  68. package/dist/vue/testing.js +225 -0
  69. package/dist/vue/testing.js.map +1 -0
  70. package/dist/vue.d.ts +211 -0
  71. package/dist/vue.d.ts.map +1 -0
  72. package/dist/vue.js +312 -0
  73. package/dist/vue.js.map +1 -0
  74. package/package.json +49 -3
  75. package/src/cli.ts +47 -57
  76. package/src/client.ts +57 -0
  77. package/src/create-fn.ts +0 -1
  78. package/src/hono.ts +22 -2
  79. package/src/live/fn-live.ts +3 -22
  80. package/src/live/index.ts +12 -1
  81. package/src/live/invalidation-stream.ts +142 -0
  82. package/src/live/ioredis.ts +2 -0
  83. package/src/live/memory.ts +11 -2
  84. package/src/live/pub-sub.ts +23 -2
  85. package/src/live/redis-bun.ts +3 -0
  86. package/src/live/redis.ts +33 -6
  87. package/src/live/scoped-change-bus.ts +98 -0
  88. package/src/live/transport.ts +12 -0
  89. package/src/procedure-cli.ts +99 -0
  90. package/src/react.ts +355 -0
  91. package/src/realtime.ts +416 -0
  92. package/src/vue/testing.ts +296 -0
  93. package/src/vue.ts +783 -0
package/README.md CHANGED
@@ -130,13 +130,49 @@ The `subscribe` route runs builder middleware and the same fn guards/extras/meta
130
130
  - `reauthorize` can check access before each live update. Initial authorization covers idle streams; apps that need immediate revocation should close those streams.
131
131
  - `safePublish` logs publish errors instead of throwing them; it does not retry or guarantee delivery.
132
132
 
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. **`createPublisher`** is the publish-only half.
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.
134
+
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. `bus.channel` lets an `fnLive` share the bus's events.
136
+
137
+ ```ts
138
+ import { defineScopedChangeBus, invalidationStream } from 'orpc-fn/live'
139
+
140
+ const defineChangeBus = defineScopedChangeBus(createPubSub, {
141
+ subscribe: (context) => activeOrganizationId(context), // throw to refuse
142
+ publish: (event: { organizationId: string }) => event.organizationId
143
+ })
144
+ export const dealChanges = defineChangeBus({
145
+ name: 'deal.subscribe',
146
+ prefix: 'deal:change',
147
+ inputSchema: z.object({}),
148
+ eventSchema: z.object({ organizationId: z.string(), dealId: z.string() })
149
+ })
150
+ ```
151
+
152
+ **`invalidationStream`** fans several streams into one stream of `{ domain }` events, so a client holds one connection and invalidates queries per domain. `events` optionally forwards fields from a source event, for example to raise an alert. Sources are called with `{}` and the caller's context. Equal events that queue while the consumer is busy collapse into one. When one source ends (a deploy drain) the stream ends, when one throws the stream throws, and every source is closed either way.
153
+
154
+ ```ts
155
+ export const subscribeChanges = fn({
156
+ name: 'realtime.subscribeChanges',
157
+ stream: true,
158
+ input: z.object({}),
159
+ handler: ({ context, signal }) =>
160
+ invalidationStream({
161
+ context,
162
+ signal,
163
+ sources: { deal: dealChanges.subscribe, workItem: workItemChanges.subscribe },
164
+ events: { deal: (event) => (event.kind === 'won' ? { dealId: event.dealId } : undefined) }
165
+ })
166
+ })
167
+ ```
168
+
169
+ On the client, `useInvalidationStream` from `orpc-fn/vue` or `orpc-fn/react` consumes it; its `domains` map is typed from the route's domains.
134
170
 
135
171
  **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.
136
172
 
137
173
  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.
138
174
 
139
- **Transports** implement `PubSubTransport` (`publish(messages, backlog?)`, `readBacklog(channel)`, `subscribe(channel, listener, onLost, { onReconnect }?)`):
175
+ **Transports** implement `PubSubTransport` (`publish(messages, backlog?)`, `readBacklog(channel)`, `subscribe(channel, listener, onLost, { onReconnect }?)` and optional `stats()`):
140
176
 
141
177
  | Subpath | Client | Notes |
142
178
  |---|---|---|
@@ -144,7 +180,16 @@ Recent event IDs deduplicate replay/live overlap; legacy payloads remain readabl
144
180
  | `orpc-fn/live/ioredis` | ioredis 5 | Dedicated subscriber via `duplicate()`; ioredis re-subscribes itself |
145
181
  | `orpc-fn/live/memory` | none | Tests and single-process dev |
146
182
 
147
- 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. The Redis Lua batching adapter supports standalone Redis; Redis Cluster requires compatible key slots and is not promised by this adapter.
183
+ 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.
184
+
185
+ All three return `stats()`: `{ channels, listeners, subscriberConnected }` on this instance. With `activePubSubSubscriberCount()` from `createFn` that covers pub/sub gauges; a transport opens no connection until the first subscribe, so it can be created eagerly:
186
+
187
+ ```ts
188
+ const transport = bunRedisTransport(redis)
189
+ createFn({ /* … */, pubsub: { transport, ownsTransport: true } })
190
+ meter.createObservableGauge('pubsub_subscribed_channels').addCallback((g) => g.observe(transport.stats().channels))
191
+ meter.createObservableGauge('pubsub_redis_subscriber_connected').addCallback((g) => g.observe(transport.stats().subscriberConnected ? 1 : 0))
192
+ ``` The Redis Lua batching adapter supports standalone Redis; Redis Cluster requires compatible key slots and is not promised by this adapter.
148
193
 
149
194
  `streamLiveSnapshots`, `fnLivePatch` and `throwInitialSnapshotError` are exported for custom streams.
150
195
 
@@ -196,6 +241,32 @@ The adapter installs the SDK list/call handlers on a low-level `Server` (also ac
196
241
 
197
242
  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.
198
243
 
244
+ ## `orpc-fn/cli`
245
+
246
+ Uses `hanzio/cli`, which orpc-fn depends on (`hanzio` ^3.1.0). Your `defineCli` must come from the same `hanzio` copy, so keep your own `hanzio` on a matching major.
247
+
248
+ ```ts
249
+ import { defineCli } from 'hanzio/cli'
250
+ import { procedureCommands } from 'orpc-fn/cli'
251
+
252
+ const updateDeal = fn({
253
+ name: 'deal.update',
254
+ summary: 'Move a deal to another stage', // the command's help
255
+ meta: { cli: true },
256
+ input: z.object({ id: z.string().meta({ positional: true }), stage: z.string() }),
257
+ handler: async ({ input, context }) => { /* ... */ }
258
+ })
259
+
260
+ await defineCli({
261
+ name: 'app',
262
+ globals: z.object({ as: z.string().optional() }),
263
+ context: async ({ as }) => ({ user: as ? await loadUser(as) : undefined }),
264
+ commands: procedureCommands(router)
265
+ }).run() // app deal update d_1 --stage won --as ada@example.com
266
+ ```
267
+
268
+ `procedureCommands(router)` turns the routes declared with `meta: { cli: true }` into commands nested like the router, kebab-cased (`dealRoom.update` is `deal-room update`); add `cli?: boolean` to your meta type, or select routes with `filter`. Flags and positionals come from the route's `z.object()` input. A command calls the procedure through oRPC with the CLI's context, so middleware, guards and tracing run as over HTTP; `context: (cliContext, meta) => ...` maps that context per procedure and can refuse with a `CliError`. A 4xx `ORPCError` prints as `CODE: message`; anything else keeps its stack. Streams and non-object inputs throw when the commands are built. The procedure parses the already-validated flags again, so transforms must accept their own output.
269
+
199
270
  ## `orpc-fn/hono`
200
271
 
201
272
  Optional peers: `hono`, `@orpc/openapi`, `@orpc/zod`, `@orpc/json-schema`.
@@ -221,6 +292,7 @@ Behaviour:
221
292
  - Scalar docs are served at the OpenAPI prefix.
222
293
  - `onError` sees only unexpected errors.
223
294
  - `openapi.filter`, `openapi.smartCoercion` and `openapi.spec` (for security schemes) cover an external API with its own auth.
295
+ - `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.
224
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.
225
297
 
226
298
  ## `orpc-fn/client`
@@ -242,8 +314,11 @@ export const client: RouterClient<typeof router> = createORPCClient(link)
242
314
  export const orpc = createTanstackQueryUtils(client) // Vue Query or React Query
243
315
 
244
316
  if (hasOrpcErrorCode(error, 'NOT_FOUND')) { /* … */ }
317
+ toast(userFacingErrorMessage(error, t('errors.generic'))) // server text only when it opts in
245
318
  ```
246
319
 
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.
321
+
247
322
  `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:
248
323
 
249
324
  ```sh
@@ -272,7 +347,74 @@ const link = createBetterAuthExpoLink({
272
347
  })
273
348
  ```
274
349
 
275
- On native, calls go through `expo/fetch`, the only fetch that streams there. The bridge forwards the request body and the abort signal. Without the signal, cancelled subscriptions keep their sockets open, the device's HTTP pool runs out, and later requests hang. Cookies are sent in a header (`credentials: 'omit'`), together with `expo-origin` and `x-skip-oauth-proxy`, the same headers `@better-auth/expo` sends. On the server, `mountOrpc({ normalizeHeaders: normalizeExpoOrigin })` copies `expo-origin` to `origin`. The generic `createExpoLink` forwards upstream headers (including async/context-aware resolvers) and adds no provider-specific headers. `createBetterAuthExpoLink` supplies the cookie/origin preset above. On web, the link uses the platform fetch with cookies, or your explicit `webFetch` override. `createExpoFetch` is exported for a custom link.
350
+ On native, calls go through `expo/fetch`, the only fetch that streams there. In Expo SDK 55, cancelling a stream makes `expo/fetch` close its body twice and throw `Invalid state: Controller is already closed`; patch `src/winter/fetch/FetchResponse.ts` so its `didComplete` and `didFailWithError` listeners return when `isControllerClosed` is already set (with `bun patch expo` or `patch-package`). The bridge forwards the request body and the abort signal. Without the signal, cancelled subscriptions keep their sockets open, the device's HTTP pool runs out, and later requests hang. Cookies are sent in a header (`credentials: 'omit'`), together with `expo-origin` and `x-skip-oauth-proxy`, the same headers `@better-auth/expo` sends. On the server, `mountOrpc({ normalizeHeaders: normalizeExpoOrigin })` copies `expo-origin` to `origin`. The generic `createExpoLink` forwards upstream headers (including async/context-aware resolvers) and adds no provider-specific headers. `createBetterAuthExpoLink` supplies the cookie/origin preset above. On web, the link uses the platform fetch with cookies, or your explicit `webFetch` override. `createExpoFetch` is exported for a custom link.
351
+
352
+ ## `orpc-fn/vue`
353
+
354
+ Peers: `vue`, `@tanstack/vue-query`.
355
+
356
+ ```ts
357
+ // main.ts
358
+ app.use(VueQueryPlugin, { queryClient })
359
+ provideOrpc(app, { client, utils: orpc })
360
+
361
+ declare module 'orpc-fn/vue' {
362
+ interface Register { client: typeof client; utils: typeof orpc }
363
+ }
364
+ ```
365
+
366
+ ```ts
367
+ const { useQuery, useMutation, invalidate } = useApiClient()
368
+ 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()) }))
370
+
371
+ // The client half of `fnLive`: HTTP seeds the cache, the stream keeps it current.
372
+ const order = useLiveQuery(
373
+ () => ({ id: props.id }),
374
+ (input, o) => o.order.get.queryOptions({ input }),
375
+ (o) => o.order.watch.call // the composable passes the input and an abort signal
376
+ )
377
+
378
+ // Once, in the signed-in shell: one stream of `{ domain }` events invalidates list queries.
379
+ useInvalidationStream({}, (o) => o.realtime.subscribeChanges.call, {
380
+ domains: { order: { debounceMs: 100, queryKeys: [orpc.order.key()] } },
381
+ logger
382
+ })
383
+ ```
384
+
385
+ `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`.
386
+
387
+ `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.
388
+
389
+ ## `orpc-fn/react`
390
+
391
+ Peers: `react`, `@tanstack/react-query`. Works on React Native: pausing follows TanStack's `focusManager`, so wire it to `AppState` once, as TanStack recommends:
392
+
393
+ ```ts
394
+ focusManager.setEventListener((setFocused) => {
395
+ const subscription = AppState.addEventListener('change', (state) => setFocused(state === 'active'))
396
+ return () => subscription.remove()
397
+ })
398
+ ```
399
+
400
+ ```tsx
401
+ const { status } = useOrpcSubscription({ id }, client.order.watch, { onEvent: (order) => setOrder(order) })
402
+
403
+ // Same contract as the Vue `useLiveQuery`, with the client procedure passed as is.
404
+ // For a large cache your `applyEvent` keeps immutable, pass `structuralSharing: false` in the query options.
405
+ const order = useLiveQuery({ id }, (input) => orpc.order.get.queryOptions({ input }), client.order.watch)
406
+
407
+ useInvalidationStream({}, client.realtime.subscribeChanges, {
408
+ enabled: signedIn,
409
+ domains: { order: { debounceMs: 500, queryKeys: [orpc.order.list.key()] } }
410
+ })
411
+
412
+ // Persist the cache per user: the buster drops someone else's saved cache, the hook clears memory.
413
+ const owner = session ? `${session.user.id}:${session.activeOrganizationId ?? ''}` : null
414
+ useQueryCacheOwner(owner)
415
+ const persister = createAsyncStoragePersister({ storage: AsyncStorage, ...queryCacheSerializer })
416
+ <PersistQueryClientProvider client={queryClient} persistOptions={{ persister, buster: owner ?? '' }} />
417
+ ```
276
418
 
277
419
  ## Docs
278
420
 
package/dist/cli.js CHANGED
@@ -1,56 +1,45 @@
1
1
  #!/usr/bin/env node
2
- // biome-ignore-all lint/suspicious/noConsole: a CLI reports on the console
3
2
  import { readFile, writeFile } from 'node:fs/promises';
4
3
  import { resolve } from 'node:path';
5
4
  import { pathToFileURL } from 'node:url';
6
- import { parseArgs } from 'node:util';
5
+ import { CliError, defineCli, defineCommand } from 'hanzio/cli';
6
+ import { z } from 'zod';
7
7
  import { createStreamManifestAsync, renderStreamManifest } from './router.js';
8
- const usage = `Usage: orpc-fn stream-manifest <module>[#export] --out <file.ts|file.json> [--name streamPaths] [--check]
9
-
10
- Imports the router (export "default" unless #export is given) and writes the
11
- paths of its streaming routes for createRpcLink({ streamPaths }). The file is
12
- only written when its content changes. --check writes nothing and exits 1 when
13
- the file is missing or stale. Importing the router runs its module code; run
14
- TypeScript routers with a runtime that loads .ts (bun, tsx, node >= 22.18).`;
15
- async function run(argv) {
16
- const { positionals, values } = parseArgs({
17
- args: [...argv],
18
- allowPositionals: true,
19
- options: {
20
- out: { type: 'string' },
21
- name: { type: 'string' },
22
- check: { type: 'boolean' },
23
- help: { type: 'boolean', short: 'h' }
24
- }
25
- });
26
- const [command, target] = positionals;
27
- if (values.help || command !== 'stream-manifest' || !target || !values.out) {
28
- console.error(usage);
29
- return values.help ? 0 : 2;
8
+ const streamManifest = defineCommand({
9
+ description: "Write the paths of a router's streaming routes for createRpcLink({ streamPaths }), only when they change. Importing the router runs its module code: load .ts routers with bun, tsx or node >= 22.18.",
10
+ input: z.object({
11
+ module: z
12
+ .string()
13
+ .describe('Router module, with #export for a named export')
14
+ .meta({ positional: true }),
15
+ out: z.string().describe('Manifest to write: file.ts or file.json'),
16
+ name: z
17
+ .string()
18
+ .optional()
19
+ .describe('Exported constant name in a .ts file'),
20
+ check: z.boolean().describe('Exit 1 when the file is missing or stale')
21
+ }),
22
+ run: async (input) => {
23
+ const [modulePath = '', exportName = 'default'] = input.module.split('#');
24
+ const loaded = await import(pathToFileURL(resolve(modulePath)).href);
25
+ const router = loaded[exportName];
26
+ if (!router || typeof router !== 'object')
27
+ throw new CliError(`orpc-fn: ${modulePath} has no router export "${exportName}"`, { exitCode: 2 });
28
+ const out = resolve(input.out);
29
+ const source = renderStreamManifest(await createStreamManifestAsync(router), { format: out.endsWith('.json') ? 'json' : 'ts', name: input.name });
30
+ const current = await readFile(out, 'utf8').catch(() => undefined);
31
+ if (current === source)
32
+ return;
33
+ if (input.check)
34
+ throw new CliError(`orpc-fn: ${input.out} is stale; rerun without --check to update it`);
35
+ await writeFile(out, source);
36
+ return `orpc-fn: wrote ${input.out}`;
30
37
  }
31
- const [modulePath = '', exportName = 'default'] = target.split('#');
32
- const loaded = await import(pathToFileURL(resolve(modulePath)).href);
33
- const router = loaded[exportName];
34
- if (!router || typeof router !== 'object') {
35
- console.error(`orpc-fn: ${modulePath} has no router export "${exportName}"`);
36
- return 2;
37
- }
38
- const out = resolve(values.out);
39
- const source = renderStreamManifest(await createStreamManifestAsync(router), { format: out.endsWith('.json') ? 'json' : 'ts', name: values.name });
40
- const current = await readFile(out, 'utf8').catch(() => undefined);
41
- if (current === source)
42
- return 0;
43
- if (values.check) {
44
- console.error(`orpc-fn: ${values.out} is stale; rerun without --check to update it`);
45
- return 1;
46
- }
47
- await writeFile(out, source);
48
- console.log(`orpc-fn: wrote ${values.out}`);
49
- return 0;
50
- }
51
- // Exit explicitly: the imported router may hold open database or broker handles.
52
- run(process.argv.slice(2)).then((code) => process.exit(code), (error) => {
53
- console.error(error);
54
- process.exit(1);
55
38
  });
39
+ const cli = defineCli({
40
+ name: 'orpc-fn',
41
+ commands: { 'stream-manifest': streamManifest }
42
+ });
43
+ // Exit explicitly: the imported router may hold open database or broker handles.
44
+ process.exit(await cli.run());
56
45
  //# sourceMappingURL=cli.js.map
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n// biome-ignore-all lint/suspicious/noConsole: a CLI reports on the console\nimport { readFile, writeFile } from 'node:fs/promises'\nimport { resolve } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport { parseArgs } from 'node:util'\nimport type { AnyRouter } from '@orpc/server'\nimport { createStreamManifestAsync, renderStreamManifest } from './router.js'\n\nconst usage = `Usage: orpc-fn stream-manifest <module>[#export] --out <file.ts|file.json> [--name streamPaths] [--check]\n\nImports the router (export \"default\" unless #export is given) and writes the\npaths of its streaming routes for createRpcLink({ streamPaths }). The file is\nonly written when its content changes. --check writes nothing and exits 1 when\nthe file is missing or stale. Importing the router runs its module code; run\nTypeScript routers with a runtime that loads .ts (bun, tsx, node >= 22.18).`\n\nasync function run(argv: readonly string[]): Promise<number> {\n\tconst { positionals, values } = parseArgs({\n\t\targs: [...argv],\n\t\tallowPositionals: true,\n\t\toptions: {\n\t\t\tout: { type: 'string' },\n\t\t\tname: { type: 'string' },\n\t\t\tcheck: { type: 'boolean' },\n\t\t\thelp: { type: 'boolean', short: 'h' }\n\t\t}\n\t})\n\tconst [command, target] = positionals\n\tif (values.help || command !== 'stream-manifest' || !target || !values.out) {\n\t\tconsole.error(usage)\n\t\treturn values.help ? 0 : 2\n\t}\n\tconst [modulePath = '', exportName = 'default'] = target.split('#')\n\tconst loaded: Record<string, unknown> = await import(\n\t\tpathToFileURL(resolve(modulePath)).href\n\t)\n\tconst router = loaded[exportName]\n\tif (!router || typeof router !== 'object') {\n\t\tconsole.error(`orpc-fn: ${modulePath} has no router export \"${exportName}\"`)\n\t\treturn 2\n\t}\n\tconst out = resolve(values.out)\n\tconst source = renderStreamManifest(\n\t\tawait createStreamManifestAsync(router as AnyRouter),\n\t\t{ format: out.endsWith('.json') ? 'json' : 'ts', name: values.name }\n\t)\n\tconst current = await readFile(out, 'utf8').catch(() => undefined)\n\tif (current === source) return 0\n\tif (values.check) {\n\t\tconsole.error(\n\t\t\t`orpc-fn: ${values.out} is stale; rerun without --check to update it`\n\t\t)\n\t\treturn 1\n\t}\n\tawait writeFile(out, source)\n\tconsole.log(`orpc-fn: wrote ${values.out}`)\n\treturn 0\n}\n\n// Exit explicitly: the imported router may hold open database or broker handles.\nrun(process.argv.slice(2)).then(\n\t(code) => process.exit(code),\n\t(error) => {\n\t\tconsole.error(error)\n\t\tprocess.exit(1)\n\t}\n)\n"]}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC/D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAE7E,MAAM,cAAc,GAAG,aAAa,CAAC;IACpC,WAAW,EACV,uMAAuM;IACxM,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,MAAM,EAAE,CAAC;aACP,MAAM,EAAE;aACR,QAAQ,CAAC,gDAAgD,CAAC;aAC1D,IAAI,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;QAC5B,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,yCAAyC,CAAC;QACnE,IAAI,EAAE,CAAC;aACL,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,sCAAsC,CAAC;QAClD,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,0CAA0C,CAAC;KACvE,CAAC;IACF,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;QACpB,MAAM,CAAC,UAAU,GAAG,EAAE,EAAE,UAAU,GAAG,SAAS,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QACzE,MAAM,MAAM,GAA4B,MAAM,MAAM,CACnD,aAAa,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CACvC,CAAA;QACD,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,CAAA;QACjC,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;YACxC,MAAM,IAAI,QAAQ,CACjB,YAAY,UAAU,0BAA0B,UAAU,GAAG,EAC7D,EAAE,QAAQ,EAAE,CAAC,EAAE,CACf,CAAA;QACF,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAC9B,MAAM,MAAM,GAAG,oBAAoB,CAClC,MAAM,yBAAyB,CAAC,MAAmB,CAAC,EACpD,EAAE,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CACnE,CAAA;QACD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;QAClE,IAAI,OAAO,KAAK,MAAM;YAAE,OAAM;QAC9B,IAAI,KAAK,CAAC,KAAK;YACd,MAAM,IAAI,QAAQ,CACjB,YAAY,KAAK,CAAC,GAAG,+CAA+C,CACpE,CAAA;QACF,MAAM,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;QAC5B,OAAO,kBAAkB,KAAK,CAAC,GAAG,EAAE,CAAA;IACrC,CAAC;CACD,CAAC,CAAA;AAEF,MAAM,GAAG,GAAG,SAAS,CAAC;IACrB,IAAI,EAAE,SAAS;IACf,QAAQ,EAAE,EAAE,iBAAiB,EAAE,cAAc,EAAE;CAC/C,CAAC,CAAA;AAEF,iFAAiF;AACjF,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,CAAA","sourcesContent":["#!/usr/bin/env node\nimport { readFile, writeFile } from 'node:fs/promises'\nimport { resolve } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport type { AnyRouter } from '@orpc/server'\nimport { CliError, defineCli, defineCommand } from 'hanzio/cli'\nimport { z } from 'zod'\nimport { createStreamManifestAsync, renderStreamManifest } from './router.js'\n\nconst streamManifest = defineCommand({\n\tdescription:\n\t\t\"Write the paths of a router's streaming routes for createRpcLink({ streamPaths }), only when they change. Importing the router runs its module code: load .ts routers with bun, tsx or node >= 22.18.\",\n\tinput: z.object({\n\t\tmodule: z\n\t\t\t.string()\n\t\t\t.describe('Router module, with #export for a named export')\n\t\t\t.meta({ positional: true }),\n\t\tout: z.string().describe('Manifest to write: file.ts or file.json'),\n\t\tname: z\n\t\t\t.string()\n\t\t\t.optional()\n\t\t\t.describe('Exported constant name in a .ts file'),\n\t\tcheck: z.boolean().describe('Exit 1 when the file is missing or stale')\n\t}),\n\trun: async (input) => {\n\t\tconst [modulePath = '', exportName = 'default'] = input.module.split('#')\n\t\tconst loaded: Record<string, unknown> = await import(\n\t\t\tpathToFileURL(resolve(modulePath)).href\n\t\t)\n\t\tconst router = loaded[exportName]\n\t\tif (!router || typeof router !== 'object')\n\t\t\tthrow new CliError(\n\t\t\t\t`orpc-fn: ${modulePath} has no router export \"${exportName}\"`,\n\t\t\t\t{ exitCode: 2 }\n\t\t\t)\n\t\tconst out = resolve(input.out)\n\t\tconst source = renderStreamManifest(\n\t\t\tawait createStreamManifestAsync(router as AnyRouter),\n\t\t\t{ format: out.endsWith('.json') ? 'json' : 'ts', name: input.name }\n\t\t)\n\t\tconst current = await readFile(out, 'utf8').catch(() => undefined)\n\t\tif (current === source) return\n\t\tif (input.check)\n\t\t\tthrow new CliError(\n\t\t\t\t`orpc-fn: ${input.out} is stale; rerun without --check to update it`\n\t\t\t)\n\t\tawait writeFile(out, source)\n\t\treturn `orpc-fn: wrote ${input.out}`\n\t}\n})\n\nconst cli = defineCli({\n\tname: 'orpc-fn',\n\tcommands: { 'stream-manifest': streamManifest }\n})\n\n// Exit explicitly: the imported router may hold open database or broker handles.\nprocess.exit(await cli.run())\n"]}
package/dist/client.d.ts CHANGED
@@ -42,5 +42,34 @@ export declare function createRpcLink<T extends ClientContext = ClientContext>(o
42
42
  * route errors as `ORPCError`, so `instanceof` plus `code` is the contract.
43
43
  */
44
44
  export declare function hasOrpcErrorCode<TCode extends ORPCErrorCode>(error: unknown, code: TCode): error is ORPCError<TCode, unknown>;
45
+ /**
46
+ * The server's message, only when the backend marked it user-safe by putting
47
+ * a `userMessage` string on the error's `data`. Route error messages are
48
+ * developer text (English, internal model names), so they are not shown by
49
+ * default; they stay on the error for logs and error tracking.
50
+ *
51
+ * ```ts
52
+ * throw new ORPCError('BAD_REQUEST', {
53
+ * message: 'Deal has no pipeline stage', // logs, telemetry
54
+ * data: { userMessage: 'Choose a stage first.' } // the user reads this
55
+ * })
56
+ * ```
57
+ */
58
+ export declare function userSafeServerMessage(error: unknown): string | null;
59
+ /** User-facing text for a caught error: the opted-in server message, else your localized fallback. */
60
+ export declare function userFacingErrorMessage(error: unknown, fallback: string): string;
61
+ /**
62
+ * `serialize`/`deserialize` for a TanStack Query persister. API payloads carry
63
+ * `Date`s (and maybe `Map`s, `Set`s, `BigInt`s) that plain JSON returns as
64
+ * strings; oRPC's own serializer round-trips them.
65
+ *
66
+ * ```ts
67
+ * createAsyncStoragePersister({ storage: AsyncStorage, ...queryCacheSerializer })
68
+ * ```
69
+ */
70
+ export declare const queryCacheSerializer: {
71
+ serialize(value: unknown): string;
72
+ deserialize<T>(text: string): T;
73
+ };
45
74
  export {};
46
75
  //# 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;AAEtD,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"}
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"}
package/dist/client.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { ORPCError } from '@orpc/client';
2
2
  import { RPCLink } from '@orpc/client/fetch';
3
3
  import { BatchLinkPlugin } from '@orpc/client/plugins';
4
+ import { StandardRPCJsonSerializer } from '@orpc/client/standard';
4
5
  /**
5
6
  * Streaming responses cannot be batched, and the request does not say a
6
7
  * response will stream - so streaming routes are recognised by name. The
@@ -48,4 +49,52 @@ export function createRpcLink(options) {
48
49
  export function hasOrpcErrorCode(error, code) {
49
50
  return error instanceof ORPCError && error.code === code;
50
51
  }
52
+ /**
53
+ * The server's message, only when the backend marked it user-safe by putting
54
+ * a `userMessage` string on the error's `data`. Route error messages are
55
+ * developer text (English, internal model names), so they are not shown by
56
+ * default; they stay on the error for logs and error tracking.
57
+ *
58
+ * ```ts
59
+ * throw new ORPCError('BAD_REQUEST', {
60
+ * message: 'Deal has no pipeline stage', // logs, telemetry
61
+ * data: { userMessage: 'Choose a stage first.' } // the user reads this
62
+ * })
63
+ * ```
64
+ */
65
+ export function userSafeServerMessage(error) {
66
+ if (!(error instanceof ORPCError))
67
+ return null;
68
+ const data = error.data;
69
+ if (typeof data !== 'object' || data === null)
70
+ return null;
71
+ const userMessage = data.userMessage;
72
+ return typeof userMessage === 'string' && userMessage.trim().length > 0
73
+ ? userMessage
74
+ : null;
75
+ }
76
+ /** User-facing text for a caught error: the opted-in server message, else your localized fallback. */
77
+ export function userFacingErrorMessage(error, fallback) {
78
+ return userSafeServerMessage(error) ?? fallback;
79
+ }
80
+ const cacheSerializer = new StandardRPCJsonSerializer();
81
+ /**
82
+ * `serialize`/`deserialize` for a TanStack Query persister. API payloads carry
83
+ * `Date`s (and maybe `Map`s, `Set`s, `BigInt`s) that plain JSON returns as
84
+ * strings; oRPC's own serializer round-trips them.
85
+ *
86
+ * ```ts
87
+ * createAsyncStoragePersister({ storage: AsyncStorage, ...queryCacheSerializer })
88
+ * ```
89
+ */
90
+ export const queryCacheSerializer = {
91
+ serialize(value) {
92
+ const [json, meta] = cacheSerializer.serialize(value);
93
+ return JSON.stringify({ json, meta });
94
+ },
95
+ deserialize(text) {
96
+ const { json, meta } = JSON.parse(text);
97
+ return cacheSerializer.deserialize(json, meta);
98
+ }
99
+ };
51
100
  //# sourceMappingURL=client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,SAAS,EAAsB,MAAM,cAAc,CAAA;AAChF,OAAO,EAAE,OAAO,EAAuB,MAAM,oBAAoB,CAAA;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AA4BtD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,IAAuB,EAAE,EAAE,CAC7D,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAA;AAEpE;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAC5B,OAAgC;IAEhC,MAAM,EAAE,KAAK,GAAG,IAAI,EAAE,OAAO,GAAG,EAAE,EAAE,WAAW,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,CAAA;IACpE,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACzE,IAAI,KAAK,KAAK,KAAK;QAAE,OAAO,IAAI,OAAO,CAAI,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,CAAC,CAAA;IAChE,MAAM,EACL,OAAO,GAAG,EAAE,EACZ,OAAO,GAAG,kBAAkB,EAC5B,MAAM,EACN,GAAG,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAA;IAC/B,OAAO,IAAI,OAAO,CAAI;QACrB,GAAG,IAAI;QACP,OAAO,EAAE;YACR,IAAI,eAAe,CAAI;gBACtB,OAAO;gBACP,MAAM,EAAE,MAAM,IAAI;oBACjB,kEAAkE;oBAClE,kDAAkD;oBAClD,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAO,EAAE;iBAC3C;gBACD,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAC9B,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;oBACjC,OAAO,CAAC,IAAI,CAAC;oBACb,CAAC,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;aAC1D,CAAC;YACF,GAAG,OAAO;SACV;KACD,CAAC,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC/B,KAAc,EACd,IAAW;IAEX,OAAO,KAAK,YAAY,SAAS,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,CAAA;AACzD,CAAC","sourcesContent":["import { type ClientContext, ORPCError, type ORPCErrorCode } from '@orpc/client'\nimport { RPCLink, type RPCLinkOptions } from '@orpc/client/fetch'\nimport { BatchLinkPlugin } from '@orpc/client/plugins'\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"]}
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 +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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"}
package/dist/create-fn.js CHANGED
@@ -259,7 +259,6 @@ export function createFn(options) {
259
259
  fnLive: createFnLive({
260
260
  fn,
261
261
  createChannel: live.createChannel,
262
- createLogger,
263
262
  isGuard
264
263
  }),
265
264
  createPubSub: live.createPubSub,