๐Ÿ“„ src/web3/vue/composables/useSubscriptionSync.ts
D-OPEN SOVEREIGN

Documentation & Insights

Read-only reactive wrapper over subscription-sync state for one userId
(spec ยง15.8 / ยง14.11). It does NOT trigger a sync and does NOT touch
patches โ€” syncAccount() (session/sync.ts) is the only trigger, on the
post-write and boot/discovery paths (ยง4.6), and it already decodes,
persists, and merges any newly-delivered patches into the ORM internally.
This composable only surfaces whatever cursorStore currently holds plus an
applied-patch count from WalletEvents.emitSubscriptionPatchesSynced, for
UX (toast/badge) use โ€” the same relationship useWeb3Session() has to
sessionStore.
1import { onScopeDispose, ref, type Ref } from 'vue';
2import { WalletEvents } from '../../events/wallet_events';
3import { cursorStore } from '../../subscription/sync-cursor';
4import type { SyncCursor } from '../../subscription/types';
5
6export interface UseSubscriptionSyncReturn {
7  cursor: Ref<SyncCursor | null>;
8  appliedCount: Ref<number>;
9  isLoading: Ref<boolean>;
10  refresh(): Promise<void>;
11}
12
13/**
14 * Read-only reactive wrapper over subscription-sync state for one userId
15 * (spec ยง15.8 / ยง14.11). It does NOT trigger a sync and does NOT touch
16 * patches โ€” syncAccount() (session/sync.ts) is the only trigger, on the
17 * post-write and boot/discovery paths (ยง4.6), and it already decodes,
18 * persists, and merges any newly-delivered patches into the ORM internally.
19 * This composable only surfaces whatever cursorStore currently holds plus an
20 * applied-patch count from WalletEvents.emitSubscriptionPatchesSynced, for
21 * UX (toast/badge) use โ€” the same relationship useWeb3Session() has to
22 * sessionStore.
23 */
24export function useSubscriptionSync(userId: number): UseSubscriptionSyncReturn {
25  const cursor = ref<SyncCursor | null>(null) as Ref<SyncCursor | null>;
26  const appliedCount = ref(0);
27  const isLoading = ref(false);
28
29  async function refresh(): Promise<void> {
30    isLoading.value = true;
31    try {
32      cursor.value = (await cursorStore.get(userId)) ?? null;
33    } finally {
34      isLoading.value = false;
35    }
36  }
37
38  refresh();
39
40  const unsubscribe = WalletEvents.onSubscriptionPatchesSynced((event) => {
41    if (event.rootUserId !== userId) return;
42    appliedCount.value = event.appliedCount;
43    void refresh(); // pick up the cursor advance that shipped with this batch
44  });
45
46  onScopeDispose(unsubscribe);
47
48  return { cursor, appliedCount, isLoading, refresh };
49}
50