@dabble/patches 0.5.22 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +221 -208
- package/dist/BaseDoc-DkP3tUhT.d.ts +206 -0
- package/dist/algorithms/client/applyCommittedChanges.d.ts +7 -0
- package/dist/algorithms/client/applyCommittedChanges.js +6 -3
- package/dist/algorithms/lww/consolidateOps.d.ts +40 -0
- package/dist/algorithms/lww/consolidateOps.js +103 -0
- package/dist/algorithms/lww/index.d.ts +2 -0
- package/dist/algorithms/lww/index.js +1 -0
- package/dist/algorithms/lww/mergeServerWithLocal.d.ts +22 -0
- package/dist/algorithms/lww/mergeServerWithLocal.js +32 -0
- package/dist/algorithms/server/commitChanges.d.ts +32 -8
- package/dist/algorithms/server/commitChanges.js +24 -10
- package/dist/algorithms/server/createVersion.d.ts +1 -1
- package/dist/algorithms/server/createVersion.js +2 -4
- package/dist/algorithms/server/getSnapshotAtRevision.d.ts +1 -1
- package/dist/algorithms/server/getStateAtRevision.d.ts +1 -1
- package/dist/algorithms/server/handleOfflineSessionsAndBatches.d.ts +1 -1
- package/dist/algorithms/server/handleOfflineSessionsAndBatches.js +5 -7
- 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 +6 -35
- 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/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 +1 -1
- package/dist/data/change.js +4 -3
- package/dist/fractionalIndex.d.ts +67 -0
- package/dist/fractionalIndex.js +241 -0
- package/dist/index.d.ts +13 -4
- package/dist/index.js +1 -1
- package/dist/json-patch/types.d.ts +2 -0
- package/dist/net/PatchesClient.js +15 -15
- package/dist/net/PatchesSync.d.ts +24 -12
- package/dist/net/PatchesSync.js +56 -64
- package/dist/net/index.d.ts +6 -10
- 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 +8 -6
- package/dist/server/CompressedStoreBackend.js +3 -9
- 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 +191 -0
- package/dist/server/LWWServer.d.ts +130 -0
- package/dist/server/LWWServer.js +207 -0
- package/dist/server/{PatchesBranchManager.d.ts → OTBranchManager.d.ts} +32 -12
- package/dist/server/{PatchesBranchManager.js → OTBranchManager.js} +26 -42
- package/dist/server/OTServer.d.ts +108 -0
- package/dist/server/OTServer.js +141 -0
- package/dist/server/PatchesHistoryManager.d.ts +20 -7
- package/dist/server/PatchesHistoryManager.js +26 -3
- package/dist/server/PatchesServer.d.ts +70 -81
- package/dist/server/PatchesServer.js +0 -176
- package/dist/server/branchUtils.d.ts +82 -0
- package/dist/server/branchUtils.js +66 -0
- package/dist/server/index.d.ts +17 -6
- 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 +129 -27
- package/dist/server/utils.d.ts +12 -0
- package/dist/server/utils.js +23 -0
- package/dist/solid/context.d.ts +5 -4
- package/dist/solid/doc-manager.d.ts +3 -3
- package/dist/solid/index.d.ts +5 -4
- package/dist/solid/primitives.d.ts +2 -3
- package/dist/types.d.ts +16 -14
- package/dist/vue/composables.d.ts +2 -3
- package/dist/vue/doc-manager.d.ts +3 -3
- package/dist/vue/index.d.ts +5 -4
- package/dist/vue/provider.d.ts +5 -4
- package/package.json +1 -1
- package/dist/algorithms/client/collapsePendingChanges.d.ts +0 -30
- package/dist/algorithms/client/collapsePendingChanges.js +0 -78
- package/dist/net/websocket/RPCServer.d.ts +0 -141
- package/dist/net/websocket/RPCServer.js +0 -204
- package/dist/utils/dates.d.ts +0 -43
- package/dist/utils/dates.js +0 -47
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
import "../chunk-IZ2YBCUP.js";
|
|
2
|
-
import { commitChanges } from "../algorithms/server/commitChanges.js";
|
|
3
|
-
import { createVersion } from "../algorithms/server/createVersion.js";
|
|
4
|
-
import { getSnapshotAtRevision } from "../algorithms/server/getSnapshotAtRevision.js";
|
|
5
|
-
import { getStateAtRevision } from "../algorithms/server/getStateAtRevision.js";
|
|
6
|
-
import { applyChanges } from "../algorithms/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 { getISO } from "../utils/dates.js";
|
|
11
|
-
import { CompressedStoreBackend } from "./CompressedStoreBackend.js";
|
|
12
|
-
class PatchesServer {
|
|
13
|
-
sessionTimeoutMillis;
|
|
14
|
-
maxStorageBytes;
|
|
15
|
-
store;
|
|
16
|
-
/** Notifies listeners whenever a batch of changes is *successfully* committed. */
|
|
17
|
-
onChangesCommitted = signal();
|
|
18
|
-
/** Notifies listeners when a document is deleted. */
|
|
19
|
-
onDocDeleted = signal();
|
|
20
|
-
constructor(store, options = {}) {
|
|
21
|
-
this.sessionTimeoutMillis = (options.sessionTimeoutMinutes ?? 30) * 60 * 1e3;
|
|
22
|
-
this.maxStorageBytes = options.maxStorageBytes;
|
|
23
|
-
if (options.compressor) {
|
|
24
|
-
this.store = new CompressedStoreBackend(store, options.compressor);
|
|
25
|
-
} else {
|
|
26
|
-
this.store = store;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Get the state of a document at a specific revision (or the latest state if no revision is provided).
|
|
31
|
-
* @param docId - The ID of the document.
|
|
32
|
-
* @param rev - The revision number.
|
|
33
|
-
* @returns The state of the document at the specified revision.
|
|
34
|
-
*/
|
|
35
|
-
async getDoc(docId, atRev) {
|
|
36
|
-
return getStateAtRevision(this.store, docId, atRev);
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Get the state of a document at a specific revision.
|
|
40
|
-
* @param docId - The ID of the document.
|
|
41
|
-
* @param rev - The revision number.
|
|
42
|
-
* @returns The state of the document at the specified revision.
|
|
43
|
-
*/
|
|
44
|
-
async getStateAtRevision(docId, atRev) {
|
|
45
|
-
return getStateAtRevision(this.store, docId, atRev);
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Get changes that occurred after a specific revision.
|
|
49
|
-
* @param docId - The ID of the document.
|
|
50
|
-
* @param rev - The revision number.
|
|
51
|
-
* @returns The changes that occurred after the specified revision.
|
|
52
|
-
*/
|
|
53
|
-
getChangesSince(docId, rev) {
|
|
54
|
-
return this.store.listChanges(docId, { startAfter: rev });
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Commits a set of changes to a document, applying operational transformation as needed.
|
|
58
|
-
* @param docId - The ID of the document.
|
|
59
|
-
* @param changes - The changes to commit.
|
|
60
|
-
* @param options - Optional commit settings (e.g., forceCommit for migrations).
|
|
61
|
-
* @param originClientId - The ID of the client that initiated the commit (used by transport layer for broadcast filtering).
|
|
62
|
-
* @returns A tuple of [committedChanges, transformedChanges] where:
|
|
63
|
-
* - committedChanges: Changes that were already committed to the server after the client's base revision
|
|
64
|
-
* - transformedChanges: The client's changes after being transformed against concurrent changes
|
|
65
|
-
*/
|
|
66
|
-
async commitChanges(docId, changes, options, originClientId) {
|
|
67
|
-
const [committedChanges, transformedChanges] = await commitChanges(
|
|
68
|
-
this.store,
|
|
69
|
-
docId,
|
|
70
|
-
changes,
|
|
71
|
-
this.sessionTimeoutMillis,
|
|
72
|
-
options,
|
|
73
|
-
this.maxStorageBytes
|
|
74
|
-
);
|
|
75
|
-
if (transformedChanges.length > 0) {
|
|
76
|
-
try {
|
|
77
|
-
await this.onChangesCommitted.emit(docId, transformedChanges, originClientId);
|
|
78
|
-
} catch (error) {
|
|
79
|
-
console.error(`Failed to notify clients about committed changes for doc ${docId}:`, error);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return [committedChanges, transformedChanges];
|
|
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 originClientId - The ID of the client that initiated the delete operation.
|
|
104
|
-
* @param options - Optional deletion settings (e.g., skipTombstone for testing).
|
|
105
|
-
*/
|
|
106
|
-
async deleteDoc(docId, options, originClientId) {
|
|
107
|
-
if (this.store.createTombstone && !options?.skipTombstone) {
|
|
108
|
-
const { rev: lastRev } = await this.getDoc(docId);
|
|
109
|
-
await this.store.createTombstone({
|
|
110
|
-
docId,
|
|
111
|
-
deletedAt: getISO(),
|
|
112
|
-
lastRev,
|
|
113
|
-
deletedByClientId: originClientId
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
await this.store.deleteDoc(docId);
|
|
117
|
-
await this.onDocDeleted.emit(docId, options, originClientId);
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Removes the tombstone for a deleted document, allowing it to be recreated.
|
|
121
|
-
* @param docId The document ID.
|
|
122
|
-
* @returns True if tombstone was found and removed, false if no tombstone existed.
|
|
123
|
-
*/
|
|
124
|
-
async undeleteDoc(docId) {
|
|
125
|
-
if (!this.store.removeTombstone) {
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
128
|
-
const tombstone = await this.store.getTombstone?.(docId);
|
|
129
|
-
if (!tombstone) {
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
await this.store.removeTombstone(docId);
|
|
133
|
-
return true;
|
|
134
|
-
}
|
|
135
|
-
// === Version Operations ===
|
|
136
|
-
/**
|
|
137
|
-
* Captures the current state of a document as a new version.
|
|
138
|
-
* @param docId The document ID.
|
|
139
|
-
* @param metadata Optional metadata for the version.
|
|
140
|
-
* @returns The ID of the created version.
|
|
141
|
-
*/
|
|
142
|
-
async captureCurrentVersion(docId, metadata) {
|
|
143
|
-
assertVersionMetadata(metadata);
|
|
144
|
-
const { state: initialState, changes } = await getSnapshotAtRevision(this.store, docId);
|
|
145
|
-
let state = initialState;
|
|
146
|
-
state = applyChanges(state, changes);
|
|
147
|
-
const version = await createVersion(this.store, docId, state, changes, metadata);
|
|
148
|
-
if (!version) {
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
151
|
-
return version.id;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
const nonModifiableMetadataFields = /* @__PURE__ */ new Set([
|
|
155
|
-
"id",
|
|
156
|
-
"parentId",
|
|
157
|
-
"groupId",
|
|
158
|
-
"origin",
|
|
159
|
-
"branchName",
|
|
160
|
-
"startedAt",
|
|
161
|
-
"endedAt",
|
|
162
|
-
"rev",
|
|
163
|
-
"baseRev"
|
|
164
|
-
]);
|
|
165
|
-
function assertVersionMetadata(metadata) {
|
|
166
|
-
if (!metadata) return;
|
|
167
|
-
for (const key in metadata) {
|
|
168
|
-
if (nonModifiableMetadataFields.has(key)) {
|
|
169
|
-
throw new Error(`Cannot modify version field ${key}`);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
export {
|
|
174
|
-
PatchesServer,
|
|
175
|
-
assertVersionMetadata
|
|
176
|
-
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { ApiDefinition } from '../net/protocol/JSONRPCServer.js';
|
|
2
|
+
import { EditableBranchMetadata, Branch, BranchStatus } from '../types.js';
|
|
3
|
+
import '../event-signal.js';
|
|
4
|
+
import '../net/websocket/AuthorizationProvider.js';
|
|
5
|
+
import './types.js';
|
|
6
|
+
import '../json-patch/types.js';
|
|
7
|
+
import '../json-patch/JSONPatch.js';
|
|
8
|
+
import '@dabble/delta';
|
|
9
|
+
import '../net/protocol/types.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Standard API definition for branch managers.
|
|
13
|
+
* Used with JSONRPCServer.register() to expose branch methods.
|
|
14
|
+
*/
|
|
15
|
+
declare const branchManagerApi: ApiDefinition;
|
|
16
|
+
/**
|
|
17
|
+
* Validates that branch metadata doesn't contain non-modifiable fields.
|
|
18
|
+
* @throws Error if metadata contains protected fields.
|
|
19
|
+
*/
|
|
20
|
+
declare function assertBranchMetadata(metadata?: EditableBranchMetadata): void;
|
|
21
|
+
/**
|
|
22
|
+
* Store interface for branch ID generation.
|
|
23
|
+
*/
|
|
24
|
+
interface BranchIdGenerator {
|
|
25
|
+
createBranchId?: (docId: string) => Promise<string> | string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Generates a branch ID, using the store's custom generator if available.
|
|
29
|
+
* @param store - Store that may have a custom createBranchId method.
|
|
30
|
+
* @param docId - The source document ID.
|
|
31
|
+
* @returns A unique branch document ID.
|
|
32
|
+
*/
|
|
33
|
+
declare function generateBranchId(store: BranchIdGenerator, docId: string): Promise<string>;
|
|
34
|
+
/**
|
|
35
|
+
* Creates a Branch record with standard fields.
|
|
36
|
+
* @param branchDocId - The branch document ID.
|
|
37
|
+
* @param sourceDocId - The source document being branched from.
|
|
38
|
+
* @param branchedAtRev - The revision at which the branch was created.
|
|
39
|
+
* @param metadata - Optional branch metadata (name, etc.).
|
|
40
|
+
* @returns A new Branch object.
|
|
41
|
+
*/
|
|
42
|
+
declare function createBranchRecord(branchDocId: string, sourceDocId: string, branchedAtRev: number, metadata?: EditableBranchMetadata): Branch;
|
|
43
|
+
/**
|
|
44
|
+
* Store interface for branch loading.
|
|
45
|
+
*/
|
|
46
|
+
interface BranchLoader {
|
|
47
|
+
loadBranch(docId: string): Promise<Branch | null>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Validates that a document is not already a branch (prevents branch-of-branch).
|
|
51
|
+
* @param store - Store with loadBranch capability.
|
|
52
|
+
* @param docId - The document ID to check.
|
|
53
|
+
* @throws Error if the document is already a branch.
|
|
54
|
+
*/
|
|
55
|
+
declare function assertNotABranch(store: BranchLoader, docId: string): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Validates that a branch exists and is open for merging.
|
|
58
|
+
* @param branch - The branch to validate (may be null).
|
|
59
|
+
* @param branchId - The branch ID (for error messages).
|
|
60
|
+
* @throws Error if branch not found or not open.
|
|
61
|
+
*/
|
|
62
|
+
declare function assertBranchOpenForMerge(branch: Branch | null, branchId: string): asserts branch is Branch;
|
|
63
|
+
/**
|
|
64
|
+
* Wraps a merge commit operation with standard error handling.
|
|
65
|
+
* @param branchId - The branch being merged.
|
|
66
|
+
* @param sourceDocId - The source document being merged into.
|
|
67
|
+
* @param commitFn - The async function that performs the commit.
|
|
68
|
+
* @returns The result of the commit function.
|
|
69
|
+
* @throws Error with standardized message if commit fails.
|
|
70
|
+
*/
|
|
71
|
+
declare function wrapMergeCommit<T>(branchId: string, sourceDocId: string, commitFn: () => Promise<T>): Promise<T>;
|
|
72
|
+
/**
|
|
73
|
+
* Standard close branch operation.
|
|
74
|
+
* @param store - Store with updateBranch capability.
|
|
75
|
+
* @param branchId - The branch to close.
|
|
76
|
+
* @param status - The status to set (defaults to 'closed').
|
|
77
|
+
*/
|
|
78
|
+
declare function closeBranch(store: {
|
|
79
|
+
updateBranch(branchId: string, updates: Partial<Pick<Branch, 'status' | 'name'>>): Promise<void>;
|
|
80
|
+
}, branchId: string, status?: Exclude<BranchStatus, 'open'>): Promise<void>;
|
|
81
|
+
|
|
82
|
+
export { type BranchIdGenerator, type BranchLoader, assertBranchMetadata, assertBranchOpenForMerge, assertNotABranch, branchManagerApi, closeBranch, createBranchRecord, generateBranchId, wrapMergeCommit };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import "../chunk-IZ2YBCUP.js";
|
|
2
|
+
import { createId } from "crypto-id";
|
|
3
|
+
const branchManagerApi = {
|
|
4
|
+
listBranches: "read",
|
|
5
|
+
createBranch: "write",
|
|
6
|
+
updateBranch: "write",
|
|
7
|
+
closeBranch: "write",
|
|
8
|
+
mergeBranch: "write"
|
|
9
|
+
};
|
|
10
|
+
const nonModifiableBranchFields = /* @__PURE__ */ new Set(["id", "docId", "branchedAtRev", "createdAt", "status"]);
|
|
11
|
+
function assertBranchMetadata(metadata) {
|
|
12
|
+
if (!metadata) return;
|
|
13
|
+
for (const key in metadata) {
|
|
14
|
+
if (nonModifiableBranchFields.has(key)) {
|
|
15
|
+
throw new Error(`Cannot modify branch field ${key}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async function generateBranchId(store, docId) {
|
|
20
|
+
return store.createBranchId ? await Promise.resolve(store.createBranchId(docId)) : createId(22);
|
|
21
|
+
}
|
|
22
|
+
function createBranchRecord(branchDocId, sourceDocId, branchedAtRev, metadata) {
|
|
23
|
+
return {
|
|
24
|
+
...metadata,
|
|
25
|
+
id: branchDocId,
|
|
26
|
+
docId: sourceDocId,
|
|
27
|
+
branchedAtRev,
|
|
28
|
+
createdAt: Date.now(),
|
|
29
|
+
status: "open"
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
async function assertNotABranch(store, docId) {
|
|
33
|
+
const maybeBranch = await store.loadBranch(docId);
|
|
34
|
+
if (maybeBranch) {
|
|
35
|
+
throw new Error("Cannot create a branch from another branch.");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function assertBranchOpenForMerge(branch, branchId) {
|
|
39
|
+
if (!branch) {
|
|
40
|
+
throw new Error(`Branch with ID ${branchId} not found.`);
|
|
41
|
+
}
|
|
42
|
+
if (branch.status !== "open") {
|
|
43
|
+
throw new Error(`Branch ${branchId} is not open (status: ${branch.status}). Cannot merge.`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function wrapMergeCommit(branchId, sourceDocId, commitFn) {
|
|
47
|
+
try {
|
|
48
|
+
return await commitFn();
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error(`Failed to merge branch ${branchId} into ${sourceDocId}:`, error);
|
|
51
|
+
throw new Error(`Merge failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function closeBranch(store, branchId, status = "closed") {
|
|
55
|
+
await store.updateBranch(branchId, { status });
|
|
56
|
+
}
|
|
57
|
+
export {
|
|
58
|
+
assertBranchMetadata,
|
|
59
|
+
assertBranchOpenForMerge,
|
|
60
|
+
assertNotABranch,
|
|
61
|
+
branchManagerApi,
|
|
62
|
+
closeBranch,
|
|
63
|
+
createBranchRecord,
|
|
64
|
+
generateBranchId,
|
|
65
|
+
wrapMergeCommit
|
|
66
|
+
};
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,12 +1,23 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
1
|
+
export { LWWServer, LWWServerOptions } from './LWWServer.js';
|
|
2
|
+
export { OTServer, OTServerOptions } from './OTServer.js';
|
|
3
|
+
export { BranchManager } from './BranchManager.js';
|
|
4
|
+
export { LWWBranchManager } from './LWWBranchManager.js';
|
|
5
|
+
export { OTBranchManager, PatchesBranchManager } from './OTBranchManager.js';
|
|
6
|
+
export { LWWMemoryStoreBackend } from './LWWMemoryStoreBackend.js';
|
|
3
7
|
export { PatchesHistoryManager } from './PatchesHistoryManager.js';
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
8
|
+
export { BranchIdGenerator, BranchLoader, assertBranchMetadata, assertBranchOpenForMerge, assertNotABranch, branchManagerApi, createBranchRecord, generateBranchId, wrapMergeCommit } from './branchUtils.js';
|
|
9
|
+
export { CompressedStoreBackend } from './CompressedStoreBackend.js';
|
|
10
|
+
export { createTombstoneIfSupported, isTombstoneStore, removeTombstoneIfExists } from './tombstone.js';
|
|
11
|
+
export { assertVersionMetadata } from './utils.js';
|
|
6
12
|
export { CommitChangesOptions, DeleteDocOptions } from '../types.js';
|
|
13
|
+
export { PatchesServer } from './PatchesServer.js';
|
|
14
|
+
export { BranchingStoreBackend, LWWStoreBackend, LWWVersioningStoreBackend, ListFieldsOptions, OTStoreBackend, PatchesStoreBackend, ServerStoreBackend, TombstoneStoreBackend, VersioningStoreBackend } from './types.js';
|
|
15
|
+
import '../event-signal.js';
|
|
16
|
+
import '../net/protocol/JSONRPCServer.js';
|
|
17
|
+
import '../net/websocket/AuthorizationProvider.js';
|
|
18
|
+
import '../net/protocol/types.js';
|
|
19
|
+
import '../json-patch/types.js';
|
|
7
20
|
import '../compression/index.js';
|
|
8
21
|
import '../algorithms/shared/lz.js';
|
|
9
|
-
import '../json-patch/types.js';
|
|
10
|
-
import '../event-signal.js';
|
|
11
22
|
import '../json-patch/JSONPatch.js';
|
|
12
23
|
import '@dabble/delta';
|
package/dist/server/index.js
CHANGED
|
@@ -1,11 +1,40 @@
|
|
|
1
1
|
import "../chunk-IZ2YBCUP.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { LWWServer } from "./LWWServer.js";
|
|
3
|
+
import { OTServer } from "./OTServer.js";
|
|
4
|
+
import { LWWBranchManager } from "./LWWBranchManager.js";
|
|
5
|
+
import { OTBranchManager, PatchesBranchManager } from "./OTBranchManager.js";
|
|
6
|
+
import { LWWMemoryStoreBackend } from "./LWWMemoryStoreBackend.js";
|
|
4
7
|
import { PatchesHistoryManager } from "./PatchesHistoryManager.js";
|
|
5
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
assertBranchMetadata,
|
|
10
|
+
assertBranchOpenForMerge,
|
|
11
|
+
assertNotABranch,
|
|
12
|
+
branchManagerApi,
|
|
13
|
+
createBranchRecord,
|
|
14
|
+
generateBranchId,
|
|
15
|
+
wrapMergeCommit
|
|
16
|
+
} from "./branchUtils.js";
|
|
17
|
+
import { CompressedStoreBackend } from "./CompressedStoreBackend.js";
|
|
18
|
+
import { createTombstoneIfSupported, isTombstoneStore, removeTombstoneIfExists } from "./tombstone.js";
|
|
19
|
+
import { assertVersionMetadata } from "./utils.js";
|
|
6
20
|
export {
|
|
7
21
|
CompressedStoreBackend,
|
|
22
|
+
LWWBranchManager,
|
|
23
|
+
LWWMemoryStoreBackend,
|
|
24
|
+
LWWServer,
|
|
25
|
+
OTBranchManager,
|
|
26
|
+
OTServer,
|
|
8
27
|
PatchesBranchManager,
|
|
9
28
|
PatchesHistoryManager,
|
|
10
|
-
|
|
29
|
+
assertBranchMetadata,
|
|
30
|
+
assertBranchOpenForMerge,
|
|
31
|
+
assertNotABranch,
|
|
32
|
+
assertVersionMetadata,
|
|
33
|
+
branchManagerApi,
|
|
34
|
+
createBranchRecord,
|
|
35
|
+
createTombstoneIfSupported,
|
|
36
|
+
generateBranchId,
|
|
37
|
+
isTombstoneStore,
|
|
38
|
+
removeTombstoneIfExists,
|
|
39
|
+
wrapMergeCommit
|
|
11
40
|
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ServerStoreBackend, TombstoneStoreBackend } from './types.js';
|
|
2
|
+
import '../json-patch/types.js';
|
|
3
|
+
import '../types.js';
|
|
4
|
+
import '../json-patch/JSONPatch.js';
|
|
5
|
+
import '@dabble/delta';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Type guard to check if a store implements TombstoneStoreBackend.
|
|
9
|
+
*/
|
|
10
|
+
declare function isTombstoneStore(store: ServerStoreBackend): store is ServerStoreBackend & TombstoneStoreBackend;
|
|
11
|
+
/**
|
|
12
|
+
* Creates a tombstone for a document if the store supports it.
|
|
13
|
+
* @param store - The store backend
|
|
14
|
+
* @param docId - The document ID
|
|
15
|
+
* @param lastRev - The last revision before deletion
|
|
16
|
+
* @param clientId - The client that initiated deletion
|
|
17
|
+
* @param skipTombstone - If true, skip creating the tombstone
|
|
18
|
+
* @returns true if tombstone was created, false if skipped or store doesn't support it
|
|
19
|
+
*/
|
|
20
|
+
declare function createTombstoneIfSupported(store: ServerStoreBackend, docId: string, lastRev: number, clientId?: string, skipTombstone?: boolean): Promise<boolean>;
|
|
21
|
+
/**
|
|
22
|
+
* Removes a tombstone for a document if it exists.
|
|
23
|
+
* @param store - The store backend
|
|
24
|
+
* @param docId - The document ID
|
|
25
|
+
* @returns true if tombstone was found and removed, false otherwise
|
|
26
|
+
*/
|
|
27
|
+
declare function removeTombstoneIfExists(store: ServerStoreBackend, docId: string): Promise<boolean>;
|
|
28
|
+
|
|
29
|
+
export { createTombstoneIfSupported, isTombstoneStore, removeTombstoneIfExists };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import "../chunk-IZ2YBCUP.js";
|
|
2
|
+
function isTombstoneStore(store) {
|
|
3
|
+
return "createTombstone" in store && "getTombstone" in store && "removeTombstone" in store;
|
|
4
|
+
}
|
|
5
|
+
async function createTombstoneIfSupported(store, docId, lastRev, clientId, skipTombstone) {
|
|
6
|
+
if (!isTombstoneStore(store) || skipTombstone) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
await store.createTombstone({
|
|
10
|
+
docId,
|
|
11
|
+
deletedAt: Date.now(),
|
|
12
|
+
lastRev,
|
|
13
|
+
deletedByClientId: clientId
|
|
14
|
+
});
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
async function removeTombstoneIfExists(store, docId) {
|
|
18
|
+
if (!isTombstoneStore(store)) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
const tombstone = await store.getTombstone(docId);
|
|
22
|
+
if (!tombstone) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
await store.removeTombstone(docId);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
createTombstoneIfSupported,
|
|
30
|
+
isTombstoneStore,
|
|
31
|
+
removeTombstoneIfExists
|
|
32
|
+
};
|
package/dist/server/types.d.ts
CHANGED
|
@@ -1,41 +1,137 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { JSONPatchOp } from '../json-patch/types.js';
|
|
2
|
+
import { VersionMetadata, Change, ListVersionsOptions, EditableVersionMetadata, ListChangesOptions, DocumentTombstone, Branch } from '../types.js';
|
|
2
3
|
import '../json-patch/JSONPatch.js';
|
|
3
4
|
import '@dabble/delta';
|
|
4
|
-
import '../json-patch/types.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Base interface for all server store backends.
|
|
8
|
+
* Provides the minimal deletion capability that all servers need.
|
|
9
9
|
*/
|
|
10
|
-
interface
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
10
|
+
interface ServerStoreBackend {
|
|
11
|
+
/** Deletes a document and all its associated data. */
|
|
12
|
+
deleteDoc(docId: string): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Interface for version storage, shared between OT and LWW.
|
|
16
|
+
* OT requires this, LWW can optionally implement it for user-visible versioning.
|
|
17
|
+
*/
|
|
18
|
+
interface VersioningStoreBackend {
|
|
19
19
|
/**
|
|
20
|
-
* Saves version metadata, its state snapshot, and the original changes
|
|
20
|
+
* Saves version metadata, its state snapshot, and optionally the original changes.
|
|
21
21
|
* State and changes are stored separately from the core metadata.
|
|
22
|
+
* @param changes - Optional for LWW (which doesn't store changes), required for OT.
|
|
22
23
|
*/
|
|
23
|
-
createVersion(docId: string, metadata: VersionMetadata, state: any, changes
|
|
24
|
-
/** Update a version's metadata. */
|
|
25
|
-
updateVersion(docId: string, versionId: string, metadata: EditableVersionMetadata): Promise<void>;
|
|
26
|
-
/**
|
|
27
|
-
* Appends changes to an existing version, updating its state snapshot, endedAt, and endRev.
|
|
28
|
-
* Used when a session spans multiple batch submissions.
|
|
29
|
-
*/
|
|
30
|
-
appendVersionChanges(docId: string, versionId: string, changes: Change[], newEndedAt: string, newEndRev: number, newState: any): Promise<void>;
|
|
24
|
+
createVersion(docId: string, metadata: VersionMetadata, state: any, changes?: Change[]): Promise<void>;
|
|
31
25
|
/** Lists version metadata based on filtering/sorting options. */
|
|
32
26
|
listVersions(docId: string, options: ListVersionsOptions): Promise<VersionMetadata[]>;
|
|
33
27
|
/** Loads the state snapshot for a specific version ID. */
|
|
34
28
|
loadVersionState(docId: string, versionId: string): Promise<any | undefined>;
|
|
29
|
+
/** Update a version's metadata. */
|
|
30
|
+
updateVersion(docId: string, versionId: string, metadata: EditableVersionMetadata): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Interface for OT (Operational Transformation) storage backend.
|
|
34
|
+
* Extends ServerStoreBackend and VersioningStoreBackend because OT requires versioning
|
|
35
|
+
* for session tracking and state snapshots.
|
|
36
|
+
*/
|
|
37
|
+
interface OTStoreBackend extends ServerStoreBackend, VersioningStoreBackend {
|
|
38
|
+
/** Saves a batch of committed server changes. */
|
|
39
|
+
saveChanges(docId: string, changes: Change[]): Promise<void>;
|
|
40
|
+
/** Lists committed server changes based on revision numbers. */
|
|
41
|
+
listChanges(docId: string, options: ListChangesOptions): Promise<Change[]>;
|
|
35
42
|
/** Loads the original Change objects associated with a specific version ID. */
|
|
36
43
|
loadVersionChanges(docId: string, versionId: string): Promise<Change[]>;
|
|
37
|
-
/**
|
|
38
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Appends changes to an existing version, updating its state snapshot, endedAt, and endRev.
|
|
46
|
+
* Used when a session spans multiple batch submissions.
|
|
47
|
+
*/
|
|
48
|
+
appendVersionChanges(docId: string, versionId: string, changes: Change[], newEndedAt: number, newEndRev: number, newState: any): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Options for listing fields. Use either sinceRev OR paths, not both.
|
|
52
|
+
*/
|
|
53
|
+
type ListFieldsOptions = {
|
|
54
|
+
sinceRev: number;
|
|
55
|
+
} | {
|
|
56
|
+
paths: string[];
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Interface for LWW (Last-Write-Wins) storage backend.
|
|
60
|
+
* LWW stores fields (not changes) and reconstructs state from fields.
|
|
61
|
+
*/
|
|
62
|
+
interface LWWStoreBackend extends ServerStoreBackend {
|
|
63
|
+
/**
|
|
64
|
+
* Get the current revision number without reconstructing state.
|
|
65
|
+
* More efficient than getSnapshot() when only the revision is needed.
|
|
66
|
+
* @param docId - The document ID.
|
|
67
|
+
* @returns The current revision number, or 0 if document doesn't exist.
|
|
68
|
+
*/
|
|
69
|
+
getCurrentRev(docId: string): Promise<number>;
|
|
70
|
+
/**
|
|
71
|
+
* Get the latest snapshot of document state.
|
|
72
|
+
* @param docId - The document ID.
|
|
73
|
+
* @returns The snapshot state and revision, or null if no snapshot exists.
|
|
74
|
+
*/
|
|
75
|
+
getSnapshot(docId: string): Promise<{
|
|
76
|
+
state: any;
|
|
77
|
+
rev: number;
|
|
78
|
+
} | null>;
|
|
79
|
+
/**
|
|
80
|
+
* Save a snapshot of document state (overwrites previous snapshot).
|
|
81
|
+
* @param docId - The document ID.
|
|
82
|
+
* @param state - The document state.
|
|
83
|
+
* @param rev - The revision number.
|
|
84
|
+
*/
|
|
85
|
+
saveSnapshot(docId: string, state: any, rev: number): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* List field metadata, optionally filtered by paths or revision.
|
|
88
|
+
*
|
|
89
|
+
* Options are mutually exclusive:
|
|
90
|
+
* - `{ sinceRev: number }` - Get fields changed since a revision
|
|
91
|
+
* - `{ paths: string[] }` - Get fields at specific paths
|
|
92
|
+
* - No options - Get all fields
|
|
93
|
+
*
|
|
94
|
+
* @param docId - The document ID.
|
|
95
|
+
* @param options - Optional filter options.
|
|
96
|
+
* @returns Array of field metadata matching the criteria.
|
|
97
|
+
*/
|
|
98
|
+
listOps(docId: string, options?: ListFieldsOptions): Promise<JSONPatchOp[]>;
|
|
99
|
+
/**
|
|
100
|
+
* Save field metadata and atomically increment the revision.
|
|
101
|
+
*
|
|
102
|
+
* Implementation requirements:
|
|
103
|
+
* - Atomically increment the document revision
|
|
104
|
+
* - Set the rev on all saved fields to the new revision
|
|
105
|
+
* - Delete children atomically when saving a parent (e.g., saving /obj deletes /obj/name)
|
|
106
|
+
* - Delete paths in pathsToDelete atomically with saving ops
|
|
107
|
+
*
|
|
108
|
+
* @param docId - The document ID.
|
|
109
|
+
* @param ops - Array of ops to save.
|
|
110
|
+
* @param pathsToDelete - Optional paths to delete atomically.
|
|
111
|
+
* @returns The new revision number.
|
|
112
|
+
*/
|
|
113
|
+
saveOps(docId: string, ops: JSONPatchOp[], pathsToDelete?: string[]): Promise<number>;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Extended storage interface for LWWServer with versioning support.
|
|
117
|
+
* Versioning is optional - not all LWW docs need it.
|
|
118
|
+
*/
|
|
119
|
+
interface LWWVersioningStoreBackend extends LWWStoreBackend {
|
|
120
|
+
/**
|
|
121
|
+
* Creates a new version snapshot.
|
|
122
|
+
* @param docId - The document ID.
|
|
123
|
+
* @param versionId - The version ID.
|
|
124
|
+
* @param state - The document state snapshot.
|
|
125
|
+
* @param rev - The revision number.
|
|
126
|
+
* @param metadata - Optional version metadata.
|
|
127
|
+
*/
|
|
128
|
+
createVersion(docId: string, versionId: string, state: any, rev: number, metadata?: EditableVersionMetadata): Promise<void>;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Interface for tombstone storage, providing soft-delete capabilities.
|
|
132
|
+
* Optional add-on for servers that need to track deleted documents.
|
|
133
|
+
*/
|
|
134
|
+
interface TombstoneStoreBackend {
|
|
39
135
|
/** Creates a tombstone for a deleted document. Called before deleteDoc() to preserve deletion metadata. */
|
|
40
136
|
createTombstone(tombstone: DocumentTombstone): Promise<void>;
|
|
41
137
|
/** Retrieves a tombstone for a document if it exists. Returns undefined if the document was never deleted or tombstone has expired. */
|
|
@@ -44,9 +140,10 @@ interface PatchesStoreBackend {
|
|
|
44
140
|
removeTombstone(docId: string): Promise<void>;
|
|
45
141
|
}
|
|
46
142
|
/**
|
|
47
|
-
*
|
|
143
|
+
* Interface for branch storage. Standalone interface that can be composed
|
|
144
|
+
* with OTStoreBackend or LWWStoreBackend as needed.
|
|
48
145
|
*/
|
|
49
|
-
interface BranchingStoreBackend
|
|
146
|
+
interface BranchingStoreBackend {
|
|
50
147
|
/**
|
|
51
148
|
* Generates a unique ID for a new branch document.
|
|
52
149
|
* If not provided, a random 22-character ID is generated using createId().
|
|
@@ -67,5 +164,10 @@ interface BranchingStoreBackend extends PatchesStoreBackend {
|
|
|
67
164
|
*/
|
|
68
165
|
closeBranch(branchId: string): Promise<void>;
|
|
69
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* @deprecated Use OTStoreBackend instead.
|
|
169
|
+
* Alias for backwards compatibility.
|
|
170
|
+
*/
|
|
171
|
+
type PatchesStoreBackend = OTStoreBackend;
|
|
70
172
|
|
|
71
|
-
export type { BranchingStoreBackend, PatchesStoreBackend };
|
|
173
|
+
export type { BranchingStoreBackend, LWWStoreBackend, LWWVersioningStoreBackend, ListFieldsOptions, OTStoreBackend, PatchesStoreBackend, ServerStoreBackend, TombstoneStoreBackend, VersioningStoreBackend };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { EditableVersionMetadata } from '../types.js';
|
|
2
|
+
import '../json-patch/JSONPatch.js';
|
|
3
|
+
import '@dabble/delta';
|
|
4
|
+
import '../json-patch/types.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Validates that version metadata does not contain non-modifiable fields.
|
|
8
|
+
* @throws Error if metadata contains any non-modifiable fields.
|
|
9
|
+
*/
|
|
10
|
+
declare function assertVersionMetadata(metadata?: EditableVersionMetadata): void;
|
|
11
|
+
|
|
12
|
+
export { assertVersionMetadata };
|