@dabble/patches 0.6.0 → 0.7.0

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.
Files changed (114) hide show
  1. package/README.md +221 -208
  2. package/dist/BaseDoc-DkP3tUhT.d.ts +206 -0
  3. package/dist/algorithms/client/applyCommittedChanges.d.ts +7 -0
  4. package/dist/algorithms/client/applyCommittedChanges.js +6 -3
  5. package/dist/algorithms/lww/consolidateOps.d.ts +40 -0
  6. package/dist/algorithms/lww/consolidateOps.js +103 -0
  7. package/dist/algorithms/lww/index.d.ts +2 -0
  8. package/dist/algorithms/lww/index.js +1 -0
  9. package/dist/algorithms/lww/mergeServerWithLocal.d.ts +22 -0
  10. package/dist/algorithms/lww/mergeServerWithLocal.js +32 -0
  11. package/dist/algorithms/server/commitChanges.d.ts +32 -8
  12. package/dist/algorithms/server/commitChanges.js +20 -5
  13. package/dist/algorithms/server/createVersion.d.ts +1 -1
  14. package/dist/algorithms/server/getSnapshotAtRevision.d.ts +1 -1
  15. package/dist/algorithms/server/getStateAtRevision.d.ts +1 -1
  16. package/dist/algorithms/server/handleOfflineSessionsAndBatches.d.ts +1 -1
  17. package/dist/client/BaseDoc.d.ts +6 -0
  18. package/dist/client/BaseDoc.js +70 -0
  19. package/dist/client/ClientAlgorithm.d.ts +101 -0
  20. package/dist/client/ClientAlgorithm.js +0 -0
  21. package/dist/client/InMemoryStore.d.ts +5 -7
  22. package/dist/client/InMemoryStore.js +6 -35
  23. package/dist/client/IndexedDBStore.d.ts +39 -73
  24. package/dist/client/IndexedDBStore.js +17 -220
  25. package/dist/client/LWWAlgorithm.d.ts +43 -0
  26. package/dist/client/LWWAlgorithm.js +87 -0
  27. package/dist/client/LWWClientStore.d.ts +73 -0
  28. package/dist/client/LWWClientStore.js +0 -0
  29. package/dist/client/LWWDoc.d.ts +56 -0
  30. package/dist/client/LWWDoc.js +84 -0
  31. package/dist/client/LWWInMemoryStore.d.ts +88 -0
  32. package/dist/client/LWWInMemoryStore.js +208 -0
  33. package/dist/client/LWWIndexedDBStore.d.ts +91 -0
  34. package/dist/client/LWWIndexedDBStore.js +275 -0
  35. package/dist/client/OTAlgorithm.d.ts +42 -0
  36. package/dist/client/OTAlgorithm.js +113 -0
  37. package/dist/client/OTClientStore.d.ts +50 -0
  38. package/dist/client/OTClientStore.js +0 -0
  39. package/dist/client/OTDoc.d.ts +6 -0
  40. package/dist/client/OTDoc.js +97 -0
  41. package/dist/client/OTIndexedDBStore.d.ts +84 -0
  42. package/dist/client/OTIndexedDBStore.js +163 -0
  43. package/dist/client/Patches.d.ts +36 -16
  44. package/dist/client/Patches.js +60 -27
  45. package/dist/client/PatchesDoc.d.ts +4 -113
  46. package/dist/client/PatchesDoc.js +3 -153
  47. package/dist/client/PatchesStore.d.ts +8 -105
  48. package/dist/client/factories.d.ts +72 -0
  49. package/dist/client/factories.js +80 -0
  50. package/dist/client/index.d.ts +14 -5
  51. package/dist/client/index.js +9 -0
  52. package/dist/compression/index.d.ts +1 -1
  53. package/dist/data/change.js +2 -0
  54. package/dist/fractionalIndex.d.ts +67 -0
  55. package/dist/fractionalIndex.js +241 -0
  56. package/dist/index.d.ts +13 -3
  57. package/dist/index.js +1 -0
  58. package/dist/json-patch/types.d.ts +2 -0
  59. package/dist/net/PatchesClient.js +15 -15
  60. package/dist/net/PatchesSync.d.ts +24 -12
  61. package/dist/net/PatchesSync.js +56 -64
  62. package/dist/net/index.d.ts +6 -10
  63. package/dist/net/index.js +6 -1
  64. package/dist/net/protocol/JSONRPCClient.d.ts +4 -4
  65. package/dist/net/protocol/JSONRPCClient.js +6 -4
  66. package/dist/net/protocol/JSONRPCServer.d.ts +45 -9
  67. package/dist/net/protocol/JSONRPCServer.js +63 -8
  68. package/dist/net/serverContext.d.ts +38 -0
  69. package/dist/net/serverContext.js +20 -0
  70. package/dist/net/webrtc/WebRTCTransport.js +1 -1
  71. package/dist/net/websocket/AuthorizationProvider.d.ts +3 -3
  72. package/dist/net/websocket/WebSocketServer.d.ts +29 -20
  73. package/dist/net/websocket/WebSocketServer.js +23 -12
  74. package/dist/server/BranchManager.d.ts +50 -0
  75. package/dist/server/BranchManager.js +0 -0
  76. package/dist/server/CompressedStoreBackend.d.ts +7 -5
  77. package/dist/server/CompressedStoreBackend.js +3 -9
  78. package/dist/server/LWWBranchManager.d.ts +82 -0
  79. package/dist/server/LWWBranchManager.js +99 -0
  80. package/dist/server/LWWMemoryStoreBackend.d.ts +78 -0
  81. package/dist/server/LWWMemoryStoreBackend.js +191 -0
  82. package/dist/server/LWWServer.d.ts +130 -0
  83. package/dist/server/LWWServer.js +207 -0
  84. package/dist/server/{PatchesBranchManager.d.ts → OTBranchManager.d.ts} +32 -12
  85. package/dist/server/{PatchesBranchManager.js → OTBranchManager.js} +25 -40
  86. package/dist/server/OTServer.d.ts +108 -0
  87. package/dist/server/OTServer.js +141 -0
  88. package/dist/server/PatchesHistoryManager.d.ts +20 -7
  89. package/dist/server/PatchesHistoryManager.js +26 -3
  90. package/dist/server/PatchesServer.d.ts +70 -81
  91. package/dist/server/PatchesServer.js +0 -175
  92. package/dist/server/branchUtils.d.ts +82 -0
  93. package/dist/server/branchUtils.js +66 -0
  94. package/dist/server/index.d.ts +17 -6
  95. package/dist/server/index.js +33 -4
  96. package/dist/server/tombstone.d.ts +29 -0
  97. package/dist/server/tombstone.js +32 -0
  98. package/dist/server/types.d.ts +128 -26
  99. package/dist/server/utils.d.ts +12 -0
  100. package/dist/server/utils.js +23 -0
  101. package/dist/solid/context.d.ts +5 -4
  102. package/dist/solid/doc-manager.d.ts +3 -3
  103. package/dist/solid/index.d.ts +5 -4
  104. package/dist/solid/primitives.d.ts +2 -3
  105. package/dist/types.d.ts +4 -2
  106. package/dist/vue/composables.d.ts +2 -3
  107. package/dist/vue/doc-manager.d.ts +3 -3
  108. package/dist/vue/index.d.ts +5 -4
  109. package/dist/vue/provider.d.ts +5 -4
  110. package/package.json +1 -1
  111. package/dist/algorithms/client/collapsePendingChanges.d.ts +0 -30
  112. package/dist/algorithms/client/collapsePendingChanges.js +0 -78
  113. package/dist/net/websocket/RPCServer.d.ts +0 -141
  114. package/dist/net/websocket/RPCServer.js +0 -204
@@ -0,0 +1,88 @@
1
+ import { JSONPatchOp } from '../json-patch/types.js';
2
+ import { PatchesSnapshot, PatchesState, Change } from '../types.js';
3
+ import { LWWClientStore } from './LWWClientStore.js';
4
+ import { TrackedDoc } from './PatchesStore.js';
5
+ import '../json-patch/JSONPatch.js';
6
+ import '@dabble/delta';
7
+
8
+ /**
9
+ * In-memory implementation of LWWClientStore for LWW (Last-Write-Wins) sync algorithm.
10
+ *
11
+ * Uses field-level storage for LWW conflict resolution:
12
+ * - committedFields: Server-confirmed field values
13
+ * - pendingOps: Local changes waiting to be sent (keyed by path)
14
+ * - sendingChange: In-flight change being sent to server
15
+ *
16
+ * Useful for unit tests or when you want stateless realtime behavior with LWW semantics.
17
+ */
18
+ declare class LWWInMemoryStore implements LWWClientStore {
19
+ private docs;
20
+ /**
21
+ * Rebuilds a document state from snapshot + committed fields + sending + pending.
22
+ */
23
+ getDoc(docId: string): Promise<PatchesSnapshot | undefined>;
24
+ /**
25
+ * Returns the last committed revision for a document.
26
+ */
27
+ getCommittedRev(docId: string): Promise<number>;
28
+ /**
29
+ * List all documents in the store.
30
+ */
31
+ listDocs(includeDeleted?: boolean): Promise<TrackedDoc[]>;
32
+ /**
33
+ * Saves the current document state to storage.
34
+ * Clears all committed fields, pending ops, and sending change.
35
+ */
36
+ saveDoc(docId: string, docState: PatchesState): Promise<void>;
37
+ /**
38
+ * Track documents.
39
+ */
40
+ trackDocs(docIds: string[]): Promise<void>;
41
+ /**
42
+ * Untrack documents by removing all their data.
43
+ */
44
+ untrackDocs(docIds: string[]): Promise<void>;
45
+ /**
46
+ * Marks a document as deleted and clears all associated data.
47
+ */
48
+ deleteDoc(docId: string): Promise<void>;
49
+ /**
50
+ * Confirm the deletion of a document.
51
+ */
52
+ confirmDeleteDoc(docId: string): Promise<void>;
53
+ /**
54
+ * Closes the store and releases resources.
55
+ */
56
+ close(): Promise<void>;
57
+ /**
58
+ * Get pending ops, optionally filtered by path prefixes.
59
+ */
60
+ getPendingOps(docId: string, pathPrefixes?: string[]): Promise<JSONPatchOp[]>;
61
+ /**
62
+ * Save pending ops, optionally deleting paths.
63
+ */
64
+ savePendingOps(docId: string, ops: JSONPatchOp[], pathsToDelete?: string[]): Promise<void>;
65
+ /**
66
+ * Get the in-flight change for retry/reconnect scenarios.
67
+ */
68
+ getSendingChange(docId: string): Promise<Change | null>;
69
+ /**
70
+ * Atomically save sending change AND clear all pending ops.
71
+ */
72
+ saveSendingChange(docId: string, change: Change): Promise<void>;
73
+ /**
74
+ * Clear sendingChange after server ack, move ops to committed.
75
+ */
76
+ confirmSendingChange(docId: string): Promise<void>;
77
+ /**
78
+ * Apply server changes using LWW timestamp resolution.
79
+ */
80
+ applyServerChanges(docId: string, serverChanges: Change[]): Promise<void>;
81
+ private getOrCreateBuffer;
82
+ /**
83
+ * Converts pending ops to an array of Change objects.
84
+ */
85
+ private pendingOpsToChanges;
86
+ }
87
+
88
+ export { LWWInMemoryStore };
@@ -0,0 +1,208 @@
1
+ import "../chunk-IZ2YBCUP.js";
2
+ import { createChange } from "../data/change.js";
3
+ import { applyPatch } from "../json-patch/applyPatch.js";
4
+ class LWWInMemoryStore {
5
+ docs = /* @__PURE__ */ new Map();
6
+ // ─── Document Operations ─────────────────────────────────────────────────
7
+ /**
8
+ * Rebuilds a document state from snapshot + committed fields + sending + pending.
9
+ */
10
+ async getDoc(docId) {
11
+ const buf = this.docs.get(docId);
12
+ if (!buf || buf.deleted) return void 0;
13
+ let state = buf.snapshot?.state ? { ...buf.snapshot.state } : {};
14
+ const committedOps = Array.from(buf.committedFields.entries()).map(([path, value]) => ({
15
+ op: "replace",
16
+ path,
17
+ value
18
+ }));
19
+ if (committedOps.length > 0) {
20
+ state = applyPatch(state, committedOps, { partial: true });
21
+ }
22
+ if (buf.sendingChange?.ops?.length) {
23
+ state = applyPatch(state, buf.sendingChange.ops, { partial: true });
24
+ }
25
+ const pendingOps = Array.from(buf.pendingOps.values());
26
+ if (pendingOps.length > 0) {
27
+ state = applyPatch(state, pendingOps, { partial: true });
28
+ }
29
+ const pendingChanges = this.pendingOpsToChanges(buf.pendingOps, buf.committedRev);
30
+ return {
31
+ state,
32
+ rev: buf.committedRev,
33
+ changes: buf.sendingChange ? [buf.sendingChange, ...pendingChanges] : pendingChanges
34
+ };
35
+ }
36
+ /**
37
+ * Returns the last committed revision for a document.
38
+ */
39
+ async getCommittedRev(docId) {
40
+ return this.docs.get(docId)?.committedRev ?? 0;
41
+ }
42
+ /**
43
+ * List all documents in the store.
44
+ */
45
+ async listDocs(includeDeleted = false) {
46
+ return Array.from(this.docs.entries()).filter(([, buf]) => includeDeleted || !buf.deleted).map(([docId, buf]) => ({
47
+ docId,
48
+ committedRev: buf.committedRev,
49
+ deleted: buf.deleted
50
+ }));
51
+ }
52
+ /**
53
+ * Saves the current document state to storage.
54
+ * Clears all committed fields, pending ops, and sending change.
55
+ */
56
+ async saveDoc(docId, docState) {
57
+ this.docs.set(docId, {
58
+ snapshot: { state: docState.state, rev: docState.rev },
59
+ committedFields: /* @__PURE__ */ new Map(),
60
+ pendingOps: /* @__PURE__ */ new Map(),
61
+ sendingChange: null,
62
+ committedRev: docState.rev
63
+ });
64
+ }
65
+ // ─── Tracking ────────────────────────────────────────────────────────────
66
+ /**
67
+ * Track documents.
68
+ */
69
+ async trackDocs(docIds) {
70
+ for (const docId of docIds) {
71
+ const buf = this.getOrCreateBuffer(docId);
72
+ delete buf.deleted;
73
+ }
74
+ }
75
+ /**
76
+ * Untrack documents by removing all their data.
77
+ */
78
+ async untrackDocs(docIds) {
79
+ for (const docId of docIds) {
80
+ this.docs.delete(docId);
81
+ }
82
+ }
83
+ // ─── Deletion ────────────────────────────────────────────────────────────
84
+ /**
85
+ * Marks a document as deleted and clears all associated data.
86
+ */
87
+ async deleteDoc(docId) {
88
+ const buf = this.getOrCreateBuffer(docId);
89
+ buf.deleted = true;
90
+ buf.snapshot = void 0;
91
+ buf.committedFields.clear();
92
+ buf.pendingOps.clear();
93
+ buf.sendingChange = null;
94
+ }
95
+ /**
96
+ * Confirm the deletion of a document.
97
+ */
98
+ async confirmDeleteDoc(docId) {
99
+ this.docs.delete(docId);
100
+ }
101
+ /**
102
+ * Closes the store and releases resources.
103
+ */
104
+ async close() {
105
+ this.docs.clear();
106
+ }
107
+ // ─── LWWClientStore Methods ─────────────────────────────────────────────
108
+ /**
109
+ * Get pending ops, optionally filtered by path prefixes.
110
+ */
111
+ async getPendingOps(docId, pathPrefixes) {
112
+ const buf = this.docs.get(docId);
113
+ if (!buf) return [];
114
+ const ops = Array.from(buf.pendingOps.values());
115
+ if (!pathPrefixes || pathPrefixes.length === 0) {
116
+ return ops;
117
+ }
118
+ return ops.filter((op) => pathPrefixes.some((prefix) => op.path === prefix || op.path.startsWith(prefix + "/")));
119
+ }
120
+ /**
121
+ * Save pending ops, optionally deleting paths.
122
+ */
123
+ async savePendingOps(docId, ops, pathsToDelete) {
124
+ const buf = this.getOrCreateBuffer(docId);
125
+ if (buf.deleted) {
126
+ delete buf.deleted;
127
+ }
128
+ if (pathsToDelete) {
129
+ for (const path of pathsToDelete) {
130
+ buf.pendingOps.delete(path);
131
+ }
132
+ }
133
+ for (const op of ops) {
134
+ buf.pendingOps.set(op.path, op);
135
+ }
136
+ }
137
+ /**
138
+ * Get the in-flight change for retry/reconnect scenarios.
139
+ */
140
+ async getSendingChange(docId) {
141
+ return this.docs.get(docId)?.sendingChange ?? null;
142
+ }
143
+ /**
144
+ * Atomically save sending change AND clear all pending ops.
145
+ */
146
+ async saveSendingChange(docId, change) {
147
+ const buf = this.docs.get(docId);
148
+ if (!buf) return;
149
+ buf.sendingChange = change;
150
+ buf.pendingOps.clear();
151
+ }
152
+ /**
153
+ * Clear sendingChange after server ack, move ops to committed.
154
+ */
155
+ async confirmSendingChange(docId) {
156
+ const buf = this.docs.get(docId);
157
+ if (!buf?.sendingChange) return;
158
+ for (const op of buf.sendingChange.ops) {
159
+ buf.committedFields.set(op.path, op.value);
160
+ }
161
+ if (buf.sendingChange.rev > buf.committedRev) {
162
+ buf.committedRev = buf.sendingChange.rev;
163
+ }
164
+ buf.sendingChange = null;
165
+ }
166
+ /**
167
+ * Apply server changes using LWW timestamp resolution.
168
+ */
169
+ async applyServerChanges(docId, serverChanges) {
170
+ const buf = this.getOrCreateBuffer(docId);
171
+ for (const change of serverChanges) {
172
+ for (const op of change.ops) {
173
+ buf.committedFields.set(op.path, op.value);
174
+ }
175
+ }
176
+ const lastRev = serverChanges.at(-1)?.rev;
177
+ if (lastRev !== void 0 && lastRev > buf.committedRev) {
178
+ buf.committedRev = lastRev;
179
+ }
180
+ }
181
+ // ─── Helper Methods ──────────────────────────────────────────────────────
182
+ getOrCreateBuffer(docId) {
183
+ let buf = this.docs.get(docId);
184
+ if (!buf) {
185
+ buf = {
186
+ committedFields: /* @__PURE__ */ new Map(),
187
+ pendingOps: /* @__PURE__ */ new Map(),
188
+ sendingChange: null,
189
+ committedRev: 0
190
+ };
191
+ this.docs.set(docId, buf);
192
+ }
193
+ return buf;
194
+ }
195
+ /**
196
+ * Converts pending ops to an array of Change objects.
197
+ */
198
+ pendingOpsToChanges(ops, baseRev) {
199
+ if (ops.size === 0) {
200
+ return [];
201
+ }
202
+ const opsArray = Array.from(ops.values());
203
+ return [createChange(baseRev, baseRev + 1, opsArray)];
204
+ }
205
+ }
206
+ export {
207
+ LWWInMemoryStore
208
+ };
@@ -0,0 +1,91 @@
1
+ import { JSONPatchOp } from '../json-patch/types.js';
2
+ import { PatchesSnapshot, PatchesState, Change } from '../types.js';
3
+ import { IndexedDBStore } from './IndexedDBStore.js';
4
+ import { LWWClientStore } from './LWWClientStore.js';
5
+ import '../json-patch/JSONPatch.js';
6
+ import '@dabble/delta';
7
+ import '../utils/deferred.js';
8
+ import './PatchesStore.js';
9
+
10
+ /**
11
+ * IndexedDB store implementation for Last-Writer-Wins (LWW) sync strategy.
12
+ *
13
+ * Creates stores:
14
+ * - docs<{ docId: string; committedRev: number; deleted?: boolean }> (primary key: docId) [shared with OT]
15
+ * - snapshots<{ docId: string; rev: number; state: any }> (primary key: docId) [shared with OT]
16
+ * - committedOps<{ docId: string; op: string; path: string; from?: string; value?: any }> (primary key: [docId, path])
17
+ * - pendingOps<{ docId: string; path: string; op: string; ts: number; value: any }> (primary key: [docId, path])
18
+ * - sendingChanges<{ docId: string; change: Change }> (primary key: docId)
19
+ *
20
+ * This store manages field-level operations for LWW conflict resolution:
21
+ * - Committed ops represent confirmed server state
22
+ * - Pending ops are local changes waiting to be sent
23
+ * - Sending changes are in-flight operations
24
+ *
25
+ * Every 200 ops, committed ops are compacted into the snapshot.
26
+ */
27
+ declare class LWWIndexedDBStore extends IndexedDBStore implements LWWClientStore {
28
+ protected getDBVersion(): number;
29
+ protected onUpgrade(db: IDBDatabase, _oldVersion: number): void;
30
+ /**
31
+ * Rebuilds a document state from snapshot + committed ops + sending + pending.
32
+ *
33
+ * 1. Load the snapshot (base state + rev)
34
+ * 2. Apply all committedOps for docId
35
+ * 3. Check sendingChanges - if exists, apply its ops
36
+ * 4. Apply all pendingOps
37
+ * 5. Return reconstructed state
38
+ */
39
+ getDoc(docId: string): Promise<PatchesSnapshot | undefined>;
40
+ /**
41
+ * Saves the current document state to storage.
42
+ * Clears all committed fields and pending ops.
43
+ */
44
+ saveDoc(docId: string, docState: PatchesState): Promise<void>;
45
+ /**
46
+ * Marks a document as deleted and clears all associated data.
47
+ */
48
+ deleteDoc(docId: string): Promise<void>;
49
+ /**
50
+ * Untracks documents by removing all their data.
51
+ */
52
+ untrackDocs(docIds: string[]): Promise<void>;
53
+ /**
54
+ * Get pending ops, optionally filtered by path prefixes.
55
+ */
56
+ getPendingOps(docId: string, pathPrefixes?: string[]): Promise<JSONPatchOp[]>;
57
+ /**
58
+ * Save pending ops, optionally deleting paths.
59
+ */
60
+ savePendingOps(docId: string, ops: JSONPatchOp[], pathsToDelete?: string[]): Promise<void>;
61
+ /**
62
+ * Get the in-flight change for retry/reconnect scenarios.
63
+ */
64
+ getSendingChange(docId: string): Promise<Change | null>;
65
+ /**
66
+ * Atomically save sending change AND clear all pending ops.
67
+ */
68
+ saveSendingChange(docId: string, change: Change): Promise<void>;
69
+ /**
70
+ * Clear sendingChange after server ack, move ops to committed.
71
+ */
72
+ confirmSendingChange(docId: string): Promise<void>;
73
+ /**
74
+ * Apply server changes using LWW timestamp resolution.
75
+ */
76
+ applyServerChanges(docId: string, serverChanges: Change[]): Promise<void>;
77
+ /**
78
+ * Converts pending ops to an array of Change objects.
79
+ */
80
+ private pendingOpsToChanges;
81
+ /**
82
+ * Deletes all entries for a document from a store.
83
+ */
84
+ private deleteFieldsForDoc;
85
+ /**
86
+ * Compacts committed ops into the snapshot.
87
+ */
88
+ private compactSnapshot;
89
+ }
90
+
91
+ export { LWWIndexedDBStore };
@@ -0,0 +1,275 @@
1
+ import {
2
+ __decorateElement,
3
+ __decoratorMetadata,
4
+ __decoratorStart,
5
+ __runInitializers
6
+ } from "../chunk-IZ2YBCUP.js";
7
+ var _applyServerChanges_dec, _confirmSendingChange_dec, _saveSendingChange_dec, _getSendingChange_dec, _savePendingOps_dec, _getPendingOps_dec, _deleteDoc_dec, _saveDoc_dec, _getDoc_dec, _a, _init;
8
+ import { createChange } from "../data/change.js";
9
+ import { applyPatch } from "../json-patch/applyPatch.js";
10
+ import { blockable } from "../utils/concurrency.js";
11
+ import { IDBStoreWrapper, IndexedDBStore } from "./IndexedDBStore.js";
12
+ const DB_VERSION = 1;
13
+ const SNAPSHOT_INTERVAL = 200;
14
+ class LWWIndexedDBStore extends (_a = IndexedDBStore, _getDoc_dec = [blockable], _saveDoc_dec = [blockable], _deleteDoc_dec = [blockable], _getPendingOps_dec = [blockable], _savePendingOps_dec = [blockable], _getSendingChange_dec = [blockable], _saveSendingChange_dec = [blockable], _confirmSendingChange_dec = [blockable], _applyServerChanges_dec = [blockable], _a) {
15
+ constructor() {
16
+ super(...arguments);
17
+ __runInitializers(_init, 5, this);
18
+ }
19
+ getDBVersion() {
20
+ return DB_VERSION;
21
+ }
22
+ onUpgrade(db, _oldVersion) {
23
+ if (!db.objectStoreNames.contains("committedOps")) {
24
+ db.createObjectStore("committedOps", { keyPath: ["docId", "path"] });
25
+ }
26
+ if (!db.objectStoreNames.contains("pendingOps")) {
27
+ db.createObjectStore("pendingOps", { keyPath: ["docId", "path"] });
28
+ }
29
+ if (!db.objectStoreNames.contains("sendingChanges")) {
30
+ db.createObjectStore("sendingChanges", { keyPath: "docId" });
31
+ }
32
+ }
33
+ async getDoc(docId) {
34
+ const [tx, docsStore, snapshots, committedOps, pendingOps, sendingChanges] = await this.transaction(
35
+ ["docs", "snapshots", "committedOps", "pendingOps", "sendingChanges"],
36
+ "readonly"
37
+ );
38
+ const docMeta = await docsStore.get(docId);
39
+ if (docMeta?.deleted) {
40
+ await tx.complete();
41
+ return void 0;
42
+ }
43
+ const snapshot = await snapshots.get(docId);
44
+ const committed = await committedOps.getAll([docId, ""], [docId, "\uFFFF"]);
45
+ const sending = await sendingChanges.get(docId);
46
+ const pending = await pendingOps.getAll([docId, ""], [docId, "\uFFFF"]);
47
+ if (!snapshot && !committed.length && !pending.length && !sending) {
48
+ await tx.complete();
49
+ return void 0;
50
+ }
51
+ let state = snapshot?.state ? { ...snapshot.state } : {};
52
+ if (committed.length > 0) {
53
+ const ops = committed.map(({ docId: _docId, ...op }) => op);
54
+ state = applyPatch(state, ops, { partial: true });
55
+ }
56
+ if (sending?.change?.ops?.length) {
57
+ state = applyPatch(state, sending.change.ops, { partial: true });
58
+ }
59
+ if (pending.length > 0) {
60
+ const pendingOps2 = pending.map((op) => ({
61
+ op: op.op,
62
+ path: op.path,
63
+ value: op.value,
64
+ ts: op.ts
65
+ }));
66
+ state = applyPatch(state, pendingOps2, { partial: true });
67
+ }
68
+ const pendingChanges = this.pendingOpsToChanges(docId, pending, snapshot?.rev ?? 0);
69
+ await tx.complete();
70
+ return {
71
+ state,
72
+ rev: docMeta?.committedRev ?? snapshot?.rev ?? 0,
73
+ changes: sending?.change ? [sending.change, ...pendingChanges] : pendingChanges
74
+ };
75
+ }
76
+ async saveDoc(docId, docState) {
77
+ const [tx, snapshots, committedOps, pendingOps, sendingChanges, docsStore] = await this.transaction(
78
+ ["snapshots", "committedOps", "pendingOps", "sendingChanges", "docs"],
79
+ "readwrite"
80
+ );
81
+ const { rev, state } = docState;
82
+ await Promise.all([
83
+ docsStore.put({ docId, committedRev: rev }),
84
+ snapshots.put({ docId, state, rev }),
85
+ this.deleteFieldsForDoc(committedOps, docId),
86
+ this.deleteFieldsForDoc(pendingOps, docId),
87
+ sendingChanges.delete(docId)
88
+ ]);
89
+ await tx.complete();
90
+ }
91
+ async deleteDoc(docId) {
92
+ const [tx, snapshots, committedOps, pendingOps, sendingChanges, docsStore] = await this.transaction(
93
+ ["snapshots", "committedOps", "pendingOps", "sendingChanges", "docs"],
94
+ "readwrite"
95
+ );
96
+ const docMeta = await docsStore.get(docId) ?? { docId, committedRev: 0 };
97
+ await docsStore.put({ ...docMeta, deleted: true });
98
+ await Promise.all([
99
+ snapshots.delete(docId),
100
+ this.deleteFieldsForDoc(committedOps, docId),
101
+ this.deleteFieldsForDoc(pendingOps, docId),
102
+ sendingChanges.delete(docId)
103
+ ]);
104
+ await tx.complete();
105
+ }
106
+ /**
107
+ * Untracks documents by removing all their data.
108
+ */
109
+ async untrackDocs(docIds) {
110
+ const [tx, docsStore, snapshots, committedOps, pendingOps, sendingChanges] = await this.transaction(
111
+ ["docs", "snapshots", "committedOps", "pendingOps", "sendingChanges"],
112
+ "readwrite"
113
+ );
114
+ await Promise.all(
115
+ docIds.map(
116
+ (docId) => Promise.all([
117
+ docsStore.delete(docId),
118
+ snapshots.delete(docId),
119
+ this.deleteFieldsForDoc(committedOps, docId),
120
+ this.deleteFieldsForDoc(pendingOps, docId),
121
+ sendingChanges.delete(docId)
122
+ ])
123
+ )
124
+ );
125
+ await tx.complete();
126
+ }
127
+ async getPendingOps(docId, pathPrefixes) {
128
+ const [tx, pendingOpsStore] = await this.transaction(["pendingOps"], "readonly");
129
+ let pending;
130
+ if (!pathPrefixes || pathPrefixes.length === 0) {
131
+ pending = await pendingOpsStore.getAll([docId, ""], [docId, "\uFFFF"]);
132
+ } else {
133
+ const allPending = await pendingOpsStore.getAll([docId, ""], [docId, "\uFFFF"]);
134
+ pending = allPending.filter(
135
+ (op) => pathPrefixes.some((prefix) => op.path === prefix || op.path.startsWith(prefix + "/"))
136
+ );
137
+ }
138
+ await tx.complete();
139
+ return pending.map((op) => ({
140
+ op: op.op,
141
+ path: op.path,
142
+ value: op.value,
143
+ ts: op.ts
144
+ }));
145
+ }
146
+ async savePendingOps(docId, ops, pathsToDelete) {
147
+ const [tx, pendingOpsStore, docsStore] = await this.transaction(["pendingOps", "docs"], "readwrite");
148
+ let docMeta = await docsStore.get(docId);
149
+ if (!docMeta) {
150
+ docMeta = { docId, committedRev: 0 };
151
+ await docsStore.put(docMeta);
152
+ } else if (docMeta.deleted) {
153
+ delete docMeta.deleted;
154
+ await docsStore.put(docMeta);
155
+ }
156
+ if (pathsToDelete) {
157
+ await Promise.all(pathsToDelete.map((path) => pendingOpsStore.delete([docId, path])));
158
+ }
159
+ await Promise.all(
160
+ ops.map(
161
+ (op) => pendingOpsStore.put({
162
+ docId,
163
+ path: op.path,
164
+ op: op.op,
165
+ ts: op.ts ?? Date.now(),
166
+ value: op.value
167
+ })
168
+ )
169
+ );
170
+ await tx.complete();
171
+ }
172
+ async getSendingChange(docId) {
173
+ const [tx, sendingChanges] = await this.transaction(["sendingChanges"], "readonly");
174
+ const sending = await sendingChanges.get(docId);
175
+ await tx.complete();
176
+ return sending?.change ?? null;
177
+ }
178
+ async saveSendingChange(docId, change) {
179
+ const [tx, pendingOpsStore, sendingChanges] = await this.transaction(["pendingOps", "sendingChanges"], "readwrite");
180
+ await sendingChanges.put({ docId, change });
181
+ await this.deleteFieldsForDoc(pendingOpsStore, docId);
182
+ await tx.complete();
183
+ }
184
+ async confirmSendingChange(docId) {
185
+ const [tx, sendingChanges, committedOps, docsStore] = await this.transaction(
186
+ ["sendingChanges", "committedOps", "docs"],
187
+ "readwrite"
188
+ );
189
+ const sending = await sendingChanges.get(docId);
190
+ if (!sending) {
191
+ await tx.complete();
192
+ return;
193
+ }
194
+ await Promise.all(sending.change.ops.map((op) => committedOps.put({ ...op, docId })));
195
+ const docMeta = await docsStore.get(docId) ?? { docId, committedRev: 0 };
196
+ if (sending.change.rev > docMeta.committedRev) {
197
+ await docsStore.put({ ...docMeta, committedRev: sending.change.rev });
198
+ }
199
+ await sendingChanges.delete(docId);
200
+ await tx.complete();
201
+ }
202
+ async applyServerChanges(docId, serverChanges) {
203
+ const [tx, committedOps, snapshots, docsStore] = await this.transaction(
204
+ ["committedOps", "snapshots", "docs"],
205
+ "readwrite"
206
+ );
207
+ const allOps = serverChanges.flatMap((change) => change.ops);
208
+ await Promise.all(allOps.map((op) => committedOps.put({ ...op, docId })));
209
+ const lastCommittedRev = serverChanges.at(-1)?.rev;
210
+ if (lastCommittedRev !== void 0) {
211
+ const docMeta = await docsStore.get(docId) ?? { docId, committedRev: 0 };
212
+ if (lastCommittedRev > docMeta.committedRev) {
213
+ await docsStore.put({ ...docMeta, committedRev: lastCommittedRev });
214
+ }
215
+ }
216
+ const fieldCount = await committedOps.count([docId, ""], [docId, "\uFFFF"]);
217
+ if (fieldCount >= SNAPSHOT_INTERVAL) {
218
+ await this.compactSnapshot(docId, snapshots, committedOps, docsStore);
219
+ }
220
+ await tx.complete();
221
+ }
222
+ // ─── Helper Methods ──────────────────────────────────────────────────────
223
+ /**
224
+ * Converts pending ops to an array of Change objects.
225
+ */
226
+ pendingOpsToChanges(_docId, ops, baseRev) {
227
+ if (ops.length === 0) {
228
+ return [];
229
+ }
230
+ const opsArray = ops.map((op) => ({
231
+ op: op.op,
232
+ path: op.path,
233
+ value: op.value,
234
+ ts: op.ts
235
+ }));
236
+ return [createChange(baseRev, baseRev + 1, opsArray)];
237
+ }
238
+ /**
239
+ * Deletes all entries for a document from a store.
240
+ */
241
+ async deleteFieldsForDoc(store, docId) {
242
+ const entries = await store.getAll([docId, ""], [docId, "\uFFFF"]);
243
+ await Promise.all(entries.map((e) => store.delete([e.docId, e.path])));
244
+ }
245
+ /**
246
+ * Compacts committed ops into the snapshot.
247
+ */
248
+ async compactSnapshot(docId, snapshots, committedOps, docsStore) {
249
+ const snapshot = await snapshots.get(docId);
250
+ const committed = await committedOps.getAll([docId, ""], [docId, "\uFFFF"]);
251
+ if (committed.length === 0) {
252
+ return;
253
+ }
254
+ let state = snapshot?.state ? { ...snapshot.state } : {};
255
+ state = applyPatch(state, committed, { partial: true });
256
+ const docMeta = await docsStore.get(docId);
257
+ const rev = docMeta?.committedRev ?? snapshot?.rev ?? 0;
258
+ await snapshots.put({ docId, state, rev });
259
+ await this.deleteFieldsForDoc(committedOps, docId);
260
+ }
261
+ }
262
+ _init = __decoratorStart(_a);
263
+ __decorateElement(_init, 1, "getDoc", _getDoc_dec, LWWIndexedDBStore);
264
+ __decorateElement(_init, 1, "saveDoc", _saveDoc_dec, LWWIndexedDBStore);
265
+ __decorateElement(_init, 1, "deleteDoc", _deleteDoc_dec, LWWIndexedDBStore);
266
+ __decorateElement(_init, 1, "getPendingOps", _getPendingOps_dec, LWWIndexedDBStore);
267
+ __decorateElement(_init, 1, "savePendingOps", _savePendingOps_dec, LWWIndexedDBStore);
268
+ __decorateElement(_init, 1, "getSendingChange", _getSendingChange_dec, LWWIndexedDBStore);
269
+ __decorateElement(_init, 1, "saveSendingChange", _saveSendingChange_dec, LWWIndexedDBStore);
270
+ __decorateElement(_init, 1, "confirmSendingChange", _confirmSendingChange_dec, LWWIndexedDBStore);
271
+ __decorateElement(_init, 1, "applyServerChanges", _applyServerChanges_dec, LWWIndexedDBStore);
272
+ __decoratorMetadata(_init, LWWIndexedDBStore);
273
+ export {
274
+ LWWIndexedDBStore
275
+ };