@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,14 +1,24 @@
|
|
|
1
1
|
import "../chunk-IZ2YBCUP.js";
|
|
2
|
-
import {
|
|
3
|
-
import { breakChanges } from "../algorithms/shared/changeBatching.js";
|
|
2
|
+
import { getStateAtRevision } from "../algorithms/ot/server/getStateAtRevision.js";
|
|
3
|
+
import { breakChanges } from "../algorithms/ot/shared/changeBatching.js";
|
|
4
4
|
import { createChange } from "../data/change.js";
|
|
5
5
|
import { createVersionMetadata } from "../data/version.js";
|
|
6
|
-
|
|
6
|
+
import {
|
|
7
|
+
assertBranchMetadata,
|
|
8
|
+
assertBranchOpenForMerge,
|
|
9
|
+
assertNotABranch,
|
|
10
|
+
branchManagerApi,
|
|
11
|
+
createBranchRecord,
|
|
12
|
+
generateBranchId,
|
|
13
|
+
wrapMergeCommit
|
|
14
|
+
} from "./branchUtils.js";
|
|
15
|
+
class OTBranchManager {
|
|
7
16
|
constructor(store, patchesServer, maxPayloadBytes) {
|
|
8
17
|
this.store = store;
|
|
9
18
|
this.patchesServer = patchesServer;
|
|
10
19
|
this.maxPayloadBytes = maxPayloadBytes;
|
|
11
20
|
}
|
|
21
|
+
static api = branchManagerApi;
|
|
12
22
|
/**
|
|
13
23
|
* Lists all open branches for a document.
|
|
14
24
|
* @param docId - The ID of the document.
|
|
@@ -26,12 +36,9 @@ class PatchesBranchManager {
|
|
|
26
36
|
* @returns The ID of the new branch document.
|
|
27
37
|
*/
|
|
28
38
|
async createBranch(docId, rev, metadata) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
const stateAtRev = (await this.patchesServer.getStateAtRevision(docId, rev)).state;
|
|
34
|
-
const branchDocId = this.store.createBranchId ? await Promise.resolve(this.store.createBranchId(docId)) : createId(22);
|
|
39
|
+
await assertNotABranch(this.store, docId);
|
|
40
|
+
const { state: stateAtRev } = await getStateAtRevision(this.store, docId, rev);
|
|
41
|
+
const branchDocId = await generateBranchId(this.store, docId);
|
|
35
42
|
const now = Date.now();
|
|
36
43
|
const initialVersionMetadata = createVersionMetadata({
|
|
37
44
|
origin: "main",
|
|
@@ -45,14 +52,7 @@ class PatchesBranchManager {
|
|
|
45
52
|
branchName: metadata?.name
|
|
46
53
|
});
|
|
47
54
|
await this.store.createVersion(branchDocId, initialVersionMetadata, stateAtRev, []);
|
|
48
|
-
const branch =
|
|
49
|
-
...metadata,
|
|
50
|
-
id: branchDocId,
|
|
51
|
-
docId,
|
|
52
|
-
branchedAtRev: rev,
|
|
53
|
-
createdAt: now,
|
|
54
|
-
status: "open"
|
|
55
|
-
};
|
|
55
|
+
const branch = createBranchRecord(branchDocId, docId, rev, metadata);
|
|
56
56
|
await this.store.createBranch(branch);
|
|
57
57
|
return branchDocId;
|
|
58
58
|
}
|
|
@@ -81,12 +81,7 @@ class PatchesBranchManager {
|
|
|
81
81
|
*/
|
|
82
82
|
async mergeBranch(branchId) {
|
|
83
83
|
const branch = await this.store.loadBranch(branchId);
|
|
84
|
-
|
|
85
|
-
throw new Error(`Branch with ID ${branchId} not found.`);
|
|
86
|
-
}
|
|
87
|
-
if (branch.status !== "open") {
|
|
88
|
-
throw new Error(`Branch ${branchId} is not open (status: ${branch.status}). Cannot merge.`);
|
|
89
|
-
}
|
|
84
|
+
assertBranchOpenForMerge(branch, branchId);
|
|
90
85
|
const sourceDocId = branch.docId;
|
|
91
86
|
const branchStartRevOnSource = branch.branchedAtRev;
|
|
92
87
|
const branchChanges = await this.store.listChanges(branchId, {});
|
|
@@ -113,12 +108,11 @@ class PatchesBranchManager {
|
|
|
113
108
|
parentId: lastVersionId
|
|
114
109
|
});
|
|
115
110
|
const state = await this.store.loadVersionState(branchId, v.id);
|
|
116
|
-
const changes = await this.store.loadVersionChanges(branchId, v.id);
|
|
111
|
+
const changes = await this.store.loadVersionChanges?.(branchId, v.id);
|
|
117
112
|
await this.store.createVersion(sourceDocId, newVersionMetadata, state, changes);
|
|
118
113
|
lastVersionId = newVersionMetadata.id;
|
|
119
114
|
}
|
|
120
|
-
|
|
121
|
-
try {
|
|
115
|
+
const committedMergeChanges = await wrapMergeCommit(branchId, sourceDocId, async () => {
|
|
122
116
|
if (canFastForward) {
|
|
123
117
|
const adjustedChanges = branchChanges.map((c) => ({
|
|
124
118
|
...c,
|
|
@@ -126,7 +120,7 @@ class PatchesBranchManager {
|
|
|
126
120
|
rev: void 0
|
|
127
121
|
// Let commitChanges assign sequential revs
|
|
128
122
|
}));
|
|
129
|
-
|
|
123
|
+
return this.patchesServer.commitChanges(sourceDocId, adjustedChanges);
|
|
130
124
|
} else {
|
|
131
125
|
const rev = branchStartRevOnSource + branchChanges.length;
|
|
132
126
|
const flattenedChange = createChange(
|
|
@@ -138,26 +132,17 @@ class PatchesBranchManager {
|
|
|
138
132
|
if (this.maxPayloadBytes) {
|
|
139
133
|
changesToCommit = breakChanges(changesToCommit, this.maxPayloadBytes);
|
|
140
134
|
}
|
|
141
|
-
|
|
135
|
+
return this.patchesServer.commitChanges(sourceDocId, changesToCommit);
|
|
142
136
|
}
|
|
143
|
-
}
|
|
144
|
-
console.error(`Failed to merge branch ${branchId} into ${sourceDocId}:`, error);
|
|
145
|
-
throw new Error(`Merge failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
146
|
-
}
|
|
137
|
+
});
|
|
147
138
|
await this.closeBranch(branchId, "merged");
|
|
148
139
|
return committedMergeChanges;
|
|
149
140
|
}
|
|
150
141
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (!metadata) return;
|
|
154
|
-
for (const key in metadata) {
|
|
155
|
-
if (nonModifiableMetadataFields.has(key)) {
|
|
156
|
-
throw new Error(`Cannot modify branch field ${key}`);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
142
|
+
import { assertBranchMetadata as assertBranchMetadata2 } from "./branchUtils.js";
|
|
143
|
+
const PatchesBranchManager = OTBranchManager;
|
|
160
144
|
export {
|
|
145
|
+
OTBranchManager,
|
|
161
146
|
PatchesBranchManager,
|
|
162
|
-
assertBranchMetadata
|
|
147
|
+
assertBranchMetadata2 as assertBranchMetadata
|
|
163
148
|
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Signal } from '../event-signal.js';
|
|
2
|
+
import { PatchesServer } from './PatchesServer.js';
|
|
3
|
+
import { OTStoreBackend } from './types.js';
|
|
4
|
+
import { Change, DeleteDocOptions, PatchesState, ChangeInput, CommitChangesOptions, ChangeMutator, EditableVersionMetadata } from '../types.js';
|
|
5
|
+
import { ApiDefinition } from '../net/protocol/JSONRPCServer.js';
|
|
6
|
+
import '../json-patch/JSONPatch.js';
|
|
7
|
+
import '@dabble/delta';
|
|
8
|
+
import '../json-patch/types.js';
|
|
9
|
+
import '../net/websocket/AuthorizationProvider.js';
|
|
10
|
+
import '../net/protocol/types.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Configuration options for the OTServer.
|
|
14
|
+
*/
|
|
15
|
+
interface OTServerOptions {
|
|
16
|
+
/**
|
|
17
|
+
* The maximum time difference in minutes between consecutive changes
|
|
18
|
+
* to be considered part of the same editing session for versioning.
|
|
19
|
+
* Defaults to 30 minutes.
|
|
20
|
+
*/
|
|
21
|
+
sessionTimeoutMinutes?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Maximum size in bytes for a single change's storage representation.
|
|
24
|
+
* Useful for databases with row size limits.
|
|
25
|
+
*/
|
|
26
|
+
maxStorageBytes?: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Handles the server-side Operational Transformation (OT) logic,
|
|
30
|
+
* coordinating batches of changes, managing versioning based on sessions (including offline),
|
|
31
|
+
* and persisting data using a backend store.
|
|
32
|
+
*
|
|
33
|
+
* For compression support, wrap your store with CompressedStoreBackend before passing it:
|
|
34
|
+
* @example
|
|
35
|
+
* import { OTServer, CompressedStoreBackend } from '@dabble/patches/server';
|
|
36
|
+
* import { base64Compressor } from '@dabble/patches/compression';
|
|
37
|
+
*
|
|
38
|
+
* const compressedStore = new CompressedStoreBackend(store, base64Compressor);
|
|
39
|
+
* const server = new OTServer(compressedStore);
|
|
40
|
+
*/
|
|
41
|
+
declare class OTServer implements PatchesServer {
|
|
42
|
+
/**
|
|
43
|
+
* Static API definition for use with JSONRPCServer.register().
|
|
44
|
+
* Maps method names to required access levels.
|
|
45
|
+
*/
|
|
46
|
+
static api: ApiDefinition;
|
|
47
|
+
private readonly sessionTimeoutMillis;
|
|
48
|
+
private readonly maxStorageBytes?;
|
|
49
|
+
readonly store: OTStoreBackend;
|
|
50
|
+
/** Notifies listeners whenever a batch of changes is *successfully* committed. */
|
|
51
|
+
readonly onChangesCommitted: Signal<(docId: string, changes: Change[], originClientId?: string) => void>;
|
|
52
|
+
/** Notifies listeners when a document is deleted. */
|
|
53
|
+
readonly onDocDeleted: Signal<(docId: string, options?: DeleteDocOptions, originClientId?: string) => void>;
|
|
54
|
+
constructor(store: OTStoreBackend, options?: OTServerOptions);
|
|
55
|
+
/**
|
|
56
|
+
* Get the current state of a document.
|
|
57
|
+
* @param docId - The ID of the document.
|
|
58
|
+
* @returns The current state of the document.
|
|
59
|
+
*/
|
|
60
|
+
getDoc(docId: string): Promise<PatchesState>;
|
|
61
|
+
/**
|
|
62
|
+
* Get changes that occurred after a specific revision.
|
|
63
|
+
* @param docId - The ID of the document.
|
|
64
|
+
* @param rev - The revision number.
|
|
65
|
+
* @returns The changes that occurred after the specified revision.
|
|
66
|
+
*/
|
|
67
|
+
getChangesSince(docId: string, rev: number): Promise<Change[]>;
|
|
68
|
+
/**
|
|
69
|
+
* Commits a set of changes to a document, applying operational transformation as needed.
|
|
70
|
+
*
|
|
71
|
+
* Returns all changes the client needs to apply: both catchup changes (from other
|
|
72
|
+
* clients) and the client's own transformed changes. Only the new changes are
|
|
73
|
+
* broadcast to other clients.
|
|
74
|
+
*
|
|
75
|
+
* @param docId - The ID of the document.
|
|
76
|
+
* @param changes - The changes to commit.
|
|
77
|
+
* @param options - Optional commit settings (e.g., forceCommit for migrations).
|
|
78
|
+
* @returns Combined array of catchup changes followed by the client's committed changes.
|
|
79
|
+
*/
|
|
80
|
+
commitChanges(docId: string, changes: ChangeInput[], options?: CommitChangesOptions): Promise<Change[]>;
|
|
81
|
+
/**
|
|
82
|
+
* Make a server-side change to a document.
|
|
83
|
+
* @param mutator
|
|
84
|
+
* @returns
|
|
85
|
+
*/
|
|
86
|
+
change<T = Record<string, any>>(docId: string, mutator: ChangeMutator<T>, metadata?: Record<string, any>): Promise<Change | null>;
|
|
87
|
+
/**
|
|
88
|
+
* Deletes a document.
|
|
89
|
+
* @param docId The document ID.
|
|
90
|
+
* @param options - Optional deletion settings (e.g., skipTombstone for testing).
|
|
91
|
+
*/
|
|
92
|
+
deleteDoc(docId: string, options?: DeleteDocOptions): Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* Removes the tombstone for a deleted document, allowing it to be recreated.
|
|
95
|
+
* @param docId The document ID.
|
|
96
|
+
* @returns True if tombstone was found and removed, false if no tombstone existed.
|
|
97
|
+
*/
|
|
98
|
+
undeleteDoc(docId: string): Promise<boolean>;
|
|
99
|
+
/**
|
|
100
|
+
* Captures the current state of a document as a new version.
|
|
101
|
+
* @param docId The document ID.
|
|
102
|
+
* @param metadata Optional metadata for the version.
|
|
103
|
+
* @returns The ID of the created version.
|
|
104
|
+
*/
|
|
105
|
+
captureCurrentVersion(docId: string, metadata?: EditableVersionMetadata): Promise<string | null>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export { CommitChangesOptions, OTServer, type OTServerOptions };
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import "../chunk-IZ2YBCUP.js";
|
|
2
|
+
import { commitChanges } from "../algorithms/ot/server/commitChanges.js";
|
|
3
|
+
import { createVersion } from "../algorithms/ot/server/createVersion.js";
|
|
4
|
+
import { getSnapshotAtRevision } from "../algorithms/ot/server/getSnapshotAtRevision.js";
|
|
5
|
+
import { getStateAtRevision } from "../algorithms/ot/server/getStateAtRevision.js";
|
|
6
|
+
import { applyChanges } from "../algorithms/ot/shared/applyChanges.js";
|
|
7
|
+
import { createChange } from "../data/change.js";
|
|
8
|
+
import { signal } from "../event-signal.js";
|
|
9
|
+
import { createJSONPatch } from "../json-patch/createJSONPatch.js";
|
|
10
|
+
import { getClientId } from "../net/serverContext.js";
|
|
11
|
+
import { createTombstoneIfSupported, removeTombstoneIfExists } from "./tombstone.js";
|
|
12
|
+
import { assertVersionMetadata } from "./utils.js";
|
|
13
|
+
class OTServer {
|
|
14
|
+
/**
|
|
15
|
+
* Static API definition for use with JSONRPCServer.register().
|
|
16
|
+
* Maps method names to required access levels.
|
|
17
|
+
*/
|
|
18
|
+
static api = {
|
|
19
|
+
getDoc: "read",
|
|
20
|
+
getChangesSince: "read",
|
|
21
|
+
commitChanges: "write",
|
|
22
|
+
deleteDoc: "write",
|
|
23
|
+
undeleteDoc: "write"
|
|
24
|
+
};
|
|
25
|
+
sessionTimeoutMillis;
|
|
26
|
+
maxStorageBytes;
|
|
27
|
+
store;
|
|
28
|
+
/** Notifies listeners whenever a batch of changes is *successfully* committed. */
|
|
29
|
+
onChangesCommitted = signal();
|
|
30
|
+
/** Notifies listeners when a document is deleted. */
|
|
31
|
+
onDocDeleted = signal();
|
|
32
|
+
constructor(store, options = {}) {
|
|
33
|
+
this.sessionTimeoutMillis = (options.sessionTimeoutMinutes ?? 30) * 60 * 1e3;
|
|
34
|
+
this.maxStorageBytes = options.maxStorageBytes;
|
|
35
|
+
this.store = store;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Get the current state of a document.
|
|
39
|
+
* @param docId - The ID of the document.
|
|
40
|
+
* @returns The current state of the document.
|
|
41
|
+
*/
|
|
42
|
+
async getDoc(docId) {
|
|
43
|
+
return getStateAtRevision(this.store, docId);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get changes that occurred after a specific revision.
|
|
47
|
+
* @param docId - The ID of the document.
|
|
48
|
+
* @param rev - The revision number.
|
|
49
|
+
* @returns The changes that occurred after the specified revision.
|
|
50
|
+
*/
|
|
51
|
+
getChangesSince(docId, rev) {
|
|
52
|
+
return this.store.listChanges(docId, { startAfter: rev });
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Commits a set of changes to a document, applying operational transformation as needed.
|
|
56
|
+
*
|
|
57
|
+
* Returns all changes the client needs to apply: both catchup changes (from other
|
|
58
|
+
* clients) and the client's own transformed changes. Only the new changes are
|
|
59
|
+
* broadcast to other clients.
|
|
60
|
+
*
|
|
61
|
+
* @param docId - The ID of the document.
|
|
62
|
+
* @param changes - The changes to commit.
|
|
63
|
+
* @param options - Optional commit settings (e.g., forceCommit for migrations).
|
|
64
|
+
* @returns Combined array of catchup changes followed by the client's committed changes.
|
|
65
|
+
*/
|
|
66
|
+
async commitChanges(docId, changes, options) {
|
|
67
|
+
const { catchupChanges, newChanges } = await commitChanges(
|
|
68
|
+
this.store,
|
|
69
|
+
docId,
|
|
70
|
+
changes,
|
|
71
|
+
this.sessionTimeoutMillis,
|
|
72
|
+
options,
|
|
73
|
+
this.maxStorageBytes
|
|
74
|
+
);
|
|
75
|
+
if (newChanges.length > 0) {
|
|
76
|
+
try {
|
|
77
|
+
await this.onChangesCommitted.emit(docId, newChanges, getClientId());
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error(`Failed to notify clients about committed changes for doc ${docId}:`, error);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return [...catchupChanges, ...newChanges];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Make a server-side change to a document.
|
|
86
|
+
* @param mutator
|
|
87
|
+
* @returns
|
|
88
|
+
*/
|
|
89
|
+
async change(docId, mutator, metadata) {
|
|
90
|
+
const { state, rev } = await this.getDoc(docId);
|
|
91
|
+
const patch = createJSONPatch(mutator);
|
|
92
|
+
if (patch.ops.length === 0) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const change = createChange(rev, rev + 1, patch.ops, metadata);
|
|
96
|
+
patch.apply(state);
|
|
97
|
+
await this.commitChanges(docId, [change]);
|
|
98
|
+
return change;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Deletes a document.
|
|
102
|
+
* @param docId The document ID.
|
|
103
|
+
* @param options - Optional deletion settings (e.g., skipTombstone for testing).
|
|
104
|
+
*/
|
|
105
|
+
async deleteDoc(docId, options) {
|
|
106
|
+
const clientId = getClientId();
|
|
107
|
+
const { rev } = await this.getDoc(docId);
|
|
108
|
+
await createTombstoneIfSupported(this.store, docId, rev, clientId, options?.skipTombstone);
|
|
109
|
+
await this.store.deleteDoc(docId);
|
|
110
|
+
await this.onDocDeleted.emit(docId, options, clientId);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Removes the tombstone for a deleted document, allowing it to be recreated.
|
|
114
|
+
* @param docId The document ID.
|
|
115
|
+
* @returns True if tombstone was found and removed, false if no tombstone existed.
|
|
116
|
+
*/
|
|
117
|
+
async undeleteDoc(docId) {
|
|
118
|
+
return removeTombstoneIfExists(this.store, docId);
|
|
119
|
+
}
|
|
120
|
+
// === Version Operations ===
|
|
121
|
+
/**
|
|
122
|
+
* Captures the current state of a document as a new version.
|
|
123
|
+
* @param docId The document ID.
|
|
124
|
+
* @param metadata Optional metadata for the version.
|
|
125
|
+
* @returns The ID of the created version.
|
|
126
|
+
*/
|
|
127
|
+
async captureCurrentVersion(docId, metadata) {
|
|
128
|
+
assertVersionMetadata(metadata);
|
|
129
|
+
const { state: initialState, changes } = await getSnapshotAtRevision(this.store, docId);
|
|
130
|
+
let state = initialState;
|
|
131
|
+
state = applyChanges(state, changes);
|
|
132
|
+
const version = await createVersion(this.store, docId, state, changes, metadata);
|
|
133
|
+
if (!version) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
return version.id;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
export {
|
|
140
|
+
OTServer
|
|
141
|
+
};
|
|
@@ -1,21 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ApiDefinition } from '../net/protocol/JSONRPCServer.js';
|
|
2
|
+
import { ListVersionsOptions, VersionMetadata, EditableVersionMetadata, Change } from '../types.js';
|
|
2
3
|
import { PatchesServer } from './PatchesServer.js';
|
|
4
|
+
import { VersioningStoreBackend } from './types.js';
|
|
5
|
+
import '../event-signal.js';
|
|
6
|
+
import '../net/websocket/AuthorizationProvider.js';
|
|
7
|
+
import '../json-patch/types.js';
|
|
3
8
|
import '../json-patch/JSONPatch.js';
|
|
4
9
|
import '@dabble/delta';
|
|
5
|
-
import '../
|
|
6
|
-
import '../event-signal.js';
|
|
7
|
-
import './types.js';
|
|
8
|
-
import '../compression/index.js';
|
|
9
|
-
import '../algorithms/shared/lz.js';
|
|
10
|
+
import '../net/protocol/types.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Helps retrieve historical information (versions, changes) for a document
|
|
13
|
-
* using the
|
|
14
|
+
* using the versioning model based on IDs and metadata.
|
|
15
|
+
*
|
|
16
|
+
* Works with any PatchesServer that implements captureCurrentVersion
|
|
17
|
+
* (both OTServer and LWWServer with VersioningStoreBackend).
|
|
14
18
|
*/
|
|
15
19
|
declare class PatchesHistoryManager {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
protected readonly patches: PatchesServer;
|
|
21
|
+
static api: ApiDefinition;
|
|
22
|
+
protected readonly store: VersioningStoreBackend;
|
|
23
|
+
constructor(patches: PatchesServer, store: VersioningStoreBackend);
|
|
19
24
|
/**
|
|
20
25
|
* Lists version metadata for the document, supporting various filters.
|
|
21
26
|
* @param docId - The ID of the document.
|
|
@@ -55,13 +60,13 @@ declare class PatchesHistoryManager {
|
|
|
55
60
|
*/
|
|
56
61
|
getChangesForVersion(docId: string, versionId: string): Promise<Change[]>;
|
|
57
62
|
/**
|
|
58
|
-
*
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
*
|
|
63
|
+
* Alias for getStateAtVersion for RPC API compatibility.
|
|
64
|
+
*/
|
|
65
|
+
getVersionState(docId: string, versionId: string): Promise<any>;
|
|
66
|
+
/**
|
|
67
|
+
* Alias for getChangesForVersion for RPC API compatibility.
|
|
63
68
|
*/
|
|
64
|
-
|
|
69
|
+
getVersionChanges(docId: string, versionId: string): Promise<Change[]>;
|
|
65
70
|
}
|
|
66
71
|
|
|
67
72
|
export { PatchesHistoryManager };
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import "../chunk-IZ2YBCUP.js";
|
|
2
|
-
import { assertVersionMetadata } from "./
|
|
2
|
+
import { assertVersionMetadata } from "./utils.js";
|
|
3
3
|
class PatchesHistoryManager {
|
|
4
|
-
constructor(patches) {
|
|
4
|
+
constructor(patches, store) {
|
|
5
5
|
this.patches = patches;
|
|
6
|
-
this.store =
|
|
6
|
+
this.store = store;
|
|
7
7
|
}
|
|
8
|
+
static api = {
|
|
9
|
+
listVersions: "read",
|
|
10
|
+
createVersion: "write",
|
|
11
|
+
updateVersion: "write",
|
|
12
|
+
getVersionState: "read",
|
|
13
|
+
getVersionChanges: "read"
|
|
14
|
+
};
|
|
8
15
|
store;
|
|
9
16
|
/**
|
|
10
17
|
* Lists version metadata for the document, supporting various filters.
|
|
@@ -63,21 +70,26 @@ class PatchesHistoryManager {
|
|
|
63
70
|
*/
|
|
64
71
|
async getChangesForVersion(docId, versionId) {
|
|
65
72
|
try {
|
|
66
|
-
return await this.store.loadVersionChanges(docId, versionId);
|
|
73
|
+
return await this.store.loadVersionChanges?.(docId, versionId) ?? [];
|
|
67
74
|
} catch (error) {
|
|
68
75
|
console.error(`Failed to load changes for version ${versionId} of doc ${docId}.`, error);
|
|
69
76
|
throw new Error(`Could not load changes for version ${versionId}.`);
|
|
70
77
|
}
|
|
71
78
|
}
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Alias methods for RPC API compatibility
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
72
82
|
/**
|
|
73
|
-
*
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
* Alias for getStateAtVersion for RPC API compatibility.
|
|
84
|
+
*/
|
|
85
|
+
async getVersionState(docId, versionId) {
|
|
86
|
+
return this.getStateAtVersion(docId, versionId);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Alias for getChangesForVersion for RPC API compatibility.
|
|
78
90
|
*/
|
|
79
|
-
async
|
|
80
|
-
return
|
|
91
|
+
async getVersionChanges(docId, versionId) {
|
|
92
|
+
return this.getChangesForVersion(docId, versionId);
|
|
81
93
|
}
|
|
82
94
|
}
|
|
83
95
|
export {
|