@dabble/patches 0.6.0 → 0.7.1
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/README.md +221 -208
- package/dist/BaseDoc-DkP3tUhT.d.ts +206 -0
- package/dist/algorithms/lww/consolidateOps.d.ts +40 -0
- package/dist/algorithms/lww/consolidateOps.js +103 -0
- package/dist/algorithms/lww/mergeServerWithLocal.d.ts +22 -0
- package/dist/algorithms/lww/mergeServerWithLocal.js +32 -0
- package/dist/algorithms/{client → ot/client}/applyCommittedChanges.d.ts +10 -3
- package/dist/algorithms/{client → ot/client}/applyCommittedChanges.js +7 -4
- package/dist/algorithms/{client → ot/client}/createStateFromSnapshot.d.ts +3 -3
- package/dist/algorithms/{client → ot/client}/createStateFromSnapshot.js +1 -1
- package/dist/algorithms/ot/server/commitChanges.d.ts +43 -0
- package/dist/algorithms/{server → ot/server}/commitChanges.js +22 -7
- package/dist/algorithms/{server → ot/server}/createVersion.d.ts +5 -5
- package/dist/algorithms/{server → ot/server}/createVersion.js +2 -2
- package/dist/algorithms/{server → ot/server}/getSnapshotAtRevision.d.ts +5 -5
- package/dist/algorithms/{server → ot/server}/getSnapshotAtRevision.js +1 -1
- package/dist/algorithms/{server → ot/server}/getStateAtRevision.d.ts +5 -5
- package/dist/algorithms/{server → ot/server}/getStateAtRevision.js +1 -1
- package/dist/algorithms/{server → ot/server}/handleOfflineSessionsAndBatches.d.ts +5 -5
- package/dist/algorithms/{server → ot/server}/handleOfflineSessionsAndBatches.js +3 -3
- package/dist/algorithms/{server → ot/server}/transformIncomingChanges.d.ts +3 -3
- package/dist/algorithms/{server → ot/server}/transformIncomingChanges.js +3 -3
- package/dist/algorithms/{shared → ot/shared}/applyChanges.d.ts +3 -3
- package/dist/algorithms/{shared → ot/shared}/applyChanges.js +2 -2
- package/dist/algorithms/{shared → ot/shared}/changeBatching.d.ts +3 -3
- package/dist/algorithms/{shared → ot/shared}/changeBatching.js +2 -2
- package/dist/algorithms/{shared → ot/shared}/rebaseChanges.d.ts +3 -3
- package/dist/algorithms/{shared → ot/shared}/rebaseChanges.js +2 -2
- package/dist/client/BaseDoc.d.ts +6 -0
- package/dist/client/BaseDoc.js +70 -0
- package/dist/client/ClientAlgorithm.d.ts +101 -0
- package/dist/client/ClientAlgorithm.js +0 -0
- package/dist/client/InMemoryStore.d.ts +5 -7
- package/dist/client/InMemoryStore.js +7 -36
- package/dist/client/IndexedDBStore.d.ts +39 -73
- package/dist/client/IndexedDBStore.js +17 -220
- package/dist/client/LWWAlgorithm.d.ts +43 -0
- package/dist/client/LWWAlgorithm.js +87 -0
- package/dist/client/LWWClientStore.d.ts +73 -0
- package/dist/client/LWWClientStore.js +0 -0
- package/dist/client/LWWDoc.d.ts +56 -0
- package/dist/client/LWWDoc.js +84 -0
- package/dist/client/LWWInMemoryStore.d.ts +88 -0
- package/dist/client/LWWInMemoryStore.js +208 -0
- package/dist/client/LWWIndexedDBStore.d.ts +91 -0
- package/dist/client/LWWIndexedDBStore.js +275 -0
- package/dist/client/OTAlgorithm.d.ts +42 -0
- package/dist/client/OTAlgorithm.js +113 -0
- package/dist/client/OTClientStore.d.ts +50 -0
- package/dist/client/OTClientStore.js +0 -0
- package/dist/client/OTDoc.d.ts +6 -0
- package/dist/client/OTDoc.js +97 -0
- package/dist/client/OTIndexedDBStore.d.ts +84 -0
- package/dist/client/OTIndexedDBStore.js +163 -0
- package/dist/client/Patches.d.ts +36 -16
- package/dist/client/Patches.js +60 -27
- package/dist/client/PatchesDoc.d.ts +4 -113
- package/dist/client/PatchesDoc.js +3 -153
- package/dist/client/PatchesHistoryClient.js +1 -1
- package/dist/client/PatchesStore.d.ts +8 -105
- package/dist/client/factories.d.ts +72 -0
- package/dist/client/factories.js +80 -0
- package/dist/client/index.d.ts +14 -5
- package/dist/client/index.js +9 -0
- package/dist/compression/index.d.ts +2 -2
- package/dist/compression/index.js +1 -1
- package/dist/{algorithms/shared → compression}/lz.js +1 -1
- package/dist/data/change.js +2 -0
- package/dist/fractionalIndex.d.ts +67 -0
- package/dist/fractionalIndex.js +241 -0
- package/dist/index.d.ts +13 -3
- package/dist/index.js +1 -0
- package/dist/json-patch/types.d.ts +2 -0
- package/dist/net/PatchesClient.js +15 -15
- package/dist/net/PatchesSync.d.ts +20 -8
- package/dist/net/PatchesSync.js +57 -65
- package/dist/net/index.d.ts +7 -11
- package/dist/net/index.js +6 -1
- package/dist/net/protocol/JSONRPCClient.d.ts +4 -4
- package/dist/net/protocol/JSONRPCClient.js +6 -4
- package/dist/net/protocol/JSONRPCServer.d.ts +45 -9
- package/dist/net/protocol/JSONRPCServer.js +63 -8
- package/dist/net/serverContext.d.ts +38 -0
- package/dist/net/serverContext.js +20 -0
- package/dist/net/webrtc/WebRTCTransport.js +1 -1
- package/dist/net/websocket/AuthorizationProvider.d.ts +3 -3
- package/dist/net/websocket/WebSocketServer.d.ts +29 -20
- package/dist/net/websocket/WebSocketServer.js +23 -12
- package/dist/server/BranchManager.d.ts +50 -0
- package/dist/server/BranchManager.js +0 -0
- package/dist/server/CompressedStoreBackend.d.ts +10 -8
- package/dist/server/CompressedStoreBackend.js +7 -13
- package/dist/server/LWWBranchManager.d.ts +82 -0
- package/dist/server/LWWBranchManager.js +99 -0
- package/dist/server/LWWMemoryStoreBackend.d.ts +78 -0
- package/dist/server/LWWMemoryStoreBackend.js +182 -0
- package/dist/server/LWWServer.d.ts +130 -0
- package/dist/server/LWWServer.js +214 -0
- package/dist/server/{PatchesBranchManager.d.ts → OTBranchManager.d.ts} +32 -12
- package/dist/server/{PatchesBranchManager.js → OTBranchManager.js} +27 -42
- package/dist/server/OTServer.d.ts +108 -0
- package/dist/server/OTServer.js +141 -0
- package/dist/server/PatchesHistoryManager.d.ts +21 -16
- package/dist/server/PatchesHistoryManager.js +23 -11
- package/dist/server/PatchesServer.d.ts +70 -81
- package/dist/server/PatchesServer.js +0 -175
- package/dist/server/branchUtils.d.ts +82 -0
- package/dist/server/branchUtils.js +66 -0
- package/dist/server/index.d.ts +18 -7
- package/dist/server/index.js +33 -4
- package/dist/server/tombstone.d.ts +29 -0
- package/dist/server/tombstone.js +32 -0
- package/dist/server/types.d.ts +109 -27
- package/dist/server/utils.d.ts +12 -0
- package/dist/server/utils.js +23 -0
- package/dist/solid/context.d.ts +4 -3
- package/dist/solid/doc-manager.d.ts +3 -3
- package/dist/solid/index.d.ts +4 -3
- package/dist/solid/primitives.d.ts +2 -3
- package/dist/types.d.ts +4 -2
- package/dist/vue/composables.d.ts +2 -3
- package/dist/vue/doc-manager.d.ts +3 -3
- package/dist/vue/index.d.ts +4 -3
- package/dist/vue/provider.d.ts +4 -3
- package/package.json +1 -1
- package/dist/algorithms/client/collapsePendingChanges.d.ts +0 -30
- package/dist/algorithms/client/collapsePendingChanges.js +0 -78
- package/dist/algorithms/client/makeChange.d.ts +0 -9
- package/dist/algorithms/client/makeChange.js +0 -29
- package/dist/algorithms/server/commitChanges.d.ts +0 -19
- package/dist/net/websocket/RPCServer.d.ts +0 -141
- package/dist/net/websocket/RPCServer.js +0 -204
- /package/dist/{algorithms/shared → compression}/lz.d.ts +0 -0
package/dist/client/Patches.js
CHANGED
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
var _openDoc_dec, _init;
|
|
9
9
|
import { signal } from "../event-signal.js";
|
|
10
10
|
import { singleInvocation } from "../utils/concurrency.js";
|
|
11
|
-
import { PatchesDoc } from "./PatchesDoc.js";
|
|
12
11
|
_openDoc_dec = [singleInvocation(true)];
|
|
13
12
|
class Patches {
|
|
14
13
|
constructor(opts) {
|
|
@@ -16,7 +15,8 @@ class Patches {
|
|
|
16
15
|
__publicField(this, "options");
|
|
17
16
|
__publicField(this, "docs", /* @__PURE__ */ new Map());
|
|
18
17
|
__publicField(this, "docOptions");
|
|
19
|
-
__publicField(this, "
|
|
18
|
+
__publicField(this, "algorithms");
|
|
19
|
+
__publicField(this, "defaultAlgorithm");
|
|
20
20
|
__publicField(this, "trackedDocs", /* @__PURE__ */ new Set());
|
|
21
21
|
// Public signals
|
|
22
22
|
__publicField(this, "onError", signal());
|
|
@@ -24,27 +24,54 @@ class Patches {
|
|
|
24
24
|
__publicField(this, "onTrackDocs", signal());
|
|
25
25
|
__publicField(this, "onUntrackDocs", signal());
|
|
26
26
|
__publicField(this, "onDeleteDoc", signal());
|
|
27
|
+
/** Emitted when a doc has pending changes ready to send */
|
|
27
28
|
__publicField(this, "onChange", signal());
|
|
28
29
|
this.options = opts;
|
|
29
|
-
this.
|
|
30
|
+
this.algorithms = opts.algorithms;
|
|
31
|
+
const algorithmNames = Object.keys(opts.algorithms);
|
|
32
|
+
if (algorithmNames.length === 0) {
|
|
33
|
+
throw new Error("At least one algorithm must be provided");
|
|
34
|
+
}
|
|
35
|
+
this.defaultAlgorithm = opts.defaultAlgorithm ?? algorithmNames[0];
|
|
36
|
+
if (!opts.algorithms[this.defaultAlgorithm]) {
|
|
37
|
+
throw new Error(`Default algorithm '${this.defaultAlgorithm}' not found in algorithms map`);
|
|
38
|
+
}
|
|
30
39
|
this.docOptions = opts.docOptions ?? {};
|
|
31
|
-
this.
|
|
40
|
+
this._getAlgorithm(this.defaultAlgorithm).listDocs().then((docs) => {
|
|
32
41
|
this.trackDocs(docs.map(({ docId }) => docId));
|
|
33
42
|
});
|
|
34
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Gets an algorithm by name, throwing if not found.
|
|
46
|
+
*/
|
|
47
|
+
_getAlgorithm(name) {
|
|
48
|
+
const algorithm = this.algorithms[name];
|
|
49
|
+
if (!algorithm) {
|
|
50
|
+
throw new Error(`Algorithm '${name}' not found`);
|
|
51
|
+
}
|
|
52
|
+
return algorithm;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Gets the algorithm for an open document.
|
|
56
|
+
*/
|
|
57
|
+
getDocAlgorithm(docId) {
|
|
58
|
+
return this.docs.get(docId)?.algorithm;
|
|
59
|
+
}
|
|
35
60
|
// --- Public API Methods ---
|
|
36
61
|
/**
|
|
37
62
|
* Tracks the given document IDs, adding them to the set of tracked documents and notifying listeners.
|
|
38
63
|
* Tracked docs are kept in sync with the server, even when not open locally.
|
|
39
64
|
* This allows for background syncing and updates of unopened documents.
|
|
40
65
|
* @param docIds - Array of document IDs to track.
|
|
66
|
+
* @param algorithmName - Algorithm to use for tracking (defaults to defaultAlgorithm).
|
|
41
67
|
*/
|
|
42
|
-
async trackDocs(docIds) {
|
|
68
|
+
async trackDocs(docIds, algorithmName) {
|
|
43
69
|
docIds = docIds.filter((id) => !this.trackedDocs.has(id));
|
|
44
70
|
if (!docIds.length) return;
|
|
45
71
|
docIds.forEach(this.trackedDocs.add, this.trackedDocs);
|
|
46
72
|
this.onTrackDocs.emit(docIds);
|
|
47
|
-
|
|
73
|
+
const algorithm = this._getAlgorithm(algorithmName ?? this.defaultAlgorithm);
|
|
74
|
+
await algorithm.trackDocs(docIds);
|
|
48
75
|
}
|
|
49
76
|
/**
|
|
50
77
|
* Untracks the given document IDs, removing them from the set of tracked documents and notifying listeners.
|
|
@@ -59,23 +86,28 @@ class Patches {
|
|
|
59
86
|
this.onUntrackDocs.emit(docIds);
|
|
60
87
|
const closedPromises = docIds.filter((id) => this.docs.has(id)).map((id) => this.closeDoc(id));
|
|
61
88
|
await Promise.all(closedPromises);
|
|
62
|
-
|
|
89
|
+
const byAlgorithm = /* @__PURE__ */ new Map();
|
|
90
|
+
for (const docId of docIds) {
|
|
91
|
+
const managed = this.docs.get(docId);
|
|
92
|
+
const algorithm = managed?.algorithm ?? this._getAlgorithm(this.defaultAlgorithm);
|
|
93
|
+
const list = byAlgorithm.get(algorithm) ?? [];
|
|
94
|
+
list.push(docId);
|
|
95
|
+
byAlgorithm.set(algorithm, list);
|
|
96
|
+
}
|
|
97
|
+
await Promise.all([...byAlgorithm.entries()].map(([algorithm, ids]) => algorithm.untrackDocs(ids)));
|
|
63
98
|
}
|
|
64
99
|
// ensure a second call to openDoc with the same docId returns the same promise while opening
|
|
65
100
|
async openDoc(docId, opts = {}) {
|
|
66
101
|
const existing = this.docs.get(docId);
|
|
67
102
|
if (existing) return existing.doc;
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
103
|
+
const algorithmName = opts.algorithm ?? this.defaultAlgorithm;
|
|
104
|
+
const algorithm = this._getAlgorithm(algorithmName);
|
|
105
|
+
await this.trackDocs([docId], algorithmName);
|
|
106
|
+
const snapshot = await algorithm.loadDoc(docId);
|
|
71
107
|
const mergedMetadata = { ...this.options.metadata, ...opts.metadata };
|
|
72
|
-
const doc =
|
|
73
|
-
doc.
|
|
74
|
-
|
|
75
|
-
doc.import(snapshot);
|
|
76
|
-
}
|
|
77
|
-
const unsubscribe = doc.onChange((changes) => this._savePendingChanges(docId, changes));
|
|
78
|
-
this.docs.set(docId, { doc, unsubscribe });
|
|
108
|
+
const doc = algorithm.createDoc(docId, snapshot);
|
|
109
|
+
const unsubscribe = doc.onChange((ops) => this._handleDocChange(docId, ops, doc, algorithm, mergedMetadata));
|
|
110
|
+
this.docs.set(docId, { doc, algorithm, unsubscribe });
|
|
79
111
|
return doc;
|
|
80
112
|
}
|
|
81
113
|
/**
|
|
@@ -99,13 +131,15 @@ class Patches {
|
|
|
99
131
|
* @param docId - The document ID to delete.
|
|
100
132
|
*/
|
|
101
133
|
async deleteDoc(docId) {
|
|
134
|
+
const managed = this.docs.get(docId);
|
|
135
|
+
const algorithm = managed?.algorithm ?? this._getAlgorithm(this.defaultAlgorithm);
|
|
102
136
|
if (this.docs.has(docId)) {
|
|
103
137
|
await this.closeDoc(docId);
|
|
104
138
|
}
|
|
105
139
|
if (this.trackedDocs.has(docId)) {
|
|
106
140
|
await this.untrackDocs([docId]);
|
|
107
141
|
}
|
|
108
|
-
await
|
|
142
|
+
await algorithm.deleteDoc(docId);
|
|
109
143
|
await this.onDeleteDoc.emit(docId);
|
|
110
144
|
}
|
|
111
145
|
/**
|
|
@@ -121,10 +155,10 @@ class Patches {
|
|
|
121
155
|
* Closes all open documents and cleans up listeners and store connections.
|
|
122
156
|
* Should be called when shutting down the client.
|
|
123
157
|
*/
|
|
124
|
-
close() {
|
|
158
|
+
async close() {
|
|
125
159
|
this.docs.forEach((managed) => managed.unsubscribe());
|
|
126
160
|
this.docs.clear();
|
|
127
|
-
this.
|
|
161
|
+
await Promise.all(Object.values(this.algorithms).map((s) => s?.close()));
|
|
128
162
|
this.onChange.clear();
|
|
129
163
|
this.onDeleteDoc.clear();
|
|
130
164
|
this.onUntrackDocs.clear();
|
|
@@ -133,16 +167,15 @@ class Patches {
|
|
|
133
167
|
this.onError.clear();
|
|
134
168
|
}
|
|
135
169
|
/**
|
|
136
|
-
* Internal handler for
|
|
137
|
-
*
|
|
138
|
-
* @param changes - The changes to save.
|
|
170
|
+
* Internal handler for doc changes. Called when doc.onChange emits ops.
|
|
171
|
+
* Delegates to algorithm for packaging and persisting.
|
|
139
172
|
*/
|
|
140
|
-
async
|
|
173
|
+
async _handleDocChange(docId, ops, doc, algorithm, metadata) {
|
|
141
174
|
try {
|
|
142
|
-
await
|
|
143
|
-
this.onChange.emit(docId
|
|
175
|
+
await algorithm.handleDocChange(docId, ops, doc, metadata);
|
|
176
|
+
this.onChange.emit(docId);
|
|
144
177
|
} catch (err) {
|
|
145
|
-
console.error(`Error
|
|
178
|
+
console.error(`Error handling doc change for ${docId}:`, err);
|
|
146
179
|
this.onError.emit(err, { docId });
|
|
147
180
|
}
|
|
148
181
|
}
|
|
@@ -1,115 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
1
|
+
import '../event-signal.js';
|
|
2
|
+
import '../json-patch/types.js';
|
|
3
|
+
import '../types.js';
|
|
4
|
+
export { O as OTDoc, a as PatchesDoc, O as PatchesDocClass, P as PatchesDocOptions } from '../BaseDoc-DkP3tUhT.js';
|
|
4
5
|
import '../json-patch/JSONPatch.js';
|
|
5
6
|
import '@dabble/delta';
|
|
6
|
-
import '../json-patch/types.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Options for creating a PatchesDoc instance
|
|
10
|
-
*/
|
|
11
|
-
interface PatchesDocOptions {
|
|
12
|
-
/**
|
|
13
|
-
* Maximum size in bytes for a single change's storage representation.
|
|
14
|
-
* Changes exceeding this will be split. Used for backends with row size limits.
|
|
15
|
-
*/
|
|
16
|
-
maxStorageBytes?: number;
|
|
17
|
-
/**
|
|
18
|
-
* Custom size calculator for storage limit checks.
|
|
19
|
-
* Import from '@dabble/patches/compression' for actual compression measurement,
|
|
20
|
-
* or provide your own function (e.g., ratio estimate).
|
|
21
|
-
*
|
|
22
|
-
* @example
|
|
23
|
-
* import { compressedSizeBase64 } from '@dabble/patches/compression';
|
|
24
|
-
* { sizeCalculator: compressedSizeBase64, maxStorageBytes: 1_000_000 }
|
|
25
|
-
*/
|
|
26
|
-
sizeCalculator?: SizeCalculator;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Represents a document synchronized using JSON patches.
|
|
30
|
-
* Manages committed state, pending (local-only) changes, and
|
|
31
|
-
* changes currently being sent to the server.
|
|
32
|
-
*/
|
|
33
|
-
declare class PatchesDoc<T extends object = object> {
|
|
34
|
-
protected _id: string | null;
|
|
35
|
-
protected _state: T;
|
|
36
|
-
protected _snapshot: PatchesSnapshot<T>;
|
|
37
|
-
protected _changeMetadata: Record<string, any>;
|
|
38
|
-
protected _syncing: SyncingState;
|
|
39
|
-
protected readonly _maxStorageBytes?: number;
|
|
40
|
-
protected readonly _sizeCalculator?: SizeCalculator;
|
|
41
|
-
/** Subscribe to be notified before local state changes. */
|
|
42
|
-
readonly onBeforeChange: Signal<(change: Change) => void>;
|
|
43
|
-
/** Subscribe to be notified after local state changes are applied. */
|
|
44
|
-
readonly onChange: Signal<(changes: Change[]) => void>;
|
|
45
|
-
/** Subscribe to be notified whenever state changes from any source. */
|
|
46
|
-
readonly onUpdate: Signal<(newState: T) => void>;
|
|
47
|
-
/** Subscribe to be notified when syncing state changes. */
|
|
48
|
-
readonly onSyncing: Signal<(newSyncing: SyncingState) => void>;
|
|
49
|
-
/**
|
|
50
|
-
* Creates an instance of PatchesDoc.
|
|
51
|
-
* @param initialState Optional initial state.
|
|
52
|
-
* @param initialMetadata Optional metadata to add to generated changes.
|
|
53
|
-
* @param options Additional options for the document.
|
|
54
|
-
*/
|
|
55
|
-
constructor(initialState?: T, initialMetadata?: Record<string, any>, options?: PatchesDocOptions);
|
|
56
|
-
/** The unique identifier for this document, once assigned. */
|
|
57
|
-
get id(): string | null;
|
|
58
|
-
/** Current local state (committed + pending). */
|
|
59
|
-
get state(): T;
|
|
60
|
-
/** Are we currently syncing this document? */
|
|
61
|
-
get syncing(): SyncingState;
|
|
62
|
-
/** Last committed revision number from the server. */
|
|
63
|
-
get committedRev(): number;
|
|
64
|
-
/** Are there local changes that haven't been sent yet? */
|
|
65
|
-
get hasPending(): boolean;
|
|
66
|
-
/** Subscribe to be notified whenever the state changes. */
|
|
67
|
-
subscribe(onUpdate: (newValue: T) => void): Unsubscriber;
|
|
68
|
-
/**
|
|
69
|
-
* Exports the document state for persistence.
|
|
70
|
-
* NOTE: Any changes currently marked as `sending` are included in the
|
|
71
|
-
* `changes` array alongside `pending` changes. On import, all changes
|
|
72
|
-
* are treated as pending.
|
|
73
|
-
*/
|
|
74
|
-
export(): PatchesSnapshot<T>;
|
|
75
|
-
/**
|
|
76
|
-
* Imports previously exported document state.
|
|
77
|
-
* Resets sending state and treats all imported changes as pending.
|
|
78
|
-
*/
|
|
79
|
-
import(snapshot: PatchesSnapshot<T>): void;
|
|
80
|
-
/**
|
|
81
|
-
* Sets metadata to be added to future changes.
|
|
82
|
-
*/
|
|
83
|
-
setChangeMetadata(metadata: Record<string, any>): void;
|
|
84
|
-
/**
|
|
85
|
-
* Applies an update to the local state, generating a patch and adding it to pending changes.
|
|
86
|
-
* @param mutator Function that uses JSONPatch methods with type-safe paths.
|
|
87
|
-
* @returns The generated Change objects.
|
|
88
|
-
*/
|
|
89
|
-
change(mutator: ChangeMutator<T>): Change[];
|
|
90
|
-
/**
|
|
91
|
-
* Returns the pending changes for this document.
|
|
92
|
-
* @returns The pending changes.
|
|
93
|
-
*/
|
|
94
|
-
getPendingChanges(): Change[];
|
|
95
|
-
/**
|
|
96
|
-
* Applies committed changes to the document. Should only be called from a sync provider.
|
|
97
|
-
* @param serverChanges The changes to apply.
|
|
98
|
-
* @param rebasedPendingChanges The rebased pending changes to apply.
|
|
99
|
-
*/
|
|
100
|
-
applyCommittedChanges(serverChanges: Change[], rebasedPendingChanges: Change[]): void;
|
|
101
|
-
/**
|
|
102
|
-
* Assigns an identifier to this document. Can only be set once.
|
|
103
|
-
* @param id The unique identifier for the document.
|
|
104
|
-
* @throws Error if the ID has already been set.
|
|
105
|
-
*/
|
|
106
|
-
setId(id: string): void;
|
|
107
|
-
/**
|
|
108
|
-
* Updates the syncing state of the document.
|
|
109
|
-
* @param newSyncing The new syncing state.
|
|
110
|
-
*/
|
|
111
|
-
updateSyncing(newSyncing: SyncingState): void;
|
|
112
|
-
toJSON(): PatchesSnapshot<T>;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export { PatchesDoc, type PatchesDocOptions };
|
|
@@ -1,156 +1,6 @@
|
|
|
1
1
|
import "../chunk-IZ2YBCUP.js";
|
|
2
|
-
import {
|
|
3
|
-
import { makeChange } from "../algorithms/client/makeChange.js";
|
|
4
|
-
import { applyChanges } from "../algorithms/shared/applyChanges.js";
|
|
5
|
-
import { signal } from "../event-signal.js";
|
|
6
|
-
class PatchesDoc {
|
|
7
|
-
_id = null;
|
|
8
|
-
_state;
|
|
9
|
-
_snapshot;
|
|
10
|
-
_changeMetadata = {};
|
|
11
|
-
_syncing = null;
|
|
12
|
-
_maxStorageBytes;
|
|
13
|
-
_sizeCalculator;
|
|
14
|
-
/** Subscribe to be notified before local state changes. */
|
|
15
|
-
onBeforeChange = signal();
|
|
16
|
-
/** Subscribe to be notified after local state changes are applied. */
|
|
17
|
-
onChange = signal();
|
|
18
|
-
/** Subscribe to be notified whenever state changes from any source. */
|
|
19
|
-
onUpdate = signal();
|
|
20
|
-
/** Subscribe to be notified when syncing state changes. */
|
|
21
|
-
onSyncing = signal();
|
|
22
|
-
/**
|
|
23
|
-
* Creates an instance of PatchesDoc.
|
|
24
|
-
* @param initialState Optional initial state.
|
|
25
|
-
* @param initialMetadata Optional metadata to add to generated changes.
|
|
26
|
-
* @param options Additional options for the document.
|
|
27
|
-
*/
|
|
28
|
-
constructor(initialState = {}, initialMetadata = {}, options = {}) {
|
|
29
|
-
this._state = structuredClone(initialState);
|
|
30
|
-
this._snapshot = { state: this._state, rev: 0, changes: [] };
|
|
31
|
-
this._changeMetadata = initialMetadata;
|
|
32
|
-
this._maxStorageBytes = options.maxStorageBytes;
|
|
33
|
-
this._sizeCalculator = options.sizeCalculator;
|
|
34
|
-
}
|
|
35
|
-
/** The unique identifier for this document, once assigned. */
|
|
36
|
-
get id() {
|
|
37
|
-
return this._id;
|
|
38
|
-
}
|
|
39
|
-
/** Current local state (committed + pending). */
|
|
40
|
-
get state() {
|
|
41
|
-
return this._state;
|
|
42
|
-
}
|
|
43
|
-
/** Are we currently syncing this document? */
|
|
44
|
-
get syncing() {
|
|
45
|
-
return this._syncing;
|
|
46
|
-
}
|
|
47
|
-
/** Last committed revision number from the server. */
|
|
48
|
-
get committedRev() {
|
|
49
|
-
return this._snapshot.rev;
|
|
50
|
-
}
|
|
51
|
-
/** Are there local changes that haven't been sent yet? */
|
|
52
|
-
get hasPending() {
|
|
53
|
-
return this._snapshot.changes.length > 0;
|
|
54
|
-
}
|
|
55
|
-
/** Subscribe to be notified whenever the state changes. */
|
|
56
|
-
subscribe(onUpdate) {
|
|
57
|
-
const unsub = this.onUpdate(onUpdate);
|
|
58
|
-
onUpdate(this._state);
|
|
59
|
-
return unsub;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Exports the document state for persistence.
|
|
63
|
-
* NOTE: Any changes currently marked as `sending` are included in the
|
|
64
|
-
* `changes` array alongside `pending` changes. On import, all changes
|
|
65
|
-
* are treated as pending.
|
|
66
|
-
*/
|
|
67
|
-
export() {
|
|
68
|
-
return structuredClone(this._snapshot);
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Imports previously exported document state.
|
|
72
|
-
* Resets sending state and treats all imported changes as pending.
|
|
73
|
-
*/
|
|
74
|
-
import(snapshot) {
|
|
75
|
-
this._snapshot = structuredClone(snapshot);
|
|
76
|
-
this._state = createStateFromSnapshot(snapshot);
|
|
77
|
-
this.onUpdate.emit(this._state);
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Sets metadata to be added to future changes.
|
|
81
|
-
*/
|
|
82
|
-
setChangeMetadata(metadata) {
|
|
83
|
-
this._changeMetadata = metadata;
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Applies an update to the local state, generating a patch and adding it to pending changes.
|
|
87
|
-
* @param mutator Function that uses JSONPatch methods with type-safe paths.
|
|
88
|
-
* @returns The generated Change objects.
|
|
89
|
-
*/
|
|
90
|
-
change(mutator) {
|
|
91
|
-
const changes = makeChange(
|
|
92
|
-
this._snapshot,
|
|
93
|
-
mutator,
|
|
94
|
-
this._changeMetadata,
|
|
95
|
-
this._maxStorageBytes,
|
|
96
|
-
this._sizeCalculator
|
|
97
|
-
);
|
|
98
|
-
if (changes.length === 0) {
|
|
99
|
-
return changes;
|
|
100
|
-
}
|
|
101
|
-
this._state = applyChanges(this._state, changes);
|
|
102
|
-
this._snapshot.changes.push(...changes);
|
|
103
|
-
this.onChange.emit(changes);
|
|
104
|
-
this.onUpdate.emit(this._state);
|
|
105
|
-
return changes;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Returns the pending changes for this document.
|
|
109
|
-
* @returns The pending changes.
|
|
110
|
-
*/
|
|
111
|
-
getPendingChanges() {
|
|
112
|
-
return this._snapshot.changes;
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Applies committed changes to the document. Should only be called from a sync provider.
|
|
116
|
-
* @param serverChanges The changes to apply.
|
|
117
|
-
* @param rebasedPendingChanges The rebased pending changes to apply.
|
|
118
|
-
*/
|
|
119
|
-
applyCommittedChanges(serverChanges, rebasedPendingChanges) {
|
|
120
|
-
if (this._snapshot.rev !== serverChanges[0].rev - 1) {
|
|
121
|
-
throw new Error("Cannot apply committed changes to a doc that is not at the correct revision");
|
|
122
|
-
}
|
|
123
|
-
this._snapshot.state = applyChanges(this._snapshot.state, serverChanges);
|
|
124
|
-
this._snapshot.rev = serverChanges[serverChanges.length - 1].rev;
|
|
125
|
-
this._snapshot.changes = rebasedPendingChanges;
|
|
126
|
-
this._state = createStateFromSnapshot(this._snapshot);
|
|
127
|
-
this.onUpdate.emit(this._state);
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Assigns an identifier to this document. Can only be set once.
|
|
131
|
-
* @param id The unique identifier for the document.
|
|
132
|
-
* @throws Error if the ID has already been set.
|
|
133
|
-
*/
|
|
134
|
-
setId(id) {
|
|
135
|
-
if (this._id !== null && this._id !== id) {
|
|
136
|
-
throw new Error(`Document ID cannot be changed once set. Current: ${this._id}, Attempted: ${id}`);
|
|
137
|
-
}
|
|
138
|
-
if (this._id === null) {
|
|
139
|
-
this._id = id;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Updates the syncing state of the document.
|
|
144
|
-
* @param newSyncing The new syncing state.
|
|
145
|
-
*/
|
|
146
|
-
updateSyncing(newSyncing) {
|
|
147
|
-
this._syncing = newSyncing;
|
|
148
|
-
this.onSyncing.emit(newSyncing);
|
|
149
|
-
}
|
|
150
|
-
toJSON() {
|
|
151
|
-
return this.export();
|
|
152
|
-
}
|
|
153
|
-
}
|
|
2
|
+
import { OTDoc, OTDoc as OTDoc2 } from "./OTDoc.js";
|
|
154
3
|
export {
|
|
155
|
-
|
|
4
|
+
OTDoc,
|
|
5
|
+
OTDoc2 as PatchesDocClass
|
|
156
6
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PatchesSnapshot,
|
|
1
|
+
import { PatchesSnapshot, PatchesState } from '../types.js';
|
|
2
2
|
import '../json-patch/JSONPatch.js';
|
|
3
3
|
import '@dabble/delta';
|
|
4
4
|
import '../json-patch/types.js';
|
|
@@ -10,8 +10,6 @@ interface TrackedDoc {
|
|
|
10
10
|
committedRev: number;
|
|
11
11
|
/** Optional flag indicating the document has been locally deleted. */
|
|
12
12
|
deleted?: true;
|
|
13
|
-
/** The last revision that was attempted to be submitted to the server. */
|
|
14
|
-
lastAttemptedSubmissionRev?: number;
|
|
15
13
|
}
|
|
16
14
|
/**
|
|
17
15
|
* Pluggable persistence layer contract used by Patches + PatchesSync.
|
|
@@ -81,36 +79,18 @@ interface PatchesStore {
|
|
|
81
79
|
*/
|
|
82
80
|
getDoc(docId: string): Promise<PatchesSnapshot | undefined>;
|
|
83
81
|
/**
|
|
84
|
-
*
|
|
82
|
+
* Returns the last committed revision for a document.
|
|
85
83
|
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* Used during sync to resend unconfirmed operations.
|
|
84
|
+
* The committed revision is the last revision confirmed by the server.
|
|
85
|
+
* Used during sync to fetch changes since this revision.
|
|
89
86
|
*
|
|
90
87
|
* @param docId Document identifier
|
|
91
|
-
* @returns
|
|
88
|
+
* @returns The last committed revision, or 0 if not found
|
|
92
89
|
* @example
|
|
93
|
-
* const
|
|
94
|
-
*
|
|
90
|
+
* const committedRev = await store.getCommittedRev('my-document');
|
|
91
|
+
* const serverChanges = await api.getChangesSince(docId, committedRev);
|
|
95
92
|
*/
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Returns revision counters for tracking document sync state.
|
|
99
|
-
*
|
|
100
|
-
* committedRev: Last revision confirmed by the server
|
|
101
|
-
* pendingRev: Next revision number for new local changes
|
|
102
|
-
* The gap between these indicates how many changes are pending server confirmation.
|
|
103
|
-
*
|
|
104
|
-
* @param docId Document identifier
|
|
105
|
-
* @returns Tuple of [committedRev, pendingRev]
|
|
106
|
-
* @example
|
|
107
|
-
* const [committed, pending] = await store.getLastRevs('my-document');
|
|
108
|
-
* console.log(`Server confirmed through rev ${committed}, local changes at rev ${pending}`);
|
|
109
|
-
* if (pending > committed) {
|
|
110
|
-
* console.log(`${pending - committed} changes pending server confirmation`);
|
|
111
|
-
* }
|
|
112
|
-
*/
|
|
113
|
-
getLastRevs(docId: string): Promise<[committedRev: number, pendingRev: number]>;
|
|
93
|
+
getCommittedRev(docId: string): Promise<number>;
|
|
114
94
|
/**
|
|
115
95
|
* Saves the current document state to persistent storage.
|
|
116
96
|
*
|
|
@@ -129,54 +109,6 @@ interface PatchesStore {
|
|
|
129
109
|
* });
|
|
130
110
|
*/
|
|
131
111
|
saveDoc(docId: string, docState: PatchesState): Promise<void>;
|
|
132
|
-
/**
|
|
133
|
-
* Appends new pending changes to the document's local change queue.
|
|
134
|
-
*
|
|
135
|
-
* Adds changes to the end of the pending changes list without replacing existing ones.
|
|
136
|
-
* Called when the user makes local edits that haven't been sent to the server yet.
|
|
137
|
-
* Changes should have sequential revision numbers starting after the last pending change.
|
|
138
|
-
*
|
|
139
|
-
* @param docId Document identifier
|
|
140
|
-
* @param changes Array of new changes to append
|
|
141
|
-
* @example
|
|
142
|
-
* // User made a local edit
|
|
143
|
-
* const newChange = { rev: 15, patches: [...], clientId: 'client-123' };
|
|
144
|
-
* await store.savePendingChanges('my-document', [newChange]);
|
|
145
|
-
*/
|
|
146
|
-
savePendingChanges(docId: string, changes: Change[]): Promise<void>;
|
|
147
|
-
/**
|
|
148
|
-
* Records changes confirmed by the server and optionally removes sent pending changes.
|
|
149
|
-
*
|
|
150
|
-
* Adds server-confirmed changes to the document's history and updates the committed revision.
|
|
151
|
-
* If sentPendingRange is provided, removes the specified range of pending changes that
|
|
152
|
-
* were confirmed by the server (they're no longer pending).
|
|
153
|
-
*
|
|
154
|
-
* @param docId Document identifier
|
|
155
|
-
* @param changes Server-confirmed changes to record
|
|
156
|
-
* @param sentPendingRange Optional range [startRev, endRev] of pending changes to remove
|
|
157
|
-
* @example
|
|
158
|
-
* // Server confirmed our changes
|
|
159
|
-
* await store.saveCommittedChanges('my-document', serverChanges, [10, 12]);
|
|
160
|
-
*
|
|
161
|
-
* // Server sent changes from other clients
|
|
162
|
-
* await store.saveCommittedChanges('my-document', serverChanges);
|
|
163
|
-
*/
|
|
164
|
-
saveCommittedChanges(docId: string, changes: Change[], sentPendingRange?: [number, number]): Promise<void>;
|
|
165
|
-
/**
|
|
166
|
-
* Completely replaces the document's pending changes with a new set.
|
|
167
|
-
*
|
|
168
|
-
* Discards all existing pending changes and replaces them with the provided array.
|
|
169
|
-
* Used when operational transformation rebases pending changes after receiving server updates.
|
|
170
|
-
* The new changes should have sequential revision numbers.
|
|
171
|
-
*
|
|
172
|
-
* @param docId Document identifier
|
|
173
|
-
* @param changes New complete set of pending changes
|
|
174
|
-
* @example
|
|
175
|
-
* // After rebasing pending changes due to server conflicts
|
|
176
|
-
* const rebasedChanges = transformPendingChanges(serverChanges, currentPending);
|
|
177
|
-
* await store.replacePendingChanges('my-document', rebasedChanges);
|
|
178
|
-
*/
|
|
179
|
-
replacePendingChanges(docId: string, changes: Change[]): Promise<void>;
|
|
180
112
|
/**
|
|
181
113
|
* Marks a document for collaborative deletion.
|
|
182
114
|
*
|
|
@@ -219,35 +151,6 @@ interface PatchesStore {
|
|
|
219
151
|
* // Store is no longer usable
|
|
220
152
|
*/
|
|
221
153
|
close(): Promise<void>;
|
|
222
|
-
/**
|
|
223
|
-
* Gets the last revision that was attempted to be submitted to the server.
|
|
224
|
-
*
|
|
225
|
-
* This bookmark is used by change collapsing to avoid modifying changes that
|
|
226
|
-
* may have been partially committed by the server. Returns undefined if no
|
|
227
|
-
* submission has been attempted yet.
|
|
228
|
-
*
|
|
229
|
-
* @param docId Document identifier
|
|
230
|
-
* @returns The last attempted submission revision, or undefined if none
|
|
231
|
-
* @example
|
|
232
|
-
* const lastAttempted = await store.getLastAttemptedSubmissionRev('my-document');
|
|
233
|
-
* // Use this to protect changes from collapsing
|
|
234
|
-
*/
|
|
235
|
-
getLastAttemptedSubmissionRev?(docId: string): Promise<number | undefined>;
|
|
236
|
-
/**
|
|
237
|
-
* Sets the last revision that was attempted to be submitted to the server.
|
|
238
|
-
*
|
|
239
|
-
* Called before sending changes to the server to mark them as "in flight".
|
|
240
|
-
* This prevents change collapsing from modifying these changes in case the
|
|
241
|
-
* server commits them but the client doesn't receive confirmation.
|
|
242
|
-
*
|
|
243
|
-
* @param docId Document identifier
|
|
244
|
-
* @param rev The revision being submitted
|
|
245
|
-
* @example
|
|
246
|
-
* // Before sending batch to server
|
|
247
|
-
* await store.setLastAttemptedSubmissionRev('my-document', lastChange.rev);
|
|
248
|
-
* await sendToServer(batch);
|
|
249
|
-
*/
|
|
250
|
-
setLastAttemptedSubmissionRev?(docId: string, rev: number): Promise<void>;
|
|
251
154
|
}
|
|
252
155
|
|
|
253
156
|
export type { PatchesStore, TrackedDoc };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { AlgorithmName } from './ClientAlgorithm.js';
|
|
2
|
+
import { Patches } from './Patches.js';
|
|
3
|
+
import { P as PatchesDocOptions } from '../BaseDoc-DkP3tUhT.js';
|
|
4
|
+
import '../json-patch/types.js';
|
|
5
|
+
import '../types.js';
|
|
6
|
+
import '../json-patch/JSONPatch.js';
|
|
7
|
+
import '@dabble/delta';
|
|
8
|
+
import './PatchesStore.js';
|
|
9
|
+
import '../event-signal.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Options for factory functions that create Patches instances.
|
|
13
|
+
*/
|
|
14
|
+
interface PatchesFactoryOptions {
|
|
15
|
+
/** Initial metadata to attach to changes from this client. */
|
|
16
|
+
metadata?: Record<string, any>;
|
|
17
|
+
/** Document-level options to pass to each PatchesDoc instance. */
|
|
18
|
+
docOptions?: PatchesDocOptions;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Options for factory functions with multiple algorithms.
|
|
22
|
+
*/
|
|
23
|
+
interface MultiAlgorithmFactoryOptions extends PatchesFactoryOptions {
|
|
24
|
+
/** Default algorithm to use when opening docs. */
|
|
25
|
+
defaultAlgorithm?: AlgorithmName;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Options for IndexedDB-based factory functions.
|
|
29
|
+
*/
|
|
30
|
+
interface IndexedDBFactoryOptions extends PatchesFactoryOptions {
|
|
31
|
+
/** Database name for IndexedDB storage. */
|
|
32
|
+
dbName: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Options for IndexedDB-based factory functions with multiple algorithms.
|
|
36
|
+
*/
|
|
37
|
+
interface MultiAlgorithmIndexedDBFactoryOptions extends MultiAlgorithmFactoryOptions {
|
|
38
|
+
/** Database name for IndexedDB storage. */
|
|
39
|
+
dbName: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Creates a Patches instance with OT algorithm and in-memory store.
|
|
43
|
+
* Useful for testing or when persistence isn't needed.
|
|
44
|
+
*/
|
|
45
|
+
declare function createOTPatches(options?: PatchesFactoryOptions): Patches;
|
|
46
|
+
/**
|
|
47
|
+
* Creates a Patches instance with OT algorithm and IndexedDB store.
|
|
48
|
+
* For persistent storage in browser environments.
|
|
49
|
+
*/
|
|
50
|
+
declare function createOTIndexedDBPatches(options: IndexedDBFactoryOptions): Patches;
|
|
51
|
+
/**
|
|
52
|
+
* Creates a Patches instance with LWW algorithm and in-memory store.
|
|
53
|
+
* Useful for testing or when persistence isn't needed.
|
|
54
|
+
*/
|
|
55
|
+
declare function createLWWPatches(options?: PatchesFactoryOptions): Patches;
|
|
56
|
+
/**
|
|
57
|
+
* Creates a Patches instance with LWW algorithm and IndexedDB store.
|
|
58
|
+
* For persistent storage in browser environments.
|
|
59
|
+
*/
|
|
60
|
+
declare function createLWWIndexedDBPatches(options: IndexedDBFactoryOptions): Patches;
|
|
61
|
+
/**
|
|
62
|
+
* Creates a Patches instance with both OT and LWW algorithms using in-memory stores.
|
|
63
|
+
* Useful for testing or applications that need both algorithms without persistence.
|
|
64
|
+
*/
|
|
65
|
+
declare function createAllPatches(options?: MultiAlgorithmFactoryOptions): Patches;
|
|
66
|
+
/**
|
|
67
|
+
* Creates a Patches instance with both OT and LWW algorithms using IndexedDB stores.
|
|
68
|
+
* For persistent storage in browser environments with support for both algorithms.
|
|
69
|
+
*/
|
|
70
|
+
declare function createAllIndexedDBPatches(options: MultiAlgorithmIndexedDBFactoryOptions): Patches;
|
|
71
|
+
|
|
72
|
+
export { type IndexedDBFactoryOptions, type MultiAlgorithmFactoryOptions, type MultiAlgorithmIndexedDBFactoryOptions, type PatchesFactoryOptions, createAllIndexedDBPatches, createAllPatches, createLWWIndexedDBPatches, createLWWPatches, createOTIndexedDBPatches, createOTPatches };
|