@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
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { PatchesState } from '
|
|
3
|
-
import '
|
|
1
|
+
import { OTStoreBackend } from '../../../server/types.js';
|
|
2
|
+
import { PatchesState } from '../../../types.js';
|
|
3
|
+
import '../../../json-patch/types.js';
|
|
4
|
+
import '../../../json-patch/JSONPatch.js';
|
|
4
5
|
import '@dabble/delta';
|
|
5
|
-
import '../../json-patch/types.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Gets the state at a specific revision.
|
|
@@ -10,6 +10,6 @@ import '../../json-patch/types.js';
|
|
|
10
10
|
* @param rev The revision number. If not provided, the latest state and its revision is returned.
|
|
11
11
|
* @returns The state at the specified revision *and* its revision number.
|
|
12
12
|
*/
|
|
13
|
-
declare function getStateAtRevision(store:
|
|
13
|
+
declare function getStateAtRevision(store: OTStoreBackend, docId: string, rev?: number): Promise<PatchesState>;
|
|
14
14
|
|
|
15
15
|
export { getStateAtRevision };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Change } from '
|
|
3
|
-
import '
|
|
1
|
+
import { OTStoreBackend } from '../../../server/types.js';
|
|
2
|
+
import { Change } from '../../../types.js';
|
|
3
|
+
import '../../../json-patch/types.js';
|
|
4
|
+
import '../../../json-patch/JSONPatch.js';
|
|
4
5
|
import '@dabble/delta';
|
|
5
|
-
import '../../json-patch/types.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Handles offline/large batch versioning logic for multi-batch uploads.
|
|
@@ -16,6 +16,6 @@ import '../../json-patch/types.js';
|
|
|
16
16
|
* @param maxStorageBytes If set, break collapsed changes that exceed this size
|
|
17
17
|
* @returns The changes (collapsed into one if divergent, unchanged if fast-forward)
|
|
18
18
|
*/
|
|
19
|
-
declare function handleOfflineSessionsAndBatches(store:
|
|
19
|
+
declare function handleOfflineSessionsAndBatches(store: OTStoreBackend, sessionTimeoutMillis: number, docId: string, changes: Change[], baseRev: number, batchId?: string, origin?: 'main' | 'offline-branch', isOffline?: boolean, maxStorageBytes?: number): Promise<Change[]>;
|
|
20
20
|
|
|
21
21
|
export { handleOfflineSessionsAndBatches };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import "
|
|
2
|
-
import { createVersionMetadata } from "
|
|
1
|
+
import "../../../chunk-IZ2YBCUP.js";
|
|
2
|
+
import { createVersionMetadata } from "../../../data/version.js";
|
|
3
3
|
import { applyChanges } from "../shared/applyChanges.js";
|
|
4
4
|
import { breakChanges } from "../shared/changeBatching.js";
|
|
5
5
|
import { getStateAtRevision } from "./getStateAtRevision.js";
|
|
@@ -26,7 +26,7 @@ async function handleOfflineSessionsAndBatches(store, sessionTimeoutMillis, docI
|
|
|
26
26
|
const sessionChanges = changes.slice(sessionStartIndex, i);
|
|
27
27
|
if (sessionChanges.length > 0) {
|
|
28
28
|
const isContinuation = !!lastVersion && sessionChanges[0].createdAt - lastVersion.endedAt <= sessionTimeoutMillis;
|
|
29
|
-
if (isContinuation) {
|
|
29
|
+
if (isContinuation && store.appendVersionChanges) {
|
|
30
30
|
const mergedState = applyChanges(offlineBaseState, sessionChanges);
|
|
31
31
|
const newEndedAt = sessionChanges[sessionChanges.length - 1].createdAt;
|
|
32
32
|
const newRev = sessionChanges[sessionChanges.length - 1].rev;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Change } from '
|
|
2
|
-
import '
|
|
1
|
+
import { Change } from '../../../types.js';
|
|
2
|
+
import '../../../json-patch/JSONPatch.js';
|
|
3
3
|
import '@dabble/delta';
|
|
4
|
-
import '
|
|
4
|
+
import '../../../json-patch/types.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Transforms incoming changes against committed changes that happened *after* the client's baseRev.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import "
|
|
2
|
-
import { applyPatch } from "
|
|
3
|
-
import { transformPatch } from "
|
|
1
|
+
import "../../../chunk-IZ2YBCUP.js";
|
|
2
|
+
import { applyPatch } from "../../../json-patch/applyPatch.js";
|
|
3
|
+
import { transformPatch } from "../../../json-patch/transformPatch.js";
|
|
4
4
|
function transformIncomingChanges(changes, stateAtBaseRev, committedChanges, currentRev, forceCommit = false) {
|
|
5
5
|
const committedOps = committedChanges.flatMap((c) => c.ops);
|
|
6
6
|
let state = stateAtBaseRev;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Change } from '
|
|
2
|
-
import '
|
|
1
|
+
import { Change } from '../../../types.js';
|
|
2
|
+
import '../../../json-patch/JSONPatch.js';
|
|
3
3
|
import '@dabble/delta';
|
|
4
|
-
import '
|
|
4
|
+
import '../../../json-patch/types.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Applies a sequence of changes to a state object.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import "
|
|
2
|
-
import { applyPatch } from "
|
|
1
|
+
import "../../../chunk-IZ2YBCUP.js";
|
|
2
|
+
import { applyPatch } from "../../../json-patch/applyPatch.js";
|
|
3
3
|
function applyChanges(state, changes) {
|
|
4
4
|
if (!changes.length) return state;
|
|
5
5
|
for (const change of changes) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Change } from '
|
|
2
|
-
import '
|
|
1
|
+
import { Change } from '../../../types.js';
|
|
2
|
+
import '../../../json-patch/JSONPatch.js';
|
|
3
3
|
import '@dabble/delta';
|
|
4
|
-
import '
|
|
4
|
+
import '../../../json-patch/types.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Function that calculates the storage size of data.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import "
|
|
1
|
+
import "../../../chunk-IZ2YBCUP.js";
|
|
2
2
|
import { createId } from "crypto-id";
|
|
3
|
-
import { createChange } from "
|
|
3
|
+
import { createChange } from "../../../data/change.js";
|
|
4
4
|
function getJSONByteSize(data) {
|
|
5
5
|
try {
|
|
6
6
|
const stringified = JSON.stringify(data);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Change } from '
|
|
2
|
-
import '
|
|
1
|
+
import { Change } from '../../../types.js';
|
|
2
|
+
import '../../../json-patch/JSONPatch.js';
|
|
3
3
|
import '@dabble/delta';
|
|
4
|
-
import '
|
|
4
|
+
import '../../../json-patch/types.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Rebases local changes against server changes using operational transformation.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import "
|
|
2
|
-
import { JSONPatch } from "
|
|
1
|
+
import "../../../chunk-IZ2YBCUP.js";
|
|
2
|
+
import { JSONPatch } from "../../../json-patch/JSONPatch.js";
|
|
3
3
|
function rebaseChanges(serverChanges, localChanges) {
|
|
4
4
|
if (!serverChanges.length || !localChanges.length) {
|
|
5
5
|
return localChanges;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import "../chunk-IZ2YBCUP.js";
|
|
2
|
+
import { signal } from "../event-signal.js";
|
|
3
|
+
import { createJSONPatch } from "../json-patch/createJSONPatch.js";
|
|
4
|
+
class BaseDoc {
|
|
5
|
+
_id;
|
|
6
|
+
_state;
|
|
7
|
+
_syncing = null;
|
|
8
|
+
/**
|
|
9
|
+
* Subscribe to be notified when the user makes local changes.
|
|
10
|
+
* Emits the JSON Patch ops captured from the change() call.
|
|
11
|
+
* The strategy handles packaging these into Changes.
|
|
12
|
+
*/
|
|
13
|
+
onChange = signal();
|
|
14
|
+
/** Subscribe to be notified whenever state changes from any source. */
|
|
15
|
+
onUpdate = signal();
|
|
16
|
+
/** Subscribe to be notified when syncing state changes. */
|
|
17
|
+
onSyncing = signal();
|
|
18
|
+
/**
|
|
19
|
+
* Creates an instance of BaseDoc.
|
|
20
|
+
* @param id The unique identifier for this document.
|
|
21
|
+
* @param initialState Optional initial state.
|
|
22
|
+
*/
|
|
23
|
+
constructor(id, initialState = {}) {
|
|
24
|
+
this._id = id;
|
|
25
|
+
this._state = initialState;
|
|
26
|
+
}
|
|
27
|
+
/** The unique identifier for this document. */
|
|
28
|
+
get id() {
|
|
29
|
+
return this._id;
|
|
30
|
+
}
|
|
31
|
+
/** Current local state (committed + pending merged). */
|
|
32
|
+
get state() {
|
|
33
|
+
return this._state;
|
|
34
|
+
}
|
|
35
|
+
/** Are we currently syncing this document? */
|
|
36
|
+
get syncing() {
|
|
37
|
+
return this._syncing;
|
|
38
|
+
}
|
|
39
|
+
/** Subscribe to be notified whenever the state changes (calls immediately with current state). */
|
|
40
|
+
subscribe(onUpdate) {
|
|
41
|
+
const unsub = this.onUpdate(onUpdate);
|
|
42
|
+
onUpdate(this._state);
|
|
43
|
+
return unsub;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Captures an update to the document, emitting JSON Patch ops via onChange.
|
|
47
|
+
* Does NOT apply locally - the strategy handles state updates via applyChanges.
|
|
48
|
+
* @param mutator Function that uses JSONPatch methods with type-safe paths.
|
|
49
|
+
*/
|
|
50
|
+
change(mutator) {
|
|
51
|
+
const patch = createJSONPatch(mutator);
|
|
52
|
+
if (patch.ops.length === 0) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
this.onChange.emit(patch.ops);
|
|
56
|
+
}
|
|
57
|
+
// --- Internal methods (not on PatchesDoc interface) ---
|
|
58
|
+
/**
|
|
59
|
+
* Updates the syncing state of the document.
|
|
60
|
+
* Called by PatchesSync - not part of the app-facing PatchesDoc interface.
|
|
61
|
+
* @param newSyncing The new syncing state.
|
|
62
|
+
*/
|
|
63
|
+
updateSyncing(newSyncing) {
|
|
64
|
+
this._syncing = newSyncing;
|
|
65
|
+
this.onSyncing.emit(newSyncing);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export {
|
|
69
|
+
BaseDoc
|
|
70
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { JSONPatchOp } from '../json-patch/types.js';
|
|
2
|
+
import { PatchesSnapshot, Change } from '../types.js';
|
|
3
|
+
import { a as PatchesDoc } from '../BaseDoc-DkP3tUhT.js';
|
|
4
|
+
import { PatchesStore, TrackedDoc } from './PatchesStore.js';
|
|
5
|
+
import '../json-patch/JSONPatch.js';
|
|
6
|
+
import '@dabble/delta';
|
|
7
|
+
import '../event-signal.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Algorithm interface for client-side sync algorithms (OT or LWW).
|
|
11
|
+
*
|
|
12
|
+
* The ClientAlgorithm owns its store and provides methods for:
|
|
13
|
+
* - Creating appropriate doc types
|
|
14
|
+
* - Packaging ops for persistence
|
|
15
|
+
* - Getting pending changes to send
|
|
16
|
+
* - Applying server changes
|
|
17
|
+
* - Confirming sent changes
|
|
18
|
+
*
|
|
19
|
+
* Patches owns docs and coordinates between doc/algorithm/sync.
|
|
20
|
+
*
|
|
21
|
+
* This interface enables Worker-Tab architectures where a TabAlgorithm
|
|
22
|
+
* can proxy to a WorkerAlgorithm that holds the real store and sync connection.
|
|
23
|
+
* Key design decisions for Worker-Tab support:
|
|
24
|
+
* - `handleDocChange` and `applyServerChanges` return `Change[]` for broadcast
|
|
25
|
+
* - `doc` parameter can be undefined (Worker has no docs)
|
|
26
|
+
*/
|
|
27
|
+
interface ClientAlgorithm {
|
|
28
|
+
/** Algorithm identifier: 'ot' or 'lww' */
|
|
29
|
+
readonly name: string;
|
|
30
|
+
/** Algorithm owns its store */
|
|
31
|
+
readonly store: PatchesStore;
|
|
32
|
+
/**
|
|
33
|
+
* Creates a doc instance appropriate for this algorithm.
|
|
34
|
+
* OT creates OTDoc, LWW creates LWWDoc.
|
|
35
|
+
*
|
|
36
|
+
* @param docId The unique identifier for the document.
|
|
37
|
+
* @param snapshot Optional snapshot to initialize the doc with.
|
|
38
|
+
*/
|
|
39
|
+
createDoc<T extends object>(docId: string, snapshot?: PatchesSnapshot<T>): PatchesDoc<T>;
|
|
40
|
+
/**
|
|
41
|
+
* Loads initial state for a document from the store.
|
|
42
|
+
* Returns undefined if the document doesn't exist.
|
|
43
|
+
*/
|
|
44
|
+
loadDoc(docId: string): Promise<PatchesSnapshot | undefined>;
|
|
45
|
+
/**
|
|
46
|
+
* Packages ops from doc.onChange into algorithm-specific format for persistence.
|
|
47
|
+
* - OT: Creates a Change with baseRev, stores in pending
|
|
48
|
+
* - LWW: Extracts fields with timestamps, merges into pendingFields
|
|
49
|
+
*
|
|
50
|
+
* Also updates the doc's state (if provided) after processing.
|
|
51
|
+
*
|
|
52
|
+
* @param docId Document identifier
|
|
53
|
+
* @param ops The JSON Patch ops to process
|
|
54
|
+
* @param doc The open doc instance, or undefined if in Worker (no docs)
|
|
55
|
+
* @param metadata Metadata to attach to the change
|
|
56
|
+
* @returns The changes created (for broadcast to other tabs)
|
|
57
|
+
*/
|
|
58
|
+
handleDocChange<T extends object>(docId: string, ops: JSONPatchOp[], doc: PatchesDoc<T> | undefined, metadata: Record<string, any>): Promise<Change[]>;
|
|
59
|
+
/**
|
|
60
|
+
* Gets pending data to send to the server.
|
|
61
|
+
* - OT: Returns all pending changes (may batch)
|
|
62
|
+
* - LWW: Creates single Change from pendingFields (or returns existing)
|
|
63
|
+
*
|
|
64
|
+
* Returns null if nothing to send.
|
|
65
|
+
*/
|
|
66
|
+
getPendingToSend(docId: string): Promise<Change[] | null>;
|
|
67
|
+
/**
|
|
68
|
+
* Applies server changes and updates the doc (if provided).
|
|
69
|
+
* - OT: Calls applyCommittedChanges algorithm, rebases pending
|
|
70
|
+
* - LWW: Applies with LWW merge, filters old pending fields
|
|
71
|
+
*
|
|
72
|
+
* @param docId Document identifier
|
|
73
|
+
* @param serverChanges Changes from the server
|
|
74
|
+
* @param doc The open doc instance, or undefined if in Worker (no docs)
|
|
75
|
+
* @returns Changes to broadcast to tabs (OT: serverChanges + rebasedPending, LWW: serverChanges)
|
|
76
|
+
*/
|
|
77
|
+
applyServerChanges<T extends object>(docId: string, serverChanges: Change[], doc: PatchesDoc<T> | undefined): Promise<Change[]>;
|
|
78
|
+
/**
|
|
79
|
+
* Confirms that changes were acknowledged by the server.
|
|
80
|
+
* Called after successful server commit.
|
|
81
|
+
*/
|
|
82
|
+
confirmSent(docId: string, changes: Change[]): Promise<void>;
|
|
83
|
+
/** Registers documents for local tracking. */
|
|
84
|
+
trackDocs(docIds: string[]): Promise<void>;
|
|
85
|
+
/** Removes documents from local tracking. */
|
|
86
|
+
untrackDocs(docIds: string[]): Promise<void>;
|
|
87
|
+
/** Lists all tracked documents. */
|
|
88
|
+
listDocs(includeDeleted?: boolean): Promise<TrackedDoc[]>;
|
|
89
|
+
/** Gets the committed revision for a document. */
|
|
90
|
+
getCommittedRev(docId: string): Promise<number>;
|
|
91
|
+
/** Marks a document for deletion. */
|
|
92
|
+
deleteDoc(docId: string): Promise<void>;
|
|
93
|
+
/** Confirms server-side deletion. */
|
|
94
|
+
confirmDeleteDoc(docId: string): Promise<void>;
|
|
95
|
+
/** Closes the algorithm and its store. */
|
|
96
|
+
close(): Promise<void>;
|
|
97
|
+
}
|
|
98
|
+
/** Available algorithm names */
|
|
99
|
+
type AlgorithmName = 'ot' | 'lww';
|
|
100
|
+
|
|
101
|
+
export type { AlgorithmName, ClientAlgorithm };
|
|
File without changes
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PatchesSnapshot, Change, PatchesState } from '../types.js';
|
|
2
|
-
import {
|
|
2
|
+
import { OTClientStore } from './OTClientStore.js';
|
|
3
|
+
import { TrackedDoc } from './PatchesStore.js';
|
|
3
4
|
import '../json-patch/JSONPatch.js';
|
|
4
5
|
import '@dabble/delta';
|
|
5
6
|
import '../json-patch/types.js';
|
|
@@ -9,23 +10,20 @@ import '../json-patch/types.js';
|
|
|
9
10
|
* All data lives in JS objects – nothing survives a page reload.
|
|
10
11
|
* Useful for unit tests or when you want the old 'stateless realtime' behaviour.
|
|
11
12
|
*/
|
|
12
|
-
declare class InMemoryStore implements
|
|
13
|
+
declare class InMemoryStore implements OTClientStore {
|
|
13
14
|
private docs;
|
|
14
15
|
getDoc(docId: string): Promise<PatchesSnapshot | undefined>;
|
|
15
16
|
getPendingChanges(docId: string): Promise<Change[]>;
|
|
16
|
-
|
|
17
|
+
getCommittedRev(docId: string): Promise<number>;
|
|
17
18
|
listDocs(includeDeleted?: boolean): Promise<TrackedDoc[]>;
|
|
18
19
|
saveDoc(docId: string, snapshot: PatchesState): Promise<void>;
|
|
19
20
|
savePendingChanges(docId: string, changes: Change[]): Promise<void>;
|
|
20
|
-
|
|
21
|
-
replacePendingChanges(docId: string, changes: Change[]): Promise<void>;
|
|
21
|
+
applyServerChanges(docId: string, serverChanges: Change[], rebasedPendingChanges: Change[]): Promise<void>;
|
|
22
22
|
trackDocs(docIds: string[]): Promise<void>;
|
|
23
23
|
untrackDocs(docIds: string[]): Promise<void>;
|
|
24
24
|
deleteDoc(docId: string): Promise<void>;
|
|
25
25
|
confirmDeleteDoc(docId: string): Promise<void>;
|
|
26
26
|
close(): Promise<void>;
|
|
27
|
-
getLastAttemptedSubmissionRev(docId: string): Promise<number | undefined>;
|
|
28
|
-
setLastAttemptedSubmissionRev(docId: string, rev: number): Promise<void>;
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
export { InMemoryStore };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import "../chunk-IZ2YBCUP.js";
|
|
2
|
-
import { applyChanges } from "../algorithms/shared/applyChanges.js";
|
|
3
|
-
import { transformPatch } from "../json-patch/transformPatch.js";
|
|
2
|
+
import { applyChanges } from "../algorithms/ot/shared/applyChanges.js";
|
|
4
3
|
class InMemoryStore {
|
|
5
4
|
docs = /* @__PURE__ */ new Map();
|
|
6
5
|
// ─── Reconstruction ────────────────────────────────────────────────────
|
|
@@ -9,14 +8,6 @@ class InMemoryStore {
|
|
|
9
8
|
if (!buf || buf.deleted) return void 0;
|
|
10
9
|
const state = applyChanges(buf.snapshot?.state ?? null, buf.committed);
|
|
11
10
|
const committedRev = buf.committed.at(-1)?.rev ?? buf.snapshot?.rev ?? 0;
|
|
12
|
-
if (buf.pending.length && buf.pending[0].baseRev < committedRev) {
|
|
13
|
-
const patch = buf.committed.filter((c) => c.rev > buf.pending[0].baseRev).flatMap((c) => c.ops);
|
|
14
|
-
const offset = committedRev - buf.pending[0].baseRev;
|
|
15
|
-
buf.pending.forEach((ch) => {
|
|
16
|
-
ch.rev += offset;
|
|
17
|
-
ch.ops = transformPatch(state, patch, ch.ops);
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
11
|
return {
|
|
21
12
|
state,
|
|
22
13
|
rev: committedRev,
|
|
@@ -26,12 +17,10 @@ class InMemoryStore {
|
|
|
26
17
|
async getPendingChanges(docId) {
|
|
27
18
|
return this.docs.get(docId)?.pending.slice() ?? [];
|
|
28
19
|
}
|
|
29
|
-
async
|
|
20
|
+
async getCommittedRev(docId) {
|
|
30
21
|
const buf = this.docs.get(docId);
|
|
31
|
-
if (!buf) return
|
|
32
|
-
|
|
33
|
-
const pendingRev = buf.pending.at(-1)?.rev ?? committedRev;
|
|
34
|
-
return [committedRev, pendingRev];
|
|
22
|
+
if (!buf) return 0;
|
|
23
|
+
return buf.committed.at(-1)?.rev ?? buf.snapshot?.rev ?? 0;
|
|
35
24
|
}
|
|
36
25
|
async listDocs(includeDeleted = false) {
|
|
37
26
|
return Array.from(this.docs.entries()).filter(([, b]) => includeDeleted || !b.deleted).map(([docId, buf]) => ({
|
|
@@ -49,19 +38,11 @@ class InMemoryStore {
|
|
|
49
38
|
if (!this.docs.has(docId)) this.docs.set(docId, buf);
|
|
50
39
|
buf.pending.push(...changes);
|
|
51
40
|
}
|
|
52
|
-
async
|
|
53
|
-
const buf = this.docs.get(docId) ?? { committed: [], pending: [] };
|
|
54
|
-
if (!this.docs.has(docId)) this.docs.set(docId, buf);
|
|
55
|
-
buf.committed.push(...changes);
|
|
56
|
-
if (sentPendingRange) {
|
|
57
|
-
const [min, max] = sentPendingRange;
|
|
58
|
-
buf.pending = buf.pending.filter((p) => p.rev < min || p.rev > max);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
async replacePendingChanges(docId, changes) {
|
|
41
|
+
async applyServerChanges(docId, serverChanges, rebasedPendingChanges) {
|
|
62
42
|
const buf = this.docs.get(docId) ?? { committed: [], pending: [] };
|
|
63
43
|
if (!this.docs.has(docId)) this.docs.set(docId, buf);
|
|
64
|
-
buf.
|
|
44
|
+
buf.committed.push(...serverChanges);
|
|
45
|
+
buf.pending = [...rebasedPendingChanges];
|
|
65
46
|
}
|
|
66
47
|
// ─── Metadata / Tracking ───────────────────────────────────────────
|
|
67
48
|
async trackDocs(docIds) {
|
|
@@ -91,16 +72,6 @@ class InMemoryStore {
|
|
|
91
72
|
async close() {
|
|
92
73
|
this.docs.clear();
|
|
93
74
|
}
|
|
94
|
-
// ─── Submission Bookmark ───────────────────────────────────────────────
|
|
95
|
-
async getLastAttemptedSubmissionRev(docId) {
|
|
96
|
-
return this.docs.get(docId)?.lastAttemptedSubmissionRev;
|
|
97
|
-
}
|
|
98
|
-
async setLastAttemptedSubmissionRev(docId, rev) {
|
|
99
|
-
const buf = this.docs.get(docId);
|
|
100
|
-
if (buf) {
|
|
101
|
-
buf.lastAttemptedSubmissionRev = rev;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
75
|
}
|
|
105
76
|
export {
|
|
106
77
|
InMemoryStore
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PatchesSnapshot, PatchesState
|
|
1
|
+
import { PatchesSnapshot, PatchesState } from '../types.js';
|
|
2
2
|
import { Deferred } from '../utils/deferred.js';
|
|
3
3
|
import { PatchesStore, TrackedDoc } from './PatchesStore.js';
|
|
4
4
|
import '../json-patch/JSONPatch.js';
|
|
@@ -6,22 +6,36 @@ import '@dabble/delta';
|
|
|
6
6
|
import '../json-patch/types.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
* - snapshots<{ docId: string; rev: number; state: any }> (primary key: docId)
|
|
11
|
-
* - committedChanges<Change & { docId: string; }> (primary key: [docId, rev])
|
|
12
|
-
* - pendingChanges<Change & { docId: string; }> (primary key: [docId, rev])
|
|
13
|
-
* - docs<{ docId: string; committedRev: number; lastAttemptedSubmissionRev?: number; deleted?: boolean }> (primary key: docId)
|
|
9
|
+
* Abstract base class for IndexedDB-based stores implementing PatchesStore.
|
|
14
10
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
11
|
+
* Provides common functionality for IndexedDB operations:
|
|
12
|
+
* - Database lifecycle management (open, close, delete)
|
|
13
|
+
* - Transaction helpers
|
|
14
|
+
* - Document tracking (listDocs, trackDocs, untrackDocs)
|
|
15
|
+
* - Basic document operations (deleteDoc, confirmDeleteDoc)
|
|
16
|
+
* - Revision tracking
|
|
17
|
+
*
|
|
18
|
+
* Subclasses must implement strategy-specific methods for document
|
|
19
|
+
* state management and change handling.
|
|
19
20
|
*/
|
|
20
|
-
declare class IndexedDBStore implements PatchesStore {
|
|
21
|
+
declare abstract class IndexedDBStore implements PatchesStore {
|
|
21
22
|
protected db: IDBDatabase | null;
|
|
22
23
|
protected dbName?: string;
|
|
23
24
|
protected dbPromise: Deferred<IDBDatabase>;
|
|
24
25
|
constructor(dbName?: string);
|
|
26
|
+
/**
|
|
27
|
+
* Returns the database version for this store.
|
|
28
|
+
* Subclasses should return their specific version number.
|
|
29
|
+
*/
|
|
30
|
+
protected abstract getDBVersion(): number;
|
|
31
|
+
/**
|
|
32
|
+
* Hook for subclasses to create strategy-specific object stores.
|
|
33
|
+
* Called during database upgrade.
|
|
34
|
+
*
|
|
35
|
+
* @param db - The IDBDatabase instance
|
|
36
|
+
* @param oldVersion - The previous database version
|
|
37
|
+
*/
|
|
38
|
+
protected abstract onUpgrade(db: IDBDatabase, oldVersion: number): void;
|
|
25
39
|
protected initDB(): Promise<void>;
|
|
26
40
|
protected getDB(): Promise<IDBDatabase>;
|
|
27
41
|
/**
|
|
@@ -38,59 +52,24 @@ declare class IndexedDBStore implements PatchesStore {
|
|
|
38
52
|
deleteDB(): Promise<void>;
|
|
39
53
|
protected transaction(storeNames: string[], mode: IDBTransactionMode): Promise<[IDBTransactionWrapper, ...IDBStoreWrapper[]]>;
|
|
40
54
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* 5. return { state, rev, changes: pending }
|
|
55
|
+
* Retrieves the current document snapshot from storage.
|
|
56
|
+
* Implementation varies by sync strategy (OT vs LWW).
|
|
57
|
+
*/
|
|
58
|
+
abstract getDoc(docId: string): Promise<PatchesSnapshot | undefined>;
|
|
59
|
+
/**
|
|
60
|
+
* Saves the current document state to persistent storage.
|
|
61
|
+
* Implementation varies by sync strategy.
|
|
49
62
|
*/
|
|
50
|
-
|
|
63
|
+
abstract saveDoc(docId: string, docState: PatchesState): Promise<void>;
|
|
51
64
|
/**
|
|
52
65
|
* Completely remove all data for this docId and mark it as deleted (tombstone).
|
|
53
66
|
*/
|
|
54
|
-
deleteDoc(docId: string): Promise<void>;
|
|
67
|
+
abstract deleteDoc(docId: string): Promise<void>;
|
|
55
68
|
/**
|
|
56
69
|
* Confirm the deletion of a document.
|
|
57
70
|
* @param docId - The ID of the document to delete.
|
|
58
71
|
*/
|
|
59
72
|
confirmDeleteDoc(docId: string): Promise<void>;
|
|
60
|
-
/**
|
|
61
|
-
* Save a document's state to the store.
|
|
62
|
-
* @param docId - The ID of the document to save.
|
|
63
|
-
* @param docState - The state of the document to save.
|
|
64
|
-
*/
|
|
65
|
-
saveDoc(docId: string, docState: PatchesState): Promise<void>;
|
|
66
|
-
/**
|
|
67
|
-
* Append an array of local changes to the pending queue.
|
|
68
|
-
* Called *before* you attempt to send them to the server.
|
|
69
|
-
*/
|
|
70
|
-
savePendingChanges(docId: string, changes: Change[]): Promise<void>;
|
|
71
|
-
/**
|
|
72
|
-
* Read back all pending changes for this docId (in order).
|
|
73
|
-
* @param docId - The ID of the document to get the pending changes for.
|
|
74
|
-
* @returns The pending changes.
|
|
75
|
-
*/
|
|
76
|
-
getPendingChanges(docId: string): Promise<Change[]>;
|
|
77
|
-
/**
|
|
78
|
-
* Replace all pending changes for a document (used after rebasing).
|
|
79
|
-
* @param docId - The ID of the document to replace the pending changes for.
|
|
80
|
-
* @param changes - The changes to replace the pending changes with.
|
|
81
|
-
*/
|
|
82
|
-
replacePendingChanges(docId: string, changes: Change[]): Promise<void>;
|
|
83
|
-
/**
|
|
84
|
-
* Store server‐confirmed changes. Will:
|
|
85
|
-
* - persist them in the committedChanges store
|
|
86
|
-
* - remove any pending changes whose rev falls within `sentPendingRange`
|
|
87
|
-
* - optionally compact a new snapshot after N changes (hidden internally)
|
|
88
|
-
* @param docId - The ID of the document to save the changes for
|
|
89
|
-
* @param changes - The changes to save
|
|
90
|
-
* @param sentPendingRange - The range of pending changes to remove, *must* be provided after receiving the changes
|
|
91
|
-
* from the server in response to a patchesDoc request.
|
|
92
|
-
*/
|
|
93
|
-
saveCommittedChanges(docId: string, changes: Change[], sentPendingRange?: [number, number]): Promise<void>;
|
|
94
73
|
/**
|
|
95
74
|
* List all documents in the store.
|
|
96
75
|
* @param includeDeleted - Whether to include deleted documents.
|
|
@@ -106,26 +85,13 @@ declare class IndexedDBStore implements PatchesStore {
|
|
|
106
85
|
* Untrack a document.
|
|
107
86
|
* @param docIds - The IDs of the documents to untrack.
|
|
108
87
|
*/
|
|
109
|
-
untrackDocs(docIds: string[]): Promise<void>;
|
|
110
|
-
/**
|
|
111
|
-
* Tell me the last committed revision you have *and* the highest
|
|
112
|
-
* rev of any change. Use these to drive:
|
|
113
|
-
* - fetch changes: api.getChangesSince(docId, committedRev)
|
|
114
|
-
* - build new patch: newChange.rev = pendingRev; baseRev = committedRev
|
|
115
|
-
*/
|
|
116
|
-
getLastRevs(docId: string): Promise<[number, number]>;
|
|
117
|
-
/**
|
|
118
|
-
* Gets the last revision that was attempted to be submitted to the server.
|
|
119
|
-
* @param docId - The ID of the document.
|
|
120
|
-
* @returns The last attempted submission revision, or undefined if none.
|
|
121
|
-
*/
|
|
122
|
-
getLastAttemptedSubmissionRev(docId: string): Promise<number | undefined>;
|
|
88
|
+
abstract untrackDocs(docIds: string[]): Promise<void>;
|
|
123
89
|
/**
|
|
124
|
-
*
|
|
90
|
+
* Returns the last committed revision for a document.
|
|
125
91
|
* @param docId - The ID of the document.
|
|
126
|
-
* @
|
|
92
|
+
* @returns The last committed revision, or 0 if not found.
|
|
127
93
|
*/
|
|
128
|
-
|
|
94
|
+
getCommittedRev(docId: string): Promise<number>;
|
|
129
95
|
}
|
|
130
96
|
declare class IDBTransactionWrapper {
|
|
131
97
|
protected tx: IDBTransaction;
|
|
@@ -148,4 +114,4 @@ declare class IDBStoreWrapper {
|
|
|
148
114
|
getLastFromCursor<T>(lower?: any, upper?: any): Promise<T | undefined>;
|
|
149
115
|
}
|
|
150
116
|
|
|
151
|
-
export { IndexedDBStore };
|
|
117
|
+
export { IDBStoreWrapper, IDBTransactionWrapper, IndexedDBStore };
|