@dabble/patches 0.4.5 → 0.4.6
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/dist/algorithms/client/applyCommittedChanges.d.ts +8 -2
- package/dist/algorithms/client/applyCommittedChanges.js +30 -38
- package/dist/algorithms/client/batching.d.ts +8 -2
- package/dist/algorithms/client/batching.js +38 -37
- package/dist/algorithms/client/breakChange.d.ts +8 -2
- package/dist/algorithms/client/breakChange.js +191 -240
- package/dist/algorithms/client/createStateFromSnapshot.d.ts +8 -2
- package/dist/algorithms/client/createStateFromSnapshot.js +7 -8
- package/dist/algorithms/client/getJSONByteSize.d.ts +3 -1
- package/dist/algorithms/client/getJSONByteSize.js +12 -11
- package/dist/algorithms/client/makeChange.d.ts +8 -2
- package/dist/algorithms/client/makeChange.js +28 -36
- package/dist/algorithms/server/commitChanges.d.ts +9 -3
- package/dist/algorithms/server/commitChanges.js +69 -78
- package/dist/algorithms/server/createVersion.d.ts +9 -3
- package/dist/algorithms/server/createVersion.js +21 -27
- package/dist/algorithms/server/getSnapshotAtRevision.d.ts +9 -3
- package/dist/algorithms/server/getSnapshotAtRevision.js +27 -28
- package/dist/algorithms/server/getStateAtRevision.d.ts +9 -3
- package/dist/algorithms/server/getStateAtRevision.js +13 -17
- package/dist/algorithms/server/handleOfflineSessionsAndBatches.d.ts +9 -3
- package/dist/algorithms/server/handleOfflineSessionsAndBatches.js +60 -77
- package/dist/algorithms/server/transformIncomingChanges.d.ts +8 -2
- package/dist/algorithms/server/transformIncomingChanges.js +27 -39
- package/dist/algorithms/shared/applyChanges.d.ts +8 -2
- package/dist/algorithms/shared/applyChanges.js +11 -16
- package/dist/algorithms/shared/rebaseChanges.d.ts +8 -2
- package/dist/algorithms/shared/rebaseChanges.js +30 -49
- package/dist/chunk-IZ2YBCUP.js +56 -0
- package/dist/client/InMemoryStore.d.ts +9 -3
- package/dist/client/InMemoryStore.js +92 -101
- package/dist/client/IndexedDBStore.d.ts +9 -3
- package/dist/client/IndexedDBStore.js +378 -491
- package/dist/client/Patches.d.ts +18 -13
- package/dist/client/Patches.js +152 -207
- package/dist/client/PatchesDoc.d.ts +14 -8
- package/dist/client/PatchesDoc.js +147 -154
- package/dist/client/PatchesHistoryClient.d.ts +12 -5
- package/dist/client/PatchesHistoryClient.js +110 -117
- package/dist/client/PatchesStore.d.ts +9 -3
- package/dist/client/PatchesStore.js +0 -1
- package/dist/client/index.d.ts +12 -6
- package/dist/client/index.js +5 -5
- package/dist/data/change.d.ts +9 -3
- package/dist/data/change.js +23 -15
- package/dist/data/version.d.ts +9 -3
- package/dist/data/version.js +11 -15
- package/dist/event-signal.d.ts +7 -6
- package/dist/event-signal.js +24 -39
- package/dist/index-CvQws3AB.d.ts +36 -0
- package/dist/index.d.ts +27 -5
- package/dist/index.js +10 -4
- package/dist/json-patch/JSONPatch.d.ts +9 -5
- package/dist/json-patch/JSONPatch.js +175 -183
- package/dist/json-patch/applyPatch.d.ts +5 -2
- package/dist/json-patch/applyPatch.js +27 -35
- package/dist/json-patch/composePatch.d.ts +5 -2
- package/dist/json-patch/composePatch.js +34 -34
- package/dist/json-patch/createJSONPatch.d.ts +7 -2
- package/dist/json-patch/createJSONPatch.js +11 -38
- package/dist/json-patch/index.d.ts +14 -6
- package/dist/json-patch/index.js +20 -9
- package/dist/json-patch/invertPatch.d.ts +5 -2
- package/dist/json-patch/invertPatch.js +31 -30
- package/dist/json-patch/ops/add.d.ts +5 -2
- package/dist/json-patch/ops/add.js +53 -51
- package/dist/json-patch/ops/bitmask.d.ts +8 -5
- package/dist/json-patch/ops/bitmask.js +41 -44
- package/dist/json-patch/ops/copy.d.ts +5 -2
- package/dist/json-patch/ops/copy.js +32 -33
- package/dist/json-patch/ops/increment.d.ts +5 -2
- package/dist/json-patch/ops/increment.js +21 -20
- package/dist/json-patch/ops/index.d.ts +10 -21
- package/dist/json-patch/ops/index.js +34 -24
- package/dist/json-patch/ops/move.d.ts +5 -2
- package/dist/json-patch/ops/move.js +132 -198
- package/dist/json-patch/ops/remove.d.ts +5 -2
- package/dist/json-patch/ops/remove.js +33 -30
- package/dist/json-patch/ops/replace.d.ts +5 -2
- package/dist/json-patch/ops/replace.js +45 -43
- package/dist/json-patch/ops/test.d.ts +5 -2
- package/dist/json-patch/ops/test.js +25 -21
- package/dist/json-patch/ops/text.d.ts +5 -2
- package/dist/json-patch/ops/text.js +54 -54
- package/dist/json-patch/pathProxy.d.ts +9 -3
- package/dist/json-patch/pathProxy.js +27 -48
- package/dist/json-patch/state.d.ts +5 -2
- package/dist/json-patch/state.js +11 -7
- package/dist/json-patch/transformPatch.d.ts +6 -2
- package/dist/json-patch/transformPatch.js +21 -24
- package/dist/json-patch/types.d.ts +9 -7
- package/dist/json-patch/types.js +0 -1
- package/dist/json-patch/utils/deepEqual.d.ts +3 -1
- package/dist/json-patch/utils/deepEqual.js +32 -28
- package/dist/json-patch/utils/exit.d.ts +5 -2
- package/dist/json-patch/utils/exit.js +7 -3
- package/dist/json-patch/utils/get.d.ts +5 -2
- package/dist/json-patch/utils/get.js +8 -4
- package/dist/json-patch/utils/getOpData.d.ts +5 -2
- package/dist/json-patch/utils/getOpData.js +12 -9
- package/dist/json-patch/utils/getType.d.ts +6 -3
- package/dist/json-patch/utils/getType.js +9 -4
- package/dist/json-patch/utils/index.d.ts +15 -14
- package/dist/json-patch/utils/index.js +14 -14
- package/dist/json-patch/utils/log.d.ts +4 -2
- package/dist/json-patch/utils/log.js +8 -3
- package/dist/json-patch/utils/ops.d.ts +8 -5
- package/dist/json-patch/utils/ops.js +83 -100
- package/dist/json-patch/utils/paths.d.ts +12 -9
- package/dist/json-patch/utils/paths.js +54 -51
- package/dist/json-patch/utils/pluck.d.ts +8 -5
- package/dist/json-patch/utils/pluck.js +32 -26
- package/dist/json-patch/utils/shallowCopy.d.ts +3 -1
- package/dist/json-patch/utils/shallowCopy.js +22 -18
- package/dist/json-patch/utils/softWrites.d.ts +6 -3
- package/dist/json-patch/utils/softWrites.js +17 -16
- package/dist/json-patch/utils/toArrayIndex.d.ts +3 -1
- package/dist/json-patch/utils/toArrayIndex.js +14 -10
- package/dist/json-patch/utils/toKeys.d.ts +3 -1
- package/dist/json-patch/utils/toKeys.js +15 -11
- package/dist/json-patch/utils/updateArrayIndexes.d.ts +5 -2
- package/dist/json-patch/utils/updateArrayIndexes.js +33 -37
- package/dist/json-patch/utils/updateArrayPath.d.ts +5 -2
- package/dist/json-patch/utils/updateArrayPath.js +29 -42
- package/dist/net/PatchesClient.d.ts +128 -0
- package/dist/net/PatchesClient.js +161 -0
- package/dist/net/PatchesSync.d.ts +19 -9
- package/dist/net/PatchesSync.js +291 -386
- package/dist/net/error.d.ts +3 -1
- package/dist/net/error.js +9 -6
- package/dist/net/http/FetchTransport.d.ts +21 -0
- package/dist/net/http/FetchTransport.js +34 -0
- package/dist/net/index.d.ts +26 -12
- package/dist/net/index.js +12 -10
- package/dist/net/protocol/JSONRPCClient.d.ts +11 -4
- package/dist/net/protocol/JSONRPCClient.js +95 -103
- package/dist/net/protocol/JSONRPCServer.d.ts +15 -8
- package/dist/net/protocol/JSONRPCServer.js +101 -123
- package/dist/net/protocol/types.d.ts +21 -15
- package/dist/net/protocol/types.js +0 -1
- package/dist/net/protocol/utils.d.ts +12 -0
- package/dist/net/protocol/utils.js +15 -0
- package/dist/net/types.d.ts +4 -2
- package/dist/net/types.js +0 -1
- package/dist/net/webrtc/WebRTCAwareness.d.ts +14 -4
- package/dist/net/webrtc/WebRTCAwareness.js +111 -120
- package/dist/net/webrtc/WebRTCTransport.d.ts +16 -8
- package/dist/net/webrtc/WebRTCTransport.js +149 -157
- package/dist/net/webrtc/index.d.ts +10 -2
- package/dist/net/webrtc/index.js +2 -2
- package/dist/net/websocket/AuthorizationProvider.d.ts +7 -5
- package/dist/net/websocket/AuthorizationProvider.js +12 -17
- package/dist/net/websocket/PatchesWebSocket.d.ts +14 -109
- package/dist/net/websocket/PatchesWebSocket.js +37 -184
- package/dist/net/websocket/RPCServer.d.ts +19 -10
- package/dist/net/websocket/RPCServer.js +190 -192
- package/dist/net/websocket/SignalingService.d.ts +12 -32
- package/dist/net/websocket/SignalingService.js +126 -133
- package/dist/net/websocket/WebSocketServer.d.ts +17 -4
- package/dist/net/websocket/WebSocketServer.js +64 -72
- package/dist/net/websocket/WebSocketTransport.d.ts +13 -5
- package/dist/net/websocket/WebSocketTransport.js +178 -207
- package/dist/net/websocket/onlineState.d.ts +6 -3
- package/dist/net/websocket/onlineState.js +25 -21
- package/dist/server/PatchesBranchManager.d.ts +12 -5
- package/dist/server/PatchesBranchManager.js +132 -142
- package/dist/server/PatchesHistoryManager.d.ts +11 -3
- package/dist/server/PatchesHistoryManager.js +81 -84
- package/dist/server/PatchesServer.d.ts +16 -10
- package/dist/server/PatchesServer.js +131 -137
- package/dist/server/index.d.ts +7 -2
- package/dist/server/index.js +9 -3
- package/dist/server/types.d.ts +9 -3
- package/dist/server/types.js +0 -1
- package/dist/types.d.ts +38 -19
- package/dist/types.js +1 -1
- package/dist/utils/concurrency.d.ts +7 -5
- package/dist/utils/concurrency.js +43 -53
- package/dist/utils/deferred.d.ts +4 -2
- package/dist/utils/deferred.js +25 -21
- package/package.json +5 -7
|
@@ -1,504 +1,391 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
};
|
|
8
|
-
var
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
var _, done = false;
|
|
14
|
-
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
15
|
-
var context = {};
|
|
16
|
-
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
17
|
-
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
18
|
-
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
19
|
-
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
20
|
-
if (kind === "accessor") {
|
|
21
|
-
if (result === void 0) continue;
|
|
22
|
-
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
23
|
-
if (_ = accept(result.get)) descriptor.get = _;
|
|
24
|
-
if (_ = accept(result.set)) descriptor.set = _;
|
|
25
|
-
if (_ = accept(result.init)) initializers.unshift(_);
|
|
26
|
-
}
|
|
27
|
-
else if (_ = accept(result)) {
|
|
28
|
-
if (kind === "field") initializers.unshift(_);
|
|
29
|
-
else descriptor[key] = _;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
33
|
-
done = true;
|
|
34
|
-
};
|
|
35
|
-
import { applyChanges } from '../algorithms/shared/applyChanges.js';
|
|
36
|
-
import { transformPatch } from '../json-patch/transformPatch.js';
|
|
37
|
-
import { blockable } from '../utils/concurrency.js';
|
|
38
|
-
import { deferred } from '../utils/deferred.js';
|
|
1
|
+
import {
|
|
2
|
+
__decorateElement,
|
|
3
|
+
__decoratorMetadata,
|
|
4
|
+
__decoratorStart,
|
|
5
|
+
__publicField,
|
|
6
|
+
__runInitializers
|
|
7
|
+
} from "../chunk-IZ2YBCUP.js";
|
|
8
|
+
var _getLastRevs_dec, _saveCommittedChanges_dec, _replacePendingChanges_dec, _getPendingChanges_dec, _savePendingChanges_dec, _saveDoc_dec, _confirmDeleteDoc_dec, _deleteDoc_dec, _getDoc_dec, _init;
|
|
9
|
+
import { applyChanges } from "../algorithms/shared/applyChanges.js";
|
|
10
|
+
import { transformPatch } from "../json-patch/transformPatch.js";
|
|
11
|
+
import { blockable } from "../utils/concurrency.js";
|
|
12
|
+
import { deferred } from "../utils/deferred.js";
|
|
39
13
|
const DB_VERSION = 1;
|
|
40
14
|
const SNAPSHOT_INTERVAL = 200;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
*/
|
|
53
|
-
let IndexedDBStore = (() => {
|
|
54
|
-
let _instanceExtraInitializers = [];
|
|
55
|
-
let _getDoc_decorators;
|
|
56
|
-
let _deleteDoc_decorators;
|
|
57
|
-
let _confirmDeleteDoc_decorators;
|
|
58
|
-
let _saveDoc_decorators;
|
|
59
|
-
let _savePendingChanges_decorators;
|
|
60
|
-
let _getPendingChanges_decorators;
|
|
61
|
-
let _replacePendingChanges_decorators;
|
|
62
|
-
let _saveCommittedChanges_decorators;
|
|
63
|
-
let _getLastRevs_decorators;
|
|
64
|
-
return class IndexedDBStore {
|
|
65
|
-
static {
|
|
66
|
-
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
67
|
-
_getDoc_decorators = [blockable];
|
|
68
|
-
_deleteDoc_decorators = [blockable];
|
|
69
|
-
_confirmDeleteDoc_decorators = [blockable];
|
|
70
|
-
_saveDoc_decorators = [blockable];
|
|
71
|
-
_savePendingChanges_decorators = [blockable];
|
|
72
|
-
_getPendingChanges_decorators = [blockable];
|
|
73
|
-
_replacePendingChanges_decorators = [blockable];
|
|
74
|
-
_saveCommittedChanges_decorators = [blockable];
|
|
75
|
-
_getLastRevs_decorators = [blockable];
|
|
76
|
-
__esDecorate(this, null, _getDoc_decorators, { kind: "method", name: "getDoc", static: false, private: false, access: { has: obj => "getDoc" in obj, get: obj => obj.getDoc }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
77
|
-
__esDecorate(this, null, _deleteDoc_decorators, { kind: "method", name: "deleteDoc", static: false, private: false, access: { has: obj => "deleteDoc" in obj, get: obj => obj.deleteDoc }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
78
|
-
__esDecorate(this, null, _confirmDeleteDoc_decorators, { kind: "method", name: "confirmDeleteDoc", static: false, private: false, access: { has: obj => "confirmDeleteDoc" in obj, get: obj => obj.confirmDeleteDoc }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
79
|
-
__esDecorate(this, null, _saveDoc_decorators, { kind: "method", name: "saveDoc", static: false, private: false, access: { has: obj => "saveDoc" in obj, get: obj => obj.saveDoc }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
80
|
-
__esDecorate(this, null, _savePendingChanges_decorators, { kind: "method", name: "savePendingChanges", static: false, private: false, access: { has: obj => "savePendingChanges" in obj, get: obj => obj.savePendingChanges }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
81
|
-
__esDecorate(this, null, _getPendingChanges_decorators, { kind: "method", name: "getPendingChanges", static: false, private: false, access: { has: obj => "getPendingChanges" in obj, get: obj => obj.getPendingChanges }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
82
|
-
__esDecorate(this, null, _replacePendingChanges_decorators, { kind: "method", name: "replacePendingChanges", static: false, private: false, access: { has: obj => "replacePendingChanges" in obj, get: obj => obj.replacePendingChanges }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
83
|
-
__esDecorate(this, null, _saveCommittedChanges_decorators, { kind: "method", name: "saveCommittedChanges", static: false, private: false, access: { has: obj => "saveCommittedChanges" in obj, get: obj => obj.saveCommittedChanges }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
84
|
-
__esDecorate(this, null, _getLastRevs_decorators, { kind: "method", name: "getLastRevs", static: false, private: false, access: { has: obj => "getLastRevs" in obj, get: obj => obj.getLastRevs }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
85
|
-
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
86
|
-
}
|
|
87
|
-
db = (__runInitializers(this, _instanceExtraInitializers), null);
|
|
88
|
-
dbName;
|
|
89
|
-
dbPromise;
|
|
90
|
-
constructor(dbName) {
|
|
91
|
-
this.dbName = dbName;
|
|
92
|
-
this.dbPromise = deferred();
|
|
93
|
-
if (this.dbName) {
|
|
94
|
-
this.initDB();
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
async initDB() {
|
|
98
|
-
if (!this.dbName)
|
|
99
|
-
return;
|
|
100
|
-
const request = indexedDB.open(this.dbName, DB_VERSION);
|
|
101
|
-
request.onerror = () => this.dbPromise.reject(request.error);
|
|
102
|
-
request.onsuccess = () => {
|
|
103
|
-
this.db = request.result;
|
|
104
|
-
this.dbPromise.resolve(this.db);
|
|
105
|
-
};
|
|
106
|
-
request.onupgradeneeded = event => {
|
|
107
|
-
const db = event.target.result;
|
|
108
|
-
// Create stores
|
|
109
|
-
if (!db.objectStoreNames.contains('snapshots')) {
|
|
110
|
-
db.createObjectStore('snapshots', { keyPath: 'docId' });
|
|
111
|
-
}
|
|
112
|
-
if (!db.objectStoreNames.contains('committedChanges')) {
|
|
113
|
-
db.createObjectStore('committedChanges', { keyPath: ['docId', 'rev'] });
|
|
114
|
-
}
|
|
115
|
-
if (!db.objectStoreNames.contains('pendingChanges')) {
|
|
116
|
-
db.createObjectStore('pendingChanges', { keyPath: ['docId', 'rev'] });
|
|
117
|
-
}
|
|
118
|
-
if (!db.objectStoreNames.contains('docs')) {
|
|
119
|
-
db.createObjectStore('docs', { keyPath: 'docId' });
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
getDB() {
|
|
124
|
-
return this.dbPromise.promise;
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Set the name of the database, loads a new database connection.
|
|
128
|
-
* @param dbName - The new name of the database.
|
|
129
|
-
*/
|
|
130
|
-
setName(dbName) {
|
|
131
|
-
this.dbName = dbName;
|
|
132
|
-
if (this.db) {
|
|
133
|
-
this.db.close();
|
|
134
|
-
this.db = null;
|
|
135
|
-
this.dbPromise = deferred();
|
|
136
|
-
}
|
|
137
|
-
this.initDB();
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Closes the database connection. After calling this method, the store
|
|
141
|
-
* will no longer be usable. A new instance must be created to reopen
|
|
142
|
-
* the database.
|
|
143
|
-
*/
|
|
144
|
-
async close() {
|
|
145
|
-
await this.dbPromise.promise;
|
|
146
|
-
if (this.db) {
|
|
147
|
-
this.db.close();
|
|
148
|
-
this.db = null;
|
|
149
|
-
this.dbPromise = deferred();
|
|
150
|
-
this.dbPromise.resolve(null);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
async deleteDB() {
|
|
154
|
-
if (!this.dbName)
|
|
155
|
-
return;
|
|
156
|
-
await this.close();
|
|
157
|
-
await new Promise((resolve, reject) => {
|
|
158
|
-
const request = indexedDB.deleteDatabase(this.dbName);
|
|
159
|
-
request.onsuccess = () => resolve();
|
|
160
|
-
request.onerror = () => reject(request.error);
|
|
161
|
-
request.onblocked = () => reject(request.error);
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
async transaction(storeNames, mode) {
|
|
165
|
-
const db = await this.getDB();
|
|
166
|
-
const tx = new IDBTransactionWrapper(db.transaction(storeNames, mode));
|
|
167
|
-
const stores = storeNames.map(name => tx.getStore(name));
|
|
168
|
-
return [tx, ...stores];
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Rebuilds a document snapshot + pending queue *without* loading
|
|
172
|
-
* the full PatchesDoc into memory.
|
|
173
|
-
*
|
|
174
|
-
* 1. load the last snapshot (state + rev)
|
|
175
|
-
* 2. load committedChanges[rev > snapshot.rev]
|
|
176
|
-
* 3. load pendingChanges
|
|
177
|
-
* 4. apply committed changes, rebase pending
|
|
178
|
-
* 5. return { state, rev, changes: pending }
|
|
179
|
-
*/
|
|
180
|
-
async getDoc(docId) {
|
|
181
|
-
const [tx, docsStore, snapshots, committedChanges, pendingChanges] = await this.transaction(['docs', 'snapshots', 'committedChanges', 'pendingChanges'], 'readonly');
|
|
182
|
-
const docMeta = await docsStore.get(docId);
|
|
183
|
-
if (docMeta?.deleted) {
|
|
184
|
-
await tx.complete();
|
|
185
|
-
return undefined;
|
|
186
|
-
}
|
|
187
|
-
const snapshot = await snapshots.get(docId);
|
|
188
|
-
const committed = await committedChanges.getAll([docId, snapshot?.rev ?? 0], [docId, Infinity]);
|
|
189
|
-
const pending = await pendingChanges.getAll([docId, 0], [docId, Infinity]);
|
|
190
|
-
if (!snapshot && !committed.length && !pending.length)
|
|
191
|
-
return undefined;
|
|
192
|
-
// Apply any committed changes to the snapshot state
|
|
193
|
-
const state = applyChanges(snapshot?.state, committed);
|
|
194
|
-
// Rebase pending changes if there are any committed changes received since their baseRev
|
|
195
|
-
const lastCommitted = committed[committed.length - 1];
|
|
196
|
-
const baseRev = pending[0]?.baseRev;
|
|
197
|
-
if (lastCommitted && baseRev && baseRev < lastCommitted.rev) {
|
|
198
|
-
const patch = committed
|
|
199
|
-
.filter(change => change.rev > baseRev)
|
|
200
|
-
.map(change => change.ops)
|
|
201
|
-
.flat();
|
|
202
|
-
const offset = lastCommitted.rev - baseRev;
|
|
203
|
-
pending.forEach(change => {
|
|
204
|
-
change.rev += offset;
|
|
205
|
-
change.ops = transformPatch(state, patch, change.ops);
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
await tx.complete();
|
|
209
|
-
return {
|
|
210
|
-
state,
|
|
211
|
-
rev: committed[committed.length - 1]?.rev ?? snapshot?.rev ?? 0,
|
|
212
|
-
changes: pending,
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Completely remove all data for this docId and mark it as deleted (tombstone).
|
|
217
|
-
*/
|
|
218
|
-
async deleteDoc(docId) {
|
|
219
|
-
const [tx, snapshots, committedChanges, pendingChanges, docsStore] = await this.transaction(['snapshots', 'committedChanges', 'pendingChanges', 'docs'], 'readwrite');
|
|
220
|
-
const docMeta = (await docsStore.get(docId)) ?? { docId, committedRev: 0 };
|
|
221
|
-
await docsStore.put({ ...docMeta, deleted: true });
|
|
222
|
-
await Promise.all([
|
|
223
|
-
snapshots.delete(docId),
|
|
224
|
-
committedChanges.delete([docId, 0], [docId, Infinity]),
|
|
225
|
-
pendingChanges.delete([docId, 0], [docId, Infinity]),
|
|
226
|
-
]);
|
|
227
|
-
await tx.complete();
|
|
228
|
-
}
|
|
229
|
-
/**
|
|
230
|
-
* Confirm the deletion of a document.
|
|
231
|
-
* @param docId - The ID of the document to delete.
|
|
232
|
-
*/
|
|
233
|
-
async confirmDeleteDoc(docId) {
|
|
234
|
-
const [tx, docsStore] = await this.transaction(['docs'], 'readwrite');
|
|
235
|
-
await docsStore.delete(docId);
|
|
236
|
-
await tx.complete();
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* Save a document's state to the store.
|
|
240
|
-
* @param docId - The ID of the document to save.
|
|
241
|
-
* @param docState - The state of the document to save.
|
|
242
|
-
*/
|
|
243
|
-
async saveDoc(docId, docState) {
|
|
244
|
-
const [tx, snapshots, committedChanges, pendingChanges, docsStore] = await this.transaction(['snapshots', 'committedChanges', 'pendingChanges', 'docs'], 'readwrite');
|
|
245
|
-
const { rev, state } = docState;
|
|
246
|
-
await Promise.all([
|
|
247
|
-
docsStore.put({ docId, committedRev: rev }),
|
|
248
|
-
snapshots.put({ docId, state, rev }),
|
|
249
|
-
committedChanges.delete([docId, 0], [docId, Infinity]),
|
|
250
|
-
pendingChanges.delete([docId, 0], [docId, Infinity]),
|
|
251
|
-
]);
|
|
252
|
-
await tx.complete();
|
|
253
|
-
}
|
|
254
|
-
/**
|
|
255
|
-
* Append an array of local changes to the pending queue.
|
|
256
|
-
* Called *before* you attempt to send them to the server.
|
|
257
|
-
*/
|
|
258
|
-
async savePendingChanges(docId, changes) {
|
|
259
|
-
const [tx, pendingChanges, docsStore] = await this.transaction(['pendingChanges', 'docs'], 'readwrite');
|
|
260
|
-
let docMeta = await docsStore.get(docId);
|
|
261
|
-
if (!docMeta) {
|
|
262
|
-
docMeta = { docId, committedRev: 0 };
|
|
263
|
-
await docsStore.put(docMeta);
|
|
264
|
-
}
|
|
265
|
-
else if (docMeta.deleted) {
|
|
266
|
-
delete docMeta.deleted;
|
|
267
|
-
await docsStore.put(docMeta);
|
|
268
|
-
console.warn(`Revived document ${docId} by saving pending changes.`);
|
|
269
|
-
}
|
|
270
|
-
await Promise.all(changes.map(change => pendingChanges.put({ ...change, docId })));
|
|
271
|
-
await tx.complete();
|
|
272
|
-
}
|
|
273
|
-
/**
|
|
274
|
-
* Read back all pending changes for this docId (in order).
|
|
275
|
-
* @param docId - The ID of the document to get the pending changes for.
|
|
276
|
-
* @returns The pending changes.
|
|
277
|
-
*/
|
|
278
|
-
async getPendingChanges(docId) {
|
|
279
|
-
const [tx, pendingChanges] = await this.transaction(['pendingChanges'], 'readonly');
|
|
280
|
-
const result = await pendingChanges.getAll([docId, 0], [docId, Infinity]);
|
|
281
|
-
await tx.complete();
|
|
282
|
-
return result;
|
|
283
|
-
}
|
|
284
|
-
/**
|
|
285
|
-
* Replace all pending changes for a document (used after rebasing).
|
|
286
|
-
* @param docId - The ID of the document to replace the pending changes for.
|
|
287
|
-
* @param changes - The changes to replace the pending changes with.
|
|
288
|
-
*/
|
|
289
|
-
async replacePendingChanges(docId, changes) {
|
|
290
|
-
const [tx, pendingChanges, docsStore] = await this.transaction(['pendingChanges', 'docs'], 'readwrite');
|
|
291
|
-
// Ensure the document is tracked
|
|
292
|
-
let docMeta = await docsStore.get(docId);
|
|
293
|
-
if (!docMeta) {
|
|
294
|
-
docMeta = { docId, committedRev: 0 };
|
|
295
|
-
await docsStore.put(docMeta);
|
|
296
|
-
}
|
|
297
|
-
else if (docMeta.deleted) {
|
|
298
|
-
delete docMeta.deleted;
|
|
299
|
-
await docsStore.put(docMeta);
|
|
300
|
-
console.warn(`Revived document ${docId} by replacing pending changes.`);
|
|
301
|
-
}
|
|
302
|
-
// Remove all existing pending changes and add the new ones
|
|
303
|
-
await pendingChanges.delete([docId, 0], [docId, Infinity]);
|
|
304
|
-
await Promise.all(changes.map(change => pendingChanges.put({ ...change, docId })));
|
|
305
|
-
await tx.complete();
|
|
306
|
-
}
|
|
307
|
-
// ─── Committed Changes ─────────────────────────────────────────────────────
|
|
308
|
-
/**
|
|
309
|
-
* Store server‐confirmed changes. Will:
|
|
310
|
-
* - persist them in the committedChanges store
|
|
311
|
-
* - remove any pending changes whose rev falls within `sentPendingRange`
|
|
312
|
-
* - optionally compact a new snapshot after N changes (hidden internally)
|
|
313
|
-
* @param docId - The ID of the document to save the changes for
|
|
314
|
-
* @param changes - The changes to save
|
|
315
|
-
* @param sentPendingRange - The range of pending changes to remove, *must* be provided after receiving the changes
|
|
316
|
-
* from the server in response to a patchesDoc request.
|
|
317
|
-
*/
|
|
318
|
-
async saveCommittedChanges(docId, changes, sentPendingRange) {
|
|
319
|
-
const [tx, committedChanges, pendingChanges, snapshots, docsStore] = await this.transaction(['committedChanges', 'pendingChanges', 'snapshots', 'docs'], 'readwrite');
|
|
320
|
-
// Save committed changes
|
|
321
|
-
await Promise.all(changes.map(change => committedChanges.put({ ...change, docId })));
|
|
322
|
-
// Remove pending changes if range provided
|
|
323
|
-
if (sentPendingRange) {
|
|
324
|
-
await pendingChanges.delete([docId, sentPendingRange[0]], [docId, sentPendingRange[1]]);
|
|
325
|
-
}
|
|
326
|
-
// Check if we should create a snapshot
|
|
327
|
-
const count = await committedChanges.count([docId, 0], [docId, Infinity]);
|
|
328
|
-
if (count >= SNAPSHOT_INTERVAL) {
|
|
329
|
-
// Update the snapshot. A snapshot will not be updated if there are pending changes based on revisions older than
|
|
330
|
-
// the latest committed change until those pending changes are committed.
|
|
331
|
-
const [snapshot, committed, firstPending] = await Promise.all([
|
|
332
|
-
snapshots.get(docId),
|
|
333
|
-
committedChanges.getAll([docId, 0], [docId, Infinity], SNAPSHOT_INTERVAL),
|
|
334
|
-
pendingChanges.getFirstFromCursor([docId, 0], [docId, Infinity]),
|
|
335
|
-
]);
|
|
336
|
-
// Update the snapshot
|
|
337
|
-
const lastRev = committed[committed.length - 1]?.rev;
|
|
338
|
-
if (!firstPending?.baseRev || firstPending?.baseRev >= lastRev) {
|
|
339
|
-
const state = applyChanges(snapshot?.state, committed);
|
|
340
|
-
await Promise.all([
|
|
341
|
-
snapshots.put({
|
|
342
|
-
docId,
|
|
343
|
-
rev: lastRev,
|
|
344
|
-
state,
|
|
345
|
-
}),
|
|
346
|
-
committedChanges.delete([docId, 0], [docId, lastRev]),
|
|
347
|
-
]);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
// Update committedRev in the docs store if changes were saved
|
|
351
|
-
const lastCommittedRev = changes.at(-1)?.rev;
|
|
352
|
-
if (lastCommittedRev !== undefined) {
|
|
353
|
-
const docMeta = (await docsStore.get(docId)) ?? { docId, committedRev: 0 };
|
|
354
|
-
if (lastCommittedRev > docMeta.committedRev) {
|
|
355
|
-
await docsStore.put({ ...docMeta, committedRev: lastCommittedRev, deleted: undefined });
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
await tx.complete();
|
|
359
|
-
}
|
|
360
|
-
/**
|
|
361
|
-
* List all documents in the store.
|
|
362
|
-
* @param includeDeleted - Whether to include deleted documents.
|
|
363
|
-
* @returns The list of documents.
|
|
364
|
-
*/
|
|
365
|
-
async listDocs(includeDeleted = false) {
|
|
366
|
-
const [tx, docsStore] = await this.transaction(['docs'], 'readonly');
|
|
367
|
-
const allDocs = await docsStore.getAll();
|
|
368
|
-
await tx.complete();
|
|
369
|
-
return includeDeleted ? allDocs : allDocs.filter(doc => !doc.deleted);
|
|
370
|
-
}
|
|
371
|
-
/**
|
|
372
|
-
* Track a document.
|
|
373
|
-
* @param docIds - The IDs of the documents to track.
|
|
374
|
-
*/
|
|
375
|
-
async trackDocs(docIds) {
|
|
376
|
-
const [tx, docsStore] = await this.transaction(['docs'], 'readwrite');
|
|
377
|
-
await Promise.all(docIds.map(async (docId) => {
|
|
378
|
-
const existing = await docsStore.get(docId);
|
|
379
|
-
if (existing) {
|
|
380
|
-
// If exists but deleted, undelete it
|
|
381
|
-
if (existing.deleted) {
|
|
382
|
-
await docsStore.put({ ...existing, deleted: undefined });
|
|
383
|
-
}
|
|
384
|
-
// Otherwise, it's already tracked and not deleted, do nothing
|
|
385
|
-
}
|
|
386
|
-
else {
|
|
387
|
-
// If doesn't exist, add it
|
|
388
|
-
await docsStore.put({ docId, committedRev: 0 });
|
|
389
|
-
}
|
|
390
|
-
}));
|
|
391
|
-
await tx.complete();
|
|
392
|
-
}
|
|
393
|
-
/**
|
|
394
|
-
* Untrack a document.
|
|
395
|
-
* @param docIds - The IDs of the documents to untrack.
|
|
396
|
-
*/
|
|
397
|
-
async untrackDocs(docIds) {
|
|
398
|
-
const [tx, docsStore, snapshots, committedChanges, pendingChanges] = await this.transaction(['docs', 'snapshots', 'committedChanges', 'pendingChanges'], 'readwrite');
|
|
399
|
-
await Promise.all(docIds.map(docId => {
|
|
400
|
-
return Promise.all([
|
|
401
|
-
docsStore.delete(docId),
|
|
402
|
-
snapshots.delete(docId),
|
|
403
|
-
committedChanges.delete([docId, 0], [docId, Infinity]),
|
|
404
|
-
pendingChanges.delete([docId, 0], [docId, Infinity]),
|
|
405
|
-
]);
|
|
406
|
-
}));
|
|
407
|
-
await tx.complete();
|
|
408
|
-
}
|
|
409
|
-
/**
|
|
410
|
-
* Tell me the last committed revision you have *and* the highest
|
|
411
|
-
* rev of any change. Use these to drive:
|
|
412
|
-
* - fetch changes: api.getChangesSince(docId, committedRev)
|
|
413
|
-
* - build new patch: newChange.rev = pendingRev; baseRev = committedRev
|
|
414
|
-
*/
|
|
415
|
-
async getLastRevs(docId) {
|
|
416
|
-
const [tx, committedChanges, pendingChanges] = await this.transaction(['committedChanges', 'pendingChanges'], 'readonly');
|
|
417
|
-
const [lastCommitted, lastPending] = await Promise.all([
|
|
418
|
-
committedChanges.getLastFromCursor([docId, 0], [docId, Infinity]),
|
|
419
|
-
pendingChanges.getLastFromCursor([docId, 0], [docId, Infinity]),
|
|
420
|
-
]);
|
|
421
|
-
await tx.complete();
|
|
422
|
-
return [lastCommitted?.rev ?? 0, lastPending?.rev ?? lastCommitted?.rev ?? 0];
|
|
423
|
-
}
|
|
424
|
-
};
|
|
425
|
-
})();
|
|
426
|
-
export { IndexedDBStore };
|
|
427
|
-
class IDBTransactionWrapper {
|
|
428
|
-
tx;
|
|
429
|
-
promise;
|
|
430
|
-
constructor(tx) {
|
|
431
|
-
this.tx = tx;
|
|
432
|
-
this.promise = new Promise((resolve, reject) => {
|
|
433
|
-
tx.oncomplete = () => resolve();
|
|
434
|
-
tx.onerror = () => reject(tx.error);
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
getStore(name) {
|
|
438
|
-
return new IDBStoreWrapper(this.tx.objectStore(name));
|
|
15
|
+
_getDoc_dec = [blockable], _deleteDoc_dec = [blockable], _confirmDeleteDoc_dec = [blockable], _saveDoc_dec = [blockable], _savePendingChanges_dec = [blockable], _getPendingChanges_dec = [blockable], _replacePendingChanges_dec = [blockable], _saveCommittedChanges_dec = [blockable], _getLastRevs_dec = [blockable];
|
|
16
|
+
class IndexedDBStore {
|
|
17
|
+
constructor(dbName) {
|
|
18
|
+
__runInitializers(_init, 5, this);
|
|
19
|
+
__publicField(this, "db", null);
|
|
20
|
+
__publicField(this, "dbName");
|
|
21
|
+
__publicField(this, "dbPromise");
|
|
22
|
+
this.dbName = dbName;
|
|
23
|
+
this.dbPromise = deferred();
|
|
24
|
+
if (this.dbName) {
|
|
25
|
+
this.initDB();
|
|
439
26
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
27
|
+
}
|
|
28
|
+
async initDB() {
|
|
29
|
+
if (!this.dbName) return;
|
|
30
|
+
const request = indexedDB.open(this.dbName, DB_VERSION);
|
|
31
|
+
request.onerror = () => this.dbPromise.reject(request.error);
|
|
32
|
+
request.onsuccess = () => {
|
|
33
|
+
this.db = request.result;
|
|
34
|
+
this.dbPromise.resolve(this.db);
|
|
35
|
+
};
|
|
36
|
+
request.onupgradeneeded = (event) => {
|
|
37
|
+
const db = event.target.result;
|
|
38
|
+
if (!db.objectStoreNames.contains("snapshots")) {
|
|
39
|
+
db.createObjectStore("snapshots", { keyPath: "docId" });
|
|
40
|
+
}
|
|
41
|
+
if (!db.objectStoreNames.contains("committedChanges")) {
|
|
42
|
+
db.createObjectStore("committedChanges", { keyPath: ["docId", "rev"] });
|
|
43
|
+
}
|
|
44
|
+
if (!db.objectStoreNames.contains("pendingChanges")) {
|
|
45
|
+
db.createObjectStore("pendingChanges", { keyPath: ["docId", "rev"] });
|
|
46
|
+
}
|
|
47
|
+
if (!db.objectStoreNames.contains("docs")) {
|
|
48
|
+
db.createObjectStore("docs", { keyPath: "docId" });
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
getDB() {
|
|
53
|
+
return this.dbPromise.promise;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Set the name of the database, loads a new database connection.
|
|
57
|
+
* @param dbName - The new name of the database.
|
|
58
|
+
*/
|
|
59
|
+
setName(dbName) {
|
|
60
|
+
this.dbName = dbName;
|
|
61
|
+
if (this.db) {
|
|
62
|
+
this.db.close();
|
|
63
|
+
this.db = null;
|
|
64
|
+
this.dbPromise = deferred();
|
|
448
65
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
66
|
+
this.initDB();
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Closes the database connection. After calling this method, the store
|
|
70
|
+
* will no longer be usable. A new instance must be created to reopen
|
|
71
|
+
* the database.
|
|
72
|
+
*/
|
|
73
|
+
async close() {
|
|
74
|
+
await this.dbPromise.promise;
|
|
75
|
+
if (this.db) {
|
|
76
|
+
this.db.close();
|
|
77
|
+
this.db = null;
|
|
78
|
+
this.dbPromise = deferred();
|
|
79
|
+
this.dbPromise.resolve(null);
|
|
453
80
|
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
81
|
+
}
|
|
82
|
+
async deleteDB() {
|
|
83
|
+
if (!this.dbName) return;
|
|
84
|
+
await this.close();
|
|
85
|
+
await new Promise((resolve, reject) => {
|
|
86
|
+
const request = indexedDB.deleteDatabase(this.dbName);
|
|
87
|
+
request.onsuccess = () => resolve();
|
|
88
|
+
request.onerror = () => reject(request.error);
|
|
89
|
+
request.onblocked = () => reject(request.error);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
async transaction(storeNames, mode) {
|
|
93
|
+
const db = await this.getDB();
|
|
94
|
+
const tx = new IDBTransactionWrapper(db.transaction(storeNames, mode));
|
|
95
|
+
const stores = storeNames.map((name) => tx.getStore(name));
|
|
96
|
+
return [tx, ...stores];
|
|
97
|
+
}
|
|
98
|
+
async getDoc(docId) {
|
|
99
|
+
const [tx, docsStore, snapshots, committedChanges, pendingChanges] = await this.transaction(
|
|
100
|
+
["docs", "snapshots", "committedChanges", "pendingChanges"],
|
|
101
|
+
"readonly"
|
|
102
|
+
);
|
|
103
|
+
const docMeta = await docsStore.get(docId);
|
|
104
|
+
if (docMeta?.deleted) {
|
|
105
|
+
await tx.complete();
|
|
106
|
+
return void 0;
|
|
460
107
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
108
|
+
const snapshot = await snapshots.get(docId);
|
|
109
|
+
const committed = await committedChanges.getAll([docId, snapshot?.rev ?? 0], [docId, Infinity]);
|
|
110
|
+
const pending = await pendingChanges.getAll([docId, 0], [docId, Infinity]);
|
|
111
|
+
if (!snapshot && !committed.length && !pending.length) return void 0;
|
|
112
|
+
const state = applyChanges(snapshot?.state, committed);
|
|
113
|
+
const lastCommitted = committed[committed.length - 1];
|
|
114
|
+
const baseRev = pending[0]?.baseRev;
|
|
115
|
+
if (lastCommitted && baseRev && baseRev < lastCommitted.rev) {
|
|
116
|
+
const patch = committed.filter((change) => change.rev > baseRev).map((change) => change.ops).flat();
|
|
117
|
+
const offset = lastCommitted.rev - baseRev;
|
|
118
|
+
pending.forEach((change) => {
|
|
119
|
+
change.rev += offset;
|
|
120
|
+
change.ops = transformPatch(state, patch, change.ops);
|
|
121
|
+
});
|
|
467
122
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
123
|
+
await tx.complete();
|
|
124
|
+
return {
|
|
125
|
+
state,
|
|
126
|
+
rev: committed[committed.length - 1]?.rev ?? snapshot?.rev ?? 0,
|
|
127
|
+
changes: pending
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
async deleteDoc(docId) {
|
|
131
|
+
const [tx, snapshots, committedChanges, pendingChanges, docsStore] = await this.transaction(
|
|
132
|
+
["snapshots", "committedChanges", "pendingChanges", "docs"],
|
|
133
|
+
"readwrite"
|
|
134
|
+
);
|
|
135
|
+
const docMeta = await docsStore.get(docId) ?? { docId, committedRev: 0 };
|
|
136
|
+
await docsStore.put({ ...docMeta, deleted: true });
|
|
137
|
+
await Promise.all([
|
|
138
|
+
snapshots.delete(docId),
|
|
139
|
+
committedChanges.delete([docId, 0], [docId, Infinity]),
|
|
140
|
+
pendingChanges.delete([docId, 0], [docId, Infinity])
|
|
141
|
+
]);
|
|
142
|
+
await tx.complete();
|
|
143
|
+
}
|
|
144
|
+
async confirmDeleteDoc(docId) {
|
|
145
|
+
const [tx, docsStore] = await this.transaction(["docs"], "readwrite");
|
|
146
|
+
await docsStore.delete(docId);
|
|
147
|
+
await tx.complete();
|
|
148
|
+
}
|
|
149
|
+
async saveDoc(docId, docState) {
|
|
150
|
+
const [tx, snapshots, committedChanges, pendingChanges, docsStore] = await this.transaction(
|
|
151
|
+
["snapshots", "committedChanges", "pendingChanges", "docs"],
|
|
152
|
+
"readwrite"
|
|
153
|
+
);
|
|
154
|
+
const { rev, state } = docState;
|
|
155
|
+
await Promise.all([
|
|
156
|
+
docsStore.put({ docId, committedRev: rev }),
|
|
157
|
+
snapshots.put({ docId, state, rev }),
|
|
158
|
+
committedChanges.delete([docId, 0], [docId, Infinity]),
|
|
159
|
+
pendingChanges.delete([docId, 0], [docId, Infinity])
|
|
160
|
+
]);
|
|
161
|
+
await tx.complete();
|
|
162
|
+
}
|
|
163
|
+
async savePendingChanges(docId, changes) {
|
|
164
|
+
const [tx, pendingChanges, docsStore] = await this.transaction(["pendingChanges", "docs"], "readwrite");
|
|
165
|
+
let docMeta = await docsStore.get(docId);
|
|
166
|
+
if (!docMeta) {
|
|
167
|
+
docMeta = { docId, committedRev: 0 };
|
|
168
|
+
await docsStore.put(docMeta);
|
|
169
|
+
} else if (docMeta.deleted) {
|
|
170
|
+
delete docMeta.deleted;
|
|
171
|
+
await docsStore.put(docMeta);
|
|
172
|
+
console.warn(`Revived document ${docId} by saving pending changes.`);
|
|
474
173
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
174
|
+
await Promise.all(changes.map((change) => pendingChanges.put({ ...change, docId })));
|
|
175
|
+
await tx.complete();
|
|
176
|
+
}
|
|
177
|
+
async getPendingChanges(docId) {
|
|
178
|
+
const [tx, pendingChanges] = await this.transaction(["pendingChanges"], "readonly");
|
|
179
|
+
const result = await pendingChanges.getAll([docId, 0], [docId, Infinity]);
|
|
180
|
+
await tx.complete();
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
183
|
+
async replacePendingChanges(docId, changes) {
|
|
184
|
+
const [tx, pendingChanges, docsStore] = await this.transaction(["pendingChanges", "docs"], "readwrite");
|
|
185
|
+
let docMeta = await docsStore.get(docId);
|
|
186
|
+
if (!docMeta) {
|
|
187
|
+
docMeta = { docId, committedRev: 0 };
|
|
188
|
+
await docsStore.put(docMeta);
|
|
189
|
+
} else if (docMeta.deleted) {
|
|
190
|
+
delete docMeta.deleted;
|
|
191
|
+
await docsStore.put(docMeta);
|
|
192
|
+
console.warn(`Revived document ${docId} by replacing pending changes.`);
|
|
482
193
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
194
|
+
await pendingChanges.delete([docId, 0], [docId, Infinity]);
|
|
195
|
+
await Promise.all(changes.map((change) => pendingChanges.put({ ...change, docId })));
|
|
196
|
+
await tx.complete();
|
|
197
|
+
}
|
|
198
|
+
async saveCommittedChanges(docId, changes, sentPendingRange) {
|
|
199
|
+
const [tx, committedChanges, pendingChanges, snapshots, docsStore] = await this.transaction(
|
|
200
|
+
["committedChanges", "pendingChanges", "snapshots", "docs"],
|
|
201
|
+
"readwrite"
|
|
202
|
+
);
|
|
203
|
+
await Promise.all(changes.map((change) => committedChanges.put({ ...change, docId })));
|
|
204
|
+
if (sentPendingRange) {
|
|
205
|
+
await pendingChanges.delete([docId, sentPendingRange[0]], [docId, sentPendingRange[1]]);
|
|
489
206
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
207
|
+
const count = await committedChanges.count([docId, 0], [docId, Infinity]);
|
|
208
|
+
if (count >= SNAPSHOT_INTERVAL) {
|
|
209
|
+
const [snapshot, committed, firstPending] = await Promise.all([
|
|
210
|
+
snapshots.get(docId),
|
|
211
|
+
committedChanges.getAll([docId, 0], [docId, Infinity], SNAPSHOT_INTERVAL),
|
|
212
|
+
pendingChanges.getFirstFromCursor([docId, 0], [docId, Infinity])
|
|
213
|
+
]);
|
|
214
|
+
const lastRev = committed[committed.length - 1]?.rev;
|
|
215
|
+
if (!firstPending?.baseRev || firstPending?.baseRev >= lastRev) {
|
|
216
|
+
const state = applyChanges(snapshot?.state, committed);
|
|
217
|
+
await Promise.all([
|
|
218
|
+
snapshots.put({
|
|
219
|
+
docId,
|
|
220
|
+
rev: lastRev,
|
|
221
|
+
state
|
|
222
|
+
}),
|
|
223
|
+
committedChanges.delete([docId, 0], [docId, lastRev])
|
|
224
|
+
]);
|
|
225
|
+
}
|
|
496
226
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
227
|
+
const lastCommittedRev = changes.at(-1)?.rev;
|
|
228
|
+
if (lastCommittedRev !== void 0) {
|
|
229
|
+
const docMeta = await docsStore.get(docId) ?? { docId, committedRev: 0 };
|
|
230
|
+
if (lastCommittedRev > docMeta.committedRev) {
|
|
231
|
+
await docsStore.put({ ...docMeta, committedRev: lastCommittedRev, deleted: void 0 });
|
|
232
|
+
}
|
|
503
233
|
}
|
|
234
|
+
await tx.complete();
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* List all documents in the store.
|
|
238
|
+
* @param includeDeleted - Whether to include deleted documents.
|
|
239
|
+
* @returns The list of documents.
|
|
240
|
+
*/
|
|
241
|
+
async listDocs(includeDeleted = false) {
|
|
242
|
+
const [tx, docsStore] = await this.transaction(["docs"], "readonly");
|
|
243
|
+
const allDocs = await docsStore.getAll();
|
|
244
|
+
await tx.complete();
|
|
245
|
+
return includeDeleted ? allDocs : allDocs.filter((doc) => !doc.deleted);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Track a document.
|
|
249
|
+
* @param docIds - The IDs of the documents to track.
|
|
250
|
+
*/
|
|
251
|
+
async trackDocs(docIds) {
|
|
252
|
+
const [tx, docsStore] = await this.transaction(["docs"], "readwrite");
|
|
253
|
+
await Promise.all(
|
|
254
|
+
docIds.map(async (docId) => {
|
|
255
|
+
const existing = await docsStore.get(docId);
|
|
256
|
+
if (existing) {
|
|
257
|
+
if (existing.deleted) {
|
|
258
|
+
await docsStore.put({ ...existing, deleted: void 0 });
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
await docsStore.put({ docId, committedRev: 0 });
|
|
262
|
+
}
|
|
263
|
+
})
|
|
264
|
+
);
|
|
265
|
+
await tx.complete();
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Untrack a document.
|
|
269
|
+
* @param docIds - The IDs of the documents to untrack.
|
|
270
|
+
*/
|
|
271
|
+
async untrackDocs(docIds) {
|
|
272
|
+
const [tx, docsStore, snapshots, committedChanges, pendingChanges] = await this.transaction(
|
|
273
|
+
["docs", "snapshots", "committedChanges", "pendingChanges"],
|
|
274
|
+
"readwrite"
|
|
275
|
+
);
|
|
276
|
+
await Promise.all(
|
|
277
|
+
docIds.map((docId) => {
|
|
278
|
+
return Promise.all([
|
|
279
|
+
docsStore.delete(docId),
|
|
280
|
+
snapshots.delete(docId),
|
|
281
|
+
committedChanges.delete([docId, 0], [docId, Infinity]),
|
|
282
|
+
pendingChanges.delete([docId, 0], [docId, Infinity])
|
|
283
|
+
]);
|
|
284
|
+
})
|
|
285
|
+
);
|
|
286
|
+
await tx.complete();
|
|
287
|
+
}
|
|
288
|
+
async getLastRevs(docId) {
|
|
289
|
+
const [tx, committedChanges, pendingChanges] = await this.transaction(
|
|
290
|
+
["committedChanges", "pendingChanges"],
|
|
291
|
+
"readonly"
|
|
292
|
+
);
|
|
293
|
+
const [lastCommitted, lastPending] = await Promise.all([
|
|
294
|
+
committedChanges.getLastFromCursor([docId, 0], [docId, Infinity]),
|
|
295
|
+
pendingChanges.getLastFromCursor([docId, 0], [docId, Infinity])
|
|
296
|
+
]);
|
|
297
|
+
await tx.complete();
|
|
298
|
+
return [lastCommitted?.rev ?? 0, lastPending?.rev ?? lastCommitted?.rev ?? 0];
|
|
299
|
+
}
|
|
504
300
|
}
|
|
301
|
+
_init = __decoratorStart(null);
|
|
302
|
+
__decorateElement(_init, 1, "getDoc", _getDoc_dec, IndexedDBStore);
|
|
303
|
+
__decorateElement(_init, 1, "deleteDoc", _deleteDoc_dec, IndexedDBStore);
|
|
304
|
+
__decorateElement(_init, 1, "confirmDeleteDoc", _confirmDeleteDoc_dec, IndexedDBStore);
|
|
305
|
+
__decorateElement(_init, 1, "saveDoc", _saveDoc_dec, IndexedDBStore);
|
|
306
|
+
__decorateElement(_init, 1, "savePendingChanges", _savePendingChanges_dec, IndexedDBStore);
|
|
307
|
+
__decorateElement(_init, 1, "getPendingChanges", _getPendingChanges_dec, IndexedDBStore);
|
|
308
|
+
__decorateElement(_init, 1, "replacePendingChanges", _replacePendingChanges_dec, IndexedDBStore);
|
|
309
|
+
__decorateElement(_init, 1, "saveCommittedChanges", _saveCommittedChanges_dec, IndexedDBStore);
|
|
310
|
+
__decorateElement(_init, 1, "getLastRevs", _getLastRevs_dec, IndexedDBStore);
|
|
311
|
+
__decoratorMetadata(_init, IndexedDBStore);
|
|
312
|
+
class IDBTransactionWrapper {
|
|
313
|
+
tx;
|
|
314
|
+
promise;
|
|
315
|
+
constructor(tx) {
|
|
316
|
+
this.tx = tx;
|
|
317
|
+
this.promise = new Promise((resolve, reject) => {
|
|
318
|
+
tx.oncomplete = () => resolve();
|
|
319
|
+
tx.onerror = () => reject(tx.error);
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
getStore(name) {
|
|
323
|
+
return new IDBStoreWrapper(this.tx.objectStore(name));
|
|
324
|
+
}
|
|
325
|
+
async complete() {
|
|
326
|
+
return this.promise;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
class IDBStoreWrapper {
|
|
330
|
+
store;
|
|
331
|
+
constructor(store) {
|
|
332
|
+
this.store = store;
|
|
333
|
+
}
|
|
334
|
+
createRange(lower, upper) {
|
|
335
|
+
if (lower === void 0 && upper === void 0) return void 0;
|
|
336
|
+
return IDBKeyRange.bound(lower, upper);
|
|
337
|
+
}
|
|
338
|
+
async getAll(lower, upper, count) {
|
|
339
|
+
return new Promise((resolve, reject) => {
|
|
340
|
+
const request = this.store.getAll(this.createRange(lower, upper), count);
|
|
341
|
+
request.onsuccess = () => resolve(request.result);
|
|
342
|
+
request.onerror = () => reject(request.error);
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
async get(key) {
|
|
346
|
+
return new Promise((resolve, reject) => {
|
|
347
|
+
const request = this.store.get(key);
|
|
348
|
+
request.onsuccess = () => resolve(request.result);
|
|
349
|
+
request.onerror = () => reject(request.error);
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
async put(value) {
|
|
353
|
+
return new Promise((resolve, reject) => {
|
|
354
|
+
const request = this.store.put(value);
|
|
355
|
+
request.onsuccess = () => resolve(request.result);
|
|
356
|
+
request.onerror = () => reject(request.error);
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
async delete(keyOrLower, upper) {
|
|
360
|
+
return new Promise((resolve, reject) => {
|
|
361
|
+
const key = upper === void 0 ? keyOrLower : this.createRange(keyOrLower, upper);
|
|
362
|
+
const request = this.store.delete(key);
|
|
363
|
+
request.onsuccess = () => resolve();
|
|
364
|
+
request.onerror = () => reject(request.error);
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
async count(lower, upper) {
|
|
368
|
+
return new Promise((resolve, reject) => {
|
|
369
|
+
const request = this.store.count(this.createRange(lower, upper));
|
|
370
|
+
request.onsuccess = () => resolve(request.result);
|
|
371
|
+
request.onerror = () => reject(request.error);
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
async getFirstFromCursor(lower, upper) {
|
|
375
|
+
return new Promise((resolve, reject) => {
|
|
376
|
+
const request = this.store.openCursor(this.createRange(lower, upper));
|
|
377
|
+
request.onsuccess = () => resolve(request.result?.value);
|
|
378
|
+
request.onerror = () => reject(request.error);
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
async getLastFromCursor(lower, upper) {
|
|
382
|
+
return new Promise((resolve, reject) => {
|
|
383
|
+
const request = this.store.openCursor(this.createRange(lower, upper), "prev");
|
|
384
|
+
request.onsuccess = () => resolve(request.result?.value);
|
|
385
|
+
request.onerror = () => reject(request.error);
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
export {
|
|
390
|
+
IndexedDBStore
|
|
391
|
+
};
|