orpc-fn 0.3.0 → 0.5.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.
- package/README.md +146 -5
- package/dist/client.d.ts +29 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +49 -0
- package/dist/client.js.map +1 -1
- package/dist/create-fn.d.ts.map +1 -1
- package/dist/create-fn.js +2 -8
- package/dist/create-fn.js.map +1 -1
- package/dist/hono.d.ts +12 -1
- package/dist/hono.d.ts.map +1 -1
- package/dist/hono.js +15 -1
- package/dist/hono.js.map +1 -1
- package/dist/live/fn-live.d.ts +1 -6
- package/dist/live/fn-live.d.ts.map +1 -1
- package/dist/live/fn-live.js +2 -18
- package/dist/live/fn-live.js.map +1 -1
- package/dist/live/index.d.ts +3 -1
- package/dist/live/index.d.ts.map +1 -1
- package/dist/live/index.js +2 -0
- package/dist/live/index.js.map +1 -1
- package/dist/live/invalidation-stream.d.ts +52 -0
- package/dist/live/invalidation-stream.d.ts.map +1 -0
- package/dist/live/invalidation-stream.js +98 -0
- package/dist/live/invalidation-stream.js.map +1 -0
- package/dist/live/ioredis.d.ts +1 -0
- package/dist/live/ioredis.d.ts.map +1 -1
- package/dist/live/ioredis.js +1 -0
- package/dist/live/ioredis.js.map +1 -1
- package/dist/live/memory.d.ts +2 -1
- package/dist/live/memory.d.ts.map +1 -1
- package/dist/live/memory.js +6 -1
- package/dist/live/memory.js.map +1 -1
- package/dist/live/pub-sub.d.ts +5 -0
- package/dist/live/pub-sub.d.ts.map +1 -1
- package/dist/live/pub-sub.js +36 -15
- package/dist/live/pub-sub.js.map +1 -1
- package/dist/live/redis-bun.d.ts +3 -0
- package/dist/live/redis-bun.d.ts.map +1 -1
- package/dist/live/redis-bun.js +1 -0
- package/dist/live/redis-bun.js.map +1 -1
- package/dist/live/redis.d.ts +4 -1
- package/dist/live/redis.d.ts.map +1 -1
- package/dist/live/redis.js +14 -2
- package/dist/live/redis.js.map +1 -1
- package/dist/live/scoped-change-bus.d.ts +45 -0
- package/dist/live/scoped-change-bus.d.ts.map +1 -0
- package/dist/live/scoped-change-bus.js +38 -0
- package/dist/live/scoped-change-bus.js.map +1 -0
- package/dist/live/transport.d.ts +11 -0
- package/dist/live/transport.d.ts.map +1 -1
- package/dist/live/transport.js.map +1 -1
- package/dist/otel.d.ts +5 -0
- package/dist/otel.d.ts.map +1 -1
- package/dist/otel.js +7 -0
- package/dist/otel.js.map +1 -1
- package/dist/procedure-cli.d.ts +25 -0
- package/dist/procedure-cli.d.ts.map +1 -0
- package/dist/procedure-cli.js +67 -0
- package/dist/procedure-cli.js.map +1 -0
- package/dist/react.d.ts +116 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +156 -0
- package/dist/react.js.map +1 -0
- package/dist/realtime.d.ts +114 -0
- package/dist/realtime.d.ts.map +1 -0
- package/dist/realtime.js +316 -0
- package/dist/realtime.js.map +1 -0
- package/dist/vue/testing.d.ts +67 -0
- package/dist/vue/testing.d.ts.map +1 -0
- package/dist/vue/testing.js +225 -0
- package/dist/vue/testing.js.map +1 -0
- package/dist/vue.d.ts +211 -0
- package/dist/vue.d.ts.map +1 -0
- package/dist/vue.js +312 -0
- package/dist/vue.js.map +1 -0
- package/package.json +51 -4
- package/src/client.ts +57 -0
- package/src/create-fn.ts +2 -7
- package/src/hono.ts +22 -2
- package/src/live/fn-live.ts +3 -22
- package/src/live/index.ts +12 -1
- package/src/live/invalidation-stream.ts +142 -0
- package/src/live/ioredis.ts +2 -0
- package/src/live/memory.ts +11 -2
- package/src/live/pub-sub.ts +48 -17
- package/src/live/redis-bun.ts +3 -0
- package/src/live/redis.ts +33 -6
- package/src/live/scoped-change-bus.ts +98 -0
- package/src/live/transport.ts +12 -0
- package/src/otel.ts +8 -0
- package/src/procedure-cli.ts +99 -0
- package/src/react.ts +355 -0
- package/src/realtime.ts +416 -0
- package/src/vue/testing.ts +296 -0
- package/src/vue.ts +783 -0
package/dist/vue.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vue.js","sourceRoot":"","sources":["../src/vue.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype DefaultError,\n\ttype FetchQueryOptions,\n\tfocusManager,\n\thashKey,\n\ttype InferDataFromTag,\n\ttype InfiniteData,\n\ttype InvalidateQueryFilters,\n\ttype MutationObserverOptions,\n\ttype UseInfiniteQueryOptions,\n\ttype UseInfiniteQueryReturnType,\n\ttype UseMutationReturnType,\n\ttype UseQueryReturnType,\n\ttype Updater,\n\tkeepPreviousData,\n\ttype QueryKey,\n\ttype UseQueryOptions,\n\tuseInfiniteQuery as useVueInfiniteQuery,\n\tuseMutation as useVueMutation,\n\tuseQuery as useVueQuery,\n\tuseQueryClient\n} from '@tanstack/vue-query'\nimport {\n\ttype App,\n\tcomputed,\n\tgetCurrentScope,\n\thasInjectionContext,\n\ttype InjectionKey,\n\tinject,\n\ttype MaybeRefOrGetter,\n\tonScopeDispose,\n\ttype Ref,\n\tref,\n\tshallowRef,\n\ttoValue,\n\twatch\n} from 'vue'\nimport { userFacingErrorMessage } from './client.js'\nimport {\n\tcreateInvalidationCoordinator,\n\tcreateStreamScope,\n\tcreateSubscriptionRunner,\n\ttype InvalidationStreamOptions,\n\ttype LiveConnectionStatus,\n\tseedQueryFn\n} from './realtime.js'\n\nexport type { InvalidationDomain, LiveConnectionStatus } from './realtime.js'\n\n/**\n * Register your client types once so every composable infers them:\n *\n * ```ts\n * declare module 'orpc-fn/vue' {\n * interface Register { utils: typeof orpc; client: typeof client }\n * }\n * ```\n */\n// biome-ignore lint/suspicious/noEmptyInterface: filled in by app module augmentation\nexport interface Register {}\n\nexport type RegisteredOrpcUtils = Register extends { utils: infer T }\n\t? T\n\t: {\n\t\t\t'orpc-fn/vue: register your utils with `declare module \"orpc-fn/vue\" { interface Register { utils: typeof orpc } }`, or pass `orpc`': never\n\t\t}\nexport type RegisteredOrpcClient = Register extends { client: infer T }\n\t? T\n\t: unknown\n\n/** An explicit `<TData>`, else the data type oRPC tagged on the key. */\ntype CachedData<TData, TKey extends QueryKey> = [TData] extends [never]\n\t? InferDataFromTag<unknown, TKey>\n\t: TData\n\ntype ProvidedOrpc = { client: unknown; utils: unknown }\nconst orpcKey: InjectionKey<ProvidedOrpc> = Symbol('orpc-fn')\n\n/**\n * Make an oRPC client and its TanStack Query utils the app's default. A\n * surface that talks to another API (an embed with its own auth) passes\n * `orpc` to each composable instead and never needs this.\n */\nexport function provideOrpc<TClient, TUtils>(\n\tapp: App,\n\torpc: { client: TClient; utils: TUtils }\n): void {\n\tapp.provide(orpcKey, orpc)\n}\n\nconst missingOrpc = () =>\n\tnew Error(\n\t\t'orpc-fn/vue: no oRPC client provided. Call provideOrpc(app, { client, utils }) or pass `orpc` to the composable.'\n\t)\n\n/** The client and utils from {@link provideOrpc}. */\nexport function useOrpc(): {\n\tclient: RegisteredOrpcClient\n\tutils: RegisteredOrpcUtils\n} {\n\tconst provided = hasInjectionContext() ? inject(orpcKey, null) : null\n\tif (!provided) throw missingOrpc()\n\treturn provided as {\n\t\tclient: RegisteredOrpcClient\n\t\tutils: RegisteredOrpcUtils\n\t}\n}\n\n/** Resolve the provided utils now; fail only when they are needed and missing. */\nfunction useUtilsGetter<TOrpc>(\n\texplicit: MaybeRefOrGetter<TOrpc> | undefined\n): () => TOrpc {\n\tif (explicit) return () => toValue(explicit)\n\tconst provided = hasInjectionContext() ? inject(orpcKey, null) : null\n\treturn () => {\n\t\tif (!provided) throw missingOrpc()\n\t\treturn provided.utils as TOrpc\n\t}\n}\n\nlet documentVisible: Ref<boolean> | null = null\n/**\n * Whether the page is visible, from TanStack's `focusManager` (which follows\n * `visibilitychange` in browsers). Live streams pause while it is false.\n */\nfunction useDocumentVisible(): Ref<boolean> {\n\tif (!documentVisible) {\n\t\tconst visible = ref(focusManager.isFocused())\n\t\tfocusManager.subscribe((focused) => {\n\t\t\tvisible.value = focused\n\t\t})\n\t\tdocumentVisible = visible\n\t}\n\treturn documentVisible\n}\n\nfunction resolveQueryEnabled(enabled: unknown): boolean {\n\tif (enabled === undefined) return true\n\tif (typeof enabled === 'function') return Boolean(enabled())\n\treturn Boolean(toValue(enabled))\n}\n\n/**\n * Picks the stream to open from the utils. A procedure's `call` fits as is:\n * `(o) => o.order.watch.call`. The composable passes the input and an abort signal.\n */\nexport type OrpcSubscribeFn<TInput, TEvent, TOrpc = RegisteredOrpcUtils> = (\n\torpc: TOrpc\n) => (\n\tinput: TInput,\n\toptions: { signal: AbortSignal }\n) => Promise<AsyncIterable<TEvent>>\n\nexport interface UseSubscriptionOptions<TEvent, TOrpc = RegisteredOrpcUtils> {\n\t/** Start the stream on its own (default: true). With false, call `connect()`. */\n\timmediate?: MaybeRefOrGetter<boolean> | undefined\n\tonEvent?: (event: TEvent, meta?: { connectionId: number }) => void\n\tonError?: ((error: Error) => void) | undefined\n\t/** Every successful connect; `connectionId > 1` is a reconnect. */\n\tonConnect?: (connectionId: number) => void\n\tonClose?: () => void\n\t/** Events kept in `events` (default: 100). 1 keeps only `latestEvent`. */\n\tmaxEvents?: number\n\t/** Reconnect when the input changes (default: true). */\n\tautoReconnectOnInputChange?: boolean\n\t/**\n\t * After a connect, reconnect with backoff when the stream ends or errors\n\t * unexpectedly (proxy idle timeout, network blip). Deliberate teardown\n\t * does not retry. Default: true.\n\t */\n\tautoReconnectOnClose?: boolean\n\torpc?: MaybeRefOrGetter<TOrpc> | undefined\n\t/**\n\t * Close the stream while the page is hidden (default: true), so a\n\t * background tab holds no server connection. It reconnects, and a live\n\t * route re-sends its snapshot, when the page is visible again.\n\t */\n\trespectVisibility?: boolean | undefined\n}\n\nexport interface UseSubscriptionReturn<TEvent> {\n\tevents: Ref<TEvent[]>\n\tlatestEvent: Ref<TEvent | undefined>\n\tstatus: Ref<LiveConnectionStatus>\n\terror: Ref<Error | null>\n\tisConnected: Ref<boolean>\n\tconnect: () => Promise<void>\n\tdisconnect: () => Promise<void>\n\tclearEvents: () => void\n}\n\n/**\n * An oRPC event iterator as reactive state, with backoff reconnects and a\n * pause while the page is hidden. Closed when the owning effect scope stops.\n */\nexport function useOrpcSubscription<\n\tTInput,\n\tTEvent,\n\tTOrpc = RegisteredOrpcUtils\n>(\n\tinput: MaybeRefOrGetter<TInput>,\n\tsubscribeFn: OrpcSubscribeFn<TInput, TEvent, TOrpc>,\n\toptions: UseSubscriptionOptions<TEvent, TOrpc> = {}\n): UseSubscriptionReturn<TEvent> {\n\tconst {\n\t\timmediate = true,\n\t\tmaxEvents = 100,\n\t\tautoReconnectOnInputChange = true,\n\t\tautoReconnectOnClose = true,\n\t\trespectVisibility = true\n\t} = options\n\tconst getUtils = useUtilsGetter(options.orpc)\n\tconst visible = useDocumentVisible()\n\n\t// May a stream run at all? This gates a manual connect() too.\n\tconst isStreamAllowed = computed(() => !respectVisibility || visible.value)\n\t// Should it start on its own? That is what `immediate` means.\n\tconst shouldAutoConnect = computed(\n\t\t() => toValue(immediate) && isStreamAllowed.value\n\t)\n\n\t// Shallow: event payloads are replaced, never mutated, so deep proxies are wasted work.\n\tconst events = shallowRef<TEvent[]>([])\n\tconst latestEvent = shallowRef<TEvent | undefined>(undefined)\n\tconst status = ref<LiveConnectionStatus>('idle')\n\tconst error = ref<Error | null>(null)\n\n\tconst runner = createSubscriptionRunner<TEvent>({\n\t\topen: (signal) => subscribeFn(getUtils())(toValue(input), { signal }),\n\t\tcanConnect: () => isStreamAllowed.value,\n\t\tshouldReconnect: () => autoReconnectOnClose && shouldAutoConnect.value,\n\t\tonStatus: (next) => {\n\t\t\tstatus.value = next\n\t\t\tif (next === 'connecting') error.value = null\n\t\t},\n\t\tonEvent: (event, connectionId) => {\n\t\t\tlatestEvent.value = event\n\t\t\tif (maxEvents > 1) {\n\t\t\t\tevents.value = [...events.value.slice(-(maxEvents - 1)), event]\n\t\t\t}\n\t\t\toptions.onEvent?.(event, { connectionId })\n\t\t},\n\t\tonError: (err) => {\n\t\t\terror.value = err\n\t\t\toptions.onError?.(err)\n\t\t},\n\t\tonConnect: options.onConnect,\n\t\tonClose: options.onClose\n\t})\n\n\tfunction clearEvents() {\n\t\tevents.value = []\n\t\tlatestEvent.value = undefined\n\t}\n\n\tif (autoReconnectOnInputChange) {\n\t\twatch(\n\t\t\t() => toValue(input),\n\t\t\tasync () => {\n\t\t\t\tconst wasLive = runner.isLive()\n\t\t\t\tif (runner.getStatus() !== 'idle') {\n\t\t\t\t\tawait runner.disconnect()\n\t\t\t\t\tclearEvents()\n\t\t\t\t}\n\t\t\t\t// Reconnect in auto mode, or in manual mode when it was running.\n\t\t\t\tif (shouldAutoConnect.value || (wasLive && isStreamAllowed.value)) {\n\t\t\t\t\tawait runner.start()\n\t\t\t\t}\n\t\t\t},\n\t\t\t{ deep: true }\n\t\t)\n\t}\n\n\t// Visibility closes the stream in both modes; only the re-open waits for `immediate`.\n\twatch(\n\t\tisStreamAllowed,\n\t\t(allowed) => {\n\t\t\tif (!allowed) void runner.disconnect()\n\t\t\telse if (toValue(immediate)) {\n\t\t\t\terror.value = null\n\t\t\t\tvoid runner.start()\n\t\t\t}\n\t\t},\n\t\t{ immediate: true }\n\t)\n\n\twatch(\n\t\t() => toValue(immediate),\n\t\t(auto) => {\n\t\t\tif (!auto) void runner.disconnect()\n\t\t\telse if (isStreamAllowed.value) {\n\t\t\t\terror.value = null\n\t\t\t\tvoid runner.start()\n\t\t\t}\n\t\t}\n\t)\n\n\t// A detached effect scope may own the stream, not just a component.\n\tif (getCurrentScope()) onScopeDispose(() => void runner.dispose())\n\n\treturn {\n\t\tevents,\n\t\tlatestEvent,\n\t\tstatus,\n\t\terror,\n\t\tisConnected: computed(() => status.value === 'connected'),\n\t\tconnect: runner.connect,\n\t\tdisconnect: runner.disconnect,\n\t\tclearEvents\n\t}\n}\n\n/**\n * TanStack Query composables that receive the oRPC utils, so call sites read\n * `useQuery((o) => o.order.get.queryOptions({ input }))`. Pass `orpc` to bind\n * another client than the provided one.\n */\nexport function useApiClient<TOrpc = RegisteredOrpcUtils>(clientOptions?: {\n\torpc?: MaybeRefOrGetter<TOrpc> | undefined\n}) {\n\tconst queryClient = useQueryClient()\n\tconst getO = useUtilsGetter(clientOptions?.orpc)\n\n\t// Each wrapper forwards TanStack's own generics, so `select`, typed errors,\n\t// page params and oRPC's tagged keys infer exactly as with vue-query itself.\n\tfunction useQuery<\n\t\tTQueryFnData,\n\t\tTError = DefaultError,\n\t\tTData = TQueryFnData,\n\t\tTQueryKey extends QueryKey = QueryKey\n\t>(\n\t\toptionsFn: (\n\t\t\torpc: TOrpc\n\t\t) => UseQueryOptions<TQueryFnData, TError, TData, TQueryFnData, TQueryKey>\n\t): UseQueryReturnType<TData, TError> {\n\t\t// A computed is a ref at runtime; TypeScript cannot relate a computed of\n\t\t// the generic options to vue-query's MaybeRef parameter, so it is asserted.\n\t\tconst options = computed(() => optionsFn(getO()))\n\t\treturn useVueQuery<TQueryFnData, TError, TData, TQueryKey>(\n\t\t\toptions as unknown as UseQueryOptions<\n\t\t\t\tTQueryFnData,\n\t\t\t\tTError,\n\t\t\t\tTData,\n\t\t\t\tTQueryFnData,\n\t\t\t\tTQueryKey\n\t\t\t>\n\t\t)\n\t}\n\n\t/** `useInfiniteQuery((o) => o.order.list.infiniteOptions({ input, initialPageParam, getNextPageParam }))` */\n\tfunction useInfiniteQuery<\n\t\tTQueryFnData,\n\t\tTError = DefaultError,\n\t\tTData = InfiniteData<TQueryFnData>,\n\t\tTQueryKey extends QueryKey = QueryKey,\n\t\tTPageParam = unknown\n\t>(\n\t\toptionsFn: (\n\t\t\torpc: TOrpc\n\t\t) => UseInfiniteQueryOptions<\n\t\t\tTQueryFnData,\n\t\t\tTError,\n\t\t\tTData,\n\t\t\tTQueryKey,\n\t\t\tTPageParam\n\t\t>\n\t): UseInfiniteQueryReturnType<TData, TError> {\n\t\tconst options = computed(() => optionsFn(getO()))\n\t\treturn useVueInfiniteQuery<\n\t\t\tTQueryFnData,\n\t\t\tTError,\n\t\t\tTData,\n\t\t\tTQueryKey,\n\t\t\tTPageParam\n\t\t>(\n\t\t\toptions as unknown as UseInfiniteQueryOptions<\n\t\t\t\tTQueryFnData,\n\t\t\t\tTError,\n\t\t\t\tTData,\n\t\t\t\tTQueryKey,\n\t\t\t\tTPageParam\n\t\t\t>\n\t\t)\n\t}\n\n\t/**\n\t * With `initialInput`, also returns `inputData`, a ref that `mutate()` and\n\t * `mutateAsync()` use when called without variables (form state).\n\t */\n\tfunction useMutation<\n\t\tTData = unknown,\n\t\tTError = DefaultError,\n\t\tTVariables = void,\n\t\tTContext = unknown\n\t>(\n\t\toptionsFn: (\n\t\t\torpc: TOrpc\n\t\t) => MutationObserverOptions<TData, TError, TVariables, TContext>,\n\t\tconfig: { initialInput: TVariables }\n\t): UseMutationReturnType<TData, TError, TVariables, TContext> & {\n\t\tinputData: Ref<TVariables>\n\t\tmutate: (variables?: TVariables) => void\n\t\tmutateAsync: (variables?: TVariables) => Promise<TData>\n\t}\n\tfunction useMutation<\n\t\tTData = unknown,\n\t\tTError = DefaultError,\n\t\tTVariables = void,\n\t\tTContext = unknown\n\t>(\n\t\toptionsFn: (\n\t\t\torpc: TOrpc\n\t\t) => MutationObserverOptions<TData, TError, TVariables, TContext>\n\t): UseMutationReturnType<TData, TError, TVariables, TContext>\n\tfunction useMutation<TData, TError, TVariables, TContext>(\n\t\toptionsFn: (\n\t\t\torpc: TOrpc\n\t\t) => MutationObserverOptions<TData, TError, TVariables, TContext>,\n\t\tconfig?: { initialInput: TVariables }\n\t) {\n\t\tconst mutation = useVueMutation(computed(() => optionsFn(getO())))\n\t\tif (config?.initialInput === undefined) return mutation\n\t\tconst inputData = ref(config.initialInput) as Ref<TVariables>\n\t\treturn {\n\t\t\t...mutation,\n\t\t\tinputData,\n\t\t\tmutate: (variables?: TVariables) =>\n\t\t\t\tmutation.mutate(variables ?? inputData.value),\n\t\t\tmutateAsync: (variables?: TVariables) =>\n\t\t\t\tmutation.mutateAsync(variables ?? inputData.value)\n\t\t}\n\t}\n\n\tfunction useSubscription<TInput, TEvent>(\n\t\tinput: MaybeRefOrGetter<TInput>,\n\t\tsubscribeFn: OrpcSubscribeFn<TInput, TEvent, TOrpc>,\n\t\toptions: UseSubscriptionOptions<TEvent, TOrpc> = {}\n\t) {\n\t\treturn useOrpcSubscription<TInput, TEvent, TOrpc>(input, subscribeFn, {\n\t\t\t...options,\n\t\t\torpc: getO\n\t\t})\n\t}\n\n\tconst keyOf = <TKey extends QueryKey>(keyFn: (orpc: TOrpc) => TKey) =>\n\t\tkeyFn(getO())\n\n\treturn {\n\t\tuseQuery,\n\t\tuseInfiniteQuery,\n\t\tuseMutation,\n\t\tuseSubscription,\n\t\tinvalidate: (\n\t\t\tkeyFn: (orpc: TOrpc) => QueryKey,\n\t\t\tfilters?: Omit<InvalidateQueryFilters, 'queryKey'>\n\t\t) => queryClient.invalidateQueries({ queryKey: keyOf(keyFn), ...filters }),\n\t\t/** Route preloading and hover-to-prefetch. */\n\t\tprefetch: <\n\t\t\tTQueryFnData,\n\t\t\tTError = DefaultError,\n\t\t\tTData = TQueryFnData,\n\t\t\tTQueryKey extends QueryKey = QueryKey\n\t\t>(\n\t\t\toptionsFn: (\n\t\t\t\torpc: TOrpc\n\t\t\t) => FetchQueryOptions<TQueryFnData, TError, TData, TQueryKey>\n\t\t) => queryClient.prefetchQuery(optionsFn(getO())),\n\t\t/** Data is typed from oRPC's tagged key; pass `<TData>` for an untagged one. */\n\t\tgetQueryData: <TData = never, TKey extends QueryKey = QueryKey>(\n\t\t\tkeyFn: (orpc: TOrpc) => TKey\n\t\t): CachedData<TData, TKey> | undefined =>\n\t\t\tqueryClient.getQueryData<CachedData<TData, TKey>>(keyOf(keyFn)),\n\t\t/** Optimistic updates, typed like `getQueryData`. */\n\t\tsetQueryData: <TData = never, TKey extends QueryKey = QueryKey>(\n\t\t\tkeyFn: (orpc: TOrpc) => TKey,\n\t\t\tupdater: Updater<\n\t\t\t\tCachedData<TData, TKey> | undefined,\n\t\t\t\tCachedData<TData, TKey> | undefined\n\t\t\t>\n\t\t) =>\n\t\t\tqueryClient.setQueryData<CachedData<TData, TKey>>(keyOf(keyFn), updater),\n\t\tcancelQueries: (keyFn: (orpc: TOrpc) => QueryKey) =>\n\t\t\tqueryClient.cancelQueries({ queryKey: keyOf(keyFn) }),\n\t\tremoveQueries: (keyFn: (orpc: TOrpc) => QueryKey) =>\n\t\t\tqueryClient.removeQueries({ queryKey: keyOf(keyFn) }),\n\t\tisFetching: (keyFn?: (orpc: TOrpc) => QueryKey) =>\n\t\t\tqueryClient.isFetching(keyFn ? { queryKey: keyOf(keyFn) } : {}),\n\t\tqueryClient\n\t}\n}\n\ntype ApplyEvent<TStreamEvent, TData> = (\n\tevent: TStreamEvent,\n\tprevious: TData | undefined\n) => TData | undefined\n\ntype LiveQueryOptions<\n\tTInput,\n\tTRouteData,\n\tTStreamEvent,\n\tTData,\n\tTOrpc,\n\tTSubscriptionInput\n> = {\n\timmediate?: MaybeRefOrGetter<boolean>\n\torpc?: MaybeRefOrGetter<TOrpc>\n\t/** Input for the stream when it differs from the query's. */\n\tsubscriptionInput?: MaybeRefOrGetter<TSubscriptionInput>\n\t/**\n\t * Convert the route output once at fetch time into a differently typed\n\t * cache; `applyEvent` then reduces stream events into that cache.\n\t */\n\tselect?: (data: TRouteData) => TData\n\t/**\n\t * The raw route output of a seed before `select` drops fields. `input` is\n\t * what the seed was fetched for, so a seed that resolves after the input\n\t * moved on can be told apart.\n\t */\n\tonSeed?: (data: TRouteData, input: TInput) => void\n\t/**\n\t * Fold a stream event into the cached data. Required when the stream sends\n\t * something other than the cached type (an `fnLive` patch route, or a\n\t * `select`ed cache). Return `undefined` to skip the write.\n\t */\n\tapplyEvent?: ApplyEvent<TStreamEvent, NoInfer<TData>>\n\tonEvent?: (event: TStreamEvent, meta?: { connectionId: number }) => void\n\tonError?: (error: Error) => void\n\t/**\n\t * Reconcile a fetched seed with what the stream already wrote. The HTTP\n\t * request can resolve last, so a plain overwrite would drop events that\n\t * arrived while it was in flight. Default: take the fetched value.\n\t */\n\tmergeSeed?: (\n\t\tfetched: NoInfer<TData>,\n\t\tcached: NoInfer<TData> | undefined\n\t) => NoInfer<TData>\n\t/**\n\t * Show the previous key's data while a new key loads (default: true). Pass\n\t * false when the key says which entity is shown, not just how it is\n\t * filtered: another conversation's timeline must not stand in for this one.\n\t */\n\tkeepPreviousData?: boolean\n\t/** Pause the stream while the page is hidden (default: true). */\n\trespectVisibility?: boolean\n}\n\ntype LiveQueryReturn<TData, TError> = ReturnType<\n\ttypeof useVueQuery<TData, TError>\n> & { liveStatus: Ref<LiveConnectionStatus> }\n\n/**\n * The client half of `fnLive`: the HTTP query seeds the cache, and the live\n * route's stream writes each snapshot into the same cache entry. Marked\n * `meta.live`, so invalidation streams leave it alone.\n *\n * One signature, not overloads: TypeScript fixes an untyped callback's\n * parameters at the first overload it tries, which made valid calls fail.\n */\nexport function useLiveQuery<\n\tTInput,\n\tTRouteData,\n\tTStreamEvent,\n\tTData = TRouteData,\n\tTError = Error,\n\tTOrpc = RegisteredOrpcUtils,\n\tTSubscriptionInput = TInput\n>(\n\tinput: MaybeRefOrGetter<TInput>,\n\tqueryOptionsFn: (\n\t\tinput: TInput,\n\t\torpc: TOrpc\n\t) => UseQueryOptions<TRouteData, TError> & { queryKey: QueryKey },\n\tsubscribeFn: OrpcSubscribeFn<\n\t\tNoInfer<TSubscriptionInput>,\n\t\tTStreamEvent,\n\t\tTOrpc\n\t>,\n\t...options: [TStreamEvent] extends [TData]\n\t\t? [\n\t\t\t\toptions?: LiveQueryOptions<\n\t\t\t\t\tTInput,\n\t\t\t\t\tTRouteData,\n\t\t\t\t\tTStreamEvent,\n\t\t\t\t\tTData,\n\t\t\t\t\tTOrpc,\n\t\t\t\t\tTSubscriptionInput\n\t\t\t\t>\n\t\t\t]\n\t\t: [\n\t\t\t\toptions: LiveQueryOptions<\n\t\t\t\t\tTInput,\n\t\t\t\t\tTRouteData,\n\t\t\t\t\tTStreamEvent,\n\t\t\t\t\tTData,\n\t\t\t\t\tTOrpc,\n\t\t\t\t\tTSubscriptionInput\n\t\t\t\t> & { applyEvent: ApplyEvent<TStreamEvent, NoInfer<TData>> }\n\t\t\t]\n): LiveQueryReturn<TData, TError>\nexport function useLiveQuery<\n\tTInput,\n\tTRouteData,\n\tTStreamEvent,\n\tTData,\n\tTError,\n\tTOrpc,\n\tTSubscriptionInput\n>(\n\tinput: MaybeRefOrGetter<TInput>,\n\tqueryOptionsFn: (\n\t\tinput: TInput,\n\t\torpc: TOrpc\n\t) => UseQueryOptions<TRouteData, TError> & { queryKey: QueryKey },\n\tsubscribeFn: OrpcSubscribeFn<TSubscriptionInput, TStreamEvent, TOrpc>,\n\toptions: LiveQueryOptions<\n\t\tTInput,\n\t\tTRouteData,\n\t\tTStreamEvent,\n\t\tTData,\n\t\tTOrpc,\n\t\tTSubscriptionInput\n\t> = {}\n): LiveQueryReturn<TData, TError> {\n\tconst queryClient = useQueryClient()\n\tconst getO = useUtilsGetter(options.orpc)\n\tconst visible = useDocumentVisible()\n\t// Without `applyEvent` the stream event is the cache type (the first overload).\n\tconst applyEvent =\n\t\toptions.applyEvent ?? ((event: TStreamEvent) => event as unknown as TData)\n\t// Recomputed when the input changes, not once per stream event.\n\tconst queryKey = computed(\n\t\t() => queryOptionsFn(toValue(input), getO()).queryKey\n\t)\n\tconst queryHash = computed(() => hashKey(queryKey.value))\n\tconst scope = createStreamScope()\n\n\tconst subscription = useOrpcSubscription(\n\t\tcomputed(\n\t\t\t() =>\n\t\t\t\t(options.subscriptionInput === undefined\n\t\t\t\t\t? toValue(input)\n\t\t\t\t\t: toValue(options.subscriptionInput)) as TSubscriptionInput\n\t\t),\n\t\tsubscribeFn,\n\t\t{\n\t\t\timmediate: options.immediate,\n\t\t\trespectVisibility: options.respectVisibility,\n\t\t\tonError: options.onError,\n\t\t\tmaxEvents: 1,\n\t\t\torpc: getO,\n\t\t\tonConnect: (connectionId) => scope.open(connectionId, queryHash.value),\n\t\t\tonEvent: (event, meta) => {\n\t\t\t\t// The consumer callback must not see an event for a scope no longer shown.\n\t\t\t\tif (!meta || !scope.owns(meta.connectionId, queryHash.value)) return\n\t\t\t\tconst key = queryKey.value\n\t\t\t\tconst next = applyEvent(event, queryClient.getQueryData<TData>(key))\n\t\t\t\tif (next !== undefined) queryClient.setQueryData(key, next)\n\t\t\t\toptions.onEvent?.(event, meta)\n\t\t\t}\n\t\t}\n\t)\n\n\tconst query = useVueQuery<TData, TError>(\n\t\tcomputed(() => {\n\t\t\tconst inputValue = toValue(input)\n\t\t\t// oRPC query options are plain objects at runtime; vue-query's type\n\t\t\t// wraps fields in MaybeRefDeep, so `queryFn` is read through one shape.\n\t\t\tconst raw = queryOptionsFn(inputValue, getO()) as unknown as {\n\t\t\t\tqueryKey: QueryKey\n\t\t\t\tqueryFn?: (context: unknown) => Promise<TRouteData>\n\t\t\t\tenabled?: unknown\n\t\t\t} & Record<string, unknown>\n\t\t\tconst wrapsSeed = options.select || options.mergeSeed\n\t\t\tconst queryFn =\n\t\t\t\traw.queryFn && wrapsSeed\n\t\t\t\t\t? seedQueryFn(raw.queryFn, {\n\t\t\t\t\t\t\tinput: inputValue,\n\t\t\t\t\t\t\treadCache: () => queryClient.getQueryData<TData>(raw.queryKey),\n\t\t\t\t\t\t\tselect: options.select,\n\t\t\t\t\t\t\tonSeed: options.onSeed,\n\t\t\t\t\t\t\tmergeSeed: options.mergeSeed\n\t\t\t\t\t\t})\n\t\t\t\t\t: raw.queryFn\n\t\t\treturn {\n\t\t\t\t...raw,\n\t\t\t\t...(queryFn ? { queryFn } : {}),\n\t\t\t\tenabled: visible.value && resolveQueryEnabled(raw.enabled),\n\t\t\t\t...(options.keepPreviousData === false\n\t\t\t\t\t? {}\n\t\t\t\t\t: { placeholderData: keepPreviousData }),\n\t\t\t\tstaleTime: Number.POSITIVE_INFINITY,\n\t\t\t\tmeta: { live: true }\n\t\t\t} as unknown as UseQueryOptions<TData, TError>\n\t\t})\n\t)\n\n\treturn Object.assign(query, { liveStatus: subscription.status })\n}\n\n/**\n * Treat a stream as a \"something changed\" ping: invalidate `keyFn` on each\n * event. Prefer {@link useLiveQuery} when the stream carries the data itself.\n */\nexport function useSubscriptionInvalidates<\n\tTInput,\n\tTEvent,\n\tTOrpc = RegisteredOrpcUtils\n>(\n\tinput: MaybeRefOrGetter<TInput>,\n\tsubscribeFn: OrpcSubscribeFn<TInput, TEvent, TOrpc>,\n\tkeyFn: (orpc: TOrpc) => QueryKey,\n\toptions: Pick<\n\t\tUseSubscriptionOptions<TEvent, TOrpc>,\n\t\t'immediate' | 'maxEvents' | 'respectVisibility' | 'orpc'\n\t> & {\n\t\tonEvent?: (event: TEvent) => void\n\t\t/** Return false to skip invalidation for this event. */\n\t\tshouldInvalidate?: (event: TEvent) => boolean\n\t} = {}\n): UseSubscriptionReturn<TEvent> {\n\tconst { onEvent, shouldInvalidate, ...subscriptionOptions } = options\n\tconst { useSubscription, invalidate } = useApiClient<TOrpc>({\n\t\torpc: options.orpc\n\t})\n\treturn useSubscription(input, subscribeFn, {\n\t\t...subscriptionOptions,\n\t\tonEvent: (event) => {\n\t\t\tonEvent?.(event)\n\t\t\tif (shouldInvalidate?.(event) === false) return\n\t\t\tvoid invalidate(keyFn)\n\t\t}\n\t})\n}\n\n/**\n * One app-wide stream of `{ domain }` change events that keeps list and count\n * queries fresh: each event invalidates its domain's query keys (debounced),\n * and a reconnect that proves healthy invalidates every domain to cover\n * events lost while it was down. Call once, in the signed-in shell.\n *\n * ```ts\n * useInvalidationStream({}, (o) => o.realtime.subscribeChanges.call, {\n * domains: { order: { debounceMs: 100, queryKeys: [orpc.order.key()] } }\n * })\n * ```\n */\nexport function useInvalidationStream<\n\tTInput,\n\tTEvent extends { domain: string },\n\tTOrpc = RegisteredOrpcUtils\n>(\n\tinput: MaybeRefOrGetter<TInput>,\n\tsubscribe: OrpcSubscribeFn<TInput, TEvent, TOrpc>,\n\toptions: InvalidationStreamOptions<TEvent> &\n\t\tPick<\n\t\t\tUseSubscriptionOptions<TEvent, TOrpc>,\n\t\t\t'immediate' | 'respectVisibility' | 'orpc'\n\t\t>\n): UseSubscriptionReturn<TEvent> {\n\tconst coordinator = createInvalidationCoordinator(useQueryClient(), options)\n\tif (getCurrentScope()) onScopeDispose(coordinator.dispose)\n\treturn useOrpcSubscription<TInput, TEvent, TOrpc>(input, subscribe, {\n\t\timmediate: options.immediate,\n\t\trespectVisibility: options.respectVisibility,\n\t\torpc: options.orpc,\n\t\tmaxEvents: 1,\n\t\tonEvent: coordinator.event,\n\t\tonConnect: coordinator.connect,\n\t\tonError: coordinator.lost,\n\t\tonClose: coordinator.lost\n\t})\n}\n\n/** Localized text for a failed query: your fallback unless the server marked its message user-safe. */\nexport function useQueryErrorMessage(\n\terror: MaybeRefOrGetter<unknown>,\n\tfallback: MaybeRefOrGetter<string>\n) {\n\treturn computed(() => {\n\t\tconst err = toValue(error)\n\t\treturn err ? userFacingErrorMessage(err, toValue(fallback)) : null\n\t})\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orpc-fn",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Define a backend function once: a typed oRPC procedure with tracing, logging, guards, injected handler extras, nested calls and live queries over Redis.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,11 +21,14 @@
|
|
|
21
21
|
"live-query",
|
|
22
22
|
"mastra",
|
|
23
23
|
"mcp",
|
|
24
|
+
"cli",
|
|
24
25
|
"hono",
|
|
25
26
|
"typescript",
|
|
26
27
|
"tanstack-query",
|
|
27
28
|
"expo",
|
|
28
|
-
"react-native"
|
|
29
|
+
"react-native",
|
|
30
|
+
"vue",
|
|
31
|
+
"react"
|
|
29
32
|
],
|
|
30
33
|
"engines": {
|
|
31
34
|
"node": ">=20"
|
|
@@ -71,6 +74,18 @@
|
|
|
71
74
|
"types": "./dist/expo.d.ts",
|
|
72
75
|
"import": "./dist/expo.js"
|
|
73
76
|
},
|
|
77
|
+
"./vue": {
|
|
78
|
+
"types": "./dist/vue.d.ts",
|
|
79
|
+
"import": "./dist/vue.js"
|
|
80
|
+
},
|
|
81
|
+
"./vue/testing": {
|
|
82
|
+
"types": "./dist/vue/testing.d.ts",
|
|
83
|
+
"import": "./dist/vue/testing.js"
|
|
84
|
+
},
|
|
85
|
+
"./react": {
|
|
86
|
+
"types": "./dist/react.d.ts",
|
|
87
|
+
"import": "./dist/react.js"
|
|
88
|
+
},
|
|
74
89
|
"./mcp/sdk": {
|
|
75
90
|
"types": "./dist/mcp/sdk.d.ts",
|
|
76
91
|
"import": "./dist/mcp/sdk.js"
|
|
@@ -78,6 +93,10 @@
|
|
|
78
93
|
"./mcp/server": {
|
|
79
94
|
"types": "./dist/mcp/server.d.ts",
|
|
80
95
|
"import": "./dist/mcp/server.js"
|
|
96
|
+
},
|
|
97
|
+
"./cli": {
|
|
98
|
+
"types": "./dist/procedure-cli.d.ts",
|
|
99
|
+
"import": "./dist/procedure-cli.js"
|
|
81
100
|
}
|
|
82
101
|
},
|
|
83
102
|
"types": "./dist/index.d.ts",
|
|
@@ -117,11 +136,16 @@
|
|
|
117
136
|
"@orpc/openapi": "^1.14.0",
|
|
118
137
|
"@orpc/server": "^1.14.0",
|
|
119
138
|
"@orpc/zod": "^1.14.0",
|
|
139
|
+
"hanzio": "^3.1.0",
|
|
120
140
|
"hono": "^4.0.0",
|
|
121
141
|
"ioredis": "^5.0.0",
|
|
122
142
|
"zod": "^4.0.0",
|
|
123
143
|
"@modelcontextprotocol/sdk": "^1.32.0",
|
|
124
|
-
"@modelcontextprotocol/server": "^2.0.0"
|
|
144
|
+
"@modelcontextprotocol/server": "^2.0.0",
|
|
145
|
+
"@tanstack/react-query": "^5.80.0",
|
|
146
|
+
"@tanstack/vue-query": "^5.80.0",
|
|
147
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
148
|
+
"vue": "^3.3.0"
|
|
125
149
|
},
|
|
126
150
|
"peerDependenciesMeta": {
|
|
127
151
|
"@mastra/core": {
|
|
@@ -139,6 +163,9 @@
|
|
|
139
163
|
"@orpc/zod": {
|
|
140
164
|
"optional": true
|
|
141
165
|
},
|
|
166
|
+
"hanzio": {
|
|
167
|
+
"optional": true
|
|
168
|
+
},
|
|
142
169
|
"hono": {
|
|
143
170
|
"optional": true
|
|
144
171
|
},
|
|
@@ -150,6 +177,18 @@
|
|
|
150
177
|
},
|
|
151
178
|
"@modelcontextprotocol/server": {
|
|
152
179
|
"optional": true
|
|
180
|
+
},
|
|
181
|
+
"@tanstack/react-query": {
|
|
182
|
+
"optional": true
|
|
183
|
+
},
|
|
184
|
+
"@tanstack/vue-query": {
|
|
185
|
+
"optional": true
|
|
186
|
+
},
|
|
187
|
+
"react": {
|
|
188
|
+
"optional": true
|
|
189
|
+
},
|
|
190
|
+
"vue": {
|
|
191
|
+
"optional": true
|
|
153
192
|
}
|
|
154
193
|
},
|
|
155
194
|
"devDependencies": {
|
|
@@ -165,14 +204,22 @@
|
|
|
165
204
|
"@orpc/json-schema": "1.14.8",
|
|
166
205
|
"@orpc/openapi": "1.14.8",
|
|
167
206
|
"@orpc/server": "1.14.8",
|
|
207
|
+
"@orpc/tanstack-query": "1.14.8",
|
|
168
208
|
"@orpc/zod": "1.14.8",
|
|
209
|
+
"@tanstack/react-query": "5.101.2",
|
|
210
|
+
"@tanstack/vue-query": "5.101.2",
|
|
169
211
|
"@types/bun": "^1.2.0",
|
|
170
212
|
"@types/node": "^22.0.0",
|
|
213
|
+
"@types/react": "19",
|
|
214
|
+
"@types/react-test-renderer": "19",
|
|
215
|
+
"hanzio": "^3.1.0",
|
|
171
216
|
"hono": "^4.12.31",
|
|
172
217
|
"ioredis": "5.8.2",
|
|
173
218
|
"publint": "^0.3.12",
|
|
219
|
+
"react": "19.2.3",
|
|
220
|
+
"react-test-renderer": "19.2.3",
|
|
174
221
|
"typescript": "^7.0.2",
|
|
175
|
-
"
|
|
222
|
+
"vue": "3.5.40",
|
|
176
223
|
"zod": "4.4.3"
|
|
177
224
|
},
|
|
178
225
|
"bugs": {
|
package/src/client.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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
|
+
import { StandardRPCJsonSerializer } from '@orpc/client/standard'
|
|
4
5
|
|
|
5
6
|
type BatchOptions<T extends ClientContext> = ConstructorParameters<
|
|
6
7
|
typeof BatchLinkPlugin<T>
|
|
@@ -87,3 +88,59 @@ export function hasOrpcErrorCode<TCode extends ORPCErrorCode>(
|
|
|
87
88
|
): error is ORPCError<TCode, unknown> {
|
|
88
89
|
return error instanceof ORPCError && error.code === code
|
|
89
90
|
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The server's message, only when the backend marked it user-safe by putting
|
|
94
|
+
* a `userMessage` string on the error's `data`. Route error messages are
|
|
95
|
+
* developer text (English, internal model names), so they are not shown by
|
|
96
|
+
* default; they stay on the error for logs and error tracking.
|
|
97
|
+
*
|
|
98
|
+
* ```ts
|
|
99
|
+
* throw new ORPCError('BAD_REQUEST', {
|
|
100
|
+
* message: 'Deal has no pipeline stage', // logs, telemetry
|
|
101
|
+
* data: { userMessage: 'Choose a stage first.' } // the user reads this
|
|
102
|
+
* })
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
export function userSafeServerMessage(error: unknown): string | null {
|
|
106
|
+
if (!(error instanceof ORPCError)) return null
|
|
107
|
+
const data: unknown = error.data
|
|
108
|
+
if (typeof data !== 'object' || data === null) return null
|
|
109
|
+
const userMessage = (data as { userMessage?: unknown }).userMessage
|
|
110
|
+
return typeof userMessage === 'string' && userMessage.trim().length > 0
|
|
111
|
+
? userMessage
|
|
112
|
+
: null
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** User-facing text for a caught error: the opted-in server message, else your localized fallback. */
|
|
116
|
+
export function userFacingErrorMessage(
|
|
117
|
+
error: unknown,
|
|
118
|
+
fallback: string
|
|
119
|
+
): string {
|
|
120
|
+
return userSafeServerMessage(error) ?? fallback
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const cacheSerializer = new StandardRPCJsonSerializer()
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* `serialize`/`deserialize` for a TanStack Query persister. API payloads carry
|
|
127
|
+
* `Date`s (and maybe `Map`s, `Set`s, `BigInt`s) that plain JSON returns as
|
|
128
|
+
* strings; oRPC's own serializer round-trips them.
|
|
129
|
+
*
|
|
130
|
+
* ```ts
|
|
131
|
+
* createAsyncStoragePersister({ storage: AsyncStorage, ...queryCacheSerializer })
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
export const queryCacheSerializer = {
|
|
135
|
+
serialize(value: unknown): string {
|
|
136
|
+
const [json, meta] = cacheSerializer.serialize(value)
|
|
137
|
+
return JSON.stringify({ json, meta })
|
|
138
|
+
},
|
|
139
|
+
deserialize<T>(text: string): T {
|
|
140
|
+
const { json, meta } = JSON.parse(text) as {
|
|
141
|
+
json: unknown
|
|
142
|
+
meta: Parameters<typeof cacheSerializer.deserialize>[1]
|
|
143
|
+
}
|
|
144
|
+
return cacheSerializer.deserialize(json, meta) as T
|
|
145
|
+
}
|
|
146
|
+
}
|
package/src/create-fn.ts
CHANGED
|
@@ -25,6 +25,7 @@ import { readFnMeta } from './meta.js'
|
|
|
25
25
|
import {
|
|
26
26
|
type AttributeValue,
|
|
27
27
|
createTracing,
|
|
28
|
+
errorAttributes,
|
|
28
29
|
errorMessageOf,
|
|
29
30
|
type OtelApiLike,
|
|
30
31
|
type SpanLike,
|
|
@@ -523,12 +524,7 @@ export function createFn<
|
|
|
523
524
|
procedure: event.procedure,
|
|
524
525
|
duration_ms: event.durationMs,
|
|
525
526
|
status: success ? 'success' : 'failed',
|
|
526
|
-
...(error === undefined
|
|
527
|
-
? {}
|
|
528
|
-
: {
|
|
529
|
-
error_type: error instanceof Error ? error.name : typeof error,
|
|
530
|
-
error_message: errorMessageOf(error)
|
|
531
|
-
})
|
|
527
|
+
...(error === undefined ? {} : errorAttributes(error))
|
|
532
528
|
})
|
|
533
529
|
}
|
|
534
530
|
|
|
@@ -546,7 +542,6 @@ export function createFn<
|
|
|
546
542
|
fnLive: createFnLive({
|
|
547
543
|
fn,
|
|
548
544
|
createChannel: live.createChannel,
|
|
549
|
-
createLogger,
|
|
550
545
|
isGuard
|
|
551
546
|
}),
|
|
552
547
|
createPubSub: live.createPubSub,
|
package/src/hono.ts
CHANGED
|
@@ -75,7 +75,10 @@ export type MountOrpcOptions<
|
|
|
75
75
|
plugins?: Plugin<InferRouterInitialContext<R>>[]
|
|
76
76
|
/** Rewrite request headers before they reach the context (see `normalizeExpoOrigin`). */
|
|
77
77
|
normalizeHeaders?: (headers: Headers) => Headers
|
|
78
|
-
/**
|
|
78
|
+
/**
|
|
79
|
+
* Time spent before the handler ran, reported as `queue`. Default: time
|
|
80
|
+
* since {@link markRequestArrival}, when the server marked the request.
|
|
81
|
+
*/
|
|
79
82
|
queueMs?: (request: Request) => number | undefined
|
|
80
83
|
/**
|
|
81
84
|
* Unexpected procedure errors (oRPC turns thrown errors into responses, so
|
|
@@ -86,6 +89,23 @@ export type MountOrpcOptions<
|
|
|
86
89
|
isExpectedError?: (error: unknown) => boolean
|
|
87
90
|
}
|
|
88
91
|
|
|
92
|
+
const arrivals = new WeakMap<Request, number>()
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Marks a request at the server's front door (`Bun.serve({ fetch })`), before
|
|
96
|
+
* routing and middleware. `mountOrpc` reports the time until the procedure
|
|
97
|
+
* runs as `queue_ms`: in-process queueing no handler timer can see.
|
|
98
|
+
*/
|
|
99
|
+
export function markRequestArrival(request: Request): void {
|
|
100
|
+
arrivals.set(request, performance.now())
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Milliseconds since {@link markRequestArrival}, or undefined when unmarked. */
|
|
104
|
+
export function msSinceRequestArrival(request: Request): number | undefined {
|
|
105
|
+
const arrivedAt = arrivals.get(request)
|
|
106
|
+
return arrivedAt === undefined ? undefined : performance.now() - arrivedAt
|
|
107
|
+
}
|
|
108
|
+
|
|
89
109
|
/**
|
|
90
110
|
* Expo native sends `expo-origin` but not `origin`; copy it so cookie-based
|
|
91
111
|
* auth (e.g. Better Auth) sees the origin.
|
|
@@ -175,7 +195,7 @@ export function mountOrpc<E extends Env, R extends AnyRouter>(
|
|
|
175
195
|
) => {
|
|
176
196
|
app.use(`${prefix}/*`, async (c, next) => {
|
|
177
197
|
const timing: RequestTiming = {}
|
|
178
|
-
const queueMs = options.queueMs
|
|
198
|
+
const queueMs = (options.queueMs ?? msSinceRequestArrival)(c.req.raw)
|
|
179
199
|
if (queueMs !== undefined)
|
|
180
200
|
timing.queue_ms = Math.round(queueMs * 100) / 100
|
|
181
201
|
const started = performance.now()
|
package/src/live/fn-live.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { ZodType, z } from 'zod'
|
|
|
4
4
|
import { RAW_INPUT } from '../timing.js'
|
|
5
5
|
import { LIVE_GAP, positiveInteger } from './lifecycle.js'
|
|
6
6
|
import type { FnLogger } from '../logger.js'
|
|
7
|
-
import { errorMessageOf
|
|
7
|
+
import { errorMessageOf } from '../otel.js'
|
|
8
8
|
import type {
|
|
9
9
|
FnContext,
|
|
10
10
|
FnDefinition,
|
|
@@ -100,8 +100,6 @@ export type FnLiveConfig<
|
|
|
100
100
|
}
|
|
101
101
|
) => MaybePromise<boolean>
|
|
102
102
|
authFn?: AuthFn<TInput, FnContext<TDef, TKey>>
|
|
103
|
-
/** Log publish failures instead of throwing them into the publishing route. */
|
|
104
|
-
safePublish?: boolean
|
|
105
103
|
/** Batch events arriving within this window into one snapshot update. */
|
|
106
104
|
coalesceMs?: number
|
|
107
105
|
/** Maximum events collected in one coalescing batch (default 1000). */
|
|
@@ -441,7 +439,6 @@ type RuntimeLiveConfig = Omit<ChannelOptions, 'name'> & {
|
|
|
441
439
|
reauthorize?: AuthFn<Record<string, unknown>, unknown>
|
|
442
440
|
transformerFn?: (params: RuntimeParams & Record<string, unknown>) => unknown
|
|
443
441
|
shouldUpdate?: (params: RuntimeParams & Record<string, unknown>) => unknown
|
|
444
|
-
safePublish?: boolean
|
|
445
442
|
coalesceMs?: number
|
|
446
443
|
maxBatchSize?: number
|
|
447
444
|
summary?: string
|
|
@@ -456,11 +453,9 @@ export function createFnLive(runtime: {
|
|
|
456
453
|
// biome-ignore lint/suspicious/noExplicitAny: runtime half of the typed `Fn`
|
|
457
454
|
fn: (options: any) => AnyProcedure
|
|
458
455
|
createChannel: (options: ChannelOptions) => Channel
|
|
459
|
-
createLogger: (scope: string, span: SpanLike | undefined) => FnLogger
|
|
460
456
|
isGuard: (option: string) => boolean
|
|
461
457
|
}) {
|
|
462
458
|
const { fn, createChannel, isGuard } = runtime
|
|
463
|
-
const liveLogger = runtime.createLogger('fn-live', undefined)
|
|
464
459
|
|
|
465
460
|
return (
|
|
466
461
|
options: {
|
|
@@ -495,22 +490,8 @@ export function createFnLive(runtime: {
|
|
|
495
490
|
// `createChannel` reads only the channel keys of the live config.
|
|
496
491
|
const channel = createChannel({ ...live, name: liveName })
|
|
497
492
|
|
|
498
|
-
const publish = async (event: unknown) => {
|
|
499
|
-
if (!live.safePublish) return channel.publish(event)
|
|
500
|
-
try {
|
|
501
|
-
await channel.publish(event)
|
|
502
|
-
} catch (err) {
|
|
503
|
-
// Live update failures must not roll back the route that emitted them,
|
|
504
|
-
// but they should still be visible in logs.
|
|
505
|
-
liveLogger.error('fnLive publish failed', {
|
|
506
|
-
name: liveName,
|
|
507
|
-
err: errorMessageOf(err)
|
|
508
|
-
})
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
493
|
const runHandler = (params: RuntimeParams) =>
|
|
513
|
-
handler({ ...params, publish })
|
|
494
|
+
handler({ ...params, publish: channel.publish })
|
|
514
495
|
|
|
515
496
|
const procedure = fn({
|
|
516
497
|
...routeConfig,
|
|
@@ -640,7 +621,7 @@ export function createFnLive(runtime: {
|
|
|
640
621
|
return {
|
|
641
622
|
procedure,
|
|
642
623
|
subscribe,
|
|
643
|
-
publish,
|
|
624
|
+
publish: channel.publish,
|
|
644
625
|
getChannelName: channel.getChannelName,
|
|
645
626
|
getSubscriptionChannelName: channel.getSubscriptionChannelName,
|
|
646
627
|
getPublishChannelName: channel.getPublishChannelName
|
package/src/live/index.ts
CHANGED
|
@@ -8,6 +8,10 @@ export {
|
|
|
8
8
|
streamLiveSnapshots,
|
|
9
9
|
throwInitialSnapshotError
|
|
10
10
|
} from './fn-live.js'
|
|
11
|
+
export {
|
|
12
|
+
type InvalidationEvent,
|
|
13
|
+
invalidationStream
|
|
14
|
+
} from './invalidation-stream.js'
|
|
11
15
|
export {
|
|
12
16
|
type AuthFn,
|
|
13
17
|
type ChannelDefinition,
|
|
@@ -25,9 +29,16 @@ export {
|
|
|
25
29
|
type PubSubOptions,
|
|
26
30
|
type PubSubRuntimeOptions
|
|
27
31
|
} from './pub-sub.js'
|
|
32
|
+
export {
|
|
33
|
+
type ChangeBusChannel,
|
|
34
|
+
type ChangeBusScope,
|
|
35
|
+
defineScopedChangeBus,
|
|
36
|
+
type ScopedChangeBus
|
|
37
|
+
} from './scoped-change-bus.js'
|
|
28
38
|
export {
|
|
29
39
|
type BacklogOptions,
|
|
30
40
|
backlogKey,
|
|
31
41
|
type PubSubMessage,
|
|
32
|
-
type PubSubTransport
|
|
42
|
+
type PubSubTransport,
|
|
43
|
+
type PubSubTransportStats
|
|
33
44
|
} from './transport.js'
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AnyProcedure,
|
|
3
|
+
call,
|
|
4
|
+
type Procedure,
|
|
5
|
+
type Schema
|
|
6
|
+
} from '@orpc/server'
|
|
7
|
+
|
|
8
|
+
/** A streaming procedure taking no input, such as a `createPubSub` subscribe route. */
|
|
9
|
+
/* biome-ignore-start lint/suspicious/noExplicitAny: matches any context, input, errors and meta */
|
|
10
|
+
type StreamProcedure<TEvent> = Procedure<
|
|
11
|
+
any,
|
|
12
|
+
any,
|
|
13
|
+
any,
|
|
14
|
+
Schema<any, AsyncIterable<TEvent>>,
|
|
15
|
+
any,
|
|
16
|
+
any
|
|
17
|
+
>
|
|
18
|
+
/* biome-ignore-end lint/suspicious/noExplicitAny: matches any context, input, errors and meta */
|
|
19
|
+
|
|
20
|
+
/** One event of an invalidation stream: the domain that changed, plus whatever its mapper added. */
|
|
21
|
+
export type InvalidationEvent<
|
|
22
|
+
TEvents extends Record<string, unknown>,
|
|
23
|
+
TExtras extends { [K in keyof TEvents]?: object }
|
|
24
|
+
> = {
|
|
25
|
+
[K in keyof TEvents & string]: { domain: K } & (K extends keyof TExtras
|
|
26
|
+
? Partial<TExtras[K]>
|
|
27
|
+
: unknown)
|
|
28
|
+
}[keyof TEvents & string]
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Fans several change streams into one stream of `{ domain }` events, so a
|
|
32
|
+
* client keeps one connection and invalidates queries per domain. Use it as
|
|
33
|
+
* the body of a streaming `fn`:
|
|
34
|
+
*
|
|
35
|
+
* ```ts
|
|
36
|
+
* fn({
|
|
37
|
+
* name: 'realtime.changes',
|
|
38
|
+
* stream: true,
|
|
39
|
+
* input: z.object({}),
|
|
40
|
+
* handler: ({ context, signal }) =>
|
|
41
|
+
* invalidationStream({
|
|
42
|
+
* context,
|
|
43
|
+
* signal,
|
|
44
|
+
* sources: { deal: dealChanges.subscribe, workItem: workItemChanges.subscribe },
|
|
45
|
+
* // Optional: forward fields a client acts on (alerts), not just the domain.
|
|
46
|
+
* events: { deal: (event) => (event.kind === 'won' ? { dealId: event.dealId } : undefined) }
|
|
47
|
+
* })
|
|
48
|
+
* })
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* Each source is called with `{}`, the caller's context and a signal of its
|
|
52
|
+
* own. Events that queue up while the consumer is busy collapse when equal.
|
|
53
|
+
* When one source ends, the stream ends (a deploy drain); when one throws,
|
|
54
|
+
* the stream throws. Either way every source is closed and the client
|
|
55
|
+
* reconnects, which recreates them all.
|
|
56
|
+
*/
|
|
57
|
+
export async function* invalidationStream<
|
|
58
|
+
TEvents extends Record<string, unknown>,
|
|
59
|
+
TExtras extends { [K in keyof TEvents]?: object } = Record<never, never>
|
|
60
|
+
>(options: {
|
|
61
|
+
context: unknown
|
|
62
|
+
signal?: AbortSignal | undefined
|
|
63
|
+
sources: { [K in keyof TEvents]: StreamProcedure<TEvents[K]> }
|
|
64
|
+
events?: {
|
|
65
|
+
[K in keyof TExtras]: (
|
|
66
|
+
value: NoInfer<TEvents[K & keyof TEvents]>
|
|
67
|
+
) => TExtras[K] | undefined
|
|
68
|
+
}
|
|
69
|
+
}): AsyncGenerator<InvalidationEvent<TEvents, TExtras>, void, unknown> {
|
|
70
|
+
type Event = InvalidationEvent<TEvents, TExtras>
|
|
71
|
+
const controller = new AbortController()
|
|
72
|
+
const abort = () => controller.abort()
|
|
73
|
+
if (options.signal?.aborted) abort()
|
|
74
|
+
options.signal?.addEventListener('abort', abort, { once: true })
|
|
75
|
+
|
|
76
|
+
// Keyed by the serialized event: equal events collapse while queued.
|
|
77
|
+
const queue = new Map<string, Event>()
|
|
78
|
+
let wake: (() => void) | undefined
|
|
79
|
+
let ended = false
|
|
80
|
+
let failure: { error: unknown } | undefined
|
|
81
|
+
const stop = (error?: { error: unknown }) => {
|
|
82
|
+
if (ended) return
|
|
83
|
+
ended = true
|
|
84
|
+
failure = error
|
|
85
|
+
controller.abort()
|
|
86
|
+
wake?.()
|
|
87
|
+
}
|
|
88
|
+
controller.signal.addEventListener('abort', () => stop(), { once: true })
|
|
89
|
+
|
|
90
|
+
const mappers = (options.events ?? {}) as Record<
|
|
91
|
+
string,
|
|
92
|
+
((value: unknown) => object | undefined) | undefined
|
|
93
|
+
>
|
|
94
|
+
const pump = async (domain: string, procedure: AnyProcedure) => {
|
|
95
|
+
let iterator: AsyncIterator<unknown> | undefined
|
|
96
|
+
try {
|
|
97
|
+
// oRPC's `call`, not the handler's bound call: closing a source needs
|
|
98
|
+
// a signal of its own, because return() waits behind a pending next().
|
|
99
|
+
const stream = (await call(
|
|
100
|
+
procedure,
|
|
101
|
+
{},
|
|
102
|
+
{ context: options.context, signal: controller.signal }
|
|
103
|
+
)) as AsyncIterable<unknown>
|
|
104
|
+
iterator = stream[Symbol.asyncIterator]()
|
|
105
|
+
while (!ended) {
|
|
106
|
+
const result = await iterator.next()
|
|
107
|
+
if (result.done) return stop()
|
|
108
|
+
const event = { ...mappers[domain]?.(result.value), domain } as Event
|
|
109
|
+
queue.set(JSON.stringify(event), event)
|
|
110
|
+
wake?.()
|
|
111
|
+
}
|
|
112
|
+
} catch (error) {
|
|
113
|
+
stop({ error })
|
|
114
|
+
} finally {
|
|
115
|
+
await iterator?.return?.().catch(() => {})
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const pumps = Object.entries(options.sources).map(([domain, procedure]) =>
|
|
119
|
+
pump(domain, procedure as AnyProcedure)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
while (true) {
|
|
124
|
+
const [next] = queue
|
|
125
|
+
if (next) {
|
|
126
|
+
queue.delete(next[0])
|
|
127
|
+
yield next[1]
|
|
128
|
+
continue
|
|
129
|
+
}
|
|
130
|
+
if (ended) break
|
|
131
|
+
await new Promise<void>((resolve) => {
|
|
132
|
+
wake = resolve
|
|
133
|
+
})
|
|
134
|
+
wake = undefined
|
|
135
|
+
}
|
|
136
|
+
if (failure) throw failure.error
|
|
137
|
+
} finally {
|
|
138
|
+
stop()
|
|
139
|
+
options.signal?.removeEventListener('abort', abort)
|
|
140
|
+
await Promise.allSettled(pumps)
|
|
141
|
+
}
|
|
142
|
+
}
|
package/src/live/ioredis.ts
CHANGED
|
@@ -74,6 +74,8 @@ export function ioredisTransport(
|
|
|
74
74
|
ours.delete(channel)
|
|
75
75
|
return subscriber.unsubscribe(channel)
|
|
76
76
|
},
|
|
77
|
+
connected: () =>
|
|
78
|
+
subscriber.status === undefined || subscriber.status === 'ready',
|
|
77
79
|
close() {
|
|
78
80
|
// Detach first: a given subscriber outlives this transport's use.
|
|
79
81
|
subscriber.off('ready', onReady)
|
package/src/live/memory.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PubSubTransport } from './transport.js'
|
|
1
|
+
import type { PubSubTransport, PubSubTransportStats } from './transport.js'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* In-process transport for tests and single-process development. Delivery is
|
|
@@ -7,6 +7,7 @@ import type { PubSubTransport } from './transport.js'
|
|
|
7
7
|
export function memoryTransport(): PubSubTransport & {
|
|
8
8
|
/** Active listeners per channel, for assertions. */
|
|
9
9
|
listenerCount(channel: string): number
|
|
10
|
+
stats(): PubSubTransportStats
|
|
10
11
|
} {
|
|
11
12
|
const listeners = new Map<string, Set<(payload: string) => void>>()
|
|
12
13
|
const backlogs = new Map<string, { items: string[]; expiresAt: number }>()
|
|
@@ -49,6 +50,14 @@ export function memoryTransport(): PubSubTransport & {
|
|
|
49
50
|
if (set.size === 0) listeners.delete(channel)
|
|
50
51
|
}
|
|
51
52
|
},
|
|
52
|
-
listenerCount: (channel) => listeners.get(channel)?.size ?? 0
|
|
53
|
+
listenerCount: (channel) => listeners.get(channel)?.size ?? 0,
|
|
54
|
+
stats: () => ({
|
|
55
|
+
channels: listeners.size,
|
|
56
|
+
listeners: [...listeners.values()].reduce(
|
|
57
|
+
(sum, set) => sum + set.size,
|
|
58
|
+
0
|
|
59
|
+
),
|
|
60
|
+
subscriberConnected: true
|
|
61
|
+
})
|
|
53
62
|
}
|
|
54
63
|
}
|