@pylonsync/react 0.3.131 → 0.3.134
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/package.json +3 -3
- package/src/db.ts +27 -0
- package/src/hooks.ts +82 -0
- package/src/index.ts +2 -0
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.3.
|
|
6
|
+
"version": "0.3.134",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "src/index.ts",
|
|
9
9
|
"types": "src/index.ts",
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
"check": "tsc -p tsconfig.json --noEmit"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@pylonsync/sdk": "0.3.
|
|
16
|
-
"@pylonsync/sync": "0.3.
|
|
15
|
+
"@pylonsync/sdk": "0.3.134",
|
|
16
|
+
"@pylonsync/sync": "0.3.134"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
19
19
|
"react": ">=19.0.0"
|
package/src/db.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { SyncEngine, createSyncEngine, type Row, type SyncEngineConfig } from "@
|
|
|
4
4
|
import {
|
|
5
5
|
useQuery as useQueryHook,
|
|
6
6
|
useQueryOne as useQueryOneHook,
|
|
7
|
+
useReactiveQuery as useReactiveQueryHook,
|
|
7
8
|
useMutation as useMutationHook,
|
|
8
9
|
useInfiniteQuery as useInfiniteQueryHook,
|
|
9
10
|
useAggregate as useAggregateHook,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
type QueryOptions,
|
|
13
14
|
type UseQueryReturn,
|
|
14
15
|
type UseQueryOneReturn,
|
|
16
|
+
type UseReactiveQueryReturn,
|
|
15
17
|
type UseMutationReturn,
|
|
16
18
|
type UseInfiniteQueryReturn,
|
|
17
19
|
type AggregateSpec,
|
|
@@ -99,6 +101,31 @@ export const db = {
|
|
|
99
101
|
return useQueryOneHook<T>(getSync(), entity, id);
|
|
100
102
|
},
|
|
101
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Reactive query — Convex-style auto-rerunning server handler.
|
|
106
|
+
*
|
|
107
|
+
* The server runs your `query()` handler with dependency tracking
|
|
108
|
+
* (every `ctx.db.*` read is recorded), registers the subscription,
|
|
109
|
+
* and pushes the initial result. Any future mutation touching the
|
|
110
|
+
* dep set triggers a re-run + push.
|
|
111
|
+
*
|
|
112
|
+
* ```tsx
|
|
113
|
+
* const { data: feed, loading } = db.useReactiveQuery<FeedItem[]>(
|
|
114
|
+
* "getFeed",
|
|
115
|
+
* { userId: currentUser.id },
|
|
116
|
+
* );
|
|
117
|
+
* ```
|
|
118
|
+
*
|
|
119
|
+
* Authoring side: define the handler with `query()` from
|
|
120
|
+
* `@pylonsync/functions`. Any handler is eligible — no opt-in flag.
|
|
121
|
+
*/
|
|
122
|
+
useReactiveQuery<T = unknown>(
|
|
123
|
+
fnName: string,
|
|
124
|
+
args?: unknown,
|
|
125
|
+
): UseReactiveQueryReturn<T> {
|
|
126
|
+
return useReactiveQueryHook<T>(getSync(), fnName, args);
|
|
127
|
+
},
|
|
128
|
+
|
|
102
129
|
/**
|
|
103
130
|
* Server-side function call with mutation state (loading, data, error).
|
|
104
131
|
*
|
package/src/hooks.ts
CHANGED
|
@@ -184,6 +184,88 @@ export function useQueryOne<T = Row>(
|
|
|
184
184
|
return { data, loading: loading.current, error: error.current, refetch };
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
// useReactiveQuery — Convex-style auto-rerunning server query
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
|
|
191
|
+
export interface UseReactiveQueryReturn<T> {
|
|
192
|
+
/** Latest server-pushed result. `null` until the initial run lands. */
|
|
193
|
+
data: T | null;
|
|
194
|
+
/** True until the first result lands (or the first error). */
|
|
195
|
+
loading: boolean;
|
|
196
|
+
/** Most recent error from the server-side handler, if any. */
|
|
197
|
+
error: Error | null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Subscribe to a server-side `query()` handler with automatic re-run
|
|
202
|
+
* on dependency changes. Mirrors Convex's reactive query model:
|
|
203
|
+
*
|
|
204
|
+
* 1. Mount: client sends `reactive-subscribe` over WS with `fn_name`
|
|
205
|
+
* + `args`. Server runs the handler under the connection's auth,
|
|
206
|
+
* records which entities the handler read via `ctx.db.*`, registers
|
|
207
|
+
* the subscription, and pushes the initial result.
|
|
208
|
+
* 2. On every server-side mutation, the runtime's reactive registry
|
|
209
|
+
* looks up subs whose dep set overlaps the changed entity, re-runs
|
|
210
|
+
* them, hashes the result, and pushes only when the hash changed.
|
|
211
|
+
* 3. Unmount: client sends `reactive-unsubscribe`; server tears down
|
|
212
|
+
* the registration and stops re-running.
|
|
213
|
+
*
|
|
214
|
+
* Auth context for re-runs is captured at subscribe time — the
|
|
215
|
+
* subscriber's identity, not the mutating user's. Policy gates the
|
|
216
|
+
* handler runs at first execution apply on every re-run.
|
|
217
|
+
*
|
|
218
|
+
* ```tsx
|
|
219
|
+
* const { data, loading } = useReactiveQuery<MessageWithAuthor[]>(
|
|
220
|
+
* sync,
|
|
221
|
+
* "getMessagesWithAuthors",
|
|
222
|
+
* { channelId: "c_1" },
|
|
223
|
+
* );
|
|
224
|
+
* ```
|
|
225
|
+
*
|
|
226
|
+
* Args object identity matters: changing the args reference triggers
|
|
227
|
+
* an unsubscribe + resubscribe with a fresh sub_id. Stabilize via
|
|
228
|
+
* `useMemo` if you build args inline on every render.
|
|
229
|
+
*/
|
|
230
|
+
export function useReactiveQuery<T = unknown>(
|
|
231
|
+
sync: SyncEngine,
|
|
232
|
+
fnName: string,
|
|
233
|
+
args?: unknown,
|
|
234
|
+
): UseReactiveQueryReturn<T> {
|
|
235
|
+
const [data, setData] = useState<T | null>(null);
|
|
236
|
+
const [loading, setLoading] = useState<boolean>(true);
|
|
237
|
+
const [error, setError] = useState<Error | null>(null);
|
|
238
|
+
// Stable serialization of args — when the JSON shape changes we
|
|
239
|
+
// re-subscribe. Object identity changes alone don't re-subscribe.
|
|
240
|
+
const argsKey = useMemo(() => JSON.stringify(args ?? null), [args]);
|
|
241
|
+
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
const sub_id = generateId();
|
|
244
|
+
setLoading(true);
|
|
245
|
+
setError(null);
|
|
246
|
+
sync.subscribeReactive(sub_id, fnName, args ?? null, (msg) => {
|
|
247
|
+
if (msg.kind === "result") {
|
|
248
|
+
setData(msg.result as T);
|
|
249
|
+
setLoading(false);
|
|
250
|
+
setError(null);
|
|
251
|
+
} else {
|
|
252
|
+
// Reactive error pushes (e.g. handler unavailable) — surface
|
|
253
|
+
// to the consumer and stop spinning.
|
|
254
|
+
setError(
|
|
255
|
+
Object.assign(new Error(msg.message || msg.code), { code: msg.code }),
|
|
256
|
+
);
|
|
257
|
+
setLoading(false);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
return () => {
|
|
261
|
+
sync.unsubscribeReactive(sub_id);
|
|
262
|
+
};
|
|
263
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
264
|
+
}, [sync, fnName, argsKey]);
|
|
265
|
+
|
|
266
|
+
return { data, loading, error };
|
|
267
|
+
}
|
|
268
|
+
|
|
187
269
|
// ---------------------------------------------------------------------------
|
|
188
270
|
// Client-side filter application (matches the server's operator set)
|
|
189
271
|
// ---------------------------------------------------------------------------
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { defaultStorage, type Storage as PylonStorage } from "@pylonsync/sync";
|
|
|
7
7
|
export {
|
|
8
8
|
useQuery,
|
|
9
9
|
useQueryOne,
|
|
10
|
+
useReactiveQuery,
|
|
10
11
|
useMutation,
|
|
11
12
|
useInfiniteQuery,
|
|
12
13
|
usePaginatedQuery,
|
|
@@ -29,6 +30,7 @@ export type {
|
|
|
29
30
|
IncludeSpec,
|
|
30
31
|
UseQueryReturn,
|
|
31
32
|
UseQueryOneReturn,
|
|
33
|
+
UseReactiveQueryReturn,
|
|
32
34
|
UseMutationReturn,
|
|
33
35
|
UseInfiniteQueryReturn,
|
|
34
36
|
UsePaginatedQueryReturn,
|