@ereo/rpc 0.1.6

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/dist/client.js ADDED
@@ -0,0 +1,433 @@
1
+ // @bun
2
+ // src/client.ts
3
+ function createClient(optionsOrEndpoint) {
4
+ const options = typeof optionsOrEndpoint === "string" ? { httpEndpoint: optionsOrEndpoint } : optionsOrEndpoint;
5
+ const fetchFn = options.fetch ?? fetch;
6
+ const reconnectOpts = {
7
+ enabled: true,
8
+ maxAttempts: 10,
9
+ delayMs: 1000,
10
+ maxDelayMs: 30000,
11
+ ...options.reconnect
12
+ };
13
+ const heartbeatOpts = {
14
+ enabled: options.heartbeatEnabled !== false,
15
+ interval: options.heartbeatInterval ?? 30000
16
+ };
17
+ let ws = null;
18
+ let wsConnecting = false;
19
+ let wsConnected = false;
20
+ let reconnectAttempts = 0;
21
+ let reconnectTimeout = null;
22
+ let heartbeatInterval = null;
23
+ let missedPongs = 0;
24
+ const subscriptions = new Map;
25
+ const pendingSubscriptions = [];
26
+ const connectionQueue = [];
27
+ function getHeaders() {
28
+ const base = { "Content-Type": "application/json" };
29
+ const custom = typeof options.headers === "function" ? options.headers() : options.headers;
30
+ return { ...base, ...custom };
31
+ }
32
+ function generateId() {
33
+ return Math.random().toString(36).slice(2) + Date.now().toString(36);
34
+ }
35
+ function connectWebSocket() {
36
+ return new Promise((resolve, reject) => {
37
+ if (!options.wsEndpoint) {
38
+ reject(new Error("WebSocket endpoint not configured"));
39
+ return;
40
+ }
41
+ if (wsConnected) {
42
+ resolve();
43
+ return;
44
+ }
45
+ if (wsConnecting) {
46
+ connectionQueue.push({ resolve, reject });
47
+ return;
48
+ }
49
+ wsConnecting = true;
50
+ startConnection(resolve, reject);
51
+ });
52
+ }
53
+ function startConnection(initialResolve, initialReject) {
54
+ connectionQueue.push({ resolve: initialResolve, reject: initialReject });
55
+ try {
56
+ ws = new WebSocket(options.wsEndpoint);
57
+ ws.onopen = () => {
58
+ wsConnecting = false;
59
+ wsConnected = true;
60
+ reconnectAttempts = 0;
61
+ missedPongs = 0;
62
+ startHeartbeat();
63
+ while (connectionQueue.length > 0) {
64
+ const { resolve } = connectionQueue.shift();
65
+ resolve();
66
+ }
67
+ for (const [id, sub] of subscriptions) {
68
+ sendSubscribe(id, sub.path, sub.input);
69
+ }
70
+ while (pendingSubscriptions.length > 0) {
71
+ const pending = pendingSubscriptions.shift();
72
+ subscriptions.set(pending.id, pending.sub);
73
+ sendSubscribe(pending.id, pending.sub.path, pending.sub.input);
74
+ }
75
+ };
76
+ ws.onmessage = (event) => {
77
+ try {
78
+ const msg = JSON.parse(event.data);
79
+ if (msg.type === "pong") {
80
+ missedPongs = 0;
81
+ return;
82
+ }
83
+ handleServerMessage(msg);
84
+ } catch (error) {
85
+ console.error("Failed to parse WebSocket message:", error);
86
+ }
87
+ };
88
+ ws.onerror = (error) => {
89
+ console.error("WebSocket error:", error);
90
+ };
91
+ ws.onclose = () => {
92
+ stopHeartbeat();
93
+ wsConnecting = false;
94
+ wsConnected = false;
95
+ ws = null;
96
+ while (connectionQueue.length > 0) {
97
+ const { reject } = connectionQueue.shift();
98
+ reject(new Error("WebSocket connection closed"));
99
+ }
100
+ if (reconnectOpts.enabled && subscriptions.size > 0) {
101
+ scheduleReconnect();
102
+ }
103
+ };
104
+ } catch (error) {
105
+ wsConnecting = false;
106
+ while (connectionQueue.length > 0) {
107
+ const { reject } = connectionQueue.shift();
108
+ reject(error instanceof Error ? error : new Error(String(error)));
109
+ }
110
+ }
111
+ }
112
+ function startHeartbeat() {
113
+ if (!heartbeatOpts.enabled)
114
+ return;
115
+ heartbeatInterval = setInterval(() => {
116
+ if (!ws || ws.readyState !== WebSocket.OPEN) {
117
+ stopHeartbeat();
118
+ return;
119
+ }
120
+ if (missedPongs >= 2) {
121
+ console.warn("WebSocket heartbeat failed, closing connection");
122
+ ws.close();
123
+ return;
124
+ }
125
+ missedPongs++;
126
+ ws.send(JSON.stringify({ type: "ping" }));
127
+ }, heartbeatOpts.interval);
128
+ }
129
+ function stopHeartbeat() {
130
+ if (heartbeatInterval) {
131
+ clearInterval(heartbeatInterval);
132
+ heartbeatInterval = null;
133
+ }
134
+ missedPongs = 0;
135
+ }
136
+ function scheduleReconnect() {
137
+ if (reconnectTimeout)
138
+ return;
139
+ stopHeartbeat();
140
+ if (reconnectAttempts >= reconnectOpts.maxAttempts) {
141
+ console.error("Max reconnection attempts reached");
142
+ for (const [, sub] of subscriptions) {
143
+ sub.callbacks.onError?.(new Error("Connection lost"));
144
+ }
145
+ return;
146
+ }
147
+ const delay = Math.min(reconnectOpts.delayMs * Math.pow(2, reconnectAttempts), reconnectOpts.maxDelayMs);
148
+ reconnectAttempts++;
149
+ console.log(`Reconnecting in ${delay}ms (attempt ${reconnectAttempts})`);
150
+ reconnectTimeout = setTimeout(() => {
151
+ reconnectTimeout = null;
152
+ connectWebSocket().catch(() => {});
153
+ }, delay);
154
+ }
155
+ function handleServerMessage(msg) {
156
+ if (msg.type === "pong") {
157
+ return;
158
+ }
159
+ const sub = subscriptions.get(msg.id);
160
+ if (!sub)
161
+ return;
162
+ switch (msg.type) {
163
+ case "data":
164
+ sub.callbacks.onData(msg.data);
165
+ break;
166
+ case "error":
167
+ sub.callbacks.onError?.(new Error(msg.error.message));
168
+ break;
169
+ case "complete":
170
+ subscriptions.delete(msg.id);
171
+ sub.callbacks.onComplete?.();
172
+ break;
173
+ }
174
+ }
175
+ function sendSubscribe(id, path, input) {
176
+ if (!ws || ws.readyState !== WebSocket.OPEN)
177
+ return;
178
+ const msg = { type: "subscribe", id, path, input };
179
+ ws.send(JSON.stringify(msg));
180
+ }
181
+ function sendUnsubscribe(id) {
182
+ if (!ws || ws.readyState !== WebSocket.OPEN)
183
+ return;
184
+ const msg = { type: "unsubscribe", id };
185
+ ws.send(JSON.stringify(msg));
186
+ }
187
+ function createProxy(path) {
188
+ return new Proxy(() => {}, {
189
+ get(_target, prop) {
190
+ if (prop === "query") {
191
+ return async (input) => {
192
+ const usePost = options.usePostForQueries ?? false;
193
+ if (usePost) {
194
+ const response = await fetchFn(options.httpEndpoint, {
195
+ method: "POST",
196
+ headers: getHeaders(),
197
+ body: JSON.stringify({ path, type: "query", input })
198
+ });
199
+ return handleHttpResponse(response, path);
200
+ } else {
201
+ const url = new URL(options.httpEndpoint, window.location.origin);
202
+ url.searchParams.set("path", path.join("."));
203
+ if (input !== undefined) {
204
+ const inputStr = JSON.stringify(input);
205
+ if (inputStr.length > 1500) {
206
+ console.warn("RPC query input is large. Consider using usePostForQueries option to avoid URL length limits.");
207
+ }
208
+ url.searchParams.set("input", inputStr);
209
+ }
210
+ const response = await fetchFn(url.toString(), {
211
+ method: "GET",
212
+ headers: getHeaders()
213
+ });
214
+ return handleHttpResponse(response, path);
215
+ }
216
+ };
217
+ }
218
+ if (prop === "mutate") {
219
+ return async (input) => {
220
+ const response = await fetchFn(options.httpEndpoint, {
221
+ method: "POST",
222
+ headers: getHeaders(),
223
+ body: JSON.stringify({ path, type: "mutation", input })
224
+ });
225
+ return handleHttpResponse(response, path);
226
+ };
227
+ }
228
+ if (prop === "subscribe") {
229
+ return (inputOrCallbacks, maybeCallbacks) => {
230
+ const hasInput = maybeCallbacks !== undefined;
231
+ const input = hasInput ? inputOrCallbacks : undefined;
232
+ const callbacks = hasInput ? maybeCallbacks : inputOrCallbacks;
233
+ const id = generateId();
234
+ const sub = { path, input, callbacks };
235
+ if (!wsConnected && !wsConnecting) {
236
+ pendingSubscriptions.push({ id, sub });
237
+ connectWebSocket().catch((error) => {
238
+ callbacks.onError?.(error instanceof Error ? error : new Error(String(error)));
239
+ });
240
+ } else if (wsConnected) {
241
+ subscriptions.set(id, sub);
242
+ sendSubscribe(id, path, input);
243
+ } else {
244
+ pendingSubscriptions.push({ id, sub });
245
+ }
246
+ return () => {
247
+ subscriptions.delete(id);
248
+ sendUnsubscribe(id);
249
+ if (subscriptions.size === 0 && pendingSubscriptions.length === 0) {
250
+ if (reconnectTimeout) {
251
+ clearTimeout(reconnectTimeout);
252
+ reconnectTimeout = null;
253
+ }
254
+ ws?.close();
255
+ ws = null;
256
+ wsConnected = false;
257
+ }
258
+ };
259
+ };
260
+ }
261
+ return createProxy([...path, prop]);
262
+ }
263
+ });
264
+ }
265
+ return createProxy([]);
266
+ }
267
+ async function handleHttpResponse(response, path) {
268
+ const result = await response.json();
269
+ if (!result.ok) {
270
+ const error = new Error(result.error.message);
271
+ error.name = "RPCClientError";
272
+ error.code = result.error.code;
273
+ error.path = path.join(".");
274
+ error.details = result.error.details;
275
+ throw error;
276
+ }
277
+ return result.data;
278
+ }
279
+ // src/hooks.ts
280
+ import { useState, useEffect, useCallback, useRef } from "react";
281
+ function useQuery(procedure, options = {}) {
282
+ const { input, enabled = true, refetchInterval } = options;
283
+ const [data, setData] = useState(undefined);
284
+ const [error, setError] = useState(undefined);
285
+ const [isLoading, setIsLoading] = useState(enabled);
286
+ const inputRef = useRef(input);
287
+ inputRef.current = input;
288
+ const fetchData = useCallback(async () => {
289
+ if (!enabled)
290
+ return;
291
+ setIsLoading(true);
292
+ setError(undefined);
293
+ try {
294
+ const result = await procedure.query(inputRef.current);
295
+ setData(result);
296
+ } catch (err) {
297
+ setError(err instanceof Error ? err : new Error(String(err)));
298
+ } finally {
299
+ setIsLoading(false);
300
+ }
301
+ }, [procedure, enabled]);
302
+ useEffect(() => {
303
+ fetchData();
304
+ }, [fetchData]);
305
+ useEffect(() => {
306
+ if (!refetchInterval || !enabled)
307
+ return;
308
+ const interval = setInterval(fetchData, refetchInterval);
309
+ return () => clearInterval(interval);
310
+ }, [refetchInterval, fetchData, enabled]);
311
+ return {
312
+ data,
313
+ error,
314
+ isLoading,
315
+ isError: error !== undefined,
316
+ isSuccess: data !== undefined && error === undefined,
317
+ refetch: fetchData
318
+ };
319
+ }
320
+ function useMutation(procedure, options = {}) {
321
+ const { onSuccess, onError, onSettled } = options;
322
+ const [data, setData] = useState(undefined);
323
+ const [error, setError] = useState(undefined);
324
+ const [isPending, setIsPending] = useState(false);
325
+ const [isSuccess, setIsSuccess] = useState(false);
326
+ const mutateAsync = useCallback(async (input) => {
327
+ setIsPending(true);
328
+ setError(undefined);
329
+ setIsSuccess(false);
330
+ try {
331
+ const result = await procedure.mutate(input);
332
+ setData(result);
333
+ setIsSuccess(true);
334
+ onSuccess?.(result);
335
+ return result;
336
+ } catch (err) {
337
+ const error2 = err instanceof Error ? err : new Error(String(err));
338
+ setError(error2);
339
+ onError?.(error2);
340
+ throw error2;
341
+ } finally {
342
+ setIsPending(false);
343
+ onSettled?.();
344
+ }
345
+ }, [procedure, onSuccess, onError, onSettled]);
346
+ const mutate = useCallback((input) => {
347
+ mutateAsync(input).catch(() => {});
348
+ }, [mutateAsync]);
349
+ const reset = useCallback(() => {
350
+ setData(undefined);
351
+ setError(undefined);
352
+ setIsPending(false);
353
+ setIsSuccess(false);
354
+ }, []);
355
+ return {
356
+ mutate,
357
+ mutateAsync,
358
+ data,
359
+ error,
360
+ isPending,
361
+ isError: error !== undefined,
362
+ isSuccess,
363
+ reset
364
+ };
365
+ }
366
+ function useSubscription(procedure, options = {}) {
367
+ const { input, enabled = true, onData, onError } = options;
368
+ const [data, setData] = useState(undefined);
369
+ const [history, setHistory] = useState([]);
370
+ const [error, setError] = useState(undefined);
371
+ const [status, setStatus] = useState("idle");
372
+ const unsubscribeRef = useRef(null);
373
+ const inputRef = useRef(input);
374
+ inputRef.current = input;
375
+ const subscribe = useCallback(() => {
376
+ if (!enabled)
377
+ return;
378
+ setStatus("connecting");
379
+ setError(undefined);
380
+ const callbacks = {
381
+ onData: (value) => {
382
+ setStatus("connected");
383
+ setData(value);
384
+ setHistory((prev) => [...prev, value]);
385
+ onData?.(value);
386
+ },
387
+ onError: (err) => {
388
+ setStatus("error");
389
+ setError(err);
390
+ onError?.(err);
391
+ },
392
+ onComplete: () => {
393
+ setStatus("closed");
394
+ }
395
+ };
396
+ if (inputRef.current !== undefined) {
397
+ unsubscribeRef.current = procedure.subscribe(inputRef.current, callbacks);
398
+ } else {
399
+ unsubscribeRef.current = procedure.subscribe(callbacks);
400
+ }
401
+ }, [procedure, enabled, onData, onError]);
402
+ const unsubscribe = useCallback(() => {
403
+ unsubscribeRef.current?.();
404
+ unsubscribeRef.current = null;
405
+ setStatus("closed");
406
+ }, []);
407
+ const resubscribe = useCallback(() => {
408
+ unsubscribe();
409
+ setHistory([]);
410
+ subscribe();
411
+ }, [subscribe, unsubscribe]);
412
+ useEffect(() => {
413
+ subscribe();
414
+ return () => {
415
+ unsubscribeRef.current?.();
416
+ };
417
+ }, [subscribe]);
418
+ return {
419
+ data,
420
+ history,
421
+ error,
422
+ status,
423
+ isActive: status === "connecting" || status === "connected",
424
+ unsubscribe,
425
+ resubscribe
426
+ };
427
+ }
428
+ export {
429
+ useSubscription,
430
+ useQuery,
431
+ useMutation,
432
+ createClient
433
+ };
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Client - typed proxy for calling RPC procedures with WebSocket subscriptions
3
+ *
4
+ * Usage:
5
+ * import { createClient } from '@ereo/rpc/client';
6
+ * import type { Api } from './api/router';
7
+ *
8
+ * const rpc = createClient<Api>({
9
+ * httpEndpoint: '/api/rpc',
10
+ * wsEndpoint: 'ws://localhost:3000/api/rpc',
11
+ * });
12
+ *
13
+ * // Queries and mutations
14
+ * const user = await rpc.users.me.query();
15
+ * const post = await rpc.posts.create.mutate({ title: 'Hello' });
16
+ *
17
+ * // Subscriptions with auto-reconnect
18
+ * const unsub = rpc.posts.onCreate.subscribe({
19
+ * onData: (post) => console.log('New post:', post),
20
+ * onError: (err) => console.error(err),
21
+ * });
22
+ */
23
+ import type { Router, RouterDef, InferClient } from './types';
24
+ export interface RPCClientOptions {
25
+ /** HTTP endpoint for queries/mutations (e.g., '/api/rpc') */
26
+ httpEndpoint: string;
27
+ /** WebSocket endpoint for subscriptions (e.g., 'ws://localhost:3000/api/rpc') */
28
+ wsEndpoint?: string;
29
+ /** Custom fetch function */
30
+ fetch?: typeof fetch;
31
+ /** Custom headers */
32
+ headers?: Record<string, string> | (() => Record<string, string>);
33
+ /** WebSocket reconnect options */
34
+ reconnect?: {
35
+ enabled?: boolean;
36
+ maxAttempts?: number;
37
+ delayMs?: number;
38
+ maxDelayMs?: number;
39
+ };
40
+ /** Use POST for all requests (queries and mutations) instead of GET for queries */
41
+ usePostForQueries?: boolean;
42
+ /** WebSocket heartbeat interval in milliseconds (default: 30000) */
43
+ heartbeatInterval?: number;
44
+ /** Enable WebSocket heartbeat (default: true) */
45
+ heartbeatEnabled?: boolean;
46
+ }
47
+ /**
48
+ * Create a typed client from a router type
49
+ */
50
+ export declare function createClient<T extends Router<RouterDef>>(optionsOrEndpoint: string | RPCClientOptions): InferClient<T['_def']>;
51
+ export interface RPCClientError extends Error {
52
+ code: string;
53
+ path: string;
54
+ details?: unknown;
55
+ }
56
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EACV,MAAM,EACN,SAAS,EACT,WAAW,EAMZ,MAAM,SAAS,CAAC;AAEjB,MAAM,WAAW,gBAAgB;IAC/B,6DAA6D;IAC7D,YAAY,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,qBAAqB;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAClE,kCAAkC;IAClC,SAAS,CAAC,EAAE;QACV,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,mFAAmF;IACnF,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iDAAiD;IACjD,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAQD;;GAEG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,CAAC,SAAS,CAAC,EACtD,iBAAiB,EAAE,MAAM,GAAG,gBAAgB,GAC3C,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAkWxB;AAiBD,MAAM,WAAW,cAAe,SAAQ,KAAK;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * @ereo/rpc - Context Bridge for RPC and Loaders/Actions
3
+ *
4
+ * Provides shared context between RPC procedures and route loaders/actions.
5
+ * This allows auth state, database connections, and other context to be
6
+ * shared seamlessly between the two patterns.
7
+ */
8
+ import type { BaseContext } from './types';
9
+ /**
10
+ * Context provider function - creates shared context from a request
11
+ * This is used by both RPC handlers and route loaders/actions
12
+ */
13
+ export type ContextProvider<TContext = any> = (request: Request) => TContext | Promise<TContext>;
14
+ /**
15
+ * Set the global context provider that will be used by both RPC and loaders
16
+ *
17
+ * Usage in your app setup:
18
+ * import { setContextProvider } from '@ereo/rpc';
19
+ *
20
+ * setContextProvider(async (request) => {
21
+ * const session = await getSession(request);
22
+ * const db = createDbConnection();
23
+ * return { session, db, user: session?.user };
24
+ * });
25
+ */
26
+ export declare function setContextProvider<TContext>(provider: ContextProvider<TContext>): void;
27
+ /**
28
+ * Get the current global context provider
29
+ */
30
+ export declare function getContextProvider(): ContextProvider | null;
31
+ /**
32
+ * Clear the global context provider (useful for testing)
33
+ */
34
+ export declare function clearContextProvider(): void;
35
+ /**
36
+ * Create context from a request using the global provider
37
+ * Falls back to an empty object if no provider is set
38
+ */
39
+ export declare function createSharedContext(request: Request): Promise<any>;
40
+ /**
41
+ * RPC Router options with context bridge support
42
+ */
43
+ export interface RouterWithContextOptions<TContext> {
44
+ /** Context provider for this router */
45
+ context?: ContextProvider<TContext>;
46
+ }
47
+ /**
48
+ * Enhanced router creation that supports context bridge
49
+ * This is an alternative to createRouter that provides better integration
50
+ */
51
+ export interface ContextBridgeConfig<TContext> {
52
+ /** Context provider - called for each request */
53
+ context: ContextProvider<TContext>;
54
+ }
55
+ /**
56
+ * Helper to create a typed context provider
57
+ * Provides better type inference for TypeScript users
58
+ */
59
+ export declare function createContextProvider<TContext>(provider: ContextProvider<TContext>): ContextProvider<TContext>;
60
+ /**
61
+ * Middleware that injects shared context into the procedure context
62
+ * Usage: procedure.use(withSharedContext())
63
+ */
64
+ export declare function withSharedContext(): (opts: {
65
+ ctx: BaseContext;
66
+ next: <T>(ctx: T) => {
67
+ ok: true;
68
+ ctx: T;
69
+ } | {
70
+ ok: false;
71
+ error: any;
72
+ };
73
+ }) => Promise<any>;
74
+ /**
75
+ * Hook for React components to access shared context
76
+ * This bridges server context to the client
77
+ */
78
+ export declare function useSharedContext<T>(): T | null;
79
+ //# sourceMappingURL=context-bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-bridge.d.ts","sourceRoot":"","sources":["../../src/context-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C;;;GAGG;AACH,MAAM,MAAM,eAAe,CAAC,QAAQ,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAOjG;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,eAAe,CAAC,QAAQ,CAAC,GAAG,IAAI,CAEtF;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,eAAe,GAAG,IAAI,CAE3D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAKxE;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB,CAAC,QAAQ;IAChD,uCAAuC;IACvC,OAAO,CAAC,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC;CACrC;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB,CAAC,QAAQ;IAC3C,iDAAiD;IACjD,OAAO,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC;CACpC;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAC5C,QAAQ,EAAE,eAAe,CAAC,QAAQ,CAAC,GAClC,eAAe,CAAC,QAAQ,CAAC,CAE3B;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,CAAC,IAAI,EAAE;IAC1C,GAAG,EAAE,WAAW,CAAC;IACjB,IAAI,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,KAAK;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,GAAG,EAAE,CAAC,CAAA;KAAE,GAAG;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,GAAG,CAAA;KAAE,CAAC;CACvE,KAAK,OAAO,CAAC,GAAG,CAAC,CAMjB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,KAAK,CAAC,GAAG,IAAI,CAQ9C"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * React hooks for RPC calls including subscriptions
3
+ *
4
+ * Usage:
5
+ * const { data, isLoading } = useQuery(rpc.users.me);
6
+ * const { mutate, isPending } = useMutation(rpc.posts.create);
7
+ * const { data, status } = useSubscription(rpc.posts.onCreate);
8
+ */
9
+ import type { SubscriptionCallbacks, Unsubscribe } from './types';
10
+ type QueryFn<TInput, TOutput> = TInput extends void ? {
11
+ query: () => Promise<TOutput>;
12
+ } : {
13
+ query: (input: TInput) => Promise<TOutput>;
14
+ };
15
+ export interface UseQueryOptions<TInput> {
16
+ input?: TInput;
17
+ enabled?: boolean;
18
+ refetchInterval?: number;
19
+ }
20
+ export interface UseQueryResult<TOutput> {
21
+ data: TOutput | undefined;
22
+ error: Error | undefined;
23
+ isLoading: boolean;
24
+ isError: boolean;
25
+ isSuccess: boolean;
26
+ refetch: () => Promise<void>;
27
+ }
28
+ export declare function useQuery<TInput, TOutput>(procedure: QueryFn<TInput, TOutput>, options?: UseQueryOptions<TInput>): UseQueryResult<TOutput>;
29
+ type MutationFn<TInput, TOutput> = TInput extends void ? {
30
+ mutate: () => Promise<TOutput>;
31
+ } : {
32
+ mutate: (input: TInput) => Promise<TOutput>;
33
+ };
34
+ export interface UseMutationOptions<TOutput> {
35
+ onSuccess?: (data: TOutput) => void;
36
+ onError?: (error: Error) => void;
37
+ onSettled?: () => void;
38
+ }
39
+ export interface UseMutationResult<TInput, TOutput> {
40
+ mutate: TInput extends void ? () => void : (input: TInput) => void;
41
+ mutateAsync: TInput extends void ? () => Promise<TOutput> : (input: TInput) => Promise<TOutput>;
42
+ data: TOutput | undefined;
43
+ error: Error | undefined;
44
+ isPending: boolean;
45
+ isError: boolean;
46
+ isSuccess: boolean;
47
+ reset: () => void;
48
+ }
49
+ export declare function useMutation<TInput, TOutput>(procedure: MutationFn<TInput, TOutput>, options?: UseMutationOptions<TOutput>): UseMutationResult<TInput, TOutput>;
50
+ type SubscribeFn<TInput, TOutput> = TInput extends void ? {
51
+ subscribe: (callbacks: SubscriptionCallbacks<TOutput>) => Unsubscribe;
52
+ } : {
53
+ subscribe: (input: TInput, callbacks: SubscriptionCallbacks<TOutput>) => Unsubscribe;
54
+ };
55
+ export type SubscriptionStatus = 'idle' | 'connecting' | 'connected' | 'error' | 'closed';
56
+ export interface UseSubscriptionOptions<TInput> {
57
+ input?: TInput;
58
+ enabled?: boolean;
59
+ onData?: (data: unknown) => void;
60
+ onError?: (error: Error) => void;
61
+ }
62
+ export interface UseSubscriptionResult<TOutput> {
63
+ /** Most recent data received */
64
+ data: TOutput | undefined;
65
+ /** All data received (for accumulating results) */
66
+ history: TOutput[];
67
+ /** Current error if any */
68
+ error: Error | undefined;
69
+ /** Connection status */
70
+ status: SubscriptionStatus;
71
+ /** Whether currently receiving data */
72
+ isActive: boolean;
73
+ /** Manually unsubscribe */
74
+ unsubscribe: () => void;
75
+ /** Resubscribe after unsubscribing */
76
+ resubscribe: () => void;
77
+ }
78
+ export declare function useSubscription<TInput, TOutput>(procedure: SubscribeFn<TInput, TOutput>, options?: UseSubscriptionOptions<TInput>): UseSubscriptionResult<TOutput>;
79
+ export {};
80
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/hooks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @ereo/rpc - Typed RPC layer for EreoJS
3
+ *
4
+ * Server-side exports for defining routers and procedures
5
+ */
6
+ export { procedure } from './procedure';
7
+ export type { ProcedureBuilder } from './procedure';
8
+ export { query, mutation, subscription } from './procedure';
9
+ export { createRouter, RPCError, errors } from './router';
10
+ export type { Router, BunWebSocketHandler } from './router';
11
+ export { rpcPlugin } from './plugin';
12
+ export type { RPCPluginOptions, RPCPluginResult, BunWebSocketConfig, RPCPlugin } from './plugin';
13
+ export { setContextProvider, getContextProvider, clearContextProvider, createSharedContext, createContextProvider, withSharedContext, useSharedContext, } from './context-bridge';
14
+ export type { ContextProvider, RouterWithContextOptions, ContextBridgeConfig, } from './context-bridge';
15
+ export { logging, rateLimit, clearRateLimitStore, createAuthMiddleware, requireRoles, validate, extend, timing, catchErrors, } from './middleware';
16
+ export type { LoggingOptions, RateLimitOptions, TimingContext } from './middleware';
17
+ export type { Schema, BaseContext, ExtendedContext, MiddlewareFn, MiddlewareDef, MiddlewareResult, ProcedureType, ProcedureDef, QueryProcedure, MutationProcedure, SubscriptionProcedure, SubscriptionYield, AnyProcedure, RouterDef, InferClient, SubscriptionCallbacks, Unsubscribe, RPCRequest, RPCResponse, RPCErrorShape, WSClientMessage, WSServerMessage, WSConnectionData, } from './types';
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGpD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG5D,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAC1D,YAAY,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAG5D,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAEjG,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,mBAAmB,EACnB,qBAAqB,EACrB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,eAAe,EACf,wBAAwB,EACxB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,OAAO,EACP,SAAS,EACT,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,EACZ,QAAQ,EACR,MAAM,EACN,MAAM,EACN,WAAW,GACZ,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAGpF,YAAY,EAEV,MAAM,EAGN,WAAW,EACX,eAAe,EAGf,YAAY,EACZ,aAAa,EACb,gBAAgB,EAGhB,aAAa,EACb,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,qBAAqB,EACrB,iBAAiB,EACjB,YAAY,EAGZ,SAAS,EAGT,WAAW,EACX,qBAAqB,EACrB,WAAW,EAGX,UAAU,EACV,WAAW,EACX,aAAa,EACb,eAAe,EACf,eAAe,EACf,gBAAgB,GACjB,MAAM,SAAS,CAAC"}