๐Ÿ“„ src/web3/subscription/sync-cursor.ts
D-OPEN SOVEREIGN

Documentation & Insights

IndexedDB-backed cursor store keyed by userId โ€” a sibling of sessionStore,
not a field on it (spec ยง15.8: SyncCursor is heavier than the counters
already on Web3SessionAccount, and bloating that struct isn't worth it for
5 component files that reference it as a prop/ref type).
1import { del, get, set } from 'idb-keyval';
2import type { SubscriptionNode, SyncCursor } from './types';
3
4interface SerializedCursor {
5  rootUserId: number;
6  knownNodes: Array<[number, SubscriptionNode]>;
7  countSnapshot: Array<[number, number]>;
8  patchIndex: Array<[number, number]>;
9}
10
11export function serializeCursor(cursor: SyncCursor): SerializedCursor {
12  return {
13    rootUserId: cursor.rootUserId,
14    knownNodes: Array.from(cursor.knownNodes.entries()),
15    countSnapshot: Array.from(cursor.countSnapshot.entries()),
16    patchIndex: Array.from(cursor.patchIndex.entries()),
17  };
18}
19
20export function deserializeCursor(serialized: SerializedCursor): SyncCursor {
21  return {
22    rootUserId: serialized.rootUserId,
23    knownNodes: new Map(serialized.knownNodes),
24    countSnapshot: new Map(serialized.countSnapshot),
25    patchIndex: new Map(serialized.patchIndex),
26  };
27}
28
29export function emptyCursor(rootUserId: number): SyncCursor {
30  return {
31    rootUserId,
32    knownNodes: new Map(),
33    countSnapshot: new Map(),
34    patchIndex: new Map(),
35  };
36}
37
38const IDB_KEY_PREFIX = 'web3-sdk:subscription-cursor:';
39
40/**
41 * IndexedDB-backed cursor store keyed by userId โ€” a sibling of sessionStore,
42 * not a field on it (spec ยง15.8: SyncCursor is heavier than the counters
43 * already on Web3SessionAccount, and bloating that struct isn't worth it for
44 * 5 component files that reference it as a prop/ref type).
45 */
46export const cursorStore = {
47  async get(userId: number): Promise<SyncCursor | undefined> {
48    try {
49      const serialized = await get<SerializedCursor>(`${IDB_KEY_PREFIX}${userId}`);
50      return serialized ? deserializeCursor(serialized) : undefined;
51    } catch (e) {
52      console.warn(`[cursorStore] Failed to read cursor for userId ${userId}:`, e);
53      return undefined;
54    }
55  },
56
57  async set(userId: number, cursor: SyncCursor): Promise<void> {
58    try {
59      await set(`${IDB_KEY_PREFIX}${userId}`, serializeCursor(cursor));
60    } catch (e) {
61      console.warn(`[cursorStore] Failed to persist cursor for userId ${userId}:`, e);
62    }
63  },
64
65  async clear(userId: number): Promise<void> {
66    try {
67      await del(`${IDB_KEY_PREFIX}${userId}`);
68    } catch {
69      // ignore
70    }
71  },
72};
73