@dabble/patches 0.8.7 → 0.8.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/algorithms/ot/shared/changeBatching.js +30 -71
- package/dist/client/BranchClientStore.d.ts +71 -0
- package/dist/client/BranchClientStore.js +0 -0
- package/dist/client/ClientAlgorithm.d.ts +12 -0
- package/dist/client/IndexedDBStore.d.ts +13 -2
- package/dist/client/IndexedDBStore.js +125 -1
- package/dist/client/LWWInMemoryStore.js +5 -3
- package/dist/client/LWWIndexedDBStore.d.ts +1 -0
- package/dist/client/LWWIndexedDBStore.js +14 -5
- package/dist/client/OTAlgorithm.d.ts +3 -0
- package/dist/client/OTAlgorithm.js +4 -0
- package/dist/client/OTClientStore.d.ts +11 -0
- package/dist/client/OTIndexedDBStore.d.ts +9 -0
- package/dist/client/OTIndexedDBStore.js +15 -3
- package/dist/client/Patches.d.ts +6 -0
- package/dist/client/Patches.js +11 -0
- package/dist/client/PatchesBranchClient.d.ts +92 -0
- package/dist/client/PatchesBranchClient.js +170 -0
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.js +1 -0
- package/dist/compression/index.d.ts +15 -7
- package/dist/compression/index.js +13 -2
- package/dist/index.d.ts +4 -1
- package/dist/net/PatchesClient.d.ts +13 -8
- package/dist/net/PatchesClient.js +15 -8
- package/dist/net/PatchesSync.d.ts +36 -3
- package/dist/net/PatchesSync.js +72 -0
- package/dist/net/index.d.ts +3 -2
- package/dist/net/protocol/types.d.ts +9 -2
- package/dist/net/rest/PatchesREST.d.ts +8 -5
- package/dist/net/rest/PatchesREST.js +29 -19
- package/dist/net/rest/SSEServer.js +1 -1
- package/dist/net/rest/index.d.ts +1 -1
- package/dist/net/rest/index.js +1 -2
- package/dist/net/rest/utils.d.ts +1 -9
- package/dist/net/rest/utils.js +0 -4
- package/dist/server/BranchManager.d.ts +11 -7
- package/dist/server/LWWBranchManager.d.ts +10 -9
- package/dist/server/LWWBranchManager.js +47 -30
- package/dist/server/LWWMemoryStoreBackend.d.ts +5 -2
- package/dist/server/LWWMemoryStoreBackend.js +21 -3
- package/dist/server/OTBranchManager.d.ts +14 -10
- package/dist/server/OTBranchManager.js +65 -63
- package/dist/server/branchUtils.d.ts +7 -15
- package/dist/server/branchUtils.js +18 -14
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/types.d.ts +10 -4
- package/dist/solid/context.d.ts +1 -0
- package/dist/solid/index.d.ts +1 -0
- package/dist/types.d.ts +43 -6
- package/dist/vue/index.d.ts +1 -0
- package/dist/vue/provider.d.ts +1 -0
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@ import { createChange } from "../data/change.js";
|
|
|
5
5
|
import { createVersionMetadata } from "../data/version.js";
|
|
6
6
|
import {
|
|
7
7
|
assertBranchMetadata,
|
|
8
|
-
|
|
8
|
+
assertBranchExists,
|
|
9
9
|
assertNotABranch,
|
|
10
10
|
branchManagerApi,
|
|
11
11
|
createBranchRecord,
|
|
@@ -22,41 +22,56 @@ class OTBranchManager {
|
|
|
22
22
|
/**
|
|
23
23
|
* Lists all open branches for a document.
|
|
24
24
|
* @param docId - The ID of the document.
|
|
25
|
+
* @param options - Optional filtering options (e.g. `since` for incremental sync).
|
|
25
26
|
* @returns The branches.
|
|
26
27
|
*/
|
|
27
|
-
async listBranches(docId) {
|
|
28
|
-
return await this.store.listBranches(docId);
|
|
28
|
+
async listBranches(docId, options) {
|
|
29
|
+
return await this.store.listBranches(docId, options);
|
|
29
30
|
}
|
|
30
31
|
/**
|
|
31
32
|
* Creates a new branch for a document.
|
|
32
33
|
* @param docId - The ID of the document to branch from.
|
|
33
34
|
* @param rev - The revision of the document to branch from.
|
|
34
|
-
* @param branchName - Optional name for the branch.
|
|
35
|
-
* @param metadata - Additional optional metadata to store with the branch.
|
|
36
35
|
* @returns The ID of the new branch document.
|
|
37
36
|
*/
|
|
38
37
|
async createBranch(docId, rev, metadata) {
|
|
38
|
+
const branchDocId = metadata?.id ?? await generateBranchId(this.store, docId);
|
|
39
|
+
if (metadata?.id) {
|
|
40
|
+
const existing = await this.store.loadBranch(branchDocId);
|
|
41
|
+
if (existing) {
|
|
42
|
+
if (existing.docId !== docId) {
|
|
43
|
+
throw new Error(`Branch ${branchDocId} already exists for a different document`);
|
|
44
|
+
}
|
|
45
|
+
return branchDocId;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
39
48
|
await assertNotABranch(this.store, docId);
|
|
40
|
-
const { state: stateAtRev } = await getStateAtRevision(this.store, docId, rev);
|
|
41
|
-
const branchDocId = await generateBranchId(this.store, docId);
|
|
42
49
|
const now = Date.now();
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
50
|
+
let contentStartRev;
|
|
51
|
+
if (metadata?.contentStartRev) {
|
|
52
|
+
contentStartRev = metadata.contentStartRev;
|
|
53
|
+
} else {
|
|
54
|
+
const { state: stateAtRev } = await getStateAtRevision(this.store, docId, rev);
|
|
55
|
+
const rootReplace = createChange(0, 1, [{ op: "replace", path: "", value: stateAtRev }], {
|
|
56
|
+
createdAt: now,
|
|
57
|
+
committedAt: now
|
|
58
|
+
});
|
|
59
|
+
const initChanges = this.maxPayloadBytes ? breakChanges([rootReplace], this.maxPayloadBytes) : [rootReplace];
|
|
60
|
+
contentStartRev = initChanges[initChanges.length - 1].rev + 1;
|
|
61
|
+
await this.store.saveChanges(branchDocId, initChanges);
|
|
62
|
+
const initialVersionMetadata = createVersionMetadata({
|
|
63
|
+
origin: "main",
|
|
64
|
+
startedAt: now,
|
|
65
|
+
endedAt: now,
|
|
66
|
+
endRev: rev,
|
|
67
|
+
startRev: rev,
|
|
68
|
+
name: metadata?.name,
|
|
69
|
+
groupId: branchDocId,
|
|
70
|
+
branchName: metadata?.name
|
|
71
|
+
});
|
|
72
|
+
await this.store.createVersion(branchDocId, initialVersionMetadata, initChanges);
|
|
73
|
+
}
|
|
74
|
+
const branch = createBranchRecord(branchDocId, docId, rev, contentStartRev, metadata);
|
|
60
75
|
await this.store.createBranch(branch);
|
|
61
76
|
return branchDocId;
|
|
62
77
|
}
|
|
@@ -67,78 +82,65 @@ class OTBranchManager {
|
|
|
67
82
|
*/
|
|
68
83
|
async updateBranch(branchId, metadata) {
|
|
69
84
|
assertBranchMetadata(metadata);
|
|
70
|
-
await this.store.updateBranch(branchId, metadata);
|
|
85
|
+
await this.store.updateBranch(branchId, { ...metadata, modifiedAt: Date.now() });
|
|
71
86
|
}
|
|
72
87
|
/**
|
|
73
|
-
*
|
|
74
|
-
* @param branchId - The ID of the branch to close.
|
|
75
|
-
* @param status - The status to set for the branch.
|
|
88
|
+
* Deletes a branch, replacing the record with a tombstone.
|
|
76
89
|
*/
|
|
77
|
-
async
|
|
78
|
-
await this.store.
|
|
90
|
+
async deleteBranch(branchId) {
|
|
91
|
+
await this.store.deleteBranch(branchId);
|
|
79
92
|
}
|
|
80
93
|
/**
|
|
81
94
|
* Merges changes from a branch back into its source document.
|
|
95
|
+
*
|
|
96
|
+
* Supports multiple merges — the branch stays open and `lastMergedRev` tracks
|
|
97
|
+
* which branch revision was last merged. Subsequent merges only pick up new changes.
|
|
98
|
+
*
|
|
99
|
+
* All merge changes use `batchId: branchId` so that `commitChanges` never transforms
|
|
100
|
+
* branch changes against each other (they share the same causal context).
|
|
101
|
+
*
|
|
82
102
|
* @param branchId - The ID of the branch document to merge.
|
|
83
103
|
* @returns The server commit change(s) applied to the source document.
|
|
84
104
|
* @throws Error if branch not found, already closed/merged, or merge fails.
|
|
85
105
|
*/
|
|
86
106
|
async mergeBranch(branchId) {
|
|
87
107
|
const branch = await this.store.loadBranch(branchId);
|
|
88
|
-
|
|
108
|
+
assertBranchExists(branch, branchId);
|
|
89
109
|
const sourceDocId = branch.docId;
|
|
90
110
|
const branchStartRevOnSource = branch.branchedAtRev;
|
|
91
|
-
const
|
|
111
|
+
const startAfter = branch.lastMergedRev ?? (branch.contentStartRev ?? 2) - 1;
|
|
112
|
+
const branchChanges = await this.store.listChanges(branchId, { startAfter });
|
|
92
113
|
if (branchChanges.length === 0) {
|
|
93
|
-
console.log(`Branch ${branchId} has no changes to merge.`);
|
|
94
|
-
await this.closeBranch(branchId, "merged");
|
|
95
114
|
return [];
|
|
96
115
|
}
|
|
97
|
-
const
|
|
98
|
-
startAfter: branchStartRevOnSource
|
|
99
|
-
});
|
|
100
|
-
const canFastForward = sourceChanges.length === 0;
|
|
116
|
+
const lastBranchRev = branchChanges[branchChanges.length - 1].rev;
|
|
101
117
|
const branchVersions = await this.store.listVersions(branchId, { origin: "main" });
|
|
102
|
-
const versionOrigin = canFastForward ? "main" : "branch";
|
|
103
118
|
let lastVersionId;
|
|
104
119
|
for (const v of branchVersions) {
|
|
105
120
|
const newVersionMetadata = createVersionMetadata({
|
|
106
121
|
...v,
|
|
107
|
-
origin:
|
|
122
|
+
origin: "branch",
|
|
108
123
|
startRev: branchStartRevOnSource,
|
|
109
124
|
groupId: branchId,
|
|
110
125
|
branchName: branch.name,
|
|
111
|
-
// Keep branchName for traceability
|
|
112
126
|
parentId: lastVersionId
|
|
113
127
|
});
|
|
114
128
|
const changes = await this.store.loadVersionChanges?.(branchId, v.id);
|
|
115
129
|
await this.store.createVersion(sourceDocId, newVersionMetadata, changes);
|
|
116
130
|
lastVersionId = newVersionMetadata.id;
|
|
117
131
|
}
|
|
132
|
+
const changesToCommit = branchChanges.map((c, i) => ({
|
|
133
|
+
...c,
|
|
134
|
+
baseRev: branchStartRevOnSource,
|
|
135
|
+
rev: branchStartRevOnSource + i + 1,
|
|
136
|
+
batchId: branchId
|
|
137
|
+
}));
|
|
118
138
|
const committedMergeChanges = await wrapMergeCommit(branchId, sourceDocId, async () => {
|
|
119
|
-
|
|
120
|
-
const adjustedChanges = branchChanges.map((c) => ({
|
|
121
|
-
...c,
|
|
122
|
-
baseRev: branchStartRevOnSource,
|
|
123
|
-
rev: void 0
|
|
124
|
-
// Let commitChanges assign sequential revs
|
|
125
|
-
}));
|
|
126
|
-
return (await this.patchesServer.commitChanges(sourceDocId, adjustedChanges)).changes;
|
|
127
|
-
} else {
|
|
128
|
-
const rev = branchStartRevOnSource + branchChanges.length;
|
|
129
|
-
const flattenedChange = createChange(
|
|
130
|
-
branchStartRevOnSource,
|
|
131
|
-
rev,
|
|
132
|
-
branchChanges.flatMap((c) => c.ops)
|
|
133
|
-
);
|
|
134
|
-
let changesToCommit = [flattenedChange];
|
|
135
|
-
if (this.maxPayloadBytes) {
|
|
136
|
-
changesToCommit = breakChanges(changesToCommit, this.maxPayloadBytes);
|
|
137
|
-
}
|
|
138
|
-
return (await this.patchesServer.commitChanges(sourceDocId, changesToCommit)).changes;
|
|
139
|
-
}
|
|
139
|
+
return (await this.patchesServer.commitChanges(sourceDocId, changesToCommit)).changes;
|
|
140
140
|
});
|
|
141
|
-
await this.
|
|
141
|
+
const currentBranch = await this.store.loadBranch(branchId);
|
|
142
|
+
const effectiveLastMergedRev = Math.max(lastBranchRev, currentBranch?.lastMergedRev ?? 0);
|
|
143
|
+
await this.store.updateBranch(branchId, { lastMergedRev: effectiveLastMergedRev, modifiedAt: Date.now() });
|
|
142
144
|
return committedMergeChanges;
|
|
143
145
|
}
|
|
144
146
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ApiDefinition } from '../net/protocol/JSONRPCServer.js';
|
|
2
|
-
import { EditableBranchMetadata, Branch,
|
|
2
|
+
import { EditableBranchMetadata, Branch, CreateBranchMetadata } from '../types.js';
|
|
3
3
|
import 'easy-signal';
|
|
4
4
|
import '../net/websocket/AuthorizationProvider.js';
|
|
5
5
|
import './types.js';
|
|
@@ -36,10 +36,11 @@ declare function generateBranchId(store: BranchIdGenerator, docId: string): Prom
|
|
|
36
36
|
* @param branchDocId - The branch document ID.
|
|
37
37
|
* @param sourceDocId - The source document being branched from.
|
|
38
38
|
* @param branchedAtRev - The revision at which the branch was created.
|
|
39
|
+
* @param contentStartRev - The first revision of user content on the branch (after init changes).
|
|
39
40
|
* @param metadata - Optional branch metadata (name, etc.).
|
|
40
41
|
* @returns A new Branch object.
|
|
41
42
|
*/
|
|
42
|
-
declare function createBranchRecord(branchDocId: string, sourceDocId: string, branchedAtRev: number, metadata?: EditableBranchMetadata): Branch;
|
|
43
|
+
declare function createBranchRecord(branchDocId: string, sourceDocId: string, branchedAtRev: number, contentStartRev: number, metadata?: CreateBranchMetadata | EditableBranchMetadata): Branch;
|
|
43
44
|
/**
|
|
44
45
|
* Store interface for branch loading.
|
|
45
46
|
*/
|
|
@@ -54,12 +55,12 @@ interface BranchLoader {
|
|
|
54
55
|
*/
|
|
55
56
|
declare function assertNotABranch(store: BranchLoader, docId: string): Promise<void>;
|
|
56
57
|
/**
|
|
57
|
-
* Validates that a branch exists
|
|
58
|
+
* Validates that a branch exists.
|
|
58
59
|
* @param branch - The branch to validate (may be null).
|
|
59
60
|
* @param branchId - The branch ID (for error messages).
|
|
60
|
-
* @throws Error if branch not found
|
|
61
|
+
* @throws Error if branch not found.
|
|
61
62
|
*/
|
|
62
|
-
declare function
|
|
63
|
+
declare function assertBranchExists(branch: Branch | null, branchId: string): asserts branch is Branch;
|
|
63
64
|
/**
|
|
64
65
|
* Wraps a merge commit operation with standard error handling.
|
|
65
66
|
* @param branchId - The branch being merged.
|
|
@@ -69,14 +70,5 @@ declare function assertBranchOpenForMerge(branch: Branch | null, branchId: strin
|
|
|
69
70
|
* @throws Error with standardized message if commit fails.
|
|
70
71
|
*/
|
|
71
72
|
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'> | null): Promise<void>;
|
|
81
73
|
|
|
82
|
-
export { type BranchIdGenerator, type BranchLoader,
|
|
74
|
+
export { type BranchIdGenerator, type BranchLoader, assertBranchExists, assertBranchMetadata, assertNotABranch, branchManagerApi, createBranchRecord, generateBranchId, wrapMergeCommit };
|
|
@@ -4,10 +4,19 @@ const branchManagerApi = {
|
|
|
4
4
|
listBranches: "read",
|
|
5
5
|
createBranch: "write",
|
|
6
6
|
updateBranch: "write",
|
|
7
|
-
|
|
7
|
+
deleteBranch: "write",
|
|
8
8
|
mergeBranch: "write"
|
|
9
9
|
};
|
|
10
|
-
const nonModifiableBranchFields = /* @__PURE__ */ new Set([
|
|
10
|
+
const nonModifiableBranchFields = /* @__PURE__ */ new Set([
|
|
11
|
+
"id",
|
|
12
|
+
"docId",
|
|
13
|
+
"branchedAtRev",
|
|
14
|
+
"createdAt",
|
|
15
|
+
"modifiedAt",
|
|
16
|
+
"contentStartRev",
|
|
17
|
+
"pendingOp",
|
|
18
|
+
"deleted"
|
|
19
|
+
]);
|
|
11
20
|
function assertBranchMetadata(metadata) {
|
|
12
21
|
if (!metadata) return;
|
|
13
22
|
for (const key in metadata) {
|
|
@@ -19,14 +28,16 @@ function assertBranchMetadata(metadata) {
|
|
|
19
28
|
async function generateBranchId(store, docId) {
|
|
20
29
|
return store.createBranchId ? await Promise.resolve(store.createBranchId(docId)) : createId(22);
|
|
21
30
|
}
|
|
22
|
-
function createBranchRecord(branchDocId, sourceDocId, branchedAtRev, metadata) {
|
|
31
|
+
function createBranchRecord(branchDocId, sourceDocId, branchedAtRev, contentStartRev, metadata) {
|
|
32
|
+
const now = Date.now();
|
|
23
33
|
return {
|
|
24
34
|
...metadata,
|
|
25
35
|
id: branchDocId,
|
|
26
36
|
docId: sourceDocId,
|
|
27
37
|
branchedAtRev,
|
|
28
|
-
|
|
29
|
-
|
|
38
|
+
contentStartRev,
|
|
39
|
+
createdAt: now,
|
|
40
|
+
modifiedAt: now
|
|
30
41
|
};
|
|
31
42
|
}
|
|
32
43
|
async function assertNotABranch(store, docId) {
|
|
@@ -35,13 +46,10 @@ async function assertNotABranch(store, docId) {
|
|
|
35
46
|
throw new Error("Cannot create a branch from another branch.");
|
|
36
47
|
}
|
|
37
48
|
}
|
|
38
|
-
function
|
|
49
|
+
function assertBranchExists(branch, branchId) {
|
|
39
50
|
if (!branch) {
|
|
40
51
|
throw new Error(`Branch with ID ${branchId} not found.`);
|
|
41
52
|
}
|
|
42
|
-
if (branch.status !== "open") {
|
|
43
|
-
throw new Error(`Branch ${branchId} is not open (status: ${branch.status}). Cannot merge.`);
|
|
44
|
-
}
|
|
45
53
|
}
|
|
46
54
|
async function wrapMergeCommit(branchId, sourceDocId, commitFn) {
|
|
47
55
|
try {
|
|
@@ -51,15 +59,11 @@ async function wrapMergeCommit(branchId, sourceDocId, commitFn) {
|
|
|
51
59
|
throw new Error(`Merge failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
52
60
|
}
|
|
53
61
|
}
|
|
54
|
-
async function closeBranch(store, branchId, status) {
|
|
55
|
-
await store.updateBranch(branchId, { status: status ?? "closed" });
|
|
56
|
-
}
|
|
57
62
|
export {
|
|
63
|
+
assertBranchExists,
|
|
58
64
|
assertBranchMetadata,
|
|
59
|
-
assertBranchOpenForMerge,
|
|
60
65
|
assertNotABranch,
|
|
61
66
|
branchManagerApi,
|
|
62
|
-
closeBranch,
|
|
63
67
|
createBranchRecord,
|
|
64
68
|
generateBranchId,
|
|
65
69
|
wrapMergeCommit
|
package/dist/server/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export { buildVersionState, getBaseStateBeforeVersion } from '../algorithms/ot/s
|
|
|
9
9
|
export { concatStreams, jsonReadable, parseVersionState, readStreamAsString } from './jsonReadable.js';
|
|
10
10
|
export { blockable, blockableResponse, blocking, releaseConcurrency, singleInvocation } from '../utils/concurrency.js';
|
|
11
11
|
export { RevConflictError } from './RevConflictError.js';
|
|
12
|
-
export { BranchIdGenerator, BranchLoader,
|
|
12
|
+
export { BranchIdGenerator, BranchLoader, assertBranchExists, assertBranchMetadata, assertNotABranch, branchManagerApi, createBranchRecord, generateBranchId, wrapMergeCommit } from './branchUtils.js';
|
|
13
13
|
export { CompressedStoreBackend } from './CompressedStoreBackend.js';
|
|
14
14
|
export { createTombstoneIfSupported, isTombstoneStore, removeTombstoneIfExists } from './tombstone.js';
|
|
15
15
|
export { assertVersionMetadata } from './utils.js';
|
package/dist/server/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import { blockable, blockableResponse, blocking, releaseConcurrency, singleInvoc
|
|
|
11
11
|
import { RevConflictError } from "./RevConflictError.js";
|
|
12
12
|
import {
|
|
13
13
|
assertBranchMetadata,
|
|
14
|
-
|
|
14
|
+
assertBranchExists,
|
|
15
15
|
assertNotABranch,
|
|
16
16
|
branchManagerApi,
|
|
17
17
|
createBranchRecord,
|
|
@@ -30,8 +30,8 @@ export {
|
|
|
30
30
|
OTServer,
|
|
31
31
|
PatchesHistoryManager,
|
|
32
32
|
RevConflictError,
|
|
33
|
+
assertBranchExists,
|
|
33
34
|
assertBranchMetadata,
|
|
34
|
-
assertBranchOpenForMerge,
|
|
35
35
|
assertNotABranch,
|
|
36
36
|
assertVersionMetadata,
|
|
37
37
|
blockable,
|
package/dist/server/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { JSONPatchOp } from '../json-patch/types.js';
|
|
2
|
-
import { DocumentTombstone, VersionMetadata, Change, ListVersionsOptions, EditableVersionMetadata, ListChangesOptions, Branch } from '../types.js';
|
|
2
|
+
import { DocumentTombstone, VersionMetadata, Change, ListVersionsOptions, EditableVersionMetadata, ListChangesOptions, ListBranchesOptions, Branch } from '../types.js';
|
|
3
3
|
import '../json-patch/JSONPatch.js';
|
|
4
4
|
import '@dabble/delta';
|
|
5
5
|
|
|
@@ -150,13 +150,19 @@ interface BranchingStoreBackend {
|
|
|
150
150
|
*/
|
|
151
151
|
createBranchId?(docId: string): Promise<string> | string;
|
|
152
152
|
/** Lists metadata records for branches originating from a document. */
|
|
153
|
-
listBranches(docId: string): Promise<Branch[]>;
|
|
153
|
+
listBranches(docId: string, options?: ListBranchesOptions): Promise<Branch[]>;
|
|
154
154
|
/** Loads the metadata record for a specific branch ID. */
|
|
155
155
|
loadBranch(branchId: string): Promise<Branch | null>;
|
|
156
156
|
/** Creates or updates the metadata record for a branch. */
|
|
157
157
|
createBranch(branch: Branch): Promise<void>;
|
|
158
|
-
/** Updates
|
|
159
|
-
updateBranch(branchId: string, updates: Partial<
|
|
158
|
+
/** Updates mutable fields of an existing branch record (excludes immutable identity fields). */
|
|
159
|
+
updateBranch(branchId: string, updates: Partial<Omit<Branch, 'id' | 'docId' | 'branchedAtRev' | 'createdAt' | 'contentStartRev'>>): Promise<void>;
|
|
160
|
+
/**
|
|
161
|
+
* Replaces a branch record with a tombstone containing only `id`, `docId`, `modifiedAt`,
|
|
162
|
+
* and `deleted: true`. Tombstones are returned by `listBranches` when `since` is provided
|
|
163
|
+
* so that clients can clean up their local cache.
|
|
164
|
+
*/
|
|
165
|
+
deleteBranch(branchId: string): Promise<void>;
|
|
160
166
|
}
|
|
161
167
|
|
|
162
168
|
export type { BranchingStoreBackend, LWWStoreBackend, ListFieldsOptions, OTStoreBackend, ServerStoreBackend, SnapshotResult, TombstoneStoreBackend, VersioningStoreBackend };
|
package/dist/solid/context.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ import '../client/ClientAlgorithm.js';
|
|
|
10
10
|
import '../BaseDoc-BT18xPxU.js';
|
|
11
11
|
import '../client/PatchesStore.js';
|
|
12
12
|
import '../algorithms/ot/shared/changeBatching.js';
|
|
13
|
+
import '../client/BranchClientStore.js';
|
|
13
14
|
import '../net/PatchesConnection.js';
|
|
14
15
|
import '../net/protocol/types.js';
|
|
15
16
|
import '../net/protocol/JSONRPCClient.js';
|
package/dist/solid/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ import '../BaseDoc-BT18xPxU.js';
|
|
|
15
15
|
import '../client/PatchesStore.js';
|
|
16
16
|
import '../net/PatchesSync.js';
|
|
17
17
|
import '../algorithms/ot/shared/changeBatching.js';
|
|
18
|
+
import '../client/BranchClientStore.js';
|
|
18
19
|
import '../net/PatchesConnection.js';
|
|
19
20
|
import '../net/protocol/types.js';
|
|
20
21
|
import '../net/protocol/JSONRPCClient.js';
|
package/dist/types.d.ts
CHANGED
|
@@ -81,8 +81,6 @@ interface DocSyncState {
|
|
|
81
81
|
syncError?: Error;
|
|
82
82
|
isLoaded: boolean;
|
|
83
83
|
}
|
|
84
|
-
/** Status options for a branch */
|
|
85
|
-
type BranchStatus = 'open' | 'closed' | 'merged' | 'archived' | 'abandoned';
|
|
86
84
|
interface Branch {
|
|
87
85
|
/** The ID of the branch document. */
|
|
88
86
|
id: string;
|
|
@@ -92,14 +90,53 @@ interface Branch {
|
|
|
92
90
|
branchedAtRev: number;
|
|
93
91
|
/** Unix timestamp in milliseconds when the branch was created. */
|
|
94
92
|
createdAt: number;
|
|
93
|
+
/** Unix timestamp in milliseconds when the branch was last modified. Updated on metadata changes. */
|
|
94
|
+
modifiedAt: number;
|
|
95
95
|
/** Optional user-friendly name for the branch. */
|
|
96
96
|
name?: string;
|
|
97
|
-
/**
|
|
98
|
-
|
|
97
|
+
/**
|
|
98
|
+
* The first revision on the branch that contains user content (after initialization changes).
|
|
99
|
+
* Initialization changes (e.g. the root-replace that seeds the branch with source state)
|
|
100
|
+
* are at revisions < contentStartRev and are skipped during merge.
|
|
101
|
+
* Typically 2 for a single-change initialization; higher when the initial state is split
|
|
102
|
+
* across multiple changes due to size limits.
|
|
103
|
+
*/
|
|
104
|
+
contentStartRev: number;
|
|
105
|
+
/**
|
|
106
|
+
* The branch-side revision through which changes have already been merged.
|
|
107
|
+
* Set after each merge so subsequent merges only pick up new changes.
|
|
108
|
+
* Undefined means the branch has never been merged.
|
|
109
|
+
*/
|
|
110
|
+
lastMergedRev?: number;
|
|
111
|
+
/** The pending operation to sync to the server. Set by BranchClientStore methods. */
|
|
112
|
+
pendingOp?: 'create' | 'update' | 'delete';
|
|
113
|
+
/** True when this branch has been deleted. Stored as a tombstone for incremental sync. */
|
|
114
|
+
deleted?: true;
|
|
99
115
|
/** Optional arbitrary metadata associated with the branch record. */
|
|
100
116
|
[metadata: string]: any;
|
|
101
117
|
}
|
|
102
|
-
type EditableBranchMetadata = Disallowed<Branch, 'id' | 'docId' | 'branchedAtRev' | 'createdAt' | '
|
|
118
|
+
type EditableBranchMetadata = Disallowed<Branch, 'id' | 'docId' | 'branchedAtRev' | 'createdAt' | 'modifiedAt' | 'contentStartRev' | 'pendingOp' | 'deleted'>;
|
|
119
|
+
/**
|
|
120
|
+
* Metadata for creating a new branch.
|
|
121
|
+
* Allows `id` and `contentStartRev` in addition to the fields allowed by `EditableBranchMetadata`.
|
|
122
|
+
* - `id`: Client-provided branch document ID. Required for offline branch creation.
|
|
123
|
+
* - `contentStartRev`: The first revision of user content. Set by the client when creating
|
|
124
|
+
* initial changes offline (the server uses this to know where user content begins during merge).
|
|
125
|
+
*/
|
|
126
|
+
type CreateBranchMetadata = Omit<Disallowed<Branch, 'docId' | 'branchedAtRev' | 'createdAt' | 'modifiedAt' | 'pendingOp' | 'deleted'>, 'contentStartRev'> & {
|
|
127
|
+
contentStartRev?: number;
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* Options for listing branches.
|
|
131
|
+
*/
|
|
132
|
+
interface ListBranchesOptions {
|
|
133
|
+
/**
|
|
134
|
+
* Only return branches modified after this timestamp (Unix ms).
|
|
135
|
+
* Enables incremental sync: after the initial full list, subsequent calls can pass the
|
|
136
|
+
* most recent `modifiedAt` value to fetch only updates.
|
|
137
|
+
*/
|
|
138
|
+
since?: number;
|
|
139
|
+
}
|
|
103
140
|
/**
|
|
104
141
|
* Represents a tombstone for a deleted document.
|
|
105
142
|
* Tombstones persist after deletion to inform late-connecting clients
|
|
@@ -239,4 +276,4 @@ type PathProxy<T = any> = IsAny<T> extends true ? DeepPathProxy : {
|
|
|
239
276
|
*/
|
|
240
277
|
type ChangeMutator<T> = (patch: JSONPatch, root: PathProxy<T>) => void;
|
|
241
278
|
|
|
242
|
-
export type { Branch,
|
|
279
|
+
export type { Branch, Change, ChangeInput, ChangeMutator, CommitChangesOptions, CreateBranchMetadata, DeleteDocOptions, DocSyncState, DocSyncStatus, DocumentTombstone, EditableBranchMetadata, EditableVersionMetadata, ListBranchesOptions, ListChangesOptions, ListVersionsOptions, PatchesSnapshot, PatchesState, PathProxy, VersionMetadata };
|
package/dist/vue/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ import '../BaseDoc-BT18xPxU.js';
|
|
|
15
15
|
import '../client/PatchesStore.js';
|
|
16
16
|
import '../net/PatchesSync.js';
|
|
17
17
|
import '../algorithms/ot/shared/changeBatching.js';
|
|
18
|
+
import '../client/BranchClientStore.js';
|
|
18
19
|
import '../net/PatchesConnection.js';
|
|
19
20
|
import '../net/protocol/types.js';
|
|
20
21
|
import '../net/protocol/JSONRPCClient.js';
|
package/dist/vue/provider.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ import '../client/ClientAlgorithm.js';
|
|
|
10
10
|
import '../BaseDoc-BT18xPxU.js';
|
|
11
11
|
import '../client/PatchesStore.js';
|
|
12
12
|
import '../algorithms/ot/shared/changeBatching.js';
|
|
13
|
+
import '../client/BranchClientStore.js';
|
|
13
14
|
import '../net/PatchesConnection.js';
|
|
14
15
|
import '../net/protocol/types.js';
|
|
15
16
|
import '../net/protocol/JSONRPCClient.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dabble/patches",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.9",
|
|
4
4
|
"description": "Immutable JSON Patch implementation based on RFC 6902 supporting operational transformation and last-writer-wins",
|
|
5
5
|
"author": "Jacob Wright <jacwright@gmail.com>",
|
|
6
6
|
"bugs": {
|