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 +38 -17
- package/dist/module.json +1 -1
- package/dist/runtime/nuxt/define-plugin.d.ts +22 -6
- package/dist/runtime/nuxt/define-plugin.js +25 -20
- package/dist/runtime/plugin.d.ts +1 -1
- package/dist/runtime/testing.d.ts +9 -3
- package/dist/runtime/testing.js +13 -2
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
483
|
-
|
|
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
|
@@ -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
|
-
/**
|
|
5
|
-
export interface
|
|
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
|
|
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 {
|
|
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
|
|
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
|
+
}
|
package/dist/runtime/plugin.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
*
|
|
31
|
-
*
|
|
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 {};
|
package/dist/runtime/testing.js
CHANGED
|
@@ -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
|
-
|
|
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
|
+
"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",
|