@arpabet/vrpc-vue 0.2.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 +26 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.js +183 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# @arpabet/vrpc-vue
|
|
2
|
+
|
|
3
|
+
Vue 3 plugin + composables for [value-rpc](https://github.com/arpabet/value-rpc).
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
// main.ts
|
|
7
|
+
import { createVrpc } from "@arpabet/vrpc-vue";
|
|
8
|
+
app.use(createVrpc({ url: import.meta.env.VITE_VRPC_URL }));
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```vue
|
|
12
|
+
<script setup lang="ts">
|
|
13
|
+
const { data, pending, error, refresh } = useVrpcCall<User>("user.get", () => [id.value]);
|
|
14
|
+
const { latest, values, state, cancel } = useVrpcStream<Event>("events.tail", ["orders"]);
|
|
15
|
+
const { status, connected } = useVrpcConnection();
|
|
16
|
+
const client = useVrpc(); // full @arpabet/vrpc client (chat, putStream, reverse calls)
|
|
17
|
+
</script>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- `useVrpcCall` re-runs when reactive args change and aborts superseded runs.
|
|
21
|
+
- `useVrpcStream` grants flow-control credit as values are consumed and
|
|
22
|
+
cancels the stream on unmount. `keepLast` bounds the retained window.
|
|
23
|
+
- SSR-safe: on the server the composables render their pending state and
|
|
24
|
+
never open a socket.
|
|
25
|
+
- For caching/dedup/SWR, feed `client.call` into `@tanstack/vue-query`'s
|
|
26
|
+
`queryFn` — this package deliberately ships no cache.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { VrpcClient, VrpcError, CallOptions, Status, ClientOptions, Value } from '@arpabet/vrpc';
|
|
2
|
+
export { CallOptions, ClientOptions, Code, Status, Value, ValueMap, VrpcChat, VrpcClient, VrpcError, VrpcStream, createClient } from '@arpabet/vrpc';
|
|
3
|
+
import { ShallowRef, Ref, ComputedRef, InjectionKey, Plugin, MaybeRefOrGetter } from 'vue';
|
|
4
|
+
|
|
5
|
+
declare const VRPC_KEY: InjectionKey<VrpcClient>;
|
|
6
|
+
interface VrpcPluginOptions extends ClientOptions {
|
|
7
|
+
/** Open the connection at install time instead of on first call. */
|
|
8
|
+
eager?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Vue plugin providing a shared {@link VrpcClient}:
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* app.use(createVrpc({ url: import.meta.env.VITE_VRPC_URL }));
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* Pass an existing client instead of options to share one across apps.
|
|
18
|
+
*/
|
|
19
|
+
declare function createVrpc(options: VrpcPluginOptions | VrpcClient): Plugin;
|
|
20
|
+
/** The client installed by {@link createVrpc}. */
|
|
21
|
+
declare function useVrpc(): VrpcClient;
|
|
22
|
+
interface UseVrpcConnection {
|
|
23
|
+
status: Ref<Status>;
|
|
24
|
+
connected: ComputedRef<boolean>;
|
|
25
|
+
}
|
|
26
|
+
/** Reactive connection state (for a status badge, offline banner, …). */
|
|
27
|
+
declare function useVrpcConnection(client?: VrpcClient): UseVrpcConnection;
|
|
28
|
+
interface UseVrpcCallOptions extends CallOptions {
|
|
29
|
+
/** Run the call on setup (client-side only). Default true. */
|
|
30
|
+
immediate?: boolean;
|
|
31
|
+
/** Re-run automatically when reactive args change. Default true. */
|
|
32
|
+
watchArgs?: boolean;
|
|
33
|
+
}
|
|
34
|
+
interface UseVrpcCall<T> {
|
|
35
|
+
data: ShallowRef<T | null>;
|
|
36
|
+
pending: Ref<boolean>;
|
|
37
|
+
error: ShallowRef<VrpcError | null>;
|
|
38
|
+
/** Re-executes the call (aborting any in-flight run). */
|
|
39
|
+
refresh: () => Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Reactive unary call: re-runs when reactive args change, exposes
|
|
43
|
+
* data/pending/error, aborts in-flight calls on re-run and unmount.
|
|
44
|
+
*
|
|
45
|
+
* ```ts
|
|
46
|
+
* const { data, pending, error, refresh } =
|
|
47
|
+
* useVrpcCall<User>("user.get", () => [route.params.id]);
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* SSR-safe: on the server it stays pending and never opens a connection
|
|
51
|
+
* (the browser fills it after hydration).
|
|
52
|
+
*/
|
|
53
|
+
declare function useVrpcCall<T = Value>(name: string, args?: MaybeRefOrGetter<Value | undefined>, opts?: UseVrpcCallOptions): UseVrpcCall<T>;
|
|
54
|
+
type StreamState = "idle" | "connecting" | "streaming" | "ended" | "error";
|
|
55
|
+
interface UseVrpcStreamOptions extends CallOptions {
|
|
56
|
+
/** Start streaming on setup (client-side only). Default true. */
|
|
57
|
+
immediate?: boolean;
|
|
58
|
+
/** Restart the stream when reactive args change. Default true. */
|
|
59
|
+
watchArgs?: boolean;
|
|
60
|
+
/** How many received values to keep in `values`. Default 100; 0 keeps none. */
|
|
61
|
+
keepLast?: number;
|
|
62
|
+
}
|
|
63
|
+
interface UseVrpcStream<T> {
|
|
64
|
+
/** The most recent value. */
|
|
65
|
+
latest: ShallowRef<T | null>;
|
|
66
|
+
/** A bounded window of received values (see keepLast). */
|
|
67
|
+
values: Ref<T[]>;
|
|
68
|
+
state: Ref<StreamState>;
|
|
69
|
+
error: ShallowRef<VrpcError | null>;
|
|
70
|
+
/** Stops the stream (sends CancelRequest). */
|
|
71
|
+
cancel: () => void;
|
|
72
|
+
/** Cancels and re-opens the stream. */
|
|
73
|
+
restart: () => void;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Reactive server->client stream: values flow into refs, the stream is
|
|
77
|
+
* cancelled automatically when the component unmounts, and backpressure is
|
|
78
|
+
* real — the server is granted credit only as values are consumed.
|
|
79
|
+
*
|
|
80
|
+
* ```ts
|
|
81
|
+
* const { latest, values, state, cancel } =
|
|
82
|
+
* useVrpcStream<OrderEvent>("events.tail", ["orders"]);
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
declare function useVrpcStream<T = Value>(name: string, args?: MaybeRefOrGetter<Value | undefined>, opts?: UseVrpcStreamOptions): UseVrpcStream<T>;
|
|
86
|
+
|
|
87
|
+
declare module "vue" {
|
|
88
|
+
interface ComponentCustomProperties {
|
|
89
|
+
$vrpc: VrpcClient;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export { type StreamState, type UseVrpcCall, type UseVrpcCallOptions, type UseVrpcConnection, type UseVrpcStream, type UseVrpcStreamOptions, VRPC_KEY, type VrpcPluginOptions, createVrpc, useVrpc, useVrpcCall, useVrpcConnection, useVrpcStream };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
VrpcClient,
|
|
4
|
+
createClient,
|
|
5
|
+
VrpcError,
|
|
6
|
+
Code
|
|
7
|
+
} from "@arpabet/vrpc";
|
|
8
|
+
import {
|
|
9
|
+
computed,
|
|
10
|
+
getCurrentScope,
|
|
11
|
+
inject,
|
|
12
|
+
onScopeDispose,
|
|
13
|
+
ref,
|
|
14
|
+
shallowRef,
|
|
15
|
+
toValue,
|
|
16
|
+
watch
|
|
17
|
+
} from "vue";
|
|
18
|
+
import { VrpcClient as VrpcClient2, VrpcError as VrpcError2, Code as Code2, createClient as createClient2 } from "@arpabet/vrpc";
|
|
19
|
+
var VRPC_KEY = /* @__PURE__ */ Symbol("vrpc");
|
|
20
|
+
var isClient = typeof window !== "undefined";
|
|
21
|
+
function createVrpc(options) {
|
|
22
|
+
return {
|
|
23
|
+
install(app) {
|
|
24
|
+
const client = options instanceof VrpcClient ? options : createClient(options);
|
|
25
|
+
if (!(options instanceof VrpcClient) && options.eager && isClient) {
|
|
26
|
+
client.connect().catch(() => {
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
app.provide(VRPC_KEY, client);
|
|
30
|
+
app.config.globalProperties.$vrpc = client;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function useVrpc() {
|
|
35
|
+
const client = inject(VRPC_KEY, null);
|
|
36
|
+
if (!client) {
|
|
37
|
+
throw new Error("vrpc: no client provided \u2014 install createVrpc() on the app first");
|
|
38
|
+
}
|
|
39
|
+
return client;
|
|
40
|
+
}
|
|
41
|
+
function useVrpcOptional() {
|
|
42
|
+
const client = inject(VRPC_KEY, null);
|
|
43
|
+
if (!client && isClient) {
|
|
44
|
+
throw new Error("vrpc: no client provided \u2014 install createVrpc() on the app first");
|
|
45
|
+
}
|
|
46
|
+
return client;
|
|
47
|
+
}
|
|
48
|
+
function useVrpcConnection(client) {
|
|
49
|
+
const c = client ?? useVrpcOptional();
|
|
50
|
+
const status = ref(c?.status ?? "idle");
|
|
51
|
+
if (c) {
|
|
52
|
+
const off = c.on("status", (s) => {
|
|
53
|
+
status.value = s;
|
|
54
|
+
});
|
|
55
|
+
if (getCurrentScope()) onScopeDispose(off);
|
|
56
|
+
}
|
|
57
|
+
return { status, connected: computed(() => status.value === "open") };
|
|
58
|
+
}
|
|
59
|
+
function useVrpcCall(name, args, opts = {}) {
|
|
60
|
+
const client = useVrpcOptional();
|
|
61
|
+
const data = shallowRef(null);
|
|
62
|
+
const error = shallowRef(null);
|
|
63
|
+
const pending = ref(opts.immediate !== false);
|
|
64
|
+
let ctrl = null;
|
|
65
|
+
let runId = 0;
|
|
66
|
+
async function refresh() {
|
|
67
|
+
if (!isClient || !client) return;
|
|
68
|
+
ctrl?.abort();
|
|
69
|
+
const mine = ++runId;
|
|
70
|
+
const myCtrl = new AbortController();
|
|
71
|
+
ctrl = myCtrl;
|
|
72
|
+
if (opts.signal) {
|
|
73
|
+
if (opts.signal.aborted) myCtrl.abort();
|
|
74
|
+
else opts.signal.addEventListener("abort", () => myCtrl.abort(), { once: true });
|
|
75
|
+
}
|
|
76
|
+
pending.value = true;
|
|
77
|
+
error.value = null;
|
|
78
|
+
try {
|
|
79
|
+
const callOpts = { signal: myCtrl.signal };
|
|
80
|
+
if (opts.timeoutMs !== void 0) callOpts.timeoutMs = opts.timeoutMs;
|
|
81
|
+
if (opts.metadata !== void 0) callOpts.metadata = opts.metadata;
|
|
82
|
+
const res = await client.call(name, toValue(args) ?? void 0, callOpts);
|
|
83
|
+
if (mine !== runId) return;
|
|
84
|
+
data.value = res;
|
|
85
|
+
} catch (err) {
|
|
86
|
+
if (mine !== runId) return;
|
|
87
|
+
error.value = err instanceof VrpcError ? err : new VrpcError(Code.Unknown, String(err), { cause: err });
|
|
88
|
+
} finally {
|
|
89
|
+
if (mine === runId) pending.value = false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (opts.watchArgs !== false && (typeof args === "function" || args !== null && typeof args === "object")) {
|
|
93
|
+
watch(
|
|
94
|
+
() => toValue(args),
|
|
95
|
+
() => void refresh(),
|
|
96
|
+
{ deep: true }
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
if (opts.immediate !== false) void refresh();
|
|
100
|
+
if (getCurrentScope()) {
|
|
101
|
+
onScopeDispose(() => {
|
|
102
|
+
runId++;
|
|
103
|
+
ctrl?.abort();
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return { data, pending, error, refresh };
|
|
107
|
+
}
|
|
108
|
+
function useVrpcStream(name, args, opts = {}) {
|
|
109
|
+
const client = useVrpcOptional();
|
|
110
|
+
const latest = shallowRef(null);
|
|
111
|
+
const values = ref([]);
|
|
112
|
+
const state = ref("idle");
|
|
113
|
+
const error = shallowRef(null);
|
|
114
|
+
const keepLast = opts.keepLast ?? 100;
|
|
115
|
+
let current = null;
|
|
116
|
+
let runId = 0;
|
|
117
|
+
function cancel() {
|
|
118
|
+
runId++;
|
|
119
|
+
current?.cancel();
|
|
120
|
+
current = null;
|
|
121
|
+
if (state.value === "streaming" || state.value === "connecting") state.value = "idle";
|
|
122
|
+
}
|
|
123
|
+
function start() {
|
|
124
|
+
if (!isClient || !client) return;
|
|
125
|
+
cancel();
|
|
126
|
+
const mine = ++runId;
|
|
127
|
+
state.value = "connecting";
|
|
128
|
+
error.value = null;
|
|
129
|
+
const callOpts = {};
|
|
130
|
+
if (opts.timeoutMs !== void 0) callOpts.timeoutMs = opts.timeoutMs;
|
|
131
|
+
if (opts.metadata !== void 0) callOpts.metadata = opts.metadata;
|
|
132
|
+
if (opts.signal !== void 0) callOpts.signal = opts.signal;
|
|
133
|
+
const stream = client.getStream(name, toValue(args) ?? void 0, callOpts);
|
|
134
|
+
current = stream;
|
|
135
|
+
stream.ready.then(
|
|
136
|
+
() => {
|
|
137
|
+
if (mine === runId) state.value = "streaming";
|
|
138
|
+
},
|
|
139
|
+
() => {
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
void (async () => {
|
|
143
|
+
try {
|
|
144
|
+
for await (const v of stream) {
|
|
145
|
+
if (mine !== runId) return;
|
|
146
|
+
latest.value = v;
|
|
147
|
+
if (keepLast > 0) {
|
|
148
|
+
values.value.push(v);
|
|
149
|
+
if (values.value.length > keepLast) values.value.splice(0, values.value.length - keepLast);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (mine === runId) state.value = "ended";
|
|
153
|
+
} catch (err) {
|
|
154
|
+
if (mine !== runId) return;
|
|
155
|
+
error.value = err instanceof VrpcError ? err : new VrpcError(Code.Unknown, String(err), { cause: err });
|
|
156
|
+
state.value = "error";
|
|
157
|
+
}
|
|
158
|
+
})();
|
|
159
|
+
}
|
|
160
|
+
if (opts.watchArgs !== false && (typeof args === "function" || args !== null && typeof args === "object")) {
|
|
161
|
+
watch(
|
|
162
|
+
() => toValue(args),
|
|
163
|
+
() => start(),
|
|
164
|
+
{ deep: true }
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
if (opts.immediate !== false) start();
|
|
168
|
+
if (getCurrentScope()) onScopeDispose(cancel);
|
|
169
|
+
return { latest, values, state, error, cancel, restart: start };
|
|
170
|
+
}
|
|
171
|
+
export {
|
|
172
|
+
Code2 as Code,
|
|
173
|
+
VRPC_KEY,
|
|
174
|
+
VrpcClient2 as VrpcClient,
|
|
175
|
+
VrpcError2 as VrpcError,
|
|
176
|
+
createClient2 as createClient,
|
|
177
|
+
createVrpc,
|
|
178
|
+
useVrpc,
|
|
179
|
+
useVrpcCall,
|
|
180
|
+
useVrpcConnection,
|
|
181
|
+
useVrpcStream
|
|
182
|
+
};
|
|
183
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/*\n * Copyright (c) 2026 Karagatan LLC.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport {\n VrpcClient,\n createClient,\n type CallOptions,\n type ClientOptions,\n type Status,\n type Value,\n VrpcError,\n Code,\n} from \"@arpabet/vrpc\";\nimport {\n computed,\n getCurrentScope,\n inject,\n onScopeDispose,\n ref,\n shallowRef,\n toValue,\n watch,\n type App,\n type ComputedRef,\n type InjectionKey,\n type MaybeRefOrGetter,\n type Plugin,\n type Ref,\n type ShallowRef,\n} from \"vue\";\n\nexport const VRPC_KEY: InjectionKey<VrpcClient> = Symbol(\"vrpc\");\n\nconst isClient = typeof window !== \"undefined\";\n\nexport interface VrpcPluginOptions extends ClientOptions {\n /** Open the connection at install time instead of on first call. */\n eager?: boolean;\n}\n\n/**\n * Vue plugin providing a shared {@link VrpcClient}:\n *\n * ```ts\n * app.use(createVrpc({ url: import.meta.env.VITE_VRPC_URL }));\n * ```\n *\n * Pass an existing client instead of options to share one across apps.\n */\nexport function createVrpc(options: VrpcPluginOptions | VrpcClient): Plugin {\n return {\n install(app: App) {\n const client = options instanceof VrpcClient ? options : createClient(options);\n if (!(options instanceof VrpcClient) && options.eager && isClient) {\n client.connect().catch(() => {\n // the client keeps resuming per its reconnect policy\n });\n }\n app.provide(VRPC_KEY, client);\n app.config.globalProperties.$vrpc = client;\n },\n };\n}\n\n/** The client installed by {@link createVrpc}. */\nexport function useVrpc(): VrpcClient {\n const client = inject(VRPC_KEY, null);\n if (!client) {\n throw new Error(\"vrpc: no client provided — install createVrpc() on the app first\");\n }\n return client;\n}\n\n/**\n * The client, or null during SSR when the (client-only) plugin has not run —\n * the composables use this to degrade to their inert pending state on the\n * server instead of throwing (WEB.md §6.6 SSR policy).\n */\nfunction useVrpcOptional(): VrpcClient | null {\n const client = inject(VRPC_KEY, null);\n if (!client && isClient) {\n throw new Error(\"vrpc: no client provided — install createVrpc() on the app first\");\n }\n return client;\n}\n\nexport interface UseVrpcConnection {\n status: Ref<Status>;\n connected: ComputedRef<boolean>;\n}\n\n/** Reactive connection state (for a status badge, offline banner, …). */\nexport function useVrpcConnection(client?: VrpcClient): UseVrpcConnection {\n const c = client ?? useVrpcOptional();\n const status = ref<Status>(c?.status ?? \"idle\");\n if (c) {\n const off = c.on(\"status\", (s) => {\n status.value = s;\n });\n if (getCurrentScope()) onScopeDispose(off);\n }\n return { status, connected: computed(() => status.value === \"open\") };\n}\n\nexport interface UseVrpcCallOptions extends CallOptions {\n /** Run the call on setup (client-side only). Default true. */\n immediate?: boolean;\n /** Re-run automatically when reactive args change. Default true. */\n watchArgs?: boolean;\n}\n\nexport interface UseVrpcCall<T> {\n data: ShallowRef<T | null>;\n pending: Ref<boolean>;\n error: ShallowRef<VrpcError | null>;\n /** Re-executes the call (aborting any in-flight run). */\n refresh: () => Promise<void>;\n}\n\n/**\n * Reactive unary call: re-runs when reactive args change, exposes\n * data/pending/error, aborts in-flight calls on re-run and unmount.\n *\n * ```ts\n * const { data, pending, error, refresh } =\n * useVrpcCall<User>(\"user.get\", () => [route.params.id]);\n * ```\n *\n * SSR-safe: on the server it stays pending and never opens a connection\n * (the browser fills it after hydration).\n */\nexport function useVrpcCall<T = Value>(\n name: string,\n args?: MaybeRefOrGetter<Value | undefined>,\n opts: UseVrpcCallOptions = {},\n): UseVrpcCall<T> {\n const client = useVrpcOptional();\n const data = shallowRef<T | null>(null);\n const error = shallowRef<VrpcError | null>(null);\n const pending = ref(opts.immediate !== false);\n\n let ctrl: AbortController | null = null;\n let runId = 0;\n\n async function refresh(): Promise<void> {\n if (!isClient || !client) return; // SSR renders the pending state\n ctrl?.abort();\n const mine = ++runId;\n const myCtrl = new AbortController();\n ctrl = myCtrl;\n if (opts.signal) {\n if (opts.signal.aborted) myCtrl.abort();\n else opts.signal.addEventListener(\"abort\", () => myCtrl.abort(), { once: true });\n }\n pending.value = true;\n error.value = null;\n try {\n const callOpts: CallOptions = { signal: myCtrl.signal };\n if (opts.timeoutMs !== undefined) callOpts.timeoutMs = opts.timeoutMs;\n if (opts.metadata !== undefined) callOpts.metadata = opts.metadata;\n const res = await client.call(name, toValue(args) ?? undefined, callOpts);\n if (mine !== runId) return; // superseded\n data.value = res as T;\n } catch (err) {\n if (mine !== runId) return;\n error.value =\n err instanceof VrpcError ? err : new VrpcError(Code.Unknown, String(err), { cause: err });\n } finally {\n if (mine === runId) pending.value = false;\n }\n }\n\n if (opts.watchArgs !== false && (typeof args === \"function\" || (args !== null && typeof args === \"object\"))) {\n watch(\n () => toValue(args),\n () => void refresh(),\n { deep: true },\n );\n }\n if (opts.immediate !== false) void refresh();\n if (getCurrentScope()) {\n onScopeDispose(() => {\n runId++;\n ctrl?.abort();\n });\n }\n\n return { data, pending, error, refresh };\n}\n\nexport type StreamState = \"idle\" | \"connecting\" | \"streaming\" | \"ended\" | \"error\";\n\nexport interface UseVrpcStreamOptions extends CallOptions {\n /** Start streaming on setup (client-side only). Default true. */\n immediate?: boolean;\n /** Restart the stream when reactive args change. Default true. */\n watchArgs?: boolean;\n /** How many received values to keep in `values`. Default 100; 0 keeps none. */\n keepLast?: number;\n}\n\nexport interface UseVrpcStream<T> {\n /** The most recent value. */\n latest: ShallowRef<T | null>;\n /** A bounded window of received values (see keepLast). */\n values: Ref<T[]>;\n state: Ref<StreamState>;\n error: ShallowRef<VrpcError | null>;\n /** Stops the stream (sends CancelRequest). */\n cancel: () => void;\n /** Cancels and re-opens the stream. */\n restart: () => void;\n}\n\n/**\n * Reactive server->client stream: values flow into refs, the stream is\n * cancelled automatically when the component unmounts, and backpressure is\n * real — the server is granted credit only as values are consumed.\n *\n * ```ts\n * const { latest, values, state, cancel } =\n * useVrpcStream<OrderEvent>(\"events.tail\", [\"orders\"]);\n * ```\n */\nexport function useVrpcStream<T = Value>(\n name: string,\n args?: MaybeRefOrGetter<Value | undefined>,\n opts: UseVrpcStreamOptions = {},\n): UseVrpcStream<T> {\n const client = useVrpcOptional();\n const latest = shallowRef<T | null>(null);\n const values = ref<T[]>([]) as Ref<T[]>;\n const state = ref<StreamState>(\"idle\");\n const error = shallowRef<VrpcError | null>(null);\n const keepLast = opts.keepLast ?? 100;\n\n let current: { cancel(): void } | null = null;\n let runId = 0;\n\n function cancel(): void {\n runId++;\n current?.cancel();\n current = null;\n if (state.value === \"streaming\" || state.value === \"connecting\") state.value = \"idle\";\n }\n\n function start(): void {\n if (!isClient || !client) return; // SSR: never open sockets during render\n cancel();\n const mine = ++runId;\n state.value = \"connecting\";\n error.value = null;\n const callOpts: CallOptions = {};\n if (opts.timeoutMs !== undefined) callOpts.timeoutMs = opts.timeoutMs;\n if (opts.metadata !== undefined) callOpts.metadata = opts.metadata;\n if (opts.signal !== undefined) callOpts.signal = opts.signal;\n const stream = client.getStream(name, toValue(args) ?? undefined, callOpts);\n current = stream;\n stream.ready.then(\n () => {\n if (mine === runId) state.value = \"streaming\";\n },\n () => {\n // surfaced through iteration below\n },\n );\n void (async () => {\n try {\n for await (const v of stream) {\n if (mine !== runId) return;\n latest.value = v as T;\n if (keepLast > 0) {\n values.value.push(v as T);\n if (values.value.length > keepLast) values.value.splice(0, values.value.length - keepLast);\n }\n }\n if (mine === runId) state.value = \"ended\";\n } catch (err) {\n if (mine !== runId) return;\n error.value =\n err instanceof VrpcError ? err : new VrpcError(Code.Unknown, String(err), { cause: err });\n state.value = \"error\";\n }\n })();\n }\n\n if (opts.watchArgs !== false && (typeof args === \"function\" || (args !== null && typeof args === \"object\"))) {\n watch(\n () => toValue(args),\n () => start(),\n { deep: true },\n );\n }\n if (opts.immediate !== false) start();\n if (getCurrentScope()) onScopeDispose(cancel);\n\n return { latest, values, state, error, cancel, restart: start };\n}\n\n// Re-export the pieces apps typically need alongside the composables.\nexport { VrpcClient, VrpcError, Code, createClient } from \"@arpabet/vrpc\";\nexport type {\n CallOptions,\n ClientOptions,\n Status,\n Value,\n ValueMap,\n VrpcChat,\n VrpcStream,\n} from \"@arpabet/vrpc\";\n\ndeclare module \"vue\" {\n interface ComponentCustomProperties {\n $vrpc: VrpcClient;\n }\n}\n"],"mappings":"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","names":["VrpcClient","VrpcError","Code","createClient"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@arpabet/vrpc-vue",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Vue 3 plugin and composables for value-rpc: useVrpcCall, useVrpcStream, useVrpcConnection",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"typecheck": "tsc --noEmit"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@arpabet/vrpc": "^0.2.0"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"vue": "^3.4.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"happy-dom": "^20.11.0",
|
|
31
|
+
"tsup": "^8.3.0",
|
|
32
|
+
"typescript": "^5.6.0",
|
|
33
|
+
"vitest": "^4.1.10",
|
|
34
|
+
"vue": "^3.5.0"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=22"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"vue",
|
|
41
|
+
"composables",
|
|
42
|
+
"rpc",
|
|
43
|
+
"websocket",
|
|
44
|
+
"value-rpc"
|
|
45
|
+
],
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "https://github.com/arpabet/value-rpc-js.git",
|
|
52
|
+
"directory": "packages/vrpc-vue"
|
|
53
|
+
}
|
|
54
|
+
}
|