@dabble/patches 0.8.8 → 0.8.10
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 +1 -1
- 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 +73 -12
- package/dist/client/PatchesBranchClient.js +143 -14
- package/dist/client/index.d.ts +3 -1
- package/dist/compression/index.d.ts +15 -7
- package/dist/compression/index.js +13 -2
- package/dist/index.d.ts +4 -2
- package/dist/net/PatchesClient.d.ts +13 -8
- package/dist/net/PatchesClient.js +17 -10
- package/dist/net/PatchesSync.d.ts +36 -3
- package/dist/net/PatchesSync.js +72 -0
- package/dist/net/index.d.ts +2 -1
- package/dist/net/protocol/types.d.ts +5 -4
- package/dist/net/rest/PatchesREST.d.ts +8 -5
- package/dist/net/rest/PatchesREST.js +30 -20
- 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 -10
- package/dist/server/LWWBranchManager.d.ts +10 -9
- package/dist/server/LWWBranchManager.js +48 -31
- package/dist/server/LWWMemoryStoreBackend.d.ts +5 -2
- package/dist/server/LWWMemoryStoreBackend.js +21 -3
- package/dist/server/OTBranchManager.d.ts +14 -12
- package/dist/server/OTBranchManager.js +58 -66
- package/dist/server/branchUtils.d.ts +6 -15
- package/dist/server/branchUtils.js +16 -13
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/types.d.ts +8 -2
- package/dist/solid/context.d.ts +1 -0
- package/dist/solid/index.d.ts +2 -1
- package/dist/solid/primitives.d.ts +30 -155
- package/dist/solid/primitives.js +53 -219
- package/dist/types.d.ts +35 -6
- package/dist/vue/composables.d.ts +29 -170
- package/dist/vue/composables.js +59 -200
- package/dist/vue/index.d.ts +2 -1
- 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,49 +22,55 @@ 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 metadata - Additional optional metadata to store with the branch.
|
|
35
|
-
* @param initialChanges - Optional pre-built initialization changes. If provided, stored
|
|
36
|
-
* directly. If omitted, a root-replace change is generated from the source state at `rev`
|
|
37
|
-
* and split via breakChanges when maxPayloadBytes is set.
|
|
38
35
|
* @returns The ID of the new branch document.
|
|
39
36
|
*/
|
|
40
|
-
async createBranch(docId, rev, metadata
|
|
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
|
+
}
|
|
41
48
|
await assertNotABranch(this.store, docId);
|
|
42
|
-
const branchDocId = await generateBranchId(this.store, docId);
|
|
43
49
|
const now = Date.now();
|
|
44
|
-
let
|
|
45
|
-
if (
|
|
46
|
-
|
|
50
|
+
let contentStartRev;
|
|
51
|
+
if (metadata?.contentStartRev) {
|
|
52
|
+
contentStartRev = metadata.contentStartRev;
|
|
47
53
|
} else {
|
|
48
54
|
const { state: stateAtRev } = await getStateAtRevision(this.store, docId, rev);
|
|
49
55
|
const rootReplace = createChange(0, 1, [{ op: "replace", path: "", value: stateAtRev }], {
|
|
50
56
|
createdAt: now,
|
|
51
57
|
committedAt: now
|
|
52
58
|
});
|
|
53
|
-
initChanges = this.maxPayloadBytes ? breakChanges([rootReplace], this.maxPayloadBytes) : [rootReplace];
|
|
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);
|
|
54
73
|
}
|
|
55
|
-
const contentStartRev = initChanges[initChanges.length - 1].rev + 1;
|
|
56
|
-
await this.store.saveChanges(branchDocId, initChanges);
|
|
57
|
-
const initialVersionMetadata = createVersionMetadata({
|
|
58
|
-
origin: "main",
|
|
59
|
-
startedAt: now,
|
|
60
|
-
endedAt: now,
|
|
61
|
-
endRev: rev,
|
|
62
|
-
startRev: rev,
|
|
63
|
-
name: metadata?.name,
|
|
64
|
-
groupId: branchDocId,
|
|
65
|
-
branchName: metadata?.name
|
|
66
|
-
});
|
|
67
|
-
await this.store.createVersion(branchDocId, initialVersionMetadata, initChanges);
|
|
68
74
|
const branch = createBranchRecord(branchDocId, docId, rev, contentStartRev, metadata);
|
|
69
75
|
await this.store.createBranch(branch);
|
|
70
76
|
return branchDocId;
|
|
@@ -76,79 +82,65 @@ class OTBranchManager {
|
|
|
76
82
|
*/
|
|
77
83
|
async updateBranch(branchId, metadata) {
|
|
78
84
|
assertBranchMetadata(metadata);
|
|
79
|
-
await this.store.updateBranch(branchId, metadata);
|
|
85
|
+
await this.store.updateBranch(branchId, { ...metadata, modifiedAt: Date.now() });
|
|
80
86
|
}
|
|
81
87
|
/**
|
|
82
|
-
*
|
|
83
|
-
* @param branchId - The ID of the branch to close.
|
|
84
|
-
* @param status - The status to set for the branch.
|
|
88
|
+
* Deletes a branch, replacing the record with a tombstone.
|
|
85
89
|
*/
|
|
86
|
-
async
|
|
87
|
-
await this.store.
|
|
90
|
+
async deleteBranch(branchId) {
|
|
91
|
+
await this.store.deleteBranch(branchId);
|
|
88
92
|
}
|
|
89
93
|
/**
|
|
90
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
|
+
*
|
|
91
102
|
* @param branchId - The ID of the branch document to merge.
|
|
92
103
|
* @returns The server commit change(s) applied to the source document.
|
|
93
104
|
* @throws Error if branch not found, already closed/merged, or merge fails.
|
|
94
105
|
*/
|
|
95
106
|
async mergeBranch(branchId) {
|
|
96
107
|
const branch = await this.store.loadBranch(branchId);
|
|
97
|
-
|
|
108
|
+
assertBranchExists(branch, branchId);
|
|
98
109
|
const sourceDocId = branch.docId;
|
|
99
110
|
const branchStartRevOnSource = branch.branchedAtRev;
|
|
100
|
-
const
|
|
101
|
-
const branchChanges = await this.store.listChanges(branchId, { startAfter
|
|
111
|
+
const startAfter = branch.lastMergedRev ?? (branch.contentStartRev ?? 2) - 1;
|
|
112
|
+
const branchChanges = await this.store.listChanges(branchId, { startAfter });
|
|
102
113
|
if (branchChanges.length === 0) {
|
|
103
|
-
console.log(`Branch ${branchId} has no changes to merge.`);
|
|
104
|
-
await this.closeBranch(branchId, "merged");
|
|
105
114
|
return [];
|
|
106
115
|
}
|
|
107
|
-
const
|
|
108
|
-
startAfter: branchStartRevOnSource
|
|
109
|
-
});
|
|
110
|
-
const canFastForward = sourceChanges.length === 0;
|
|
116
|
+
const lastBranchRev = branchChanges[branchChanges.length - 1].rev;
|
|
111
117
|
const branchVersions = await this.store.listVersions(branchId, { origin: "main" });
|
|
112
|
-
const versionOrigin = canFastForward ? "main" : "branch";
|
|
113
118
|
let lastVersionId;
|
|
114
119
|
for (const v of branchVersions) {
|
|
115
120
|
const newVersionMetadata = createVersionMetadata({
|
|
116
121
|
...v,
|
|
117
|
-
origin:
|
|
122
|
+
origin: "branch",
|
|
118
123
|
startRev: branchStartRevOnSource,
|
|
119
124
|
groupId: branchId,
|
|
120
125
|
branchName: branch.name,
|
|
121
|
-
// Keep branchName for traceability
|
|
122
126
|
parentId: lastVersionId
|
|
123
127
|
});
|
|
124
128
|
const changes = await this.store.loadVersionChanges?.(branchId, v.id);
|
|
125
129
|
await this.store.createVersion(sourceDocId, newVersionMetadata, changes);
|
|
126
130
|
lastVersionId = newVersionMetadata.id;
|
|
127
131
|
}
|
|
132
|
+
const changesToCommit = branchChanges.map((c, i) => ({
|
|
133
|
+
...c,
|
|
134
|
+
baseRev: branchStartRevOnSource,
|
|
135
|
+
rev: branchStartRevOnSource + i + 1,
|
|
136
|
+
batchId: branchId
|
|
137
|
+
}));
|
|
128
138
|
const committedMergeChanges = await wrapMergeCommit(branchId, sourceDocId, async () => {
|
|
129
|
-
|
|
130
|
-
const adjustedChanges = branchChanges.map((c) => ({
|
|
131
|
-
...c,
|
|
132
|
-
baseRev: branchStartRevOnSource,
|
|
133
|
-
rev: void 0
|
|
134
|
-
// Let commitChanges assign sequential revs
|
|
135
|
-
}));
|
|
136
|
-
return (await this.patchesServer.commitChanges(sourceDocId, adjustedChanges)).changes;
|
|
137
|
-
} else {
|
|
138
|
-
const rev = branchStartRevOnSource + branchChanges.length;
|
|
139
|
-
const flattenedChange = createChange(
|
|
140
|
-
branchStartRevOnSource,
|
|
141
|
-
rev,
|
|
142
|
-
branchChanges.flatMap((c) => c.ops)
|
|
143
|
-
);
|
|
144
|
-
let changesToCommit = [flattenedChange];
|
|
145
|
-
if (this.maxPayloadBytes) {
|
|
146
|
-
changesToCommit = breakChanges(changesToCommit, this.maxPayloadBytes);
|
|
147
|
-
}
|
|
148
|
-
return (await this.patchesServer.commitChanges(sourceDocId, changesToCommit)).changes;
|
|
149
|
-
}
|
|
139
|
+
return (await this.patchesServer.commitChanges(sourceDocId, changesToCommit)).changes;
|
|
150
140
|
});
|
|
151
|
-
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() });
|
|
152
144
|
return committedMergeChanges;
|
|
153
145
|
}
|
|
154
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';
|
|
@@ -40,7 +40,7 @@ declare function generateBranchId(store: BranchIdGenerator, docId: string): Prom
|
|
|
40
40
|
* @param metadata - Optional branch metadata (name, etc.).
|
|
41
41
|
* @returns A new Branch object.
|
|
42
42
|
*/
|
|
43
|
-
declare function createBranchRecord(branchDocId: string, sourceDocId: string, branchedAtRev: number, contentStartRev: number, metadata?: EditableBranchMetadata): Branch;
|
|
43
|
+
declare function createBranchRecord(branchDocId: string, sourceDocId: string, branchedAtRev: number, contentStartRev: number, metadata?: CreateBranchMetadata | EditableBranchMetadata): Branch;
|
|
44
44
|
/**
|
|
45
45
|
* Store interface for branch loading.
|
|
46
46
|
*/
|
|
@@ -55,12 +55,12 @@ interface BranchLoader {
|
|
|
55
55
|
*/
|
|
56
56
|
declare function assertNotABranch(store: BranchLoader, docId: string): Promise<void>;
|
|
57
57
|
/**
|
|
58
|
-
* Validates that a branch exists
|
|
58
|
+
* Validates that a branch exists.
|
|
59
59
|
* @param branch - The branch to validate (may be null).
|
|
60
60
|
* @param branchId - The branch ID (for error messages).
|
|
61
|
-
* @throws Error if branch not found
|
|
61
|
+
* @throws Error if branch not found.
|
|
62
62
|
*/
|
|
63
|
-
declare function
|
|
63
|
+
declare function assertBranchExists(branch: Branch | null, branchId: string): asserts branch is Branch;
|
|
64
64
|
/**
|
|
65
65
|
* Wraps a merge commit operation with standard error handling.
|
|
66
66
|
* @param branchId - The branch being merged.
|
|
@@ -70,14 +70,5 @@ declare function assertBranchOpenForMerge(branch: Branch | null, branchId: strin
|
|
|
70
70
|
* @throws Error with standardized message if commit fails.
|
|
71
71
|
*/
|
|
72
72
|
declare function wrapMergeCommit<T>(branchId: string, sourceDocId: string, commitFn: () => Promise<T>): Promise<T>;
|
|
73
|
-
/**
|
|
74
|
-
* Standard close branch operation.
|
|
75
|
-
* @param store - Store with updateBranch capability.
|
|
76
|
-
* @param branchId - The branch to close.
|
|
77
|
-
* @param status - The status to set (defaults to 'closed').
|
|
78
|
-
*/
|
|
79
|
-
declare function closeBranch(store: {
|
|
80
|
-
updateBranch(branchId: string, updates: Partial<Pick<Branch, 'status' | 'name'>>): Promise<void>;
|
|
81
|
-
}, branchId: string, status?: Exclude<BranchStatus, 'open'> | null): Promise<void>;
|
|
82
73
|
|
|
83
|
-
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) {
|
|
@@ -20,14 +29,15 @@ async function generateBranchId(store, docId) {
|
|
|
20
29
|
return store.createBranchId ? await Promise.resolve(store.createBranchId(docId)) : createId(22);
|
|
21
30
|
}
|
|
22
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
38
|
contentStartRev,
|
|
29
|
-
createdAt:
|
|
30
|
-
|
|
39
|
+
createdAt: now,
|
|
40
|
+
modifiedAt: now
|
|
31
41
|
};
|
|
32
42
|
}
|
|
33
43
|
async function assertNotABranch(store, docId) {
|
|
@@ -36,13 +46,10 @@ async function assertNotABranch(store, docId) {
|
|
|
36
46
|
throw new Error("Cannot create a branch from another branch.");
|
|
37
47
|
}
|
|
38
48
|
}
|
|
39
|
-
function
|
|
49
|
+
function assertBranchExists(branch, branchId) {
|
|
40
50
|
if (!branch) {
|
|
41
51
|
throw new Error(`Branch with ID ${branchId} not found.`);
|
|
42
52
|
}
|
|
43
|
-
if (branch.status !== "open") {
|
|
44
|
-
throw new Error(`Branch ${branchId} is not open (status: ${branch.status}). Cannot merge.`);
|
|
45
|
-
}
|
|
46
53
|
}
|
|
47
54
|
async function wrapMergeCommit(branchId, sourceDocId, commitFn) {
|
|
48
55
|
try {
|
|
@@ -52,15 +59,11 @@ async function wrapMergeCommit(branchId, sourceDocId, commitFn) {
|
|
|
52
59
|
throw new Error(`Merge failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
53
60
|
}
|
|
54
61
|
}
|
|
55
|
-
async function closeBranch(store, branchId, status) {
|
|
56
|
-
await store.updateBranch(branchId, { status: status ?? "closed" });
|
|
57
|
-
}
|
|
58
62
|
export {
|
|
63
|
+
assertBranchExists,
|
|
59
64
|
assertBranchMetadata,
|
|
60
|
-
assertBranchOpenForMerge,
|
|
61
65
|
assertNotABranch,
|
|
62
66
|
branchManagerApi,
|
|
63
|
-
closeBranch,
|
|
64
67
|
createBranchRecord,
|
|
65
68
|
generateBranchId,
|
|
66
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
158
|
/** Updates mutable fields of an existing branch record (excludes immutable identity fields). */
|
|
159
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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { PatchesContextValue, PatchesProvider, PatchesProviderProps, usePatchesContext } from './context.js';
|
|
2
|
-
export { MaybeAccessor, PatchesDocProviderProps,
|
|
2
|
+
export { MaybeAccessor, PatchesDocProviderProps, UsePatchesDocOptions, UsePatchesDocReturn, UsePatchesSyncReturn, createPatchesDoc, usePatchesDoc, usePatchesSync } from './primitives.js';
|
|
3
3
|
export { CreateManagedDocsOptions, CreateManagedDocsReturn, createManagedDocs } from './managed-docs.js';
|
|
4
4
|
export { fillPath } from '../shared/utils.js';
|
|
5
5
|
export { DocManager, getDocManager } from '../shared/doc-manager.js';
|
|
@@ -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';
|
|
@@ -1,172 +1,78 @@
|
|
|
1
1
|
import { Accessor } from 'solid-js';
|
|
2
2
|
import { OpenDocOptions } from '../client/Patches.js';
|
|
3
3
|
import { P as PatchesDoc } from '../BaseDoc-BT18xPxU.js';
|
|
4
|
-
import { JSONPatch } from '../json-patch/JSONPatch.js';
|
|
5
4
|
import { ChangeMutator } from '../types.js';
|
|
6
5
|
import 'easy-signal';
|
|
7
6
|
import '../json-patch/types.js';
|
|
8
7
|
import '../client/ClientAlgorithm.js';
|
|
9
8
|
import '../client/PatchesStore.js';
|
|
9
|
+
import '../json-patch/JSONPatch.js';
|
|
10
10
|
import '@dabble/delta';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Options for usePatchesDoc primitive
|
|
13
|
+
* Options for usePatchesDoc primitive.
|
|
14
14
|
*/
|
|
15
15
|
interface UsePatchesDocOptions extends OpenDocOptions {
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* - `false` (default): Explicit mode. Assumes doc is already open. Throws if not.
|
|
20
|
-
* - `true`: Opens doc on mount with ref counting, closes on cleanup (doc stays tracked).
|
|
21
|
-
* - `'untrack'`: Opens doc on mount, closes AND untracks on cleanup (removes from sync).
|
|
22
|
-
*
|
|
23
|
-
* @default false
|
|
17
|
+
* When true, the document is removed from sync tracking on close.
|
|
18
|
+
* By default documents stay tracked after closing.
|
|
24
19
|
*/
|
|
25
|
-
|
|
20
|
+
untrack?: boolean;
|
|
26
21
|
}
|
|
27
22
|
/**
|
|
28
|
-
*
|
|
29
|
-
*/
|
|
30
|
-
interface UsePatchesDocLazyOptions {
|
|
31
|
-
/**
|
|
32
|
-
* Inject doc.id into state under this key on every state update.
|
|
33
|
-
* Useful when the document ID is derived from the path but needed in the data.
|
|
34
|
-
*/
|
|
35
|
-
idProp?: string;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Return type for usePatchesDoc primitive (eager mode).
|
|
23
|
+
* Return type for usePatchesDoc primitive.
|
|
39
24
|
*/
|
|
40
25
|
interface UsePatchesDocReturn<T extends object> {
|
|
41
|
-
/**
|
|
42
|
-
* Accessor for the document state.
|
|
43
|
-
* Updated whenever the document changes (local or remote).
|
|
44
|
-
*/
|
|
26
|
+
/** Accessor for the document state. */
|
|
45
27
|
data: Accessor<T | undefined>;
|
|
46
|
-
/**
|
|
47
|
-
* Whether the document is currently loading/syncing.
|
|
48
|
-
* - `true` during initial load or updates
|
|
49
|
-
* - `false` when fully synced
|
|
50
|
-
*/
|
|
28
|
+
/** Whether the document is currently loading. */
|
|
51
29
|
loading: Accessor<boolean>;
|
|
52
|
-
/**
|
|
53
|
-
* Error that occurred during sync, if any.
|
|
54
|
-
*/
|
|
30
|
+
/** Error that occurred during sync, if any. */
|
|
55
31
|
error: Accessor<Error | undefined>;
|
|
56
|
-
/**
|
|
57
|
-
* The committed revision number.
|
|
58
|
-
* Increments each time the server confirms changes.
|
|
59
|
-
*/
|
|
32
|
+
/** The committed revision number. */
|
|
60
33
|
rev: Accessor<number>;
|
|
61
|
-
/**
|
|
62
|
-
* Whether there are pending local changes not yet committed by server.
|
|
63
|
-
*/
|
|
34
|
+
/** Whether there are pending local changes not yet committed by server. */
|
|
64
35
|
hasPending: Accessor<boolean>;
|
|
65
|
-
/**
|
|
66
|
-
* Make changes to the document.
|
|
67
|
-
*
|
|
68
|
-
* @example
|
|
69
|
-
* ```typescript
|
|
70
|
-
* change((patch, root) => {
|
|
71
|
-
* patch.replace(root.title!, 'New Title')
|
|
72
|
-
* })
|
|
73
|
-
* ```
|
|
74
|
-
*/
|
|
36
|
+
/** Make changes to the document. No-ops if the document is not loaded. */
|
|
75
37
|
change: (mutator: ChangeMutator<T>) => void;
|
|
76
|
-
/**
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
*/
|
|
38
|
+
/** Close the document and reset state. Useful for explicit cleanup. */
|
|
39
|
+
close: () => Promise<void>;
|
|
40
|
+
/** The underlying PatchesDoc instance. */
|
|
80
41
|
doc: Accessor<PatchesDoc<T> | undefined>;
|
|
81
42
|
}
|
|
82
43
|
/**
|
|
83
|
-
*
|
|
84
|
-
* Extends the eager return type with lifecycle management methods.
|
|
44
|
+
* Type for document ID — can be a static string or accessor function.
|
|
85
45
|
*/
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Current document path. `null` when no document is loaded.
|
|
89
|
-
*/
|
|
90
|
-
path: Accessor<string | null>;
|
|
91
|
-
/**
|
|
92
|
-
* Open a document by path. Closes any previously loaded document first.
|
|
93
|
-
*
|
|
94
|
-
* @param docPath - The document path to open
|
|
95
|
-
* @param options - Optional algorithm and metadata overrides
|
|
96
|
-
*/
|
|
97
|
-
load: (docPath: string, options?: OpenDocOptions) => Promise<void>;
|
|
98
|
-
/**
|
|
99
|
-
* Close the current document, unsubscribe, and reset all state.
|
|
100
|
-
* Calls `patches.closeDoc()` but does not untrack — tracking is managed separately.
|
|
101
|
-
*/
|
|
102
|
-
close: () => Promise<void>;
|
|
103
|
-
/**
|
|
104
|
-
* Create a new document: open it, set initial state, then close it.
|
|
105
|
-
* A one-shot operation that doesn't bind the document to this handle.
|
|
106
|
-
*
|
|
107
|
-
* @param docPath - The document path to create
|
|
108
|
-
* @param initialState - Initial state object or JSONPatch to apply
|
|
109
|
-
* @param options - Optional algorithm and metadata overrides
|
|
110
|
-
*/
|
|
111
|
-
create: (docPath: string, initialState: T | JSONPatch, options?: OpenDocOptions) => Promise<void>;
|
|
112
|
-
}
|
|
46
|
+
type MaybeAccessor<T> = T | Accessor<T>;
|
|
113
47
|
/**
|
|
114
48
|
* Solid primitive for reactive Patches document state.
|
|
115
49
|
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
50
|
+
* Opens the document automatically and closes it on cleanup (or when the
|
|
51
|
+
* accessor value changes). Accepts a static string or an accessor. When the
|
|
52
|
+
* value is falsy, no document is loaded.
|
|
119
53
|
*
|
|
120
54
|
* @example
|
|
121
55
|
* ```tsx
|
|
122
|
-
* //
|
|
123
|
-
* const { data,
|
|
56
|
+
* // Static
|
|
57
|
+
* const { data, change } = usePatchesDoc('doc-123')
|
|
124
58
|
*
|
|
125
|
-
* //
|
|
126
|
-
* const
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
* ## Lazy Mode (without docId)
|
|
130
|
-
*
|
|
131
|
-
* Returns a deferred handle with `load()`, `close()`, and `create()` methods.
|
|
132
|
-
* Does NOT register `onCleanup` — caller manages lifecycle.
|
|
133
|
-
*
|
|
134
|
-
* @example
|
|
135
|
-
* ```tsx
|
|
136
|
-
* const { data, load, close, change, create } = usePatchesDoc<Project>()
|
|
137
|
-
*
|
|
138
|
-
* // Later, when the user navigates:
|
|
139
|
-
* await load('projects/abc/content')
|
|
140
|
-
*
|
|
141
|
-
* // When leaving:
|
|
142
|
-
* await close()
|
|
59
|
+
* // Reactive — swaps automatically
|
|
60
|
+
* const [projectId, setProjectId] = createSignal<string | null>('abc')
|
|
61
|
+
* const { data, change } = usePatchesDoc(() => projectId() && `projects/${projectId()}`)
|
|
143
62
|
* ```
|
|
144
63
|
*/
|
|
145
|
-
declare function usePatchesDoc<T extends object>(docId: MaybeAccessor<string>, options?: UsePatchesDocOptions): UsePatchesDocReturn<T>;
|
|
146
|
-
declare function usePatchesDoc<T extends object>(options?: UsePatchesDocLazyOptions): UsePatchesDocLazyReturn<T>;
|
|
64
|
+
declare function usePatchesDoc<T extends object>(docId: MaybeAccessor<string | null | undefined | false>, options?: UsePatchesDocOptions): UsePatchesDocReturn<T>;
|
|
147
65
|
/**
|
|
148
66
|
* Return type for usePatchesSync primitive.
|
|
149
67
|
*/
|
|
150
68
|
interface UsePatchesSyncReturn {
|
|
151
|
-
/**
|
|
152
|
-
* Whether the WebSocket connection is established.
|
|
153
|
-
*/
|
|
154
69
|
connected: Accessor<boolean>;
|
|
155
|
-
/**
|
|
156
|
-
* Whether documents are currently syncing with the server.
|
|
157
|
-
*/
|
|
158
70
|
syncing: Accessor<boolean>;
|
|
159
|
-
/**
|
|
160
|
-
* Whether the client believes it has network connectivity.
|
|
161
|
-
*/
|
|
162
71
|
online: Accessor<boolean>;
|
|
163
72
|
}
|
|
164
73
|
/**
|
|
165
74
|
* Solid primitive for reactive Patches sync state.
|
|
166
75
|
*
|
|
167
|
-
* Provides reactive access to PatchesSync connection and sync status.
|
|
168
|
-
* Useful for showing "Offline" banners, global loading indicators, etc.
|
|
169
|
-
*
|
|
170
76
|
* @example
|
|
171
77
|
* ```tsx
|
|
172
78
|
* const { connected, syncing, online } = usePatchesSync();
|
|
@@ -177,68 +83,37 @@ interface UsePatchesSyncReturn {
|
|
|
177
83
|
* </Show>
|
|
178
84
|
* );
|
|
179
85
|
* ```
|
|
180
|
-
*
|
|
181
|
-
* @returns Reactive sync state
|
|
182
|
-
* @throws Error if Patches context not provided
|
|
183
|
-
* @throws Error if PatchesSync was not provided to context
|
|
184
86
|
*/
|
|
185
87
|
declare function usePatchesSync(): UsePatchesSyncReturn;
|
|
186
|
-
/**
|
|
187
|
-
* Type for document ID - can be static string or accessor function.
|
|
188
|
-
*/
|
|
189
|
-
type MaybeAccessor<T> = T | Accessor<T>;
|
|
190
88
|
/**
|
|
191
89
|
* Props for the Provider component returned by createPatchesDoc.
|
|
192
90
|
*/
|
|
193
91
|
interface PatchesDocProviderProps extends OpenDocOptions {
|
|
194
92
|
docId: MaybeAccessor<string>;
|
|
195
|
-
|
|
93
|
+
untrack?: boolean;
|
|
196
94
|
children: any;
|
|
197
95
|
}
|
|
198
96
|
/**
|
|
199
97
|
* Creates a named document context that can be provided to child components.
|
|
200
98
|
*
|
|
201
|
-
*
|
|
202
|
-
* without needing to pass the docId down through props. Supports both static and
|
|
203
|
-
* reactive docIds.
|
|
204
|
-
*
|
|
205
|
-
* ## Use Cases
|
|
206
|
-
*
|
|
207
|
-
* **Static document (user settings):**
|
|
99
|
+
* @example
|
|
208
100
|
* ```tsx
|
|
209
101
|
* const { Provider, useDoc } = createPatchesDoc<User>('user');
|
|
210
102
|
*
|
|
211
103
|
* <Provider docId="user-123">
|
|
212
104
|
* <UserProfile />
|
|
213
105
|
* </Provider>
|
|
214
|
-
* ```
|
|
215
106
|
*
|
|
216
|
-
*
|
|
217
|
-
* ```tsx
|
|
218
|
-
* const { Provider, useDoc } = createPatchesDoc<Whiteboard>('whiteboard');
|
|
107
|
+
* // Reactive
|
|
219
108
|
* const [activeTabId, setActiveTabId] = createSignal('design-1');
|
|
220
|
-
*
|
|
221
109
|
* <Provider docId={activeTabId}>
|
|
222
110
|
* <WhiteboardCanvas />
|
|
223
111
|
* </Provider>
|
|
224
112
|
* ```
|
|
225
|
-
*
|
|
226
|
-
* **With autoClose:**
|
|
227
|
-
* ```tsx
|
|
228
|
-
* const { Provider, useDoc } = createPatchesDoc<Doc>('document');
|
|
229
|
-
* const [currentDocId, setCurrentDocId] = createSignal('doc-1');
|
|
230
|
-
*
|
|
231
|
-
* <Provider docId={currentDocId} autoClose>
|
|
232
|
-
* <DocumentEditor />
|
|
233
|
-
* </Provider>
|
|
234
|
-
* ```
|
|
235
|
-
*
|
|
236
|
-
* @param name - Unique identifier for this document context (e.g., 'whiteboard', 'user')
|
|
237
|
-
* @returns Object with Provider component and useDoc hook
|
|
238
113
|
*/
|
|
239
114
|
declare function createPatchesDoc<T extends object>(name: string): {
|
|
240
115
|
Provider: (props: PatchesDocProviderProps) => any;
|
|
241
116
|
useDoc: () => UsePatchesDocReturn<T>;
|
|
242
117
|
};
|
|
243
118
|
|
|
244
|
-
export { type MaybeAccessor, type PatchesDocProviderProps, type
|
|
119
|
+
export { type MaybeAccessor, type PatchesDocProviderProps, type UsePatchesDocOptions, type UsePatchesDocReturn, type UsePatchesSyncReturn, createPatchesDoc, usePatchesDoc, usePatchesSync };
|