feat(client): per-document reactive subscription (doc_subscribe), drop polling
Expose subscribeDoc(nuri, onChange) / subscribeDocs(nuris, onChange) wrapping the real ng.doc_subscribe — per-document, event-driven (initial State + a Patch per commit, local or broker-synced from a remote peer), returning a sync unsubscribe. subscribeDocs isolates per doc (a failing/unsynced doc never aborts the others), so it sidesteps the ORM fan-out hang (never orm_start_graph(graphs:[…])). Replace the setInterval polling in inbox.watch() and discovery.watchIndex() with doc_subscribe — same public contract, now push not poll. NextGraph is subscription- first; no polling remains. Verified against the real broker (@data harness spike): the doc_subscribe callback marshals across the iframe RPC (@ng-org/web strips the callback and drives it via a MessagePort — no DataCloneError) and fires on the initial push and on a real write. Lib bun test 91 pass, tsc clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,7 @@
|
||||
*/
|
||||
|
||||
import { sparqlUpdate, sparqlQuery } from "./docs";
|
||||
import { subscribeDoc } from "./subscribe";
|
||||
import { getCurrentUser, getStoreRegistryDeps } from "./polyfill";
|
||||
import { escapeLiteral } from "./sparql";
|
||||
import type { Nuri, PrincipalId } from "./types";
|
||||
@@ -186,22 +187,31 @@ export async function read(targetInbox: Nuri): Promise<Deposit[]> {
|
||||
export const materialize = read;
|
||||
|
||||
/**
|
||||
* Subscription over an inbox. Polls {@link read} on an interval and invokes
|
||||
* `onDeposits` with the full current deposit list whenever it changes (grows).
|
||||
* Returns an unsubscribe function. The polyfill has no reactive inbox
|
||||
* subscription, so this polls; against real NextGraph it follows the recipient's
|
||||
* own inbox processing. `onDeposits` fires once immediately.
|
||||
* Subscription over an inbox — **event-driven, not polled**. Subscribes to the
|
||||
* inbox document via {@link subscribeDoc} (the platform's `doc_subscribe` push):
|
||||
* `onDeposits` fires once on the initial state push and again on every subsequent
|
||||
* change to the inbox document — a local deposit OR a broker-synced remote one.
|
||||
* Returns an unsubscribe function.
|
||||
*
|
||||
* On each push it re-reads the full deposit list ({@link read}) and invokes
|
||||
* `onDeposits` only when the deposit count changed (grew), keeping the same
|
||||
* "fires on change" contract the polling watcher had — same callback signature
|
||||
* and same behaviour, just event-driven instead of `setInterval`.
|
||||
*
|
||||
* The `intervalMs` option is accepted for signature compatibility but IGNORED:
|
||||
* there is no polling. (The inbox document is a single doc, so this is immune to
|
||||
* the ORM fan-out hang — see {@link subscribeDoc}.)
|
||||
*/
|
||||
export function watch(
|
||||
targetInbox: Nuri,
|
||||
onDeposits: (deposits: Deposit[]) => void,
|
||||
opts?: { intervalMs?: number },
|
||||
_opts?: { intervalMs?: number },
|
||||
): () => void {
|
||||
let stopped = false;
|
||||
let lastCount = -1;
|
||||
const intervalMs = opts?.intervalMs ?? 1000;
|
||||
|
||||
const tick = async (): Promise<void> => {
|
||||
// Re-read on every push; fire onDeposits only when the set changed (grew).
|
||||
const refresh = async (): Promise<void> => {
|
||||
if (stopped) return;
|
||||
try {
|
||||
const deposits = await read(targetInbox);
|
||||
@@ -214,10 +224,11 @@ export function watch(
|
||||
}
|
||||
};
|
||||
|
||||
void tick(); // fire once immediately
|
||||
const handle = setInterval(() => void tick(), intervalMs);
|
||||
// Subscribe to the inbox document: the initial State push fires the first read
|
||||
// (so onDeposits fires once immediately, as before), each later Patch a re-read.
|
||||
const unsubscribe = subscribeDoc(targetInbox, () => void refresh());
|
||||
return () => {
|
||||
stopped = true;
|
||||
clearInterval(handle);
|
||||
unsubscribe();
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user