orpc-nuxt 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ilya Semenov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,447 @@
1
+ # orpc-nuxt
2
+
3
+ oRPC v2 integration for Nuxt 3 and 4, built on TanStack Vue Query.
4
+ Add `useQuery` and `useMutation` to your oRPC procedures, with types inferred from your router.
5
+
6
+ ```ts
7
+ const orpc = useOrpc()
8
+ const { data } = await orpc.blog.posts.get.useQuery({ id: 1 })
9
+ ```
10
+
11
+ Inspired by [trpc-nuxt](https://github.com/wobsoriano/trpc-nuxt).
12
+
13
+ ## Install
14
+
15
+ ```sh
16
+ npm install orpc-nuxt @orpc/client@2.0.0-beta.35 @orpc/server@2.0.0-beta.35 @orpc/tanstack-query@2.0.0-beta.35 @tanstack/vue-query
17
+ ```
18
+
19
+ Use the oRPC v2 beta versions shown above.
20
+
21
+ ## Setup
22
+
23
+ Register the module:
24
+
25
+ ```ts
26
+ // nuxt.config.ts
27
+ export default defineNuxtConfig({
28
+ modules: ["orpc-nuxt"],
29
+ })
30
+ ```
31
+
32
+ Add a plugin for browser and SSR requests:
33
+
34
+ ```ts
35
+ // app/plugins/orpc.ts
36
+ import type { RouterClient } from "@orpc/server"
37
+ import { defineNuxtPlugin } from "orpc-nuxt/plugin"
38
+ import type { router } from "~~/server/rpc/router"
39
+
40
+ export default defineNuxtPlugin<RouterClient<typeof router>>(() => {
41
+ return {
42
+ url: "/rpc",
43
+ forwardHeaders: ["cookie"], // Forward the visitor's cookies during SSR.
44
+ }
45
+ })
46
+ ```
47
+
48
+ This assumes you have a router exported from `server/rpc/router.ts` and an RPC handler at `/rpc`; see the [oRPC Nuxt adapter](https://orpc.dev/docs/adapters/nuxt) to set them up.
49
+
50
+ The helper sends both browser and SSR requests over HTTP.
51
+ For direct router calls during SSR with your own `context`, use [manual client setup](#manual-client-setup).
52
+
53
+ ## Queries
54
+
55
+ Call `useQuery` in `<script setup>` or a Vue component's `setup()` function.
56
+ Use your router's procedure paths; the examples below use posts from a blog router.
57
+
58
+ ```ts
59
+ const orpc = useOrpc()
60
+ const query = await orpc.blog.posts.get.useQuery({ id: 1 })
61
+ ```
62
+
63
+ `query` exposes reactive state and methods, including:
64
+
65
+ - `query.data.value` contains the query result, or `undefined` before data is available.
66
+ - `query.isPending.value` is `true` before the first success or error, even when disabled.
67
+ - `query.error.value` contains the query error, or `null` when there is no error.
68
+ - `query.refetch()` fetches the current query again.
69
+ - `query.invalidate()` marks its cached result as stale and refetches it if it is active.
70
+
71
+ You can also access the client as `useNuxtApp().$orpc`; both accessors infer types from your plugin.
72
+
73
+ ### Reactive input and options
74
+
75
+ Pass a ref, reactive object, or getter when the input can change.
76
+ The query follows the current input and uses its cached result when available.
77
+
78
+ The second argument accepts both TanStack Vue Query options (such as `select`, `enabled`, and `staleTime`) and orpc-nuxt options (such as `clone` and `server`).
79
+
80
+ ```ts
81
+ const route = useRoute()
82
+ const panelOpen = ref(true)
83
+
84
+ const query = orpc.blog.posts.get.useQuery(() => ({ id: Number(route.params.id) }), {
85
+ enabled: panelOpen,
86
+ staleTime: 30_000,
87
+ })
88
+ ```
89
+
90
+ Here, changing the route ID switches to that post's query, and closing the panel disables automatic fetching.
91
+
92
+ Vue Query options such as `enabled` and `staleTime` can be reactive too.
93
+ You can also pass the entire options object as a ref or getter.
94
+
95
+ ### Waiting for input
96
+
97
+ Use TanStack Query's `skipToken` when you don't have valid input yet.
98
+ This query starts once a post is selected:
99
+
100
+ ```ts
101
+ import { skipToken } from "@tanstack/vue-query"
102
+
103
+ const selectedId = ref<number>()
104
+ const query = orpc.blog.posts.get.useQuery(() =>
105
+ selectedId.value === undefined ? skipToken : { id: selectedId.value },
106
+ )
107
+ ```
108
+
109
+ ### Awaiting queries and server rendering
110
+
111
+ You can use `useQuery` with or without `await`.
112
+ Without it, you get the query refs immediately and can show a loading state.
113
+ With it, you wait for the initial fetch.
114
+ During SSR, the page waits for active queries either way.
115
+
116
+ Set `server: false` in the query options to fetch only after the component mounts in the browser.
117
+ The query refs are still available during SSR.
118
+
119
+ `await` returns the current state immediately when no fetch is running.
120
+ This includes disabled queries, `skipToken`, queries waiting for the component to mount, and requests paused because the browser is offline.
121
+ It does not wait for these queries to become enabled or resume fetching.
122
+
123
+ Failed queries expose the error in `query.error.value`.
124
+ Set `throwOnError: true` if you also want `await` to throw and Vue to handle the error through its error hooks or boundaries.
125
+
126
+ ## Mutations
127
+
128
+ Use `useMutation` for actions such as creating or updating a post.
129
+
130
+ ```ts
131
+ const orpc = useOrpc()
132
+ const mutation = orpc.blog.posts.create.useMutation({
133
+ onSuccess: () => orpc.blog.posts.invalidate(),
134
+ })
135
+
136
+ await mutation.mutateAsync({ title: "New post" })
137
+ ```
138
+
139
+ `mutation` exposes reactive state and methods, including:
140
+
141
+ - `mutation.mutate(input)` starts the request and returns `void`; read the result from `mutation.data.value` or handle it in `onSuccess`.
142
+ - `mutation.mutateAsync(input)` starts the request and returns a `Promise` that resolves with the response or rejects with the error, so you can use `await` and `try/catch`.
143
+ - `mutation.data.value` contains the mutation result, or `undefined` before data is available.
144
+ - `mutation.isPending.value` is `true` while the mutation is running.
145
+ - `mutation.error.value` contains the mutation error, or `null` when there is no error.
146
+
147
+ The `onSuccess` callback above refreshes active post queries after creating a post.
148
+
149
+ ### Invalidation
150
+
151
+ Choose how much of the cache to invalidate:
152
+
153
+ ```ts
154
+ // Every cached input of this procedure.
155
+ await orpc.blog.posts.get.invalidate()
156
+
157
+ // Every query under this router branch.
158
+ await orpc.blog.posts.invalidate()
159
+
160
+ // Only this query's current input.
161
+ await query.invalidate()
162
+ ```
163
+
164
+ Invalidation marks matching queries as stale and refetches active ones.
165
+ Inactive queries can refresh when used again.
166
+ Create the decorated client inside your app plugin so callbacks use that app's QueryClient.
167
+
168
+ ## Updating cached data
169
+
170
+ Assigning a new response to `data.value` updates the shared cache for the query's current input.
171
+ Other components reading the same query see the update too.
172
+ Nested properties are readonly unless you enable `clone: true`.
173
+
174
+ ```ts
175
+ const { data } = await orpc.blog.posts.get.useQuery({ id: 1 })
176
+ const savePost = orpc.blog.posts.update.useMutation()
177
+
178
+ const response = await savePost.mutateAsync({
179
+ id: 1,
180
+ title: "Updated title",
181
+ })
182
+ data.value = response
183
+ ```
184
+
185
+ Treat the original `response` as readonly after assigning it to `data.value`.
186
+ The assignment passes it to the cache without a defensive copy, so changing `response.title` could modify cached data directly.
187
+
188
+ ## Editing drafts
189
+
190
+ With `clone: true`, `data.value` contains a reactive local copy that you can edit, for example in a form.
191
+ There are two ways to change it:
192
+
193
+ - Edit a nested property to change only your local copy.
194
+ - Assign a whole response to `data.value` to update the shared cache and reset the local copy.
195
+
196
+ ```ts
197
+ const id = 1
198
+ const { data } = await orpc.blog.posts.get.useQuery({ id }, { clone: true })
199
+
200
+ const savePost = orpc.blog.posts.update.useMutation()
201
+
202
+ if (data.value) {
203
+ // Only this query's local copy changes.
204
+ data.value.title = "Local draft"
205
+
206
+ // Save the draft, then share the server's response with other components.
207
+ data.value = await savePost.mutateAsync({
208
+ id,
209
+ title: data.value.title,
210
+ })
211
+ }
212
+ ```
213
+
214
+ After assigning a response, you can keep editing `data.value.title`; those edits still affect only the new local copy.
215
+
216
+ A successful refetch or cache update replaces the local copy and discards its edits, even if the returned data has not changed.
217
+ Changing the query input also switches the copy to that input's data.
218
+ Keep a separate form draft if it must survive these updates.
219
+
220
+ You cannot combine `clone: true` with `select`; selected results are readonly.
221
+
222
+ ## Direct calls and oRPC utilities
223
+
224
+ Use `.call()` when you just need a procedure's response, without query state or caching:
225
+
226
+ ```ts
227
+ const post = await orpc.blog.posts.get.call({ id: 1 })
228
+ ```
229
+
230
+ The client also exposes oRPC's `.key()`, `.queryKey()`, `.queryOptions()`, `.mutationOptions()`, and `.infiniteOptions()` utilities.
231
+ You can pass their options to Vue Query composables, for example `.infiniteOptions()` to `useInfiniteQuery` for pagination.
232
+
233
+ Subscription composables are not available yet.
234
+ Procedures that return streams keep the oRPC utilities but do not get this package's `useQuery` or `useMutation` methods.
235
+ The module does not automatically transfer streamed query results from server to browser.
236
+
237
+ ## Advanced
238
+
239
+ ### Separate API service
240
+
241
+ If your API runs in a separate service, configure the base URLs in `nuxt.config.ts`, without the `/rpc` path:
242
+
243
+ ```ts
244
+ // nuxt.config.ts
245
+ export default defineNuxtConfig({
246
+ modules: ["orpc-nuxt"],
247
+ runtimeConfig: {
248
+ orpc: {
249
+ apiOrigin: "", // Optional SSR address; empty means use public.orpc.apiOrigin.
250
+ },
251
+ public: {
252
+ orpc: {
253
+ apiOrigin: "https://api.example.com",
254
+ },
255
+ },
256
+ },
257
+ })
258
+ ```
259
+
260
+ Set `NUXT_PUBLIC_ORPC_API_ORIGIN` to the browser-facing API origin.
261
+ Use `NUXT_ORPC_API_ORIGIN` if SSR should use an internal address such as `http://api:3000`.
262
+
263
+ Use these settings in `app/plugins/orpc.ts`:
264
+
265
+ ```ts
266
+ // app/plugins/orpc.ts
267
+ import type { router } from "@my-app/api"
268
+ import type { RouterClient } from "@orpc/server"
269
+ import { defineNuxtPlugin } from "orpc-nuxt/plugin"
270
+
271
+ export default defineNuxtPlugin<RouterClient<typeof router>>(() => {
272
+ const config = useRuntimeConfig()
273
+ const serverOrigin = import.meta.server ? config.orpc.apiOrigin : ""
274
+
275
+ return {
276
+ url: `${config.public.orpc.apiOrigin}/rpc`,
277
+ serverUrl: serverOrigin ? `${serverOrigin}/rpc` : undefined,
278
+ credentials: "include",
279
+ forwardHeaders: ["cookie"],
280
+ }
281
+ })
282
+ ```
283
+
284
+ `serverUrl` overrides `url` during SSR; an empty or omitted value falls back to `url`.
285
+ Relative URLs resolve against the current request URL during SSR.
286
+ Only headers listed in `forwardHeaders` are forwarded from the incoming SSR request.
287
+ `credentials: "include"` allows browser cookies on cross-origin requests.
288
+
289
+ ### Manual client setup
290
+
291
+ Use `createORPCNuxtClient` when you need a custom transport or want SSR to call the router directly.
292
+ Instead of the shared HTTP plugin above, add a browser plugin and a server plugin.
293
+
294
+ The browser plugin sends requests to `/rpc` over HTTP:
295
+
296
+ ```ts
297
+ // app/plugins/orpc.client.ts
298
+ import { createORPCClient } from "@orpc/client"
299
+ import { RPCLink } from "@orpc/client/fetch"
300
+ import type { RouterClient } from "@orpc/server"
301
+ import { createORPCNuxtClient } from "orpc-nuxt/client"
302
+ import type { router } from "~~/server/rpc/router"
303
+
304
+ export default defineNuxtPlugin(() => {
305
+ const client = createORPCClient<RouterClient<typeof router>>(new RPCLink({ url: "/rpc" }))
306
+ const orpc = createORPCNuxtClient(client)
307
+ return {
308
+ provide: { orpc },
309
+ }
310
+ })
311
+ ```
312
+
313
+ The server plugin calls the router directly, without an HTTP request.
314
+ This example passes the current Nuxt request event as `context.event`; adjust it to match your router:
315
+
316
+ ```ts
317
+ // app/plugins/orpc.server.ts
318
+ import { createRouterClient } from "@orpc/server"
319
+ import { createORPCNuxtClient } from "orpc-nuxt/client"
320
+ import { router } from "~~/server/rpc/router"
321
+
322
+ export default defineNuxtPlugin(() => {
323
+ const event = useRequestEvent()!
324
+ const client = createRouterClient(router, {
325
+ context: { event },
326
+ })
327
+ const orpc = createORPCNuxtClient(client)
328
+ return {
329
+ provide: { orpc },
330
+ }
331
+ })
332
+ ```
333
+
334
+ ### SSR and cache configuration
335
+
336
+ The module gives each server request its own QueryClient, which manages the query cache.
337
+ It sends cached results to the browser in the Nuxt payload, so the browser can reuse data fetched during SSR.
338
+ Queries stay fresh for 5 seconds by default to avoid immediately fetching that data again.
339
+
340
+ Set static QueryClient defaults in `nuxt.config.ts`.
341
+ This example keeps results fresh for 30 seconds and disables retries:
342
+
343
+ ```ts
344
+ // nuxt.config.ts
345
+ export default defineNuxtConfig({
346
+ modules: ["orpc-nuxt"],
347
+ orpc: {
348
+ queryClient: {
349
+ defaultOptions: {
350
+ queries: {
351
+ staleTime: 30_000,
352
+ retry: false,
353
+ },
354
+ },
355
+ },
356
+ },
357
+ })
358
+ ```
359
+
360
+ Query inputs can include oRPC types such as `bigint` and `Date`; the module supports them in cache keys.
361
+ For custom classes in query results, register a Nuxt payload serializer or use TanStack dehydration options to exclude those queries from the payload.
362
+
363
+ ### Runtime configuration
364
+
365
+ For callbacks, custom cache instances, or settings that depend on the current request, add an `orpc:query-client` hook in a Nuxt plugin.
366
+ The module calls it with the configuration after applying static defaults and before creating the QueryClient.
367
+ This example logs failed queries:
368
+
369
+ ```ts
370
+ // app/plugins/query-config.ts
371
+ import { QueryCache } from "@tanstack/vue-query"
372
+
373
+ export default defineNuxtPlugin({
374
+ hooks: {
375
+ "orpc:query-client"(config) {
376
+ config.queryCache = new QueryCache({
377
+ onError(error) {
378
+ console.error("Query failed:", error)
379
+ },
380
+ })
381
+ },
382
+ },
383
+ })
384
+ ```
385
+
386
+ Nuxt registers the handlers declared in `hooks` before running plugins, so this handler is ready when the module creates the QueryClient.
387
+
388
+ ### Existing Vue Query setup
389
+
390
+ If your app already installs Vue Query and transfers its cache between server and browser, disable the module's QueryClient setup:
391
+
392
+ ```ts
393
+ // nuxt.config.ts
394
+ export default defineNuxtConfig({
395
+ modules: ["orpc-nuxt"],
396
+ orpc: { queryClient: false },
397
+ })
398
+ ```
399
+
400
+ Your Vue Query plugin must install QueryClient before the oRPC plugin runs.
401
+ Use `enforce: "pre"` and omit `parallel: true`.
402
+
403
+ ### Multiple clients
404
+
405
+ If you have multiple oRPC clients with the same procedure paths, give each a different `prefix` so they don't share cached results:
406
+
407
+ ```ts
408
+ const orpc = createORPCNuxtClient(client, { prefix: "blog" })
409
+ ```
410
+
411
+ ### Outside Vue components
412
+
413
+ You can call `.useQuery()` and `.useMutation()` outside a component, for example in tests.
414
+ Create them inside `scope.run()` so Vue can track their reactive subscriptions, then call `scope.stop()` when you are done.
415
+ When Vue injection is unavailable, pass a QueryClient explicitly:
416
+
417
+ ```ts
418
+ import { QueryClient } from "@tanstack/vue-query"
419
+ import { createORPCNuxtClient } from "orpc-nuxt/client"
420
+ import { effectScope } from "vue"
421
+
422
+ const queryClient = new QueryClient()
423
+ const orpc = createORPCNuxtClient(client, { queryClient })
424
+ const scope = effectScope()
425
+
426
+ try {
427
+ const { data } = await scope.run(() => {
428
+ return orpc.blog.posts.get.useQuery({ id: 1 })
429
+ })!
430
+
431
+ console.log(data.value)
432
+ } finally {
433
+ scope.stop()
434
+ queryClient.clear()
435
+ }
436
+ ```
437
+
438
+ ## Development
439
+
440
+ Install dependencies with `bun install`, then run `bun run build`, `bun run types`, and `bun run test`.
441
+
442
+ To try the package in a Nuxt app, build it and run `bunx nuxt dev tests/fixtures/nuxt`.
443
+ The example app uses the built package, so rebuild after changing its source.
444
+
445
+ Run `bunx playwright install chromium`, then `bun run test:nuxt` to check the packed npm archive with Nuxt 3.17.5 and Nuxt 4.5.2.
446
+ Each version is checked with both module-managed and app-managed QueryClients, including types, SSR, and hydration in development and production.
447
+ To check one version, use `bun run test:nuxt 3.17.5`.
@@ -0,0 +1,14 @@
1
+ import * as _nuxt_schema from '@nuxt/schema';
2
+ import { StaticQueryClientConfig } from '../dist/runtime/nuxt/query-config.js';
3
+ export { StaticQueryClientConfig } from '../dist/runtime/nuxt/query-config.js';
4
+ export { ORPCRuntimeHooks as ModuleRuntimeHooks } from '../dist/runtime/nuxt/hooks.js';
5
+
6
+ /** Configure the integration through the `orpc` section of nuxt.config. */
7
+ interface ModuleOptions {
8
+ /** Set static query defaults, or use false when another plugin owns Vue Query and hydration. */
9
+ queryClient?: boolean | StaticQueryClientConfig;
10
+ }
11
+ declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
12
+
13
+ export { _default as default };
14
+ export type { ModuleOptions };
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "orpc-nuxt",
3
+ "configKey": "orpc",
4
+ "compatibility": {
5
+ "nuxt": "^3.17.5 || ^4.0.0"
6
+ },
7
+ "version": "0.1.0",
8
+ "builder": {
9
+ "@nuxt/module-builder": "1.0.3",
10
+ "unbuild": "3.6.1"
11
+ }
12
+ }
@@ -0,0 +1,42 @@
1
+ import { defineNuxtModule, createResolver, addImports, addPluginTemplate } from '@nuxt/kit';
2
+ import { uneval } from 'devalue';
3
+
4
+ const module$1 = defineNuxtModule({
5
+ meta: {
6
+ name: "orpc-nuxt",
7
+ configKey: "orpc",
8
+ compatibility: { nuxt: "^3.17.5 || ^4.0.0" }
9
+ },
10
+ defaults: { queryClient: true },
11
+ setup(options, nuxt) {
12
+ const resolver = createResolver(import.meta.url);
13
+ nuxt.options.build.transpile.push("orpc-nuxt");
14
+ const optimizeDeps = nuxt.options.vite.optimizeDeps ??= {};
15
+ optimizeDeps.exclude ??= [];
16
+ optimizeDeps.exclude.push("orpc-nuxt/client", "@tanstack/vue-query");
17
+ addImports({
18
+ name: "useOrpc",
19
+ from: resolver.resolve("./runtime/composables")
20
+ });
21
+ if (options.queryClient) {
22
+ const config = options.queryClient === true ? {} : options.queryClient;
23
+ addPluginTemplate({
24
+ filename: "orpc-nuxt/query-client.mjs",
25
+ // Emit static defaults into both bundles; uneval preserves values such as Infinity.
26
+ // Functions must use the runtime hook, where their closures are available.
27
+ // Keep enforce in the generated object so Nuxt can read its order without executing it.
28
+ getContents: () => [
29
+ `import { defineNuxtPlugin } from "nuxt/app"`,
30
+ `import createQueryClientSetup from ${JSON.stringify(resolver.resolve("./runtime/nuxt/plugin"))}`,
31
+ `export default defineNuxtPlugin({`,
32
+ ` name: "orpc-nuxt:query-client",`,
33
+ ` enforce: "pre",`,
34
+ ` setup: createQueryClientSetup(${uneval(config)}),`,
35
+ `})`
36
+ ].join("\n")
37
+ });
38
+ }
39
+ }
40
+ });
41
+
42
+ export { module$1 as default };
@@ -0,0 +1,12 @@
1
+ import type { AnyNestedClient } from "@orpc/client";
2
+ import type { ORPCNuxtClient, ORPCNuxtClientOptions } from "../types.js";
3
+ /**
4
+ * Add reactive query and mutation composables while preserving oRPC's TanStack utilities.
5
+ * Accepts either an HTTP client or a server client bound to the current request context.
6
+ * Creating the wrapper does not start requests or require a Vue effect scope;
7
+ * calling its composables does require an active scope.
8
+ *
9
+ * @param client - The application-owned oRPC client whose router types are preserved.
10
+ * @param options - Cache key prefix and an optional QueryClient for standalone usage.
11
+ */
12
+ export declare function createORPCNuxtClient<T extends AnyNestedClient>(client: T, options?: ORPCNuxtClientOptions): ORPCNuxtClient<T>;
@@ -0,0 +1,31 @@
1
+ import { createTanstackQueryUtils } from "@orpc/tanstack-query";
2
+ import {
3
+ useQueryClient,
4
+ VUE_QUERY_CLIENT
5
+ } from "@tanstack/vue-query";
6
+ import { hasInjectionContext, inject } from "vue";
7
+ import { useORPCMutation } from "../vue-query/mutation.js";
8
+ import { useORPCQuery } from "../vue-query/query.js";
9
+ import { decorateClient } from "./decorate.js";
10
+ export function createORPCNuxtClient(client, options = {}) {
11
+ const utils = createTanstackQueryUtils(client, { prefix: options.prefix });
12
+ let queryClient = options.queryClient ?? (hasInjectionContext() ? inject(VUE_QUERY_CLIENT, void 0) : void 0);
13
+ function resolveQueryClient() {
14
+ return queryClient ??= useQueryClient();
15
+ }
16
+ function createMethods(target) {
17
+ return {
18
+ useQuery(input, queryOptions) {
19
+ return useORPCQuery(target, input, queryOptions, resolveQueryClient());
20
+ },
21
+ useMutation(mutationOptions) {
22
+ return useORPCMutation(target, mutationOptions, resolveQueryClient());
23
+ },
24
+ invalidate() {
25
+ const utils2 = target;
26
+ return resolveQueryClient().invalidateQueries({ queryKey: utils2.key() });
27
+ }
28
+ };
29
+ }
30
+ return decorateClient(utils, createMethods);
31
+ }
@@ -0,0 +1,14 @@
1
+ type Methods = Record<string, (...args: never[]) => unknown>;
2
+ /**
3
+ * Lazily extend an oRPC utility tree with a factory's methods at each visited node.
4
+ * HTTP clients synthesize paths, so traversal cannot depend on enumerating a router.
5
+ * Property results are cached to keep node and method identities stable.
6
+ * When a method name also identifies a router path, calls invoke the method and
7
+ * property access continues through the corresponding child node.
8
+ *
9
+ * @param target - An upstream utility object or callable utility at the current path.
10
+ * @param createMethods - Called once per decorated node; must bind methods without invoking them.
11
+ * @returns A proxy preserving upstream utility calls and exposing the additional methods.
12
+ */
13
+ export declare function decorateClient(target: object, createMethods: (target: object) => Methods): object;
14
+ export {};
@@ -0,0 +1,25 @@
1
+ import { RECURSIVE_CLIENT_UNWRAP_KEYS } from "@orpc/client";
2
+ export function decorateClient(target, createMethods) {
3
+ const methods = createMethods(target);
4
+ const children = /* @__PURE__ */ new Map();
5
+ return new Proxy(typeof target === "function" ? target : methods, {
6
+ get(_target, property, receiver) {
7
+ if (typeof property !== "string" || RECURSIVE_CLIENT_UNWRAP_KEYS.has(property)) {
8
+ return Reflect.get(target, property, receiver);
9
+ }
10
+ if (children.has(property)) return children.get(property);
11
+ const value = Reflect.get(target, property, receiver);
12
+ const method = Object.hasOwn(methods, property) ? methods[property] : void 0;
13
+ let result = method ?? value;
14
+ if (isObject(value)) {
15
+ const child = decorateClient(value, createMethods);
16
+ result = method ? new Proxy(method, { get: (_method, key) => Reflect.get(child, key) }) : child;
17
+ }
18
+ children.set(property, result);
19
+ return result;
20
+ }
21
+ });
22
+ }
23
+ function isObject(value) {
24
+ return typeof value === "object" && value !== null || typeof value === "function";
25
+ }
@@ -0,0 +1,2 @@
1
+ export { createORPCNuxtClient } from "./client/create.js";
2
+ export type { AwaitableQuery, ORPCMutationOptions, ORPCNuxtClient, ORPCNuxtClientOptions, ORPCQueryOptions, ORPCQueryResult, ORPCSelectedQueryResult, } from "./types.js";
@@ -0,0 +1 @@
1
+ export { createORPCNuxtClient } from "./client/create.js";
@@ -0,0 +1 @@
1
+ export { useOrpc } from "./nuxt/composables.js";
@@ -0,0 +1 @@
1
+ export { useOrpc } from "./nuxt/composables.js";
@@ -0,0 +1,11 @@
1
+ import { type NuxtApp } from "nuxt/app";
2
+ type InjectedORPCClient = NuxtApp extends {
3
+ $orpc: infer TClient;
4
+ } ? TClient : never;
5
+ /**
6
+ * Read the typed client provided by your application's oRPC plugin.
7
+ * Requires an active Nuxt context and a plugin that provides `orpc`.
8
+ * The router type is inferred from that plugin's return value.
9
+ */
10
+ export declare function useOrpc(): InjectedORPCClient;
11
+ export {};
@@ -0,0 +1,8 @@
1
+ import { useNuxtApp } from "nuxt/app";
2
+ export function useOrpc() {
3
+ const client = useNuxtApp().$orpc;
4
+ if (!client) {
5
+ throw new Error("Provide an oRPC client from a Nuxt plugin before calling useOrpc().");
6
+ }
7
+ return client;
8
+ }
@@ -0,0 +1,23 @@
1
+ import { type AnyNestedClient } from "@orpc/client";
2
+ import { type NuxtApp, type Plugin } from "nuxt/app";
3
+ import type { ORPCNuxtClient, ORPCNuxtClientOptions } from "../types.js";
4
+ /** Configure the HTTP transport and cache used by a Nuxt app's injected oRPC client. */
5
+ export interface OrpcPluginOptions extends ORPCNuxtClientOptions {
6
+ /** RPC handler URL, including its path; relative URLs resolve against the current page URL. */
7
+ url: string;
8
+ /** Optional SSR handler URL; an absent or empty value falls back to url. */
9
+ serverUrl?: string;
10
+ /** Fetch credentials policy, for example include for cross-origin browser cookies. */
11
+ credentials?: RequestCredentials;
12
+ /** Incoming headers to forward during SSR; only explicitly listed headers are forwarded. */
13
+ forwardHeaders?: readonly string[];
14
+ }
15
+ /**
16
+ * Create an HTTP client per Nuxt app and provide it as $orpc with inferred router types.
17
+ * The setup callback runs once per SSR request and once when the browser app starts.
18
+ * Install Vue Query before this plugin, or return an explicit queryClient from setup.
19
+ * Its QueryClient is captured immediately, so invalidate works before any composable runs.
20
+ */
21
+ export declare function defineNuxtPlugin<T extends AnyNestedClient>(setup: (nuxtApp: NuxtApp) => OrpcPluginOptions): Plugin<{
22
+ orpc: ORPCNuxtClient<T>;
23
+ }>;
@@ -0,0 +1,48 @@
1
+ import { createORPCClient } from "@orpc/client";
2
+ import { RPCLink } from "@orpc/client/fetch";
3
+ import { VUE_QUERY_CLIENT } from "@tanstack/vue-query";
4
+ import {
5
+ defineNuxtPlugin as createNuxtPlugin,
6
+ useRequestHeaders,
7
+ useRequestURL
8
+ } from "nuxt/app";
9
+ import { inject } from "vue";
10
+ import { createORPCNuxtClient } from "../client/create.js";
11
+ export function defineNuxtPlugin(setup) {
12
+ return createNuxtPlugin((nuxtApp) => {
13
+ const options = setup(nuxtApp);
14
+ const queryClient = options.queryClient ?? inject(VUE_QUERY_CLIENT, void 0);
15
+ if (!queryClient) {
16
+ throw new Error(
17
+ "orpc-nuxt: install Vue Query before the oRPC plugin. Enable the module's QueryClient, use enforce: 'pre' in your Vue Query plugin, or pass queryClient explicitly."
18
+ );
19
+ }
20
+ const url = import.meta.server ? options.serverUrl || options.url : options.url;
21
+ const endpoint = new URL(url, useRequestURL());
22
+ if (endpoint.protocol !== "http:" && endpoint.protocol !== "https:") {
23
+ throw new Error("orpc-nuxt: the RPC handler URL must use HTTP or HTTPS.");
24
+ }
25
+ const link = new RPCLink({
26
+ // oRPC v2 accepts the origin separately from the handler path and query string.
27
+ origin: endpoint.origin,
28
+ // HTTP URL.pathname always starts with the slash required by StandardUrl.
29
+ url: `${endpoint.pathname}${endpoint.search}`,
30
+ // An explicit empty list is essential: undefined would forward every incoming header.
31
+ headers: useRequestHeaders([...options.forwardHeaders ?? []]),
32
+ fetch(url2, init) {
33
+ return globalThis.fetch(url2, {
34
+ ...init,
35
+ credentials: options.credentials
36
+ });
37
+ }
38
+ });
39
+ const client = createORPCClient(link);
40
+ const orpc = createORPCNuxtClient(client, {
41
+ prefix: options.prefix,
42
+ queryClient
43
+ });
44
+ return {
45
+ provide: { orpc }
46
+ };
47
+ });
48
+ }
@@ -0,0 +1,13 @@
1
+ import type { QueryClientConfig } from "@tanstack/vue-query";
2
+ /** Nuxt hooks available while the module initializes its QueryClient. */
3
+ export interface ORPCRuntimeHooks {
4
+ /**
5
+ * Mutate the configuration before the per-app QueryClient is created and installed.
6
+ * Async handlers finish before initialization continues.
7
+ */
8
+ "orpc:query-client": (config: QueryClientConfig) => void | Promise<void>;
9
+ }
10
+ declare module "nuxt/app" {
11
+ interface RuntimeNuxtHooks extends ORPCRuntimeHooks {
12
+ }
13
+ }
File without changes
@@ -0,0 +1,4 @@
1
+ import { type NuxtApp } from "nuxt/app";
2
+ import { type StaticQueryClientConfig } from "./query-config.js";
3
+ /** Install one Vue Query cache per Nuxt app and transfer server data through its payload. */
4
+ export default function createQueryClientSetup(options?: StaticQueryClientConfig): (nuxtApp: NuxtApp) => Promise<void>;
@@ -0,0 +1,27 @@
1
+ import {
2
+ dehydrate,
3
+ hydrate,
4
+ QueryClient,
5
+ VueQueryPlugin
6
+ } from "@tanstack/vue-query";
7
+ import { useState } from "nuxt/app";
8
+ import { createQueryClientConfig } from "./query-config.js";
9
+ export default function createQueryClientSetup(options = {}) {
10
+ return async function setup(nuxtApp) {
11
+ const state = useState("orpc-nuxt:query-cache");
12
+ const config = createQueryClientConfig(options);
13
+ await nuxtApp.callHook("orpc:query-client", config);
14
+ const queryClient = new QueryClient(config);
15
+ if (import.meta.server) {
16
+ nuxtApp.hook("app:rendered", () => {
17
+ state.value = dehydrate(queryClient);
18
+ queryClient.clear();
19
+ });
20
+ nuxtApp.hook("app:error", () => queryClient.clear());
21
+ } else {
22
+ if (state.value) hydrate(queryClient, state.value);
23
+ state.value = void 0;
24
+ }
25
+ nuxtApp.vueApp.use(VueQueryPlugin, { queryClient });
26
+ };
27
+ }
@@ -0,0 +1,10 @@
1
+ import { type QueryClientConfig } from "@tanstack/vue-query";
2
+ /** Remove callbacks from module options; their closures belong in the runtime hook. */
3
+ type StaticValue<T> = T extends ((...args: never[]) => unknown) | symbol ? never : T extends readonly (infer Item)[] ? StaticValue<Item>[] : T extends object ? {
4
+ [K in keyof T]: StaticValue<T[K]>;
5
+ } : T;
6
+ /** QueryClient defaults that can be included in the Nuxt server and browser bundles. */
7
+ export type StaticQueryClientConfig = StaticValue<Pick<QueryClientConfig, "defaultOptions">>;
8
+ /** Merge module defaults without losing oRPC's support for typed values in query keys. */
9
+ export declare function createQueryClientConfig(options?: StaticQueryClientConfig): QueryClientConfig;
10
+ export {};
@@ -0,0 +1,19 @@
1
+ import { RPCJsonSerializer } from "@orpc/client";
2
+ import { hashKey } from "@tanstack/vue-query";
3
+ export function createQueryClientConfig(options = {}) {
4
+ const serializer = new RPCJsonSerializer();
5
+ return {
6
+ defaultOptions: {
7
+ ...options.defaultOptions,
8
+ queries: {
9
+ // Reuse recently fetched SSR data instead of immediately requesting it in the browser.
10
+ staleTime: 5e3,
11
+ ...options.defaultOptions?.queries,
12
+ queryKeyHashFn(queryKey) {
13
+ const { json, meta } = serializer.serialize(queryKey);
14
+ return hashKey([json, meta?.map((entry) => JSON.stringify(entry)).sort()]);
15
+ }
16
+ }
17
+ }
18
+ };
19
+ }
@@ -0,0 +1,2 @@
1
+ export { defineNuxtPlugin } from "./nuxt/define-plugin.js";
2
+ export type { OrpcPluginOptions } from "./nuxt/define-plugin.js";
@@ -0,0 +1 @@
1
+ export { defineNuxtPlugin } from "./nuxt/define-plugin.js";
@@ -0,0 +1,89 @@
1
+ import type { AnyNestedClient, Client, ClientContext } from "@orpc/client";
2
+ import type { RouterUtils } from "@orpc/tanstack-query";
3
+ import type { QueryClient, SkipToken, UseMutationOptions, UseMutationReturnType, UseQueryOptions, UseQueryReturnType } from "@tanstack/vue-query";
4
+ import type { ComputedRef, DeepReadonly, MaybeRefOrGetter, Ref, WritableComputedRef } from "vue";
5
+ /** Configure cache ownership and key namespacing when wrapping an application-owned client. */
6
+ export interface ORPCNuxtClientOptions {
7
+ /** Separate the cache keys of clients whose procedure paths overlap. */
8
+ prefix?: string;
9
+ /** Use an explicit client when creating composables in an effect scope without Vue injection. */
10
+ queryClient?: QueryClient;
11
+ }
12
+ /**
13
+ * An oRPC client decorated with Vue composables and the official TanStack Query utilities.
14
+ * Router branches retain their names; finite procedures gain query and mutation composables.
15
+ * Procedures whose output includes an async iterable retain only the upstream utilities.
16
+ */
17
+ export type ORPCNuxtClient<T extends AnyNestedClient> = RouterUtils<T> & {
18
+ /** Invalidate all inputs of this procedure, or every query under this router branch. */
19
+ invalidate: () => Promise<void>;
20
+ } & (T extends Client<infer C, infer I, infer O, infer E> ? Extract<O, AsyncIterable<unknown>> extends never ? ProcedureHooks<C, I, O, E> : object : {
21
+ [K in keyof T]: T[K] extends AnyNestedClient ? ORPCNuxtClient<T[K]> : never;
22
+ });
23
+ /**
24
+ * Query state is available synchronously; awaiting it waits for the initial active fetch.
25
+ * Awaiting an inactive query returns its current state without waiting for it to become enabled.
26
+ */
27
+ export type AwaitableQuery<T> = T & Promise<T>;
28
+ /** Vue Query state with cache-writing data assignment and an optional mutable local clone. */
29
+ export type ORPCQueryResult<TOutput, TError, TClone extends boolean = false> = Omit<UseQueryReturnType<TOutput, TError>, "data"> & {
30
+ /**
31
+ * Assign a whole response to update the shared cache for the current input.
32
+ * Nested edits require clone: true and remain local until a whole value is assigned.
33
+ * Successful cache updates replace that local clone and discard its edits.
34
+ */
35
+ data: WritableComputedRef<(TClone extends true ? TOutput : DeepReadonly<TOutput>) | undefined, TOutput>;
36
+ /** Invalidate this query's current input and refetch active observers. */
37
+ invalidate: () => Promise<void>;
38
+ };
39
+ /** Vue Query state projected through select while the cache retains the original response. */
40
+ export type ORPCSelectedQueryResult<TSelected, TError> = Omit<UseQueryReturnType<TSelected, TError>, "data"> & {
41
+ /** The selected value and its nested properties are readonly. */
42
+ data: ComputedRef<DeepReadonly<TSelected> | undefined>;
43
+ /** Invalidate the underlying query for the current input and refetch active observers. */
44
+ invalidate: () => Promise<void>;
45
+ };
46
+ /** Extract the options object from Vue Query's ref/getter wrappers before replacing fields. */
47
+ type ResolveOptions<T> = T extends Ref<infer U> ? U : T extends () => infer U ? ResolveOptions<U> : T;
48
+ /** Keep context mandatory when the wrapped client requires fields the caller must supply. */
49
+ type ContextOptions<C extends ClientContext> = object extends C ? {
50
+ context?: MaybeRefOrGetter<C>;
51
+ } : {
52
+ context: MaybeRefOrGetter<C>;
53
+ };
54
+ /**
55
+ * Reactive Vue Query options with procedure-derived keys, query functions and oRPC client context.
56
+ * The useQuery overloads add mutually exclusive select and clone options.
57
+ */
58
+ export type ORPCQueryOptions<C extends ClientContext, O, E, S = O> = Omit<ResolveOptions<UseQueryOptions<O, E, S>>, "queryKey" | "queryFn" | "select" | "shallow" | "enabled"> & ContextOptions<C> & {
59
+ /** Control automatic fetching reactively; disabled queries can still expose cached data. */
60
+ enabled?: MaybeRefOrGetter<boolean | undefined>;
61
+ /** Use false to start the query only after the component mounts in the browser. */
62
+ server?: boolean;
63
+ };
64
+ /** Vue Query mutation options with procedure-derived keys, mutation functions and client context. */
65
+ export type ORPCMutationOptions<C extends ClientContext, I, O, E, M = unknown> = Omit<ResolveOptions<UseMutationOptions<O, E, I, M>>, "mutationKey" | "mutationFn" | "shallow"> & ContextOptions<C>;
66
+ type QueryInput<I> = MaybeRefOrGetter<I | SkipToken>;
67
+ /** Allow omitted input only when its type permits it, while preserving required client context. */
68
+ type QueryArgs<C extends ClientContext, I, Options> = object extends C ? undefined extends I ? [input?: QueryInput<I>, options?: Options] : [input: QueryInput<I>, options?: Options] : [input: QueryInput<I>, options: Options];
69
+ /** Carry each procedure's input, output, error and context types through the runtime decorator. */
70
+ interface ProcedureHooks<C extends ClientContext, I, O, E> {
71
+ /** Observe a query with a mutable local clone; whole-value assignments update the shared cache. */
72
+ useQuery(input: QueryInput<I>, options: MaybeRefOrGetter<ORPCQueryOptions<C, O, E> & {
73
+ clone: true;
74
+ select?: never;
75
+ }>): AwaitableQuery<ORPCQueryResult<O, E, true>>;
76
+ /** Observe a readonly projection of cached data; select cannot be combined with clone: true. */
77
+ useQuery<S>(input: QueryInput<I>, options: MaybeRefOrGetter<ORPCQueryOptions<C, O, E, S> & {
78
+ select: (data: O) => S;
79
+ clone?: never;
80
+ }>): AwaitableQuery<ORPCSelectedQueryResult<S, E>>;
81
+ /** Observe a reactive query with readonly nested data and cache-writing whole-value assignment. */
82
+ useQuery(...args: QueryArgs<C, I, MaybeRefOrGetter<ORPCQueryOptions<C, O, E> & {
83
+ clone?: false;
84
+ select?: never;
85
+ }>>): AwaitableQuery<ORPCQueryResult<O, E>>;
86
+ /** Create a mutation observer; call mutate or mutateAsync to execute the procedure. */
87
+ useMutation<M = unknown>(...args: object extends C ? [options?: MaybeRefOrGetter<ORPCMutationOptions<C, I, O, E, M>>] : [options: MaybeRefOrGetter<ORPCMutationOptions<C, I, O, E, M>>]): UseMutationReturnType<O, E, I, M>;
88
+ }
89
+ export {};
File without changes
@@ -0,0 +1,11 @@
1
+ import { type QueryClient } from "@tanstack/vue-query";
2
+ /**
3
+ * Create a Vue Query mutation observer for one oRPC procedure in the active effect scope.
4
+ * Reactive options and client context are resolved before building the official mutation options.
5
+ * Vue Query owns execution, callbacks and observer disposal.
6
+ *
7
+ * @param target - The procedure's upstream TanStack utilities, supplied by the client decorator.
8
+ * @param options - Mutation options, optionally wrapped in a ref or getter.
9
+ * @param queryClient - An explicit cache owner; otherwise Vue Query uses the injected client.
10
+ */
11
+ export declare function useORPCMutation(target: object, options: unknown, queryClient?: QueryClient): import("@tanstack/vue-query").UseMutationReturnType<unknown, Error, unknown, unknown, Omit<import("@tanstack/query-core").MutationObserverIdleResult<unknown, Error, unknown, unknown>, "mutate" | "reset"> | Omit<import("@tanstack/query-core").MutationObserverLoadingResult<unknown, Error, unknown, unknown>, "mutate" | "reset"> | Omit<import("@tanstack/query-core").MutationObserverErrorResult<unknown, Error, unknown, unknown>, "mutate" | "reset"> | Omit<import("@tanstack/query-core").MutationObserverSuccessResult<unknown, Error, unknown, unknown>, "mutate" | "reset">>;
@@ -0,0 +1,18 @@
1
+ import { useMutation } from "@tanstack/vue-query";
2
+ import { computed, getCurrentScope, toValue } from "vue";
3
+ export function useORPCMutation(target, options, queryClient) {
4
+ if (!getCurrentScope()) {
5
+ throw new Error("useMutation() requires a component setup or an active Vue effect scope.");
6
+ }
7
+ const procedure = target;
8
+ return useMutation(
9
+ computed(() => {
10
+ const { context, ...rest } = toValue(options) ?? {};
11
+ return procedure.mutationOptions({
12
+ ...rest,
13
+ context: toValue(context)
14
+ });
15
+ }),
16
+ queryClient
17
+ );
18
+ }
@@ -0,0 +1,15 @@
1
+ import { type QueryClient } from "@tanstack/vue-query";
2
+ import type { AwaitableQuery, ORPCQueryResult } from "../types.js";
3
+ /**
4
+ * Observe one oRPC procedure with reactive input, SSR prefetching and optional local cloning.
5
+ * Registers lifecycle hooks synchronously and returns query state that can also be awaited.
6
+ * Nested edits affect only the opt-in clone; whole-value assignments write through to the cache.
7
+ * Selected data cannot be assigned back because its shape may differ from the cached response.
8
+ *
9
+ * @param target - The procedure's upstream TanStack utilities, supplied by the client decorator.
10
+ * @param input - A value, ref or getter; skipToken suppresses automatic fetching.
11
+ * @param options - Query options, optionally wrapped in a ref or getter.
12
+ * @param client - An explicit cache owner; otherwise the injected QueryClient is used.
13
+ * @returns Live query refs plus a promise waiting for the initial active fetch.
14
+ */
15
+ export declare function useORPCQuery(target: object, input: unknown, options: unknown, client?: QueryClient): AwaitableQuery<ORPCQueryResult<unknown, Error, true>>;
@@ -0,0 +1,90 @@
1
+ import { skipToken, useQuery, useQueryClient } from "@tanstack/vue-query";
2
+ import { cloneDeep } from "es-toolkit";
3
+ import {
4
+ computed,
5
+ getCurrentInstance,
6
+ getCurrentScope,
7
+ onMounted,
8
+ onScopeDispose,
9
+ onServerPrefetch,
10
+ ref,
11
+ toRaw,
12
+ toValue,
13
+ watch
14
+ } from "vue";
15
+ export function useORPCQuery(target, input, options, client) {
16
+ if (!getCurrentScope()) {
17
+ throw new Error("useQuery() requires a component setup or an active Vue effect scope.");
18
+ }
19
+ const procedure = target;
20
+ const queryClient = client ?? useQueryClient();
21
+ const instance = getCurrentInstance();
22
+ const mounted = ref(typeof window !== "undefined" && !instance);
23
+ if (instance)
24
+ onMounted(() => {
25
+ mounted.value = true;
26
+ });
27
+ const settings = computed(
28
+ () => toValue(options) ?? {}
29
+ );
30
+ const queryOptions = computed(() => {
31
+ const { clone, server, context, enabled, ...rest } = settings.value;
32
+ if (clone && rest.select) {
33
+ throw new TypeError("clone: true cannot be combined with select.");
34
+ }
35
+ const snapshot = cloneDeep(toValue(input));
36
+ const isEnabled = toValue(enabled);
37
+ return {
38
+ ...procedure.queryOptions({
39
+ ...rest,
40
+ input: snapshot,
41
+ context: toValue(context)
42
+ }),
43
+ // Omit an unspecified enabled value so QueryClient defaults still apply.
44
+ ...snapshot === skipToken || server === false && !mounted.value ? { enabled: false } : isEnabled === void 0 ? {} : { enabled: isEnabled },
45
+ shallow: false
46
+ };
47
+ });
48
+ const query = useQuery(queryOptions, queryClient);
49
+ const queryHash = computed(() => queryClient.defaultQueryOptions(queryOptions.value).queryHash);
50
+ const localData = ref();
51
+ function resetClone() {
52
+ if (settings.value.clone) localData.value = cloneDeep(toRaw(query.data.value));
53
+ }
54
+ watch([query.data, queryHash, () => settings.value.clone], resetClone, {
55
+ immediate: true,
56
+ // A queued reset could overwrite local edits made immediately after a cache assignment.
57
+ flush: "sync"
58
+ });
59
+ const unsubscribe = queryClient.getQueryCache().subscribe((event) => {
60
+ if (event.type === "updated" && event.action.type === "success" && event.query.queryHash === queryHash.value) {
61
+ resetClone();
62
+ }
63
+ });
64
+ onScopeDispose(unsubscribe);
65
+ const data = computed({
66
+ get: () => settings.value.clone ? localData.value : query.data.value,
67
+ set(value) {
68
+ if (settings.value.select) throw new TypeError("Query data is readonly when select is used.");
69
+ queryClient.setQueryData(queryOptions.value.queryKey, value);
70
+ resetClone();
71
+ }
72
+ });
73
+ const result = {
74
+ ...query,
75
+ data,
76
+ invalidate: () => queryClient.invalidateQueries({
77
+ queryKey: queryOptions.value.queryKey,
78
+ exact: true
79
+ })
80
+ };
81
+ const loaded = query.fetchStatus.value === "fetching" ? query.suspense() : Promise.resolve();
82
+ if (instance) onServerPrefetch(() => loaded);
83
+ const awaitable = Object.assign(
84
+ loaded.then(() => result),
85
+ result
86
+ );
87
+ void awaitable.catch(() => {
88
+ });
89
+ return awaitable;
90
+ }
@@ -0,0 +1,13 @@
1
+ import type { ModuleRuntimeHooks } from './module.mjs'
2
+
3
+ declare module '#app' {
4
+ interface RuntimeNuxtHooks extends ModuleRuntimeHooks {}
5
+ }
6
+
7
+ export { type StaticQueryClientConfig } from '../dist/runtime/nuxt/query-config.js'
8
+
9
+ export { type ModuleRuntimeHooks } from '../dist/runtime/nuxt/hooks.js'
10
+
11
+ export { default } from './module.mjs'
12
+
13
+ export { type ModuleOptions } from './module.mjs'
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "orpc-nuxt",
3
+ "version": "0.1.0",
4
+ "description": "oRPC integration for Nuxt.",
5
+ "homepage": "https://github.com/IlyaSemenov/orpc-nuxt#readme",
6
+ "bugs": "https://github.com/IlyaSemenov/orpc-nuxt/issues",
7
+ "license": "MIT",
8
+ "author": "Ilya Semenov",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/IlyaSemenov/orpc-nuxt.git"
12
+ },
13
+ "files": [
14
+ "README.md",
15
+ "dist"
16
+ ],
17
+ "type": "module",
18
+ "types": "./dist/types.d.mts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/types.d.mts",
22
+ "import": "./dist/module.mjs"
23
+ },
24
+ "./client": {
25
+ "types": "./dist/runtime/client.d.ts",
26
+ "import": "./dist/runtime/client.js"
27
+ },
28
+ "./composables": {
29
+ "types": "./dist/runtime/composables.d.ts",
30
+ "import": "./dist/runtime/composables.js"
31
+ },
32
+ "./plugin": {
33
+ "types": "./dist/runtime/plugin.d.ts",
34
+ "import": "./dist/runtime/plugin.js"
35
+ },
36
+ "./package.json": "./package.json"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public",
40
+ "provenance": true
41
+ },
42
+ "scripts": {
43
+ "build": "nuxt-module-build build && publint",
44
+ "lint": "oxlint --fix && oxfmt",
45
+ "lint:check": "oxlint && oxfmt --check",
46
+ "prepare": "lefthook install",
47
+ "prepublishOnly": "bun run build",
48
+ "test": "bun test src tests/runtime",
49
+ "test:nuxt": "bun tests/nuxt/run.ts",
50
+ "types": "tsc --noEmit && tsc --noEmit -p tests/tsconfig.json && nuxt typecheck tests/fixtures/nuxt"
51
+ },
52
+ "dependencies": {
53
+ "@nuxt/kit": "^4.5.2",
54
+ "devalue": "^5.9.2",
55
+ "es-toolkit": "^1.52.0"
56
+ },
57
+ "devDependencies": {
58
+ "@changesets/cli": "^2.31.1",
59
+ "@nuxt/cli": "^3.37.0",
60
+ "@nuxt/module-builder": "^1.0.3",
61
+ "@orpc/server": "2.0.0-beta.35",
62
+ "@playwright/test": "1.58.2",
63
+ "@tsconfig/bun": "^1.0.10",
64
+ "@types/bun": "^1.3.14",
65
+ "@vue/server-renderer": "^3.5.0",
66
+ "nuxt": "^4.5.2",
67
+ "oxfmt": "^0.67.0",
68
+ "oxlint": "^1.82.0",
69
+ "publint": "^0.3.22",
70
+ "typescript": "^5.9.3",
71
+ "vue-tsc": "^3.3.11",
72
+ "zod": "^4.3.6"
73
+ },
74
+ "peerDependencies": {
75
+ "@orpc/client": "2.0.0-beta.35",
76
+ "@orpc/tanstack-query": "2.0.0-beta.35",
77
+ "@tanstack/vue-query": "^5.102.8",
78
+ "nuxt": "^3.17.5 || ^4.0.0",
79
+ "vue": "^3.5.0"
80
+ },
81
+ "packageManager": "bun@1.3.14"
82
+ }