@dabble/patches 0.8.11 → 0.8.13
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/client/IndexedDBStore.d.ts +17 -3
- package/dist/client/IndexedDBStore.js +38 -12
- package/dist/client/LWWIndexedDBStore.d.ts +1 -1
- package/dist/client/LWWIndexedDBStore.js +16 -15
- package/dist/client/OTIndexedDBStore.d.ts +1 -1
- package/dist/client/OTIndexedDBStore.js +14 -13
- package/dist/client/Patches.d.ts +0 -6
- package/dist/client/Patches.js +0 -11
- package/dist/client/PatchesBranchClient.d.ts +11 -8
- package/dist/client/PatchesBranchClient.js +16 -32
- package/dist/client/factories.d.ts +16 -1
- package/dist/client/factories.js +21 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/package.json +1 -1
|
@@ -13,6 +13,12 @@ import '../json-patch/types.js';
|
|
|
13
13
|
* Can be used as a standalone store or as a shared database connection
|
|
14
14
|
* for multiple algorithm-specific stores (OT, LWW).
|
|
15
15
|
*
|
|
16
|
+
* Supports two modes:
|
|
17
|
+
* - **Managed mode** (pass a `dbName`): Opens and owns the database lifecycle.
|
|
18
|
+
* - **External mode** (pass an `IDBDatabase` or `Promise<IDBDatabase>`): Uses a
|
|
19
|
+
* caller-provided database. The caller owns the lifecycle; `close()` detaches
|
|
20
|
+
* without closing, `deleteDB()` is a no-op, and `setName()` throws.
|
|
21
|
+
*
|
|
16
22
|
* Provides:
|
|
17
23
|
* - Database lifecycle management (open, close, delete)
|
|
18
24
|
* - Transaction helpers
|
|
@@ -26,29 +32,37 @@ declare class IndexedDBStore implements PatchesStore, BranchClientStore {
|
|
|
26
32
|
protected db: IDBDatabase | null;
|
|
27
33
|
protected dbName?: string;
|
|
28
34
|
protected dbPromise: Deferred<IDBDatabase>;
|
|
35
|
+
protected external: boolean;
|
|
29
36
|
/**
|
|
30
37
|
* Signal emitted during database upgrade, allowing algorithm-specific stores
|
|
31
38
|
* to create their object stores.
|
|
32
39
|
*/
|
|
33
40
|
readonly onUpgrade: easy_signal.Signal<(db: IDBDatabase, oldVersion: number, transaction: IDBTransaction) => void>;
|
|
34
|
-
constructor(
|
|
41
|
+
constructor(dbOrName?: string | IDBDatabase | Promise<IDBDatabase>);
|
|
35
42
|
/**
|
|
36
|
-
* Creates shared object stores
|
|
43
|
+
* Creates shared object stores (docs, snapshots, branches) during database upgrade.
|
|
37
44
|
*/
|
|
38
|
-
|
|
45
|
+
static upgradeSharedStores(db: IDBDatabase, transaction: IDBTransaction): void;
|
|
39
46
|
protected initDB(): Promise<void>;
|
|
40
47
|
protected getDB(): Promise<IDBDatabase>;
|
|
41
48
|
/**
|
|
42
49
|
* Set the name of the database, loads a new database connection.
|
|
43
50
|
* @param dbName - The new name of the database.
|
|
51
|
+
* @throws When using an externally-provided database.
|
|
44
52
|
*/
|
|
45
53
|
setName(dbName: string): void;
|
|
46
54
|
/**
|
|
47
55
|
* Closes the database connection. After calling this method, the store
|
|
48
56
|
* will no longer be usable. A new instance must be created to reopen
|
|
49
57
|
* the database.
|
|
58
|
+
*
|
|
59
|
+
* When using an externally-provided database, this detaches from the
|
|
60
|
+
* database without closing it (the caller owns the lifecycle).
|
|
50
61
|
*/
|
|
51
62
|
close(): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Deletes the database. No-op when using an externally-provided database.
|
|
65
|
+
*/
|
|
52
66
|
deleteDB(): Promise<void>;
|
|
53
67
|
transaction(storeNames: string[], mode: IDBTransactionMode): Promise<[IDBTransactionWrapper, ...IDBStoreWrapper[]]>;
|
|
54
68
|
/**
|
|
@@ -7,25 +7,38 @@ class IndexedDBStore {
|
|
|
7
7
|
db = null;
|
|
8
8
|
dbName;
|
|
9
9
|
dbPromise;
|
|
10
|
+
external;
|
|
10
11
|
/**
|
|
11
12
|
* Signal emitted during database upgrade, allowing algorithm-specific stores
|
|
12
13
|
* to create their object stores.
|
|
13
14
|
*/
|
|
14
15
|
onUpgrade = signal();
|
|
15
|
-
constructor(
|
|
16
|
-
this.dbName = dbName;
|
|
16
|
+
constructor(dbOrName) {
|
|
17
17
|
this.dbPromise = deferred();
|
|
18
|
-
|
|
19
|
-
this.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
if (dbOrName != null && typeof dbOrName !== "string") {
|
|
19
|
+
this.external = true;
|
|
20
|
+
Promise.resolve(dbOrName).then(
|
|
21
|
+
(db) => {
|
|
22
|
+
this.db = db;
|
|
23
|
+
this.dbPromise.resolve(db);
|
|
24
|
+
},
|
|
25
|
+
(err) => this.dbPromise.reject(err)
|
|
26
|
+
);
|
|
27
|
+
} else {
|
|
28
|
+
this.external = false;
|
|
29
|
+
this.dbName = dbOrName;
|
|
30
|
+
this.onUpgrade((db, _oldVersion, transaction) => {
|
|
31
|
+
IndexedDBStore.upgradeSharedStores(db, transaction);
|
|
32
|
+
});
|
|
33
|
+
if (this.dbName) {
|
|
34
|
+
this.initDB();
|
|
35
|
+
}
|
|
23
36
|
}
|
|
24
37
|
}
|
|
25
38
|
/**
|
|
26
|
-
* Creates shared object stores
|
|
39
|
+
* Creates shared object stores (docs, snapshots, branches) during database upgrade.
|
|
27
40
|
*/
|
|
28
|
-
|
|
41
|
+
static upgradeSharedStores(db, transaction) {
|
|
29
42
|
if (!db.objectStoreNames.contains("docs")) {
|
|
30
43
|
const docsStore = db.createObjectStore("docs", { keyPath: "docId" });
|
|
31
44
|
docsStore.createIndex("algorithm", "algorithm", { unique: false });
|
|
@@ -38,7 +51,7 @@ class IndexedDBStore {
|
|
|
38
51
|
branchStore.createIndex("_docId", "_docId", { unique: false });
|
|
39
52
|
branchStore.createIndex("_pending", "_pending", { unique: false });
|
|
40
53
|
} else {
|
|
41
|
-
const branchStore =
|
|
54
|
+
const branchStore = transaction.objectStore("branches");
|
|
42
55
|
if (!branchStore.indexNames.contains("_pending")) {
|
|
43
56
|
branchStore.createIndex("_pending", "_pending", { unique: false });
|
|
44
57
|
}
|
|
@@ -65,8 +78,12 @@ class IndexedDBStore {
|
|
|
65
78
|
/**
|
|
66
79
|
* Set the name of the database, loads a new database connection.
|
|
67
80
|
* @param dbName - The new name of the database.
|
|
81
|
+
* @throws When using an externally-provided database.
|
|
68
82
|
*/
|
|
69
83
|
setName(dbName) {
|
|
84
|
+
if (this.external) {
|
|
85
|
+
throw new Error("Cannot set name on an externally-provided database");
|
|
86
|
+
}
|
|
70
87
|
this.dbName = dbName;
|
|
71
88
|
if (this.db) {
|
|
72
89
|
this.db.close();
|
|
@@ -79,18 +96,27 @@ class IndexedDBStore {
|
|
|
79
96
|
* Closes the database connection. After calling this method, the store
|
|
80
97
|
* will no longer be usable. A new instance must be created to reopen
|
|
81
98
|
* the database.
|
|
99
|
+
*
|
|
100
|
+
* When using an externally-provided database, this detaches from the
|
|
101
|
+
* database without closing it (the caller owns the lifecycle).
|
|
82
102
|
*/
|
|
83
103
|
async close() {
|
|
104
|
+
if (!this.db) return;
|
|
84
105
|
await this.dbPromise.promise;
|
|
85
106
|
if (this.db) {
|
|
86
|
-
this.
|
|
107
|
+
if (!this.external) {
|
|
108
|
+
this.db.close();
|
|
109
|
+
}
|
|
87
110
|
this.db = null;
|
|
88
111
|
this.dbPromise = deferred();
|
|
89
112
|
this.dbPromise.reject(new Error("Store has been closed"));
|
|
90
113
|
}
|
|
91
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Deletes the database. No-op when using an externally-provided database.
|
|
117
|
+
*/
|
|
92
118
|
async deleteDB() {
|
|
93
|
-
if (!this.dbName) return;
|
|
119
|
+
if (this.external || !this.dbName) return;
|
|
94
120
|
await this.close();
|
|
95
121
|
await new Promise((resolve, reject) => {
|
|
96
122
|
const request = indexedDB.deleteDatabase(this.dbName);
|
|
@@ -32,7 +32,7 @@ declare class LWWIndexedDBStore implements LWWClientStore {
|
|
|
32
32
|
/**
|
|
33
33
|
* Creates LWW-specific object stores during database upgrade.
|
|
34
34
|
*/
|
|
35
|
-
|
|
35
|
+
static upgradeStores(db: IDBDatabase, _transaction: IDBTransaction): void;
|
|
36
36
|
/**
|
|
37
37
|
* List documents for the LWW algorithm.
|
|
38
38
|
* Uses the algorithm index for efficient querying.
|
|
@@ -12,19 +12,19 @@ import { blockable } from "../utils/concurrency.js";
|
|
|
12
12
|
import { IDBStoreWrapper, IndexedDBStore } from "./IndexedDBStore.js";
|
|
13
13
|
const SNAPSHOT_INTERVAL = 200;
|
|
14
14
|
_getDoc_dec = [blockable], _saveDoc_dec = [blockable], _deleteDoc_dec = [blockable], _getPendingOps_dec = [blockable], _savePendingOps_dec = [blockable], _getSendingChange_dec = [blockable], _saveSendingChange_dec = [blockable], _confirmSendingChange_dec = [blockable], _applyServerChanges_dec = [blockable];
|
|
15
|
-
class
|
|
15
|
+
const _LWWIndexedDBStore = class _LWWIndexedDBStore {
|
|
16
16
|
constructor(db) {
|
|
17
17
|
__runInitializers(_init, 5, this);
|
|
18
18
|
__publicField(this, "db");
|
|
19
19
|
this.db = !db || typeof db === "string" ? new IndexedDBStore(db) : db;
|
|
20
|
-
this.db.onUpgrade((db2, _oldVersion,
|
|
21
|
-
|
|
20
|
+
this.db.onUpgrade((db2, _oldVersion, transaction) => {
|
|
21
|
+
_LWWIndexedDBStore.upgradeStores(db2, transaction);
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
25
|
* Creates LWW-specific object stores during database upgrade.
|
|
26
26
|
*/
|
|
27
|
-
|
|
27
|
+
static upgradeStores(db, _transaction) {
|
|
28
28
|
if (!db.objectStoreNames.contains("committedOps")) {
|
|
29
29
|
db.createObjectStore("committedOps", { keyPath: ["docId", "path"] });
|
|
30
30
|
}
|
|
@@ -316,18 +316,19 @@ class LWWIndexedDBStore {
|
|
|
316
316
|
await snapshots.put({ docId, state, rev });
|
|
317
317
|
await this.deleteFieldsForDoc(committedOps, docId);
|
|
318
318
|
}
|
|
319
|
-
}
|
|
319
|
+
};
|
|
320
320
|
_init = __decoratorStart(null);
|
|
321
|
-
__decorateElement(_init, 1, "getDoc", _getDoc_dec,
|
|
322
|
-
__decorateElement(_init, 1, "saveDoc", _saveDoc_dec,
|
|
323
|
-
__decorateElement(_init, 1, "deleteDoc", _deleteDoc_dec,
|
|
324
|
-
__decorateElement(_init, 1, "getPendingOps", _getPendingOps_dec,
|
|
325
|
-
__decorateElement(_init, 1, "savePendingOps", _savePendingOps_dec,
|
|
326
|
-
__decorateElement(_init, 1, "getSendingChange", _getSendingChange_dec,
|
|
327
|
-
__decorateElement(_init, 1, "saveSendingChange", _saveSendingChange_dec,
|
|
328
|
-
__decorateElement(_init, 1, "confirmSendingChange", _confirmSendingChange_dec,
|
|
329
|
-
__decorateElement(_init, 1, "applyServerChanges", _applyServerChanges_dec,
|
|
330
|
-
__decoratorMetadata(_init,
|
|
321
|
+
__decorateElement(_init, 1, "getDoc", _getDoc_dec, _LWWIndexedDBStore);
|
|
322
|
+
__decorateElement(_init, 1, "saveDoc", _saveDoc_dec, _LWWIndexedDBStore);
|
|
323
|
+
__decorateElement(_init, 1, "deleteDoc", _deleteDoc_dec, _LWWIndexedDBStore);
|
|
324
|
+
__decorateElement(_init, 1, "getPendingOps", _getPendingOps_dec, _LWWIndexedDBStore);
|
|
325
|
+
__decorateElement(_init, 1, "savePendingOps", _savePendingOps_dec, _LWWIndexedDBStore);
|
|
326
|
+
__decorateElement(_init, 1, "getSendingChange", _getSendingChange_dec, _LWWIndexedDBStore);
|
|
327
|
+
__decorateElement(_init, 1, "saveSendingChange", _saveSendingChange_dec, _LWWIndexedDBStore);
|
|
328
|
+
__decorateElement(_init, 1, "confirmSendingChange", _confirmSendingChange_dec, _LWWIndexedDBStore);
|
|
329
|
+
__decorateElement(_init, 1, "applyServerChanges", _applyServerChanges_dec, _LWWIndexedDBStore);
|
|
330
|
+
__decoratorMetadata(_init, _LWWIndexedDBStore);
|
|
331
|
+
let LWWIndexedDBStore = _LWWIndexedDBStore;
|
|
331
332
|
export {
|
|
332
333
|
LWWIndexedDBStore
|
|
333
334
|
};
|
|
@@ -33,7 +33,7 @@ declare class OTIndexedDBStore implements OTClientStore {
|
|
|
33
33
|
/**
|
|
34
34
|
* Creates OT-specific object stores during database upgrade.
|
|
35
35
|
*/
|
|
36
|
-
|
|
36
|
+
static upgradeStores(db: IDBDatabase, _transaction: IDBTransaction): void;
|
|
37
37
|
/**
|
|
38
38
|
* List documents for the OT algorithm.
|
|
39
39
|
* Uses the algorithm index for efficient querying.
|
|
@@ -11,19 +11,19 @@ import { blockable } from "../utils/concurrency.js";
|
|
|
11
11
|
import { IndexedDBStore } from "./IndexedDBStore.js";
|
|
12
12
|
const SNAPSHOT_INTERVAL = 200;
|
|
13
13
|
_getDoc_dec = [blockable], _deleteDoc_dec = [blockable], _saveDoc_dec = [blockable], _savePendingChanges_dec = [blockable], _getPendingChanges_dec = [blockable], _listChanges_dec = [blockable], _applyServerChanges_dec = [blockable];
|
|
14
|
-
class
|
|
14
|
+
const _OTIndexedDBStore = class _OTIndexedDBStore {
|
|
15
15
|
constructor(db) {
|
|
16
16
|
__runInitializers(_init, 5, this);
|
|
17
17
|
__publicField(this, "db");
|
|
18
18
|
this.db = !db || typeof db === "string" ? new IndexedDBStore(db) : db;
|
|
19
|
-
this.db.onUpgrade((db2, _oldVersion,
|
|
20
|
-
|
|
19
|
+
this.db.onUpgrade((db2, _oldVersion, transaction) => {
|
|
20
|
+
_OTIndexedDBStore.upgradeStores(db2, transaction);
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
24
|
* Creates OT-specific object stores during database upgrade.
|
|
25
25
|
*/
|
|
26
|
-
|
|
26
|
+
static upgradeStores(db, _transaction) {
|
|
27
27
|
if (!db.objectStoreNames.contains("committedChanges")) {
|
|
28
28
|
db.createObjectStore("committedChanges", { keyPath: ["docId", "rev"] });
|
|
29
29
|
}
|
|
@@ -207,16 +207,17 @@ class OTIndexedDBStore {
|
|
|
207
207
|
);
|
|
208
208
|
await tx.complete();
|
|
209
209
|
}
|
|
210
|
-
}
|
|
210
|
+
};
|
|
211
211
|
_init = __decoratorStart(null);
|
|
212
|
-
__decorateElement(_init, 1, "getDoc", _getDoc_dec,
|
|
213
|
-
__decorateElement(_init, 1, "deleteDoc", _deleteDoc_dec,
|
|
214
|
-
__decorateElement(_init, 1, "saveDoc", _saveDoc_dec,
|
|
215
|
-
__decorateElement(_init, 1, "savePendingChanges", _savePendingChanges_dec,
|
|
216
|
-
__decorateElement(_init, 1, "getPendingChanges", _getPendingChanges_dec,
|
|
217
|
-
__decorateElement(_init, 1, "listChanges", _listChanges_dec,
|
|
218
|
-
__decorateElement(_init, 1, "applyServerChanges", _applyServerChanges_dec,
|
|
219
|
-
__decoratorMetadata(_init,
|
|
212
|
+
__decorateElement(_init, 1, "getDoc", _getDoc_dec, _OTIndexedDBStore);
|
|
213
|
+
__decorateElement(_init, 1, "deleteDoc", _deleteDoc_dec, _OTIndexedDBStore);
|
|
214
|
+
__decorateElement(_init, 1, "saveDoc", _saveDoc_dec, _OTIndexedDBStore);
|
|
215
|
+
__decorateElement(_init, 1, "savePendingChanges", _savePendingChanges_dec, _OTIndexedDBStore);
|
|
216
|
+
__decorateElement(_init, 1, "getPendingChanges", _getPendingChanges_dec, _OTIndexedDBStore);
|
|
217
|
+
__decorateElement(_init, 1, "listChanges", _listChanges_dec, _OTIndexedDBStore);
|
|
218
|
+
__decorateElement(_init, 1, "applyServerChanges", _applyServerChanges_dec, _OTIndexedDBStore);
|
|
219
|
+
__decoratorMetadata(_init, _OTIndexedDBStore);
|
|
220
|
+
let OTIndexedDBStore = _OTIndexedDBStore;
|
|
220
221
|
export {
|
|
221
222
|
OTIndexedDBStore
|
|
222
223
|
};
|
package/dist/client/Patches.d.ts
CHANGED
|
@@ -123,12 +123,6 @@ declare class Patches {
|
|
|
123
123
|
* Should be called when shutting down the client.
|
|
124
124
|
*/
|
|
125
125
|
close(): Promise<void>;
|
|
126
|
-
/**
|
|
127
|
-
* Submits ops for a document through the serialized change queue.
|
|
128
|
-
* Used by PatchesBranchClient to merge branch changes without racing
|
|
129
|
-
* against concurrent user edits on the same document.
|
|
130
|
-
*/
|
|
131
|
-
submitDocChange(docId: string, ops: JSONPatchOp[], metadata?: Record<string, any>): Promise<void>;
|
|
132
126
|
/**
|
|
133
127
|
* Internal handler for doc changes. Called when doc.onChange emits ops.
|
|
134
128
|
* Serializes calls per docId to prevent concurrent handleDocChange from
|
package/dist/client/Patches.js
CHANGED
|
@@ -197,17 +197,6 @@ class Patches {
|
|
|
197
197
|
this.onServerCommit.clear();
|
|
198
198
|
this.onError.clear();
|
|
199
199
|
}
|
|
200
|
-
/**
|
|
201
|
-
* Submits ops for a document through the serialized change queue.
|
|
202
|
-
* Used by PatchesBranchClient to merge branch changes without racing
|
|
203
|
-
* against concurrent user edits on the same document.
|
|
204
|
-
*/
|
|
205
|
-
submitDocChange(docId, ops, metadata = {}) {
|
|
206
|
-
const managed = this.docs.get(docId);
|
|
207
|
-
const algorithm = this.getDocAlgorithm(docId) ?? this.algorithms[this.defaultAlgorithm];
|
|
208
|
-
if (!algorithm) throw new Error(`No algorithm found for document ${docId}`);
|
|
209
|
-
return this._handleDocChange(docId, ops, managed?.doc, algorithm, metadata);
|
|
210
|
-
}
|
|
211
200
|
/**
|
|
212
201
|
* Internal handler for doc changes. Called when doc.onChange emits ops.
|
|
213
202
|
* Serializes calls per docId to prevent concurrent handleDocChange from
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Store } from 'easy-signal';
|
|
2
2
|
import { SizeCalculator } from '../algorithms/ot/shared/changeBatching.js';
|
|
3
3
|
import { BranchAPI } from '../net/protocol/types.js';
|
|
4
|
-
import { Branch, ListBranchesOptions, CreateBranchMetadata } from '../types.js';
|
|
4
|
+
import { Branch, ListBranchesOptions, CreateBranchMetadata, EditableBranchMetadata } from '../types.js';
|
|
5
5
|
import { BranchClientStore } from './BranchClientStore.js';
|
|
6
6
|
import { Patches } from './Patches.js';
|
|
7
7
|
import { AlgorithmName } from './PatchesStore.js';
|
|
@@ -26,8 +26,8 @@ interface PatchesBranchClientOptions {
|
|
|
26
26
|
* (offline-first, local store handles caching/pending/tombstones). The API shape
|
|
27
27
|
* determines merge behavior:
|
|
28
28
|
*
|
|
29
|
-
* - `BranchAPI`
|
|
30
|
-
* - `BranchClientStore`
|
|
29
|
+
* - `BranchAPI` — server performs the merge via `mergeBranch`
|
|
30
|
+
* - `BranchClientStore` — merge is not supported; call the server merge endpoint directly
|
|
31
31
|
*/
|
|
32
32
|
declare class PatchesBranchClient {
|
|
33
33
|
private readonly api;
|
|
@@ -64,6 +64,10 @@ declare class PatchesBranchClient {
|
|
|
64
64
|
* When `initialState` is omitted, the branch is created directly via the API.
|
|
65
65
|
*/
|
|
66
66
|
createBranch(rev: number, metadata?: CreateBranchMetadata, initialState?: any): Promise<string>;
|
|
67
|
+
/**
|
|
68
|
+
* Update branch metadata (e.g. name).
|
|
69
|
+
*/
|
|
70
|
+
updateBranch(branchId: string, metadata: EditableBranchMetadata): Promise<void>;
|
|
67
71
|
/**
|
|
68
72
|
* Delete a branch.
|
|
69
73
|
* The API implementation handles tombstones (offline store) or direct deletion (online).
|
|
@@ -77,16 +81,15 @@ declare class PatchesBranchClient {
|
|
|
77
81
|
/**
|
|
78
82
|
* Merge a branch's changes back into this document.
|
|
79
83
|
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
+
* Requires a `BranchAPI` (online mode) — the server performs the merge.
|
|
85
|
+
* Throws if the API is a `BranchClientStore` (offline-first mode) because
|
|
86
|
+
* client stores don't maintain full change history needed for correct merging.
|
|
87
|
+
* Offline-first consumers should call the server merge endpoint directly.
|
|
84
88
|
*/
|
|
85
89
|
mergeBranch(branchId: string): Promise<void>;
|
|
86
90
|
/** Clear state */
|
|
87
91
|
clear(): void;
|
|
88
92
|
private _createBranchOffline;
|
|
89
|
-
private _mergeBranchLocally;
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
export { PatchesBranchClient, type PatchesBranchClientOptions };
|
|
@@ -2,6 +2,7 @@ import "../chunk-IZ2YBCUP.js";
|
|
|
2
2
|
import { store } from "easy-signal";
|
|
3
3
|
import { breakChanges } from "../algorithms/ot/shared/changeBatching.js";
|
|
4
4
|
import { createChange } from "../data/change.js";
|
|
5
|
+
const OFFLINE_MERGE_ERROR = "Branch merging requires a server connection. Use a BranchAPI or call the server merge endpoint directly.";
|
|
5
6
|
class PatchesBranchClient {
|
|
6
7
|
constructor(id, api, patches, options) {
|
|
7
8
|
this.api = api;
|
|
@@ -61,6 +62,13 @@ class PatchesBranchClient {
|
|
|
61
62
|
await this.listBranches();
|
|
62
63
|
return branchId;
|
|
63
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Update branch metadata (e.g. name).
|
|
67
|
+
*/
|
|
68
|
+
async updateBranch(branchId, metadata) {
|
|
69
|
+
await this.api.updateBranch(branchId, metadata);
|
|
70
|
+
this.branches.state = this.branches.state.map((b) => b.id === branchId ? { ...b, ...metadata } : b);
|
|
71
|
+
}
|
|
64
72
|
/**
|
|
65
73
|
* Delete a branch.
|
|
66
74
|
* The API implementation handles tombstones (offline store) or direct deletion (online).
|
|
@@ -80,18 +88,17 @@ class PatchesBranchClient {
|
|
|
80
88
|
/**
|
|
81
89
|
* Merge a branch's changes back into this document.
|
|
82
90
|
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
91
|
+
* Requires a `BranchAPI` (online mode) — the server performs the merge.
|
|
92
|
+
* Throws if the API is a `BranchClientStore` (offline-first mode) because
|
|
93
|
+
* client stores don't maintain full change history needed for correct merging.
|
|
94
|
+
* Offline-first consumers should call the server merge endpoint directly.
|
|
87
95
|
*/
|
|
88
96
|
async mergeBranch(branchId) {
|
|
89
|
-
if (
|
|
90
|
-
|
|
91
|
-
await this.listBranches();
|
|
92
|
-
return;
|
|
97
|
+
if (this.isOffline) {
|
|
98
|
+
throw new Error(OFFLINE_MERGE_ERROR);
|
|
93
99
|
}
|
|
94
|
-
await this.
|
|
100
|
+
await this.api.mergeBranch(branchId);
|
|
101
|
+
await this.listBranches();
|
|
95
102
|
}
|
|
96
103
|
/** Clear state */
|
|
97
104
|
clear() {
|
|
@@ -141,29 +148,6 @@ class PatchesBranchClient {
|
|
|
141
148
|
this.patches.onChange.emit(branchDocId);
|
|
142
149
|
return branchDocId;
|
|
143
150
|
}
|
|
144
|
-
async _mergeBranchLocally(branchId) {
|
|
145
|
-
const offlineApi = this.api;
|
|
146
|
-
const branch = this.branches.state.find((b) => b.id === branchId);
|
|
147
|
-
if (!branch) throw new Error(`Branch ${branchId} not found`);
|
|
148
|
-
const sourceDocId = branch.docId;
|
|
149
|
-
const algorithmName = this.options?.algorithm ?? this.patches.defaultAlgorithm;
|
|
150
|
-
const algorithm = this.patches.algorithms[algorithmName];
|
|
151
|
-
if (!algorithm?.listChanges) {
|
|
152
|
-
throw new Error("Offline merge requires an algorithm with listChanges support");
|
|
153
|
-
}
|
|
154
|
-
const startAfter = branch.lastMergedRev ?? (branch.contentStartRev ?? 2) - 1;
|
|
155
|
-
const branchChanges = await algorithm.listChanges(branchId, { startAfter });
|
|
156
|
-
if (branchChanges.length === 0) return;
|
|
157
|
-
const lastBranchRev = branchChanges[branchChanges.length - 1].rev;
|
|
158
|
-
for (const change of branchChanges) {
|
|
159
|
-
await this.patches.submitDocChange(sourceDocId, change.ops, { batchId: branchId });
|
|
160
|
-
}
|
|
161
|
-
await offlineApi.updateBranch(branchId, { lastMergedRev: lastBranchRev });
|
|
162
|
-
this.patches.onChange.emit(sourceDocId);
|
|
163
|
-
this.branches.state = this.branches.state.map(
|
|
164
|
-
(b) => b.id === branchId ? { ...b, lastMergedRev: lastBranchRev } : b
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
151
|
}
|
|
168
152
|
export {
|
|
169
153
|
PatchesBranchClient
|
|
@@ -69,5 +69,20 @@ declare function createMultiAlgorithmPatches(options?: MultiAlgorithmFactoryOpti
|
|
|
69
69
|
* Both algorithms share the same IndexedDB database with unified stores.
|
|
70
70
|
*/
|
|
71
71
|
declare function createMultiAlgorithmIndexedDBPatches(options: MultiAlgorithmIndexedDBFactoryOptions): Patches;
|
|
72
|
+
/**
|
|
73
|
+
* Creates a Patches instance with both OT and LWW algorithms using an externally-managed
|
|
74
|
+
* IDBDatabase. The caller is responsible for opening the database and calling
|
|
75
|
+
* `upgradePatchesDB()` during `onupgradeneeded` to create the required stores.
|
|
76
|
+
*
|
|
77
|
+
* Use this when you want to host Patches stores inside your own IndexedDB database
|
|
78
|
+
* rather than having Patches open a separate one.
|
|
79
|
+
*/
|
|
80
|
+
declare function createMultiAlgorithmExternalDBPatches(db: IDBDatabase | Promise<IDBDatabase>, options?: MultiAlgorithmFactoryOptions): Patches;
|
|
81
|
+
/**
|
|
82
|
+
* Creates all Patches object stores in an externally-managed database.
|
|
83
|
+
* Call this from your `onupgradeneeded` handler when hosting Patches stores
|
|
84
|
+
* inside your own IndexedDB database.
|
|
85
|
+
*/
|
|
86
|
+
declare function upgradePatchesDB(db: IDBDatabase, transaction: IDBTransaction): void;
|
|
72
87
|
|
|
73
|
-
export { type IndexedDBFactoryOptions, type MultiAlgorithmFactoryOptions, type MultiAlgorithmIndexedDBFactoryOptions, type PatchesFactoryOptions, createLWWIndexedDBPatches, createLWWPatches, createMultiAlgorithmIndexedDBPatches, createMultiAlgorithmPatches, createOTIndexedDBPatches, createOTPatches };
|
|
88
|
+
export { type IndexedDBFactoryOptions, type MultiAlgorithmFactoryOptions, type MultiAlgorithmIndexedDBFactoryOptions, type PatchesFactoryOptions, createLWWIndexedDBPatches, createLWWPatches, createMultiAlgorithmExternalDBPatches, createMultiAlgorithmIndexedDBPatches, createMultiAlgorithmPatches, createOTIndexedDBPatches, createOTPatches, upgradePatchesDB };
|
package/dist/client/factories.js
CHANGED
|
@@ -72,11 +72,31 @@ function createMultiAlgorithmIndexedDBPatches(options) {
|
|
|
72
72
|
docOptions: options.docOptions
|
|
73
73
|
});
|
|
74
74
|
}
|
|
75
|
+
function createMultiAlgorithmExternalDBPatches(db, options = {}) {
|
|
76
|
+
const baseStore = new IndexedDBStore(db);
|
|
77
|
+
const otStore = new OTIndexedDBStore(baseStore);
|
|
78
|
+
const lwwStore = new LWWIndexedDBStore(baseStore);
|
|
79
|
+
const otAlgorithm = new OTAlgorithm(otStore, options.docOptions);
|
|
80
|
+
const lwwAlgorithm = new LWWAlgorithm(lwwStore);
|
|
81
|
+
return new Patches({
|
|
82
|
+
algorithms: { ot: otAlgorithm, lww: lwwAlgorithm },
|
|
83
|
+
defaultAlgorithm: options.defaultAlgorithm ?? "ot",
|
|
84
|
+
metadata: options.metadata,
|
|
85
|
+
docOptions: options.docOptions
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function upgradePatchesDB(db, transaction) {
|
|
89
|
+
IndexedDBStore.upgradeSharedStores(db, transaction);
|
|
90
|
+
OTIndexedDBStore.upgradeStores(db, transaction);
|
|
91
|
+
LWWIndexedDBStore.upgradeStores(db, transaction);
|
|
92
|
+
}
|
|
75
93
|
export {
|
|
76
94
|
createLWWIndexedDBPatches,
|
|
77
95
|
createLWWPatches,
|
|
96
|
+
createMultiAlgorithmExternalDBPatches,
|
|
78
97
|
createMultiAlgorithmIndexedDBPatches,
|
|
79
98
|
createMultiAlgorithmPatches,
|
|
80
99
|
createOTIndexedDBPatches,
|
|
81
|
-
createOTPatches
|
|
100
|
+
createOTPatches,
|
|
101
|
+
upgradePatchesDB
|
|
82
102
|
};
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { B as BaseDoc, O as OTDoc, P as PatchesDoc, a as PatchesDocOptions } from '../BaseDoc-BT18xPxU.js';
|
|
2
|
-
export { IndexedDBFactoryOptions, MultiAlgorithmFactoryOptions, MultiAlgorithmIndexedDBFactoryOptions, PatchesFactoryOptions, createLWWIndexedDBPatches, createLWWPatches, createMultiAlgorithmIndexedDBPatches, createMultiAlgorithmPatches, createOTIndexedDBPatches, createOTPatches } from './factories.js';
|
|
2
|
+
export { IndexedDBFactoryOptions, MultiAlgorithmFactoryOptions, MultiAlgorithmIndexedDBFactoryOptions, PatchesFactoryOptions, createLWWIndexedDBPatches, createLWWPatches, createMultiAlgorithmExternalDBPatches, createMultiAlgorithmIndexedDBPatches, createMultiAlgorithmPatches, createOTIndexedDBPatches, createOTPatches, upgradePatchesDB } from './factories.js';
|
|
3
3
|
export { IDBStoreWrapper, IDBTransactionWrapper, IndexedDBStore } from './IndexedDBStore.js';
|
|
4
4
|
export { OTIndexedDBStore } from './OTIndexedDBStore.js';
|
|
5
5
|
export { LWWIndexedDBStore } from './LWWIndexedDBStore.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { Delta } from '@dabble/delta';
|
|
2
2
|
export { B as BaseDoc, O as OTDoc, P as PatchesDoc, a as PatchesDocOptions } from './BaseDoc-BT18xPxU.js';
|
|
3
|
-
export { IndexedDBFactoryOptions, MultiAlgorithmFactoryOptions, MultiAlgorithmIndexedDBFactoryOptions, PatchesFactoryOptions, createLWWIndexedDBPatches, createLWWPatches, createMultiAlgorithmIndexedDBPatches, createMultiAlgorithmPatches, createOTIndexedDBPatches, createOTPatches } from './client/factories.js';
|
|
3
|
+
export { IndexedDBFactoryOptions, MultiAlgorithmFactoryOptions, MultiAlgorithmIndexedDBFactoryOptions, PatchesFactoryOptions, createLWWIndexedDBPatches, createLWWPatches, createMultiAlgorithmExternalDBPatches, createMultiAlgorithmIndexedDBPatches, createMultiAlgorithmPatches, createOTIndexedDBPatches, createOTPatches, upgradePatchesDB } from './client/factories.js';
|
|
4
4
|
export { IDBStoreWrapper, IDBTransactionWrapper, IndexedDBStore } from './client/IndexedDBStore.js';
|
|
5
5
|
export { OTIndexedDBStore } from './client/OTIndexedDBStore.js';
|
|
6
6
|
export { LWWIndexedDBStore } from './client/LWWIndexedDBStore.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dabble/patches",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.13",
|
|
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": {
|