orpc-nuxt 0.3.0 → 0.4.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 CHANGED
@@ -289,9 +289,42 @@ Relative URLs resolve against the current request URL during SSR.
289
289
  Only headers listed in `forwardHeaders` are forwarded from the incoming SSR request.
290
290
  `credentials: "include"` allows browser cookies on cross-origin requests.
291
291
 
292
+ ### Custom link
293
+
294
+ Return a custom `link` when the server needs a request-scoped transport while the browser should keep a regular HTTP transport:
295
+
296
+ ```ts
297
+ // app/plugins/orpc.ts
298
+ import { RPCLink } from "@orpc/client/fetch"
299
+ import type { RouterClient } from "@orpc/server"
300
+ import { getRequestURL } from "h3"
301
+ import { defineNuxtPlugin } from "orpc-nuxt/plugin"
302
+ import type { router } from "~~/server/rpc/router"
303
+
304
+ export default defineNuxtPlugin<RouterClient<typeof router>>(() => ({
305
+ link: ({ event }) => {
306
+ if (!event) return new RPCLink({ url: "/api/rpc" })
307
+
308
+ const requestFetch = event.context.nuxtMultiApp.createFetch("web")
309
+ const endpoint = new URL("/api/rpc", getRequestURL(event))
310
+
311
+ return new RPCLink({
312
+ origin: endpoint.origin,
313
+ url: endpoint.pathname as `/${string}`,
314
+ fetch: requestFetch,
315
+ })
316
+ },
317
+ }))
318
+ ```
319
+
320
+ The link factory receives `{ nuxtApp, event }`, where `event` is the current H3 event during SSR and `undefined` in the browser.
321
+ It runs once per Nuxt application, so every SSR request gets its own link.
322
+ The plugin setup callback has the same lifecycle: once per SSR request and once when the browser app starts.
323
+ `orpc-nuxt` still creates the typed client, decorates it with Vue Query composables, and provides `$orpc`.
324
+
292
325
  ### Manual client setup
293
326
 
294
- Use `createORPCNuxtClient` when you need a custom transport or want SSR to call the router directly.
327
+ Use `createORPCNuxtClient` when you want SSR to call the router directly or need to own the complete client setup.
295
328
  Instead of the shared HTTP plugin above, add a browser plugin and a server plugin.
296
329
 
297
330
  Keep `orpc-nuxt` in `modules`: manual setup replaces that plugin, not the module that installs the QueryClient and the composables.
@@ -454,34 +487,22 @@ Each procedure leaf in `procedures` has a `.handle()` method that registers a ty
454
487
  // test/nuxt/setup.ts
455
488
  import { mockNuxtImport } from "@nuxt/test-utils/runtime"
456
489
  import type { RouterClient } from "@orpc/server"
457
- import { QueryClient } from "@tanstack/vue-query"
458
490
  import { createTestORPCClient } from "orpc-nuxt/testing"
459
491
  import { afterEach } from "vitest"
460
492
 
461
493
  import type { router } from "~~/server/rpc/router"
462
494
 
463
- const queryClient = new QueryClient({
464
- // Report a failing procedure instead of retrying it until the test times out.
465
- defaultOptions: { queries: { retry: false } },
466
- })
467
-
468
- export const { client, procedures, reset } = createTestORPCClient<RouterClient<typeof router>>({
469
- queryClient,
470
- })
495
+ export const { client, procedures, reset } = createTestORPCClient<RouterClient<typeof router>>()
471
496
 
472
497
  // Return the composable itself; Vitest hoists this factory before the setup file runs.
473
498
  mockNuxtImport("useOrpc", () => () => client)
474
499
 
475
- afterEach(() => {
476
- reset() // Remove registered handlers.
477
- queryClient.clear() // Remove cached responses.
478
- })
500
+ afterEach(reset)
479
501
  ```
480
502
 
481
503
  `orpc-nuxt/testing` does not import `nuxt/app`, so the hoisted `mockNuxtImport()` factory can load it safely.
482
- The explicit QueryClient keeps the test cache isolated and lets the setup clear it after each test.
483
- To use the application's QueryClient instead, omit the option and configure its defaults through `orpc.queryClient`.
484
- In that mode, import `useOrpcQueryClient()` only from a module that the hoisted factory cannot reach.
504
+ The client owns a QueryClient that never retries, so a failing procedure fails the test instead of timing out, and `reset()` removes the registered handlers together with the cached responses.
505
+ Reach that cache as `queryClient` to seed or inspect it, or pass your own to `createTestORPCClient()`; `reset()` clears that one as well.
485
506
 
486
507
  Register the required handlers before mounting; `mountSuspended()` waits for awaited queries before assertions:
487
508
 
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": "^3.14.1592 || ^4.0.1"
6
6
  },
7
- "version": "0.3.0",
7
+ "version": "0.4.0",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.3",
10
10
  "unbuild": "3.6.1"
@@ -1,8 +1,23 @@
1
- import { type AnyNestedClient } from "@orpc/client";
1
+ import { type AnyNestedClient, type ClientLink, type InferClientContext } from "@orpc/client";
2
2
  import { type NuxtApp, type Plugin } from "nuxt/app";
3
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 {
4
+ /** Nuxt state available while creating one app's custom oRPC link. */
5
+ export interface OrpcLinkContext {
6
+ /** The Nuxt application that will own the client. */
7
+ nuxtApp: NuxtApp;
8
+ /** The current H3 request event during SSR, or undefined in the browser. */
9
+ event: NonNullable<NuxtApp["ssrContext"]>["event"] | undefined;
10
+ }
11
+ /** Configure the transport and cache used by a Nuxt app's injected oRPC client. */
12
+ export type OrpcPluginOptions<T extends AnyNestedClient = AnyNestedClient> = ORPCNuxtClientOptions & ({
13
+ /** Use an application-owned link or create one for each Nuxt application. */
14
+ link: ClientLink<InferClientContext<T>> | ((context: OrpcLinkContext) => ClientLink<InferClientContext<T>>);
15
+ url?: never;
16
+ serverUrl?: never;
17
+ credentials?: never;
18
+ forwardHeaders?: never;
19
+ } | {
20
+ link?: never;
6
21
  /** RPC handler URL, including its path; relative URLs resolve against the current page URL. */
7
22
  url: string;
8
23
  /** Optional SSR handler URL; an absent or empty value falls back to url. */
@@ -11,13 +26,14 @@ export interface OrpcPluginOptions extends ORPCNuxtClientOptions {
11
26
  credentials?: RequestCredentials;
12
27
  /** Incoming headers to forward during SSR; only explicitly listed headers are forwarded. */
13
28
  forwardHeaders?: readonly string[];
14
- }
29
+ });
15
30
  /**
16
- * Create an HTTP client per Nuxt app and provide it as $orpc with inferred router types.
31
+ * Create an oRPC client per Nuxt app and provide it as $orpc with inferred router types.
17
32
  * The setup callback runs once per SSR request and once when the browser app starts.
33
+ * A custom link factory also runs once per app and receives its Nuxt app and H3 event.
18
34
  * Install Vue Query before this plugin, or return an explicit queryClient from setup.
19
35
  * Its QueryClient is captured immediately, so invalidate works before any composable runs.
20
36
  */
21
- export declare function defineNuxtPlugin<T extends AnyNestedClient>(setup: (nuxtApp: NuxtApp) => OrpcPluginOptions): Plugin<{
37
+ export declare function defineNuxtPlugin<T extends AnyNestedClient>(setup: (nuxtApp: NuxtApp) => OrpcPluginOptions<T>): Plugin<{
22
38
  orpc: ORPCNuxtClient<T>;
23
39
  }>;
@@ -1,4 +1,6 @@
1
- import { createORPCClient } from "@orpc/client";
1
+ import {
2
+ createORPCClient
3
+ } from "@orpc/client";
2
4
  import { RPCLink } from "@orpc/client/fetch";
3
5
  import {
4
6
  defineNuxtPlugin as createNuxtPlugin,
@@ -16,25 +18,7 @@ export function defineNuxtPlugin(setup) {
16
18
  "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."
17
19
  );
18
20
  }
19
- const url = import.meta.server ? options.serverUrl || options.url : options.url;
20
- const endpoint = new URL(url, useRequestURL());
21
- if (endpoint.protocol !== "http:" && endpoint.protocol !== "https:") {
22
- throw new Error("orpc-nuxt: the RPC handler URL must use HTTP or HTTPS.");
23
- }
24
- const link = new RPCLink({
25
- // oRPC v2 accepts the origin separately from the handler path and query string.
26
- origin: endpoint.origin,
27
- // HTTP URL.pathname always starts with the slash required by StandardUrl.
28
- url: `${endpoint.pathname}${endpoint.search}`,
29
- // An explicit empty list is essential: undefined would forward every incoming header.
30
- headers: useRequestHeaders([...options.forwardHeaders ?? []]),
31
- fetch(url2, init) {
32
- return globalThis.fetch(url2, {
33
- ...init,
34
- credentials: options.credentials
35
- });
36
- }
37
- });
21
+ const link = typeof options.link === "function" ? options.link({ nuxtApp, event: nuxtApp.ssrContext?.event }) : options.link ?? createHTTPLink(options);
38
22
  const client = createORPCClient(link);
39
23
  const orpc = createORPCNuxtClient(client, {
40
24
  prefix: options.prefix,
@@ -45,3 +29,24 @@ export function defineNuxtPlugin(setup) {
45
29
  };
46
30
  });
47
31
  }
32
+ function createHTTPLink(options) {
33
+ const url = import.meta.server ? options.serverUrl || options.url : options.url;
34
+ const endpoint = new URL(url, useRequestURL());
35
+ if (endpoint.protocol !== "http:" && endpoint.protocol !== "https:") {
36
+ throw new Error("orpc-nuxt: the RPC handler URL must use HTTP or HTTPS.");
37
+ }
38
+ return new RPCLink({
39
+ // oRPC v2 accepts the origin separately from the handler path and query string.
40
+ origin: endpoint.origin,
41
+ // HTTP URL.pathname always starts with the slash required by StandardUrl.
42
+ url: `${endpoint.pathname}${endpoint.search}`,
43
+ // An explicit empty list is essential: undefined would forward every incoming header.
44
+ headers: useRequestHeaders([...options.forwardHeaders ?? []]),
45
+ fetch(url2, init) {
46
+ return globalThis.fetch(url2, {
47
+ ...init,
48
+ credentials: options.credentials
49
+ });
50
+ }
51
+ });
52
+ }
@@ -1,2 +1,2 @@
1
1
  export { defineNuxtPlugin } from "./nuxt/define-plugin.js";
2
- export type { OrpcPluginOptions } from "./nuxt/define-plugin.js";
2
+ export type { OrpcLinkContext, OrpcPluginOptions } from "./nuxt/define-plugin.js";
@@ -1,4 +1,5 @@
1
1
  import type { AnyNestedClient, Client } from "@orpc/client";
2
+ import { QueryClient } from "@tanstack/vue-query";
2
3
  import type { Mock } from "vitest";
3
4
  import type { ORPCNuxtClient, ORPCNuxtClientOptions } from "./types.js";
4
5
  type MaybePromise<T> = T | Promise<T>;
@@ -19,7 +20,9 @@ export interface TestORPCClient<T extends AnyNestedClient> {
19
20
  client: ORPCNuxtClient<T>;
20
21
  /** Register typed implementations and receive Vitest mocks for call assertions. */
21
22
  procedures: TestORPCProcedures<T>;
22
- /** Remove every registered procedure implementation. */
23
+ /** The cache the client reads and writes, to seed or inspect from a test. */
24
+ queryClient: QueryClient;
25
+ /** Remove every registered procedure implementation and clear the query cache. */
23
26
  reset: () => void;
24
27
  }
25
28
  /**
@@ -27,8 +30,11 @@ export interface TestORPCClient<T extends AnyNestedClient> {
27
30
  * The entrypoint has no Nuxt runtime dependency.
28
31
  * A shared setup file can therefore import it when Vitest hoists `mockNuxtImport()`.
29
32
  *
30
- * @param options - Cache key prefix and an optional test-owned QueryClient.
31
- * @returns A decorated client, its typed registration tree and a registration reset function.
33
+ * Without a `queryClient` the client owns one that never retries, so a failing procedure fails
34
+ * the test instead of retrying until it times out.
35
+ *
36
+ * @param options - Cache key prefix and a QueryClient to use instead of the owned one.
37
+ * @returns A decorated client, its typed registration tree, its cache and a reset function.
32
38
  */
33
39
  export declare function createTestORPCClient<T extends AnyNestedClient>(options?: ORPCNuxtClientOptions): TestORPCClient<T>;
34
40
  export {};
@@ -1,8 +1,15 @@
1
1
  import { createORPCClient } from "@orpc/client";
2
+ import { QueryClient } from "@tanstack/vue-query";
2
3
  import { vi } from "vitest";
3
4
  import { createORPCNuxtClient } from "./client/create.js";
4
5
  export function createTestORPCClient(options = {}) {
5
6
  const registrations = /* @__PURE__ */ new Map();
7
+ const queryClient = options.queryClient ?? new QueryClient({
8
+ defaultOptions: {
9
+ // Retries turn a failing procedure into a test timeout instead of a reported failure.
10
+ queries: { retry: false }
11
+ }
12
+ });
6
13
  function registerHandler(path, handler) {
7
14
  const mock = vi.fn(handler);
8
15
  registrations.set(path, mock);
@@ -20,7 +27,7 @@ export function createTestORPCClient(options = {}) {
20
27
  return await callHandler(path.join("."), input);
21
28
  }
22
29
  };
23
- const client = createORPCNuxtClient(createORPCClient(link), options);
30
+ const client = createORPCNuxtClient(createORPCClient(link), { ...options, queryClient });
24
31
  const procedures = createRecursiveProxy([], (path, args) => {
25
32
  if (path.at(-1) !== "handle") {
26
33
  throw new Error(`Unknown test procedure call: ${path.join(".")}`);
@@ -36,7 +43,11 @@ export function createTestORPCClient(options = {}) {
36
43
  return {
37
44
  client,
38
45
  procedures,
39
- reset: () => registrations.clear()
46
+ queryClient,
47
+ reset: () => {
48
+ registrations.clear();
49
+ queryClient.clear();
50
+ }
40
51
  };
41
52
  }
42
53
  function createRecursiveProxy(path, call) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orpc-nuxt",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "oRPC integration for Nuxt with TanStack Vue Query composables.",
5
5
  "homepage": "https://github.com/IlyaSemenov/orpc-nuxt#readme",
6
6
  "bugs": "https://github.com/IlyaSemenov/orpc-nuxt/issues",