@dabble/patches 0.7.4 → 0.7.5

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.
@@ -80,7 +80,7 @@ interface ClientAlgorithm {
80
80
  * Called after successful server commit.
81
81
  */
82
82
  confirmSent(docId: string, changes: Change[]): Promise<void>;
83
- /** Registers documents for local tracking. */
83
+ /** Registers documents for local tracking with the algorithm for this instance. */
84
84
  trackDocs(docIds: string[]): Promise<void>;
85
85
  /** Removes documents from local tracking. */
86
86
  untrackDocs(docIds: string[]): Promise<void>;
@@ -95,7 +95,5 @@ interface ClientAlgorithm {
95
95
  /** Closes the algorithm and its store. */
96
96
  close(): Promise<void>;
97
97
  }
98
- /** Available algorithm names */
99
- type AlgorithmName = 'ot' | 'lww';
100
98
 
101
- export type { AlgorithmName, ClientAlgorithm };
99
+ export type { ClientAlgorithm };
@@ -1,3 +1,4 @@
1
+ import { Signal } from '../event-signal.js';
1
2
  import { PatchesSnapshot, PatchesState } from '../types.js';
2
3
  import { Deferred } from '../utils/deferred.js';
3
4
  import { PatchesStore, TrackedDoc } from './PatchesStore.js';
@@ -6,36 +7,34 @@ import '@dabble/delta';
6
7
  import '../json-patch/types.js';
7
8
 
8
9
  /**
9
- * Abstract base class for IndexedDB-based stores implementing PatchesStore.
10
+ * IndexedDB store providing common database operations for all sync strategies.
10
11
  *
11
- * Provides common functionality for IndexedDB operations:
12
+ * Can be used as a standalone store or as a shared database connection
13
+ * for multiple strategy-specific stores (OT, LWW).
14
+ *
15
+ * Provides:
12
16
  * - Database lifecycle management (open, close, delete)
13
17
  * - Transaction helpers
14
18
  * - Document tracking (listDocs, trackDocs, untrackDocs)
15
19
  * - Basic document operations (deleteDoc, confirmDeleteDoc)
16
20
  * - Revision tracking
17
- *
18
- * Subclasses must implement strategy-specific methods for document
19
- * state management and change handling.
21
+ * - Extensibility via onUpgrade signal for strategy-specific stores
20
22
  */
21
- declare abstract class IndexedDBStore implements PatchesStore {
23
+ declare class IndexedDBStore implements PatchesStore {
24
+ private static readonly DB_VERSION;
22
25
  protected db: IDBDatabase | null;
23
26
  protected dbName?: string;
24
27
  protected dbPromise: Deferred<IDBDatabase>;
25
- constructor(dbName?: string);
26
28
  /**
27
- * Returns the database version for this store.
28
- * Subclasses should return their specific version number.
29
+ * Signal emitted during database upgrade, allowing strategy-specific stores
30
+ * to create their object stores.
29
31
  */
30
- protected abstract getDBVersion(): number;
32
+ readonly onUpgrade: Signal<(db: IDBDatabase, oldVersion: number, transaction: IDBTransaction) => void>;
33
+ constructor(dbName?: string);
31
34
  /**
32
- * Hook for subclasses to create strategy-specific object stores.
33
- * Called during database upgrade.
34
- *
35
- * @param db - The IDBDatabase instance
36
- * @param oldVersion - The previous database version
35
+ * Creates shared object stores used by all sync strategies.
37
36
  */
38
- protected abstract onUpgrade(db: IDBDatabase, oldVersion: number): void;
37
+ protected createSharedStores(db: IDBDatabase, _oldVersion: number, _transaction: IDBTransaction): void;
39
38
  protected initDB(): Promise<void>;
40
39
  protected getDB(): Promise<IDBDatabase>;
41
40
  /**
@@ -50,21 +49,24 @@ declare abstract class IndexedDBStore implements PatchesStore {
50
49
  */
51
50
  close(): Promise<void>;
52
51
  deleteDB(): Promise<void>;
53
- protected transaction(storeNames: string[], mode: IDBTransactionMode): Promise<[IDBTransactionWrapper, ...IDBStoreWrapper[]]>;
52
+ transaction(storeNames: string[], mode: IDBTransactionMode): Promise<[IDBTransactionWrapper, ...IDBStoreWrapper[]]>;
54
53
  /**
55
54
  * Retrieves the current document snapshot from storage.
56
55
  * Implementation varies by sync strategy (OT vs LWW).
56
+ * This base implementation throws an error - override in strategy-specific stores.
57
57
  */
58
- abstract getDoc(docId: string): Promise<PatchesSnapshot | undefined>;
58
+ getDoc(_docId: string): Promise<PatchesSnapshot | undefined>;
59
59
  /**
60
60
  * Saves the current document state to persistent storage.
61
61
  * Implementation varies by sync strategy.
62
+ * This base implementation throws an error - override in strategy-specific stores.
62
63
  */
63
- abstract saveDoc(docId: string, docState: PatchesState): Promise<void>;
64
+ saveDoc(_docId: string, _docState: PatchesState): Promise<void>;
64
65
  /**
65
66
  * Completely remove all data for this docId and mark it as deleted (tombstone).
67
+ * This base implementation throws an error - override in strategy-specific stores.
66
68
  */
67
- abstract deleteDoc(docId: string): Promise<void>;
69
+ deleteDoc(_docId: string): Promise<void>;
68
70
  /**
69
71
  * Confirm the deletion of a document.
70
72
  * @param docId - The ID of the document to delete.
@@ -73,19 +75,22 @@ declare abstract class IndexedDBStore implements PatchesStore {
73
75
  /**
74
76
  * List all documents in the store.
75
77
  * @param includeDeleted - Whether to include deleted documents.
78
+ * @param algorithm - Optional algorithm filter ('ot' or 'lww'). If provided, uses index for efficient filtering.
76
79
  * @returns The list of documents.
77
80
  */
78
- listDocs(includeDeleted?: boolean): Promise<TrackedDoc[]>;
81
+ listDocs(includeDeleted?: boolean, algorithm?: 'ot' | 'lww'): Promise<TrackedDoc[]>;
79
82
  /**
80
83
  * Track a document.
81
84
  * @param docIds - The IDs of the documents to track.
85
+ * @param algorithm - The algorithm to use for this document.
82
86
  */
83
- trackDocs(docIds: string[]): Promise<void>;
87
+ trackDocs(docIds: string[], algorithm?: 'ot' | 'lww'): Promise<void>;
84
88
  /**
85
89
  * Untrack a document.
86
90
  * @param docIds - The IDs of the documents to untrack.
91
+ * This base implementation throws an error - override in strategy-specific stores.
87
92
  */
88
- abstract untrackDocs(docIds: string[]): Promise<void>;
93
+ untrackDocs(_docIds: string[]): Promise<void>;
89
94
  /**
90
95
  * Returns the last committed revision for a document.
91
96
  * @param docId - The ID of the document.
@@ -105,6 +110,7 @@ declare class IDBStoreWrapper {
105
110
  constructor(store: IDBObjectStore);
106
111
  protected createRange(lower?: any, upper?: any): IDBKeyRange | undefined;
107
112
  getAll<T>(lower?: any, upper?: any, count?: number): Promise<T[]>;
113
+ getAllByIndex<T>(indexName: string, query?: IDBValidKey | IDBKeyRange): Promise<T[]>;
108
114
  get<T>(key: IDBValidKey): Promise<T | undefined>;
109
115
  put<T>(value: T): Promise<IDBValidKey>;
110
116
  delete(key: IDBValidKey): Promise<void>;
@@ -1,19 +1,41 @@
1
1
  import "../chunk-IZ2YBCUP.js";
2
2
  import { deferred } from "../utils/deferred.js";
3
+ import { signal } from "../event-signal.js";
3
4
  class IndexedDBStore {
5
+ static DB_VERSION = 1;
4
6
  db = null;
5
7
  dbName;
6
8
  dbPromise;
9
+ /**
10
+ * Signal emitted during database upgrade, allowing strategy-specific stores
11
+ * to create their object stores.
12
+ */
13
+ onUpgrade = signal();
7
14
  constructor(dbName) {
8
15
  this.dbName = dbName;
9
16
  this.dbPromise = deferred();
17
+ this.onUpgrade((db, oldVersion, transaction) => {
18
+ this.createSharedStores(db, oldVersion, transaction);
19
+ });
10
20
  if (this.dbName) {
11
21
  this.initDB();
12
22
  }
13
23
  }
24
+ /**
25
+ * Creates shared object stores used by all sync strategies.
26
+ */
27
+ createSharedStores(db, _oldVersion, _transaction) {
28
+ if (!db.objectStoreNames.contains("docs")) {
29
+ const docsStore = db.createObjectStore("docs", { keyPath: "docId" });
30
+ docsStore.createIndex("algorithm", "algorithm", { unique: false });
31
+ }
32
+ if (!db.objectStoreNames.contains("snapshots")) {
33
+ db.createObjectStore("snapshots", { keyPath: "docId" });
34
+ }
35
+ }
14
36
  async initDB() {
15
37
  if (!this.dbName) return;
16
- const request = indexedDB.open(this.dbName, this.getDBVersion());
38
+ const request = indexedDB.open(this.dbName, IndexedDBStore.DB_VERSION);
17
39
  request.onerror = () => this.dbPromise.reject(request.error);
18
40
  request.onsuccess = () => {
19
41
  this.db = request.result;
@@ -21,14 +43,9 @@ class IndexedDBStore {
21
43
  };
22
44
  request.onupgradeneeded = (event) => {
23
45
  const db = event.target.result;
46
+ const transaction = event.target.transaction;
24
47
  const oldVersion = event.oldVersion;
25
- if (!db.objectStoreNames.contains("snapshots")) {
26
- db.createObjectStore("snapshots", { keyPath: "docId" });
27
- }
28
- if (!db.objectStoreNames.contains("docs")) {
29
- db.createObjectStore("docs", { keyPath: "docId" });
30
- }
31
- this.onUpgrade(db, oldVersion);
48
+ this.onUpgrade.emit(db, oldVersion, transaction);
32
49
  };
33
50
  }
34
51
  getDB() {
@@ -77,6 +94,31 @@ class IndexedDBStore {
77
94
  const stores = storeNames.map((name) => tx.getStore(name));
78
95
  return [tx, ...stores];
79
96
  }
97
+ // ─── Strategy-Specific Methods ───────────────────────────────────────────
98
+ // These are implemented by strategy-specific stores (OT, LWW)
99
+ /**
100
+ * Retrieves the current document snapshot from storage.
101
+ * Implementation varies by sync strategy (OT vs LWW).
102
+ * This base implementation throws an error - override in strategy-specific stores.
103
+ */
104
+ async getDoc(_docId) {
105
+ throw new Error("getDoc must be implemented by strategy-specific store");
106
+ }
107
+ /**
108
+ * Saves the current document state to persistent storage.
109
+ * Implementation varies by sync strategy.
110
+ * This base implementation throws an error - override in strategy-specific stores.
111
+ */
112
+ async saveDoc(_docId, _docState) {
113
+ throw new Error("saveDoc must be implemented by strategy-specific store");
114
+ }
115
+ /**
116
+ * Completely remove all data for this docId and mark it as deleted (tombstone).
117
+ * This base implementation throws an error - override in strategy-specific stores.
118
+ */
119
+ async deleteDoc(_docId) {
120
+ throw new Error("deleteDoc must be implemented by strategy-specific store");
121
+ }
80
122
  /**
81
123
  * Confirm the deletion of a document.
82
124
  * @param docId - The ID of the document to delete.
@@ -89,34 +131,60 @@ class IndexedDBStore {
89
131
  /**
90
132
  * List all documents in the store.
91
133
  * @param includeDeleted - Whether to include deleted documents.
134
+ * @param algorithm - Optional algorithm filter ('ot' or 'lww'). If provided, uses index for efficient filtering.
92
135
  * @returns The list of documents.
93
136
  */
94
- async listDocs(includeDeleted = false) {
137
+ async listDocs(includeDeleted = false, algorithm) {
95
138
  const [tx, docsStore] = await this.transaction(["docs"], "readonly");
96
- const allDocs = await docsStore.getAll();
139
+ let docs;
140
+ if (algorithm === "lww") {
141
+ docs = await docsStore.getAllByIndex("algorithm", "lww");
142
+ } else if (algorithm === "ot") {
143
+ const otDocs = await docsStore.getAllByIndex("algorithm", "ot");
144
+ const allDocs = await docsStore.getAll();
145
+ const noAlgoDocs = allDocs.filter((doc) => !doc.algorithm);
146
+ docs = [...otDocs, ...noAlgoDocs];
147
+ } else {
148
+ docs = await docsStore.getAll();
149
+ }
97
150
  await tx.complete();
98
- return includeDeleted ? allDocs : allDocs.filter((doc) => !doc.deleted);
151
+ return includeDeleted ? docs : docs.filter((doc) => !doc.deleted);
99
152
  }
100
153
  /**
101
154
  * Track a document.
102
155
  * @param docIds - The IDs of the documents to track.
156
+ * @param algorithm - The algorithm to use for this document.
103
157
  */
104
- async trackDocs(docIds) {
158
+ async trackDocs(docIds, algorithm) {
105
159
  const [tx, docsStore] = await this.transaction(["docs"], "readwrite");
106
160
  await Promise.all(
107
161
  docIds.map(async (docId) => {
108
162
  const existing = await docsStore.get(docId);
109
163
  if (existing) {
110
164
  if (existing.deleted) {
111
- await docsStore.put({ ...existing, deleted: void 0 });
165
+ await docsStore.put({
166
+ ...existing,
167
+ deleted: void 0,
168
+ ...algorithm && { algorithm }
169
+ });
170
+ } else if (algorithm && existing.algorithm !== algorithm) {
171
+ await docsStore.put({ ...existing, algorithm });
112
172
  }
113
173
  } else {
114
- await docsStore.put({ docId, committedRev: 0 });
174
+ await docsStore.put({ docId, committedRev: 0, ...algorithm && { algorithm } });
115
175
  }
116
176
  })
117
177
  );
118
178
  await tx.complete();
119
179
  }
180
+ /**
181
+ * Untrack a document.
182
+ * @param docIds - The IDs of the documents to untrack.
183
+ * This base implementation throws an error - override in strategy-specific stores.
184
+ */
185
+ async untrackDocs(_docIds) {
186
+ throw new Error("untrackDocs must be implemented by strategy-specific store");
187
+ }
120
188
  /**
121
189
  * Returns the last committed revision for a document.
122
190
  * @param docId - The ID of the document.
@@ -162,6 +230,14 @@ class IDBStoreWrapper {
162
230
  request.onerror = () => reject(request.error);
163
231
  });
164
232
  }
233
+ async getAllByIndex(indexName, query) {
234
+ return new Promise((resolve, reject) => {
235
+ const index = this.store.index(indexName);
236
+ const request = index.getAll(query);
237
+ request.onsuccess = () => resolve(request.result);
238
+ request.onerror = () => reject(request.error);
239
+ });
240
+ }
165
241
  async get(key) {
166
242
  return new Promise((resolve, reject) => {
167
243
  const request = this.store.get(key);
@@ -62,7 +62,7 @@ class LWWAlgorithm {
62
62
  }
63
63
  // --- Store forwarding methods ---
64
64
  async trackDocs(docIds) {
65
- return this.store.trackDocs(docIds);
65
+ return this.store.trackDocs(docIds, "lww");
66
66
  }
67
67
  async untrackDocs(docIds) {
68
68
  return this.store.untrackDocs(docIds);
@@ -37,7 +37,7 @@ declare class LWWInMemoryStore implements LWWClientStore {
37
37
  /**
38
38
  * Track documents.
39
39
  */
40
- trackDocs(docIds: string[]): Promise<void>;
40
+ trackDocs(docIds: string[], _algorithm?: 'ot' | 'lww'): Promise<void>;
41
41
  /**
42
42
  * Untrack documents by removing all their data.
43
43
  */
@@ -66,7 +66,7 @@ class LWWInMemoryStore {
66
66
  /**
67
67
  * Track documents.
68
68
  */
69
- async trackDocs(docIds) {
69
+ async trackDocs(docIds, _algorithm) {
70
70
  for (const docId of docIds) {
71
71
  const buf = this.getOrCreateBuffer(docId);
72
72
  delete buf.deleted;
@@ -2,10 +2,11 @@ import { JSONPatchOp } from '../json-patch/types.js';
2
2
  import { PatchesSnapshot, PatchesState, Change } from '../types.js';
3
3
  import { IndexedDBStore } from './IndexedDBStore.js';
4
4
  import { LWWClientStore } from './LWWClientStore.js';
5
+ import { TrackedDoc } from './PatchesStore.js';
5
6
  import '../json-patch/JSONPatch.js';
6
7
  import '@dabble/delta';
8
+ import '../event-signal.js';
7
9
  import '../utils/deferred.js';
8
- import './PatchesStore.js';
9
10
 
10
11
  /**
11
12
  * IndexedDB store implementation for Last-Writer-Wins (LWW) sync strategy.
@@ -24,9 +25,42 @@ import './PatchesStore.js';
24
25
  *
25
26
  * Every 200 ops, committed ops are compacted into the snapshot.
26
27
  */
27
- declare class LWWIndexedDBStore extends IndexedDBStore implements LWWClientStore {
28
- protected getDBVersion(): number;
29
- protected onUpgrade(db: IDBDatabase, _oldVersion: number): void;
28
+ declare class LWWIndexedDBStore implements LWWClientStore {
29
+ db: IndexedDBStore;
30
+ constructor(db?: string | IndexedDBStore);
31
+ /**
32
+ * Creates LWW-specific object stores during database upgrade.
33
+ */
34
+ protected createLWWStores(db: IDBDatabase): void;
35
+ /**
36
+ * List documents for the LWW algorithm.
37
+ * Uses the algorithm index for efficient querying.
38
+ */
39
+ listDocs(includeDeleted?: boolean): Promise<TrackedDoc[]>;
40
+ /**
41
+ * Track documents using the LWW algorithm.
42
+ */
43
+ trackDocs(docIds: string[]): Promise<void>;
44
+ /**
45
+ * Close the database connection.
46
+ */
47
+ close(): Promise<void>;
48
+ /**
49
+ * Delete the database.
50
+ */
51
+ deleteDB(): Promise<void>;
52
+ /**
53
+ * Set the database name.
54
+ */
55
+ setName(dbName: string): void;
56
+ /**
57
+ * Confirm the deletion of a document.
58
+ */
59
+ confirmDeleteDoc(docId: string): Promise<void>;
60
+ /**
61
+ * Get the committed revision for a document.
62
+ */
63
+ getCommittedRev(docId: string): Promise<number>;
30
64
  /**
31
65
  * Rebuilds a document state from snapshot + committed ops + sending + pending.
32
66
  *
@@ -2,24 +2,29 @@ import {
2
2
  __decorateElement,
3
3
  __decoratorMetadata,
4
4
  __decoratorStart,
5
+ __publicField,
5
6
  __runInitializers
6
7
  } from "../chunk-IZ2YBCUP.js";
7
- var _applyServerChanges_dec, _confirmSendingChange_dec, _saveSendingChange_dec, _getSendingChange_dec, _savePendingOps_dec, _getPendingOps_dec, _deleteDoc_dec, _saveDoc_dec, _getDoc_dec, _a, _init;
8
+ var _applyServerChanges_dec, _confirmSendingChange_dec, _saveSendingChange_dec, _getSendingChange_dec, _savePendingOps_dec, _getPendingOps_dec, _deleteDoc_dec, _saveDoc_dec, _getDoc_dec, _init;
8
9
  import { createChange } from "../data/change.js";
9
10
  import { applyPatch } from "../json-patch/applyPatch.js";
10
11
  import { blockable } from "../utils/concurrency.js";
11
12
  import { IDBStoreWrapper, IndexedDBStore } from "./IndexedDBStore.js";
12
- const DB_VERSION = 1;
13
13
  const SNAPSHOT_INTERVAL = 200;
14
- class LWWIndexedDBStore extends (_a = IndexedDBStore, _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], _a) {
15
- constructor() {
16
- super(...arguments);
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 LWWIndexedDBStore {
16
+ constructor(db) {
17
17
  __runInitializers(_init, 5, this);
18
+ __publicField(this, "db");
19
+ this.db = !db || typeof db === "string" ? new IndexedDBStore(db) : db;
20
+ this.db.onUpgrade((db2, _oldVersion, _transaction) => {
21
+ this.createLWWStores(db2);
22
+ });
18
23
  }
19
- getDBVersion() {
20
- return DB_VERSION;
21
- }
22
- onUpgrade(db, _oldVersion) {
24
+ /**
25
+ * Creates LWW-specific object stores during database upgrade.
26
+ */
27
+ createLWWStores(db) {
23
28
  if (!db.objectStoreNames.contains("committedOps")) {
24
29
  db.createObjectStore("committedOps", { keyPath: ["docId", "path"] });
25
30
  }
@@ -30,8 +35,51 @@ class LWWIndexedDBStore extends (_a = IndexedDBStore, _getDoc_dec = [blockable],
30
35
  db.createObjectStore("sendingChanges", { keyPath: "docId" });
31
36
  }
32
37
  }
38
+ /**
39
+ * List documents for the LWW algorithm.
40
+ * Uses the algorithm index for efficient querying.
41
+ */
42
+ async listDocs(includeDeleted = false) {
43
+ return this.db.listDocs(includeDeleted, "lww");
44
+ }
45
+ /**
46
+ * Track documents using the LWW algorithm.
47
+ */
48
+ async trackDocs(docIds) {
49
+ return this.db.trackDocs(docIds, "lww");
50
+ }
51
+ /**
52
+ * Close the database connection.
53
+ */
54
+ async close() {
55
+ return this.db.close();
56
+ }
57
+ /**
58
+ * Delete the database.
59
+ */
60
+ async deleteDB() {
61
+ return this.db.deleteDB();
62
+ }
63
+ /**
64
+ * Set the database name.
65
+ */
66
+ setName(dbName) {
67
+ return this.db.setName(dbName);
68
+ }
69
+ /**
70
+ * Confirm the deletion of a document.
71
+ */
72
+ async confirmDeleteDoc(docId) {
73
+ return this.db.confirmDeleteDoc(docId);
74
+ }
75
+ /**
76
+ * Get the committed revision for a document.
77
+ */
78
+ async getCommittedRev(docId) {
79
+ return this.db.getCommittedRev(docId);
80
+ }
33
81
  async getDoc(docId) {
34
- const [tx, docsStore, snapshots, committedOps, pendingOps, sendingChanges] = await this.transaction(
82
+ const [tx, docsStore, snapshots, committedOps, pendingOps, sendingChanges] = await this.db.transaction(
35
83
  ["docs", "snapshots", "committedOps", "pendingOps", "sendingChanges"],
36
84
  "readonly"
37
85
  );
@@ -74,7 +122,7 @@ class LWWIndexedDBStore extends (_a = IndexedDBStore, _getDoc_dec = [blockable],
74
122
  };
75
123
  }
76
124
  async saveDoc(docId, docState) {
77
- const [tx, snapshots, committedOps, pendingOps, sendingChanges, docsStore] = await this.transaction(
125
+ const [tx, snapshots, committedOps, pendingOps, sendingChanges, docsStore] = await this.db.transaction(
78
126
  ["snapshots", "committedOps", "pendingOps", "sendingChanges", "docs"],
79
127
  "readwrite"
80
128
  );
@@ -89,7 +137,7 @@ class LWWIndexedDBStore extends (_a = IndexedDBStore, _getDoc_dec = [blockable],
89
137
  await tx.complete();
90
138
  }
91
139
  async deleteDoc(docId) {
92
- const [tx, snapshots, committedOps, pendingOps, sendingChanges, docsStore] = await this.transaction(
140
+ const [tx, snapshots, committedOps, pendingOps, sendingChanges, docsStore] = await this.db.transaction(
93
141
  ["snapshots", "committedOps", "pendingOps", "sendingChanges", "docs"],
94
142
  "readwrite"
95
143
  );
@@ -107,7 +155,7 @@ class LWWIndexedDBStore extends (_a = IndexedDBStore, _getDoc_dec = [blockable],
107
155
  * Untracks documents by removing all their data.
108
156
  */
109
157
  async untrackDocs(docIds) {
110
- const [tx, docsStore, snapshots, committedOps, pendingOps, sendingChanges] = await this.transaction(
158
+ const [tx, docsStore, snapshots, committedOps, pendingOps, sendingChanges] = await this.db.transaction(
111
159
  ["docs", "snapshots", "committedOps", "pendingOps", "sendingChanges"],
112
160
  "readwrite"
113
161
  );
@@ -125,7 +173,7 @@ class LWWIndexedDBStore extends (_a = IndexedDBStore, _getDoc_dec = [blockable],
125
173
  await tx.complete();
126
174
  }
127
175
  async getPendingOps(docId, pathPrefixes) {
128
- const [tx, pendingOpsStore] = await this.transaction(["pendingOps"], "readonly");
176
+ const [tx, pendingOpsStore] = await this.db.transaction(["pendingOps"], "readonly");
129
177
  let pending;
130
178
  if (!pathPrefixes || pathPrefixes.length === 0) {
131
179
  pending = await pendingOpsStore.getAll([docId, ""], [docId, "\uFFFF"]);
@@ -144,10 +192,10 @@ class LWWIndexedDBStore extends (_a = IndexedDBStore, _getDoc_dec = [blockable],
144
192
  }));
145
193
  }
146
194
  async savePendingOps(docId, ops, pathsToDelete) {
147
- const [tx, pendingOpsStore, docsStore] = await this.transaction(["pendingOps", "docs"], "readwrite");
195
+ const [tx, pendingOpsStore, docsStore] = await this.db.transaction(["pendingOps", "docs"], "readwrite");
148
196
  let docMeta = await docsStore.get(docId);
149
197
  if (!docMeta) {
150
- docMeta = { docId, committedRev: 0 };
198
+ docMeta = { docId, committedRev: 0, algorithm: "lww" };
151
199
  await docsStore.put(docMeta);
152
200
  } else if (docMeta.deleted) {
153
201
  delete docMeta.deleted;
@@ -170,19 +218,22 @@ class LWWIndexedDBStore extends (_a = IndexedDBStore, _getDoc_dec = [blockable],
170
218
  await tx.complete();
171
219
  }
172
220
  async getSendingChange(docId) {
173
- const [tx, sendingChanges] = await this.transaction(["sendingChanges"], "readonly");
221
+ const [tx, sendingChanges] = await this.db.transaction(["sendingChanges"], "readonly");
174
222
  const sending = await sendingChanges.get(docId);
175
223
  await tx.complete();
176
224
  return sending?.change ?? null;
177
225
  }
178
226
  async saveSendingChange(docId, change) {
179
- const [tx, pendingOpsStore, sendingChanges] = await this.transaction(["pendingOps", "sendingChanges"], "readwrite");
227
+ const [tx, pendingOpsStore, sendingChanges] = await this.db.transaction(
228
+ ["pendingOps", "sendingChanges"],
229
+ "readwrite"
230
+ );
180
231
  await sendingChanges.put({ docId, change });
181
232
  await this.deleteFieldsForDoc(pendingOpsStore, docId);
182
233
  await tx.complete();
183
234
  }
184
235
  async confirmSendingChange(docId) {
185
- const [tx, sendingChanges, committedOps, docsStore] = await this.transaction(
236
+ const [tx, sendingChanges, committedOps, docsStore] = await this.db.transaction(
186
237
  ["sendingChanges", "committedOps", "docs"],
187
238
  "readwrite"
188
239
  );
@@ -200,7 +251,7 @@ class LWWIndexedDBStore extends (_a = IndexedDBStore, _getDoc_dec = [blockable],
200
251
  await tx.complete();
201
252
  }
202
253
  async applyServerChanges(docId, serverChanges) {
203
- const [tx, committedOps, snapshots, docsStore] = await this.transaction(
254
+ const [tx, committedOps, snapshots, docsStore] = await this.db.transaction(
204
255
  ["committedOps", "snapshots", "docs"],
205
256
  "readwrite"
206
257
  );
@@ -259,7 +310,7 @@ class LWWIndexedDBStore extends (_a = IndexedDBStore, _getDoc_dec = [blockable],
259
310
  await this.deleteFieldsForDoc(committedOps, docId);
260
311
  }
261
312
  }
262
- _init = __decoratorStart(_a);
313
+ _init = __decoratorStart(null);
263
314
  __decorateElement(_init, 1, "getDoc", _getDoc_dec, LWWIndexedDBStore);
264
315
  __decorateElement(_init, 1, "saveDoc", _saveDoc_dec, LWWIndexedDBStore);
265
316
  __decorateElement(_init, 1, "deleteDoc", _deleteDoc_dec, LWWIndexedDBStore);
@@ -75,7 +75,7 @@ class OTAlgorithm {
75
75
  }
76
76
  // --- Store forwarding methods ---
77
77
  async trackDocs(docIds) {
78
- return this.store.trackDocs(docIds);
78
+ return this.store.trackDocs(docIds, "ot");
79
79
  }
80
80
  async untrackDocs(docIds) {
81
81
  return this.store.untrackDocs(docIds);
@@ -6,11 +6,11 @@ import '@dabble/delta';
6
6
  import '../json-patch/types.js';
7
7
 
8
8
  /**
9
- * A trivial in‑memory implementation of OfflineStore (soon PatchesStore).
9
+ * A trivial in‑memory implementation of OTClientStore.
10
10
  * All data lives in JS objects – nothing survives a page reload.
11
11
  * Useful for unit tests or when you want the old 'stateless realtime' behaviour.
12
12
  */
13
- declare class InMemoryStore implements OTClientStore {
13
+ declare class OTInMemoryStore implements OTClientStore {
14
14
  private docs;
15
15
  getDoc(docId: string): Promise<PatchesSnapshot | undefined>;
16
16
  getPendingChanges(docId: string): Promise<Change[]>;
@@ -19,11 +19,11 @@ declare class InMemoryStore implements OTClientStore {
19
19
  saveDoc(docId: string, snapshot: PatchesState): Promise<void>;
20
20
  savePendingChanges(docId: string, changes: Change[]): Promise<void>;
21
21
  applyServerChanges(docId: string, serverChanges: Change[], rebasedPendingChanges: Change[]): Promise<void>;
22
- trackDocs(docIds: string[]): Promise<void>;
22
+ trackDocs(docIds: string[], _algorithm?: 'ot' | 'lww'): Promise<void>;
23
23
  untrackDocs(docIds: string[]): Promise<void>;
24
24
  deleteDoc(docId: string): Promise<void>;
25
25
  confirmDeleteDoc(docId: string): Promise<void>;
26
26
  close(): Promise<void>;
27
27
  }
28
28
 
29
- export { InMemoryStore };
29
+ export { OTInMemoryStore };
@@ -1,6 +1,6 @@
1
1
  import "../chunk-IZ2YBCUP.js";
2
2
  import { applyChanges } from "../algorithms/ot/shared/applyChanges.js";
3
- class InMemoryStore {
3
+ class OTInMemoryStore {
4
4
  docs = /* @__PURE__ */ new Map();
5
5
  // ─── Reconstruction ────────────────────────────────────────────────────
6
6
  async getDoc(docId) {
@@ -31,7 +31,11 @@ class InMemoryStore {
31
31
  }
32
32
  // ─── Writes ────────────────────────────────────────────────────────────
33
33
  async saveDoc(docId, snapshot) {
34
- this.docs.set(docId, { snapshot, committed: [], pending: [] });
34
+ this.docs.set(docId, {
35
+ snapshot,
36
+ committed: [],
37
+ pending: []
38
+ });
35
39
  }
36
40
  async savePendingChanges(docId, changes) {
37
41
  const buf = this.docs.get(docId) ?? { committed: [], pending: [] };
@@ -45,7 +49,7 @@ class InMemoryStore {
45
49
  buf.pending = [...rebasedPendingChanges];
46
50
  }
47
51
  // ─── Metadata / Tracking ───────────────────────────────────────────
48
- async trackDocs(docIds) {
52
+ async trackDocs(docIds, _algorithm) {
49
53
  for (const docId of docIds) {
50
54
  const buf = this.docs.get(docId) ?? { committed: [], pending: [] };
51
55
  buf.deleted = void 0;
@@ -74,5 +78,5 @@ class InMemoryStore {
74
78
  }
75
79
  }
76
80
  export {
77
- InMemoryStore
81
+ OTInMemoryStore
78
82
  };
@@ -1,20 +1,21 @@
1
1
  import { PatchesSnapshot, PatchesState, Change } from '../types.js';
2
2
  import { IndexedDBStore } from './IndexedDBStore.js';
3
3
  import { OTClientStore } from './OTClientStore.js';
4
+ import { TrackedDoc } from './PatchesStore.js';
4
5
  import '../json-patch/JSONPatch.js';
5
6
  import '@dabble/delta';
6
7
  import '../json-patch/types.js';
8
+ import '../event-signal.js';
7
9
  import '../utils/deferred.js';
8
- import './PatchesStore.js';
9
10
 
10
11
  /**
11
12
  * IndexedDB store implementation for Operational Transformation (OT) sync strategy.
12
13
  *
13
14
  * Creates stores:
14
- * - snapshots<{ docId: string; rev: number; state: any }> (primary key: docId)
15
+ * - snapshots<{ docId: string; rev: number; state: any }> (primary key: docId) [shared]
15
16
  * - committedChanges<Change & { docId: string; }> (primary key: [docId, rev])
16
17
  * - pendingChanges<Change & { docId: string; }> (primary key: [docId, rev])
17
- * - docs<{ docId: string; committedRev: number; deleted?: boolean }> (primary key: docId)
18
+ * - docs<{ docId: string; committedRev: number; deleted?: boolean }> (primary key: docId) [shared]
18
19
  *
19
20
  * Under the hood, this class stores snapshots of the document only for committed state.
20
21
  * It does not update the committed state on *every* received committed change as this
@@ -25,9 +26,42 @@ import './PatchesStore.js';
25
26
  * A snapshot will not be created if there are pending changes based on revisions older
26
27
  * than the 200th committed change until those pending changes are committed.
27
28
  */
28
- declare class OTIndexedDBStore extends IndexedDBStore implements OTClientStore {
29
- protected getDBVersion(): number;
30
- protected onUpgrade(db: IDBDatabase, _oldVersion: number): void;
29
+ declare class OTIndexedDBStore implements OTClientStore {
30
+ db: IndexedDBStore;
31
+ constructor(db?: string | IndexedDBStore);
32
+ /**
33
+ * Creates OT-specific object stores during database upgrade.
34
+ */
35
+ protected createOTStores(db: IDBDatabase): void;
36
+ /**
37
+ * List documents for the OT algorithm.
38
+ * Uses the algorithm index for efficient querying.
39
+ */
40
+ listDocs(includeDeleted?: boolean): Promise<TrackedDoc[]>;
41
+ /**
42
+ * Track documents using the OT algorithm.
43
+ */
44
+ trackDocs(docIds: string[]): Promise<void>;
45
+ /**
46
+ * Close the database connection.
47
+ */
48
+ close(): Promise<void>;
49
+ /**
50
+ * Delete the database.
51
+ */
52
+ deleteDB(): Promise<void>;
53
+ /**
54
+ * Set the database name.
55
+ */
56
+ setName(dbName: string): void;
57
+ /**
58
+ * Confirm the deletion of a document.
59
+ */
60
+ confirmDeleteDoc(docId: string): Promise<void>;
61
+ /**
62
+ * Get the committed revision for a document.
63
+ */
64
+ getCommittedRev(docId: string): Promise<number>;
31
65
  /**
32
66
  * Rebuilds a document snapshot + pending queue *without* loading
33
67
  * the full PatchesDoc into memory.
@@ -2,23 +2,28 @@ import {
2
2
  __decorateElement,
3
3
  __decoratorMetadata,
4
4
  __decoratorStart,
5
+ __publicField,
5
6
  __runInitializers
6
7
  } from "../chunk-IZ2YBCUP.js";
7
- var _applyServerChanges_dec, _getPendingChanges_dec, _savePendingChanges_dec, _saveDoc_dec, _deleteDoc_dec, _getDoc_dec, _a, _init;
8
+ var _applyServerChanges_dec, _getPendingChanges_dec, _savePendingChanges_dec, _saveDoc_dec, _deleteDoc_dec, _getDoc_dec, _init;
8
9
  import { applyChanges } from "../algorithms/ot/shared/applyChanges.js";
9
10
  import { blockable } from "../utils/concurrency.js";
10
11
  import { IndexedDBStore } from "./IndexedDBStore.js";
11
- const DB_VERSION = 1;
12
12
  const SNAPSHOT_INTERVAL = 200;
13
- class OTIndexedDBStore extends (_a = IndexedDBStore, _getDoc_dec = [blockable], _deleteDoc_dec = [blockable], _saveDoc_dec = [blockable], _savePendingChanges_dec = [blockable], _getPendingChanges_dec = [blockable], _applyServerChanges_dec = [blockable], _a) {
14
- constructor() {
15
- super(...arguments);
13
+ _getDoc_dec = [blockable], _deleteDoc_dec = [blockable], _saveDoc_dec = [blockable], _savePendingChanges_dec = [blockable], _getPendingChanges_dec = [blockable], _applyServerChanges_dec = [blockable];
14
+ class OTIndexedDBStore {
15
+ constructor(db) {
16
16
  __runInitializers(_init, 5, this);
17
+ __publicField(this, "db");
18
+ this.db = !db || typeof db === "string" ? new IndexedDBStore(db) : db;
19
+ this.db.onUpgrade((db2, _oldVersion, _transaction) => {
20
+ this.createOTStores(db2);
21
+ });
17
22
  }
18
- getDBVersion() {
19
- return DB_VERSION;
20
- }
21
- onUpgrade(db, _oldVersion) {
23
+ /**
24
+ * Creates OT-specific object stores during database upgrade.
25
+ */
26
+ createOTStores(db) {
22
27
  if (!db.objectStoreNames.contains("committedChanges")) {
23
28
  db.createObjectStore("committedChanges", { keyPath: ["docId", "rev"] });
24
29
  }
@@ -26,8 +31,51 @@ class OTIndexedDBStore extends (_a = IndexedDBStore, _getDoc_dec = [blockable],
26
31
  db.createObjectStore("pendingChanges", { keyPath: ["docId", "rev"] });
27
32
  }
28
33
  }
34
+ /**
35
+ * List documents for the OT algorithm.
36
+ * Uses the algorithm index for efficient querying.
37
+ */
38
+ async listDocs(includeDeleted = false) {
39
+ return this.db.listDocs(includeDeleted, "ot");
40
+ }
41
+ /**
42
+ * Track documents using the OT algorithm.
43
+ */
44
+ async trackDocs(docIds) {
45
+ return this.db.trackDocs(docIds, "ot");
46
+ }
47
+ /**
48
+ * Close the database connection.
49
+ */
50
+ async close() {
51
+ return this.db.close();
52
+ }
53
+ /**
54
+ * Delete the database.
55
+ */
56
+ async deleteDB() {
57
+ return this.db.deleteDB();
58
+ }
59
+ /**
60
+ * Set the database name.
61
+ */
62
+ setName(dbName) {
63
+ return this.db.setName(dbName);
64
+ }
65
+ /**
66
+ * Confirm the deletion of a document.
67
+ */
68
+ async confirmDeleteDoc(docId) {
69
+ return this.db.confirmDeleteDoc(docId);
70
+ }
71
+ /**
72
+ * Get the committed revision for a document.
73
+ */
74
+ async getCommittedRev(docId) {
75
+ return this.db.getCommittedRev(docId);
76
+ }
29
77
  async getDoc(docId) {
30
- const [tx, docsStore, snapshots, committedChanges, pendingChanges] = await this.transaction(
78
+ const [tx, docsStore, snapshots, committedChanges, pendingChanges] = await this.db.transaction(
31
79
  ["docs", "snapshots", "committedChanges", "pendingChanges"],
32
80
  "readonly"
33
81
  );
@@ -49,7 +97,7 @@ class OTIndexedDBStore extends (_a = IndexedDBStore, _getDoc_dec = [blockable],
49
97
  };
50
98
  }
51
99
  async deleteDoc(docId) {
52
- const [tx, snapshots, committedChanges, pendingChanges, docsStore] = await this.transaction(
100
+ const [tx, snapshots, committedChanges, pendingChanges, docsStore] = await this.db.transaction(
53
101
  ["snapshots", "committedChanges", "pendingChanges", "docs"],
54
102
  "readwrite"
55
103
  );
@@ -63,7 +111,7 @@ class OTIndexedDBStore extends (_a = IndexedDBStore, _getDoc_dec = [blockable],
63
111
  await tx.complete();
64
112
  }
65
113
  async saveDoc(docId, docState) {
66
- const [tx, snapshots, committedChanges, pendingChanges, docsStore] = await this.transaction(
114
+ const [tx, snapshots, committedChanges, pendingChanges, docsStore] = await this.db.transaction(
67
115
  ["snapshots", "committedChanges", "pendingChanges", "docs"],
68
116
  "readwrite"
69
117
  );
@@ -77,10 +125,10 @@ class OTIndexedDBStore extends (_a = IndexedDBStore, _getDoc_dec = [blockable],
77
125
  await tx.complete();
78
126
  }
79
127
  async savePendingChanges(docId, changes) {
80
- const [tx, pendingChanges, docsStore] = await this.transaction(["pendingChanges", "docs"], "readwrite");
128
+ const [tx, pendingChanges, docsStore] = await this.db.transaction(["pendingChanges", "docs"], "readwrite");
81
129
  let docMeta = await docsStore.get(docId);
82
130
  if (!docMeta) {
83
- docMeta = { docId, committedRev: 0 };
131
+ docMeta = { docId, committedRev: 0, algorithm: "ot" };
84
132
  await docsStore.put(docMeta);
85
133
  } else if (docMeta.deleted) {
86
134
  delete docMeta.deleted;
@@ -91,13 +139,13 @@ class OTIndexedDBStore extends (_a = IndexedDBStore, _getDoc_dec = [blockable],
91
139
  await tx.complete();
92
140
  }
93
141
  async getPendingChanges(docId) {
94
- const [tx, pendingChanges] = await this.transaction(["pendingChanges"], "readonly");
142
+ const [tx, pendingChanges] = await this.db.transaction(["pendingChanges"], "readonly");
95
143
  const result = await pendingChanges.getAll([docId, 0], [docId, Infinity]);
96
144
  await tx.complete();
97
145
  return result;
98
146
  }
99
147
  async applyServerChanges(docId, serverChanges, rebasedPendingChanges) {
100
- const [tx, committedChangesStore, pendingChangesStore, snapshots, docsStore] = await this.transaction(
148
+ const [tx, committedChangesStore, pendingChangesStore, snapshots, docsStore] = await this.db.transaction(
101
149
  ["committedChanges", "pendingChanges", "snapshots", "docs"],
102
150
  "readwrite"
103
151
  );
@@ -133,7 +181,7 @@ class OTIndexedDBStore extends (_a = IndexedDBStore, _getDoc_dec = [blockable],
133
181
  * @param docIds - The IDs of the documents to untrack.
134
182
  */
135
183
  async untrackDocs(docIds) {
136
- const [tx, docsStore, snapshots, committedChanges, pendingChanges] = await this.transaction(
184
+ const [tx, docsStore, snapshots, committedChanges, pendingChanges] = await this.db.transaction(
137
185
  ["docs", "snapshots", "committedChanges", "pendingChanges"],
138
186
  "readwrite"
139
187
  );
@@ -150,7 +198,7 @@ class OTIndexedDBStore extends (_a = IndexedDBStore, _getDoc_dec = [blockable],
150
198
  await tx.complete();
151
199
  }
152
200
  }
153
- _init = __decoratorStart(_a);
201
+ _init = __decoratorStart(null);
154
202
  __decorateElement(_init, 1, "getDoc", _getDoc_dec, OTIndexedDBStore);
155
203
  __decorateElement(_init, 1, "deleteDoc", _deleteDoc_dec, OTIndexedDBStore);
156
204
  __decorateElement(_init, 1, "saveDoc", _saveDoc_dec, OTIndexedDBStore);
@@ -1,11 +1,11 @@
1
1
  import { Unsubscriber, Signal } from '../event-signal.js';
2
2
  import { JSONPatchOp } from '../json-patch/types.js';
3
3
  import { Change } from '../types.js';
4
- import { AlgorithmName, ClientAlgorithm } from './ClientAlgorithm.js';
4
+ import { ClientAlgorithm } from './ClientAlgorithm.js';
5
5
  import { P as PatchesDocOptions, a as PatchesDoc } from '../BaseDoc-DkP3tUhT.js';
6
+ import { AlgorithmName } from './PatchesStore.js';
6
7
  import '../json-patch/JSONPatch.js';
7
8
  import '@dabble/delta';
8
- import './PatchesStore.js';
9
9
 
10
10
  /**
11
11
  * Options for creating a Patches instance.
@@ -114,7 +114,18 @@ class Patches {
114
114
  async openDoc(docId, opts = {}) {
115
115
  const existing = this.docs.get(docId);
116
116
  if (existing) return existing.doc;
117
- const algorithmName = opts.algorithm ?? this.defaultAlgorithm;
117
+ let algorithmName = opts.algorithm;
118
+ if (!algorithmName) {
119
+ for (const algo of Object.values(this.algorithms)) {
120
+ const docs = await algo.listDocs(false);
121
+ const tracked = docs.find((d) => d.docId === docId);
122
+ if (tracked?.algorithm) {
123
+ algorithmName = tracked.algorithm;
124
+ break;
125
+ }
126
+ }
127
+ algorithmName = algorithmName ?? this.defaultAlgorithm;
128
+ }
118
129
  const algorithm = this._getAlgorithm(algorithmName);
119
130
  await this.trackDocs([docId], algorithmName);
120
131
  const snapshot = await algorithm.loadDoc(docId);
@@ -3,6 +3,8 @@ import '../json-patch/JSONPatch.js';
3
3
  import '@dabble/delta';
4
4
  import '../json-patch/types.js';
5
5
 
6
+ /** Available algorithm names */
7
+ type AlgorithmName = 'ot' | 'lww';
6
8
  /** Represents metadata for a document tracked by the store. */
7
9
  interface TrackedDoc {
8
10
  docId: string;
@@ -10,6 +12,8 @@ interface TrackedDoc {
10
12
  committedRev: number;
11
13
  /** Optional flag indicating the document has been locally deleted. */
12
14
  deleted?: true;
15
+ /** The sync algorithm this document uses. */
16
+ algorithm?: AlgorithmName;
13
17
  }
14
18
  /**
15
19
  * Pluggable persistence layer contract used by Patches + PatchesSync.
@@ -24,14 +28,15 @@ interface PatchesStore {
24
28
  * Sets initial committedRev to 0 for new documents.
25
29
  *
26
30
  * @param docIds Array of document IDs to start tracking
31
+ * @param algorithm The algorithm to use for this document ('ot' or 'lww')
27
32
  * @example
28
33
  * // Start tracking two documents
29
- * await store.trackDocs(['doc1', 'doc2']);
34
+ * await store.trackDocs(['doc1', 'doc2'], 'ot');
30
35
  *
31
36
  * // Reactivate a previously deleted document
32
- * await store.trackDocs(['previously-deleted-doc']);
37
+ * await store.trackDocs(['previously-deleted-doc'], 'lww');
33
38
  */
34
- trackDocs(docIds: string[]): Promise<void>;
39
+ trackDocs(docIds: string[], algorithm?: AlgorithmName): Promise<void>;
35
40
  /**
36
41
  * Permanently removes documents from local tracking and storage.
37
42
  *
@@ -153,4 +158,4 @@ interface PatchesStore {
153
158
  close(): Promise<void>;
154
159
  }
155
160
 
156
- export type { PatchesStore, TrackedDoc };
161
+ export type { AlgorithmName, PatchesStore, TrackedDoc };
@@ -1,12 +1,12 @@
1
- import { AlgorithmName } from './ClientAlgorithm.js';
1
+ import { AlgorithmName } from './PatchesStore.js';
2
2
  import { Patches } from './Patches.js';
3
3
  import { P as PatchesDocOptions } from '../BaseDoc-DkP3tUhT.js';
4
- import '../json-patch/types.js';
5
4
  import '../types.js';
6
5
  import '../json-patch/JSONPatch.js';
7
6
  import '@dabble/delta';
8
- import './PatchesStore.js';
7
+ import '../json-patch/types.js';
9
8
  import '../event-signal.js';
9
+ import './ClientAlgorithm.js';
10
10
 
11
11
  /**
12
12
  * Options for factory functions that create Patches instances.
@@ -62,11 +62,12 @@ declare function createLWWIndexedDBPatches(options: IndexedDBFactoryOptions): Pa
62
62
  * Creates a Patches instance with both OT and LWW algorithms using in-memory stores.
63
63
  * Useful for testing or applications that need both algorithms without persistence.
64
64
  */
65
- declare function createAllPatches(options?: MultiAlgorithmFactoryOptions): Patches;
65
+ declare function createMultiAlgorithmPatches(options?: MultiAlgorithmFactoryOptions): Patches;
66
66
  /**
67
67
  * Creates a Patches instance with both OT and LWW algorithms using IndexedDB stores.
68
68
  * For persistent storage in browser environments with support for both algorithms.
69
+ * Both algorithms share the same IndexedDB database with unified stores.
69
70
  */
70
- declare function createAllIndexedDBPatches(options: MultiAlgorithmIndexedDBFactoryOptions): Patches;
71
+ declare function createMultiAlgorithmIndexedDBPatches(options: MultiAlgorithmIndexedDBFactoryOptions): Patches;
71
72
 
72
- export { type IndexedDBFactoryOptions, type MultiAlgorithmFactoryOptions, type MultiAlgorithmIndexedDBFactoryOptions, type PatchesFactoryOptions, createAllIndexedDBPatches, createAllPatches, createLWWIndexedDBPatches, createLWWPatches, createOTIndexedDBPatches, createOTPatches };
73
+ export { type IndexedDBFactoryOptions, type MultiAlgorithmFactoryOptions, type MultiAlgorithmIndexedDBFactoryOptions, type PatchesFactoryOptions, createLWWIndexedDBPatches, createLWWPatches, createMultiAlgorithmIndexedDBPatches, createMultiAlgorithmPatches, createOTIndexedDBPatches, createOTPatches };
@@ -1,13 +1,14 @@
1
1
  import "../chunk-IZ2YBCUP.js";
2
- import { InMemoryStore } from "./InMemoryStore.js";
2
+ import { OTInMemoryStore } from "./OTInMemoryStore.js";
3
3
  import { LWWInMemoryStore } from "./LWWInMemoryStore.js";
4
+ import { IndexedDBStore } from "./IndexedDBStore.js";
4
5
  import { LWWIndexedDBStore } from "./LWWIndexedDBStore.js";
5
6
  import { LWWAlgorithm } from "./LWWAlgorithm.js";
6
7
  import { OTIndexedDBStore } from "./OTIndexedDBStore.js";
7
8
  import { OTAlgorithm } from "./OTAlgorithm.js";
8
9
  import { Patches } from "./Patches.js";
9
10
  function createOTPatches(options = {}) {
10
- const store = new InMemoryStore();
11
+ const store = new OTInMemoryStore();
11
12
  const otAlgorithm = new OTAlgorithm(store, options.docOptions);
12
13
  return new Patches({
13
14
  algorithms: { ot: otAlgorithm },
@@ -46,8 +47,8 @@ function createLWWIndexedDBPatches(options) {
46
47
  docOptions: options.docOptions
47
48
  });
48
49
  }
49
- function createAllPatches(options = {}) {
50
- const otStore = new InMemoryStore();
50
+ function createMultiAlgorithmPatches(options = {}) {
51
+ const otStore = new OTInMemoryStore();
51
52
  const lwwStore = new LWWInMemoryStore();
52
53
  const otAlgorithm = new OTAlgorithm(otStore, options.docOptions);
53
54
  const lwwAlgorithm = new LWWAlgorithm(lwwStore);
@@ -58,9 +59,10 @@ function createAllPatches(options = {}) {
58
59
  docOptions: options.docOptions
59
60
  });
60
61
  }
61
- function createAllIndexedDBPatches(options) {
62
- const otStore = new OTIndexedDBStore(options.dbName);
63
- const lwwStore = new LWWIndexedDBStore(`${options.dbName}-lww`);
62
+ function createMultiAlgorithmIndexedDBPatches(options) {
63
+ const baseStore = new IndexedDBStore(options.dbName);
64
+ const otStore = new OTIndexedDBStore(baseStore);
65
+ const lwwStore = new LWWIndexedDBStore(baseStore);
64
66
  const otAlgorithm = new OTAlgorithm(otStore, options.docOptions);
65
67
  const lwwAlgorithm = new LWWAlgorithm(lwwStore);
66
68
  return new Patches({
@@ -71,10 +73,10 @@ function createAllIndexedDBPatches(options) {
71
73
  });
72
74
  }
73
75
  export {
74
- createAllIndexedDBPatches,
75
- createAllPatches,
76
76
  createLWWIndexedDBPatches,
77
77
  createLWWPatches,
78
+ createMultiAlgorithmIndexedDBPatches,
79
+ createMultiAlgorithmPatches,
78
80
  createOTIndexedDBPatches,
79
81
  createOTPatches
80
82
  };
@@ -1,9 +1,9 @@
1
1
  export { B as BaseDoc, O as OTDoc, a as PatchesDoc, O as PatchesDocClass, P as PatchesDocOptions } from '../BaseDoc-DkP3tUhT.js';
2
- export { IndexedDBFactoryOptions, MultiAlgorithmFactoryOptions, MultiAlgorithmIndexedDBFactoryOptions, PatchesFactoryOptions, createAllIndexedDBPatches, createAllPatches, createLWWIndexedDBPatches, createLWWPatches, createOTIndexedDBPatches, createOTPatches } from './factories.js';
2
+ export { IndexedDBFactoryOptions, MultiAlgorithmFactoryOptions, MultiAlgorithmIndexedDBFactoryOptions, PatchesFactoryOptions, createLWWIndexedDBPatches, createLWWPatches, createMultiAlgorithmIndexedDBPatches, createMultiAlgorithmPatches, createOTIndexedDBPatches, createOTPatches } 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';
6
- export { InMemoryStore } from './InMemoryStore.js';
6
+ export { OTInMemoryStore } from './OTInMemoryStore.js';
7
7
  export { LWWInMemoryStore } from './LWWInMemoryStore.js';
8
8
  export { LWWDoc } from './LWWDoc.js';
9
9
  export { LWWAlgorithm } from './LWWAlgorithm.js';
@@ -11,10 +11,10 @@ export { LWWBatcher } from './LWWBatcher.js';
11
11
  export { OTAlgorithm } from './OTAlgorithm.js';
12
12
  export { Patches, PatchesOptions } from './Patches.js';
13
13
  export { PatchesHistoryClient } from './PatchesHistoryClient.js';
14
- export { PatchesStore, TrackedDoc } from './PatchesStore.js';
14
+ export { AlgorithmName, PatchesStore, TrackedDoc } from './PatchesStore.js';
15
15
  export { OTClientStore } from './OTClientStore.js';
16
16
  export { LWWClientStore } from './LWWClientStore.js';
17
- export { AlgorithmName, ClientAlgorithm } from './ClientAlgorithm.js';
17
+ export { ClientAlgorithm } from './ClientAlgorithm.js';
18
18
  import '../event-signal.js';
19
19
  import '../json-patch/types.js';
20
20
  import '../types.js';
@@ -3,7 +3,7 @@ export * from "./factories.js";
3
3
  export * from "./IndexedDBStore.js";
4
4
  export * from "./OTIndexedDBStore.js";
5
5
  export * from "./LWWIndexedDBStore.js";
6
- export * from "./InMemoryStore.js";
6
+ export * from "./OTInMemoryStore.js";
7
7
  export * from "./LWWInMemoryStore.js";
8
8
  export * from "./LWWDoc.js";
9
9
  export * from "./LWWAlgorithm.js";
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  export { Delta } from '@dabble/delta';
2
2
  export { B as BaseDoc, O as OTDoc, a as PatchesDoc, O as PatchesDocClass, P as PatchesDocOptions } from './BaseDoc-DkP3tUhT.js';
3
- export { IndexedDBFactoryOptions, MultiAlgorithmFactoryOptions, MultiAlgorithmIndexedDBFactoryOptions, PatchesFactoryOptions, createAllIndexedDBPatches, createAllPatches, createLWWIndexedDBPatches, createLWWPatches, createOTIndexedDBPatches, createOTPatches } from './client/factories.js';
3
+ export { IndexedDBFactoryOptions, MultiAlgorithmFactoryOptions, MultiAlgorithmIndexedDBFactoryOptions, PatchesFactoryOptions, createLWWIndexedDBPatches, createLWWPatches, createMultiAlgorithmIndexedDBPatches, createMultiAlgorithmPatches, createOTIndexedDBPatches, createOTPatches } 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';
7
- export { InMemoryStore } from './client/InMemoryStore.js';
7
+ export { OTInMemoryStore } from './client/OTInMemoryStore.js';
8
8
  export { LWWInMemoryStore } from './client/LWWInMemoryStore.js';
9
9
  export { LWWDoc } from './client/LWWDoc.js';
10
10
  export { LWWAlgorithm } from './client/LWWAlgorithm.js';
@@ -12,10 +12,10 @@ export { LWWBatcher } from './client/LWWBatcher.js';
12
12
  export { OTAlgorithm } from './client/OTAlgorithm.js';
13
13
  export { Patches, PatchesOptions } from './client/Patches.js';
14
14
  export { PatchesHistoryClient } from './client/PatchesHistoryClient.js';
15
- export { PatchesStore, TrackedDoc } from './client/PatchesStore.js';
15
+ export { AlgorithmName, PatchesStore, TrackedDoc } from './client/PatchesStore.js';
16
16
  export { OTClientStore } from './client/OTClientStore.js';
17
17
  export { LWWClientStore } from './client/LWWClientStore.js';
18
- export { AlgorithmName, ClientAlgorithm } from './client/ClientAlgorithm.js';
18
+ export { ClientAlgorithm } from './client/ClientAlgorithm.js';
19
19
  export { createChange } from './data/change.js';
20
20
  export { createVersionId, createVersionMetadata } from './data/version.js';
21
21
  export { ErrorSubscriber, Signal, SignalSubscriber, Unsubscriber, signal } from './event-signal.js';
@@ -6,14 +6,14 @@ import { PatchesWebSocket } from './websocket/PatchesWebSocket.js';
6
6
  import { WebSocketOptions } from './websocket/WebSocketTransport.js';
7
7
  import { SizeCalculator } from '../algorithms/ot/shared/changeBatching.js';
8
8
  import { Patches } from '../client/Patches.js';
9
- import { AlgorithmName, ClientAlgorithm } from '../client/ClientAlgorithm.js';
9
+ import { AlgorithmName } from '../client/PatchesStore.js';
10
+ import { ClientAlgorithm } from '../client/ClientAlgorithm.js';
10
11
  import '../json-patch/JSONPatch.js';
11
12
  import '@dabble/delta';
12
13
  import '../json-patch/types.js';
13
14
  import './PatchesClient.js';
14
15
  import '../utils/deferred.js';
15
16
  import '../BaseDoc-DkP3tUhT.js';
16
- import '../client/PatchesStore.js';
17
17
 
18
18
  interface PatchesSyncState {
19
19
  online: boolean;
@@ -138,13 +138,19 @@ class PatchesSync {
138
138
  if (!this.state.connected) return;
139
139
  this.updateState({ syncing: "updating" });
140
140
  try {
141
- const defaultAlgorithm = this.patches.algorithms[this.patches.defaultAlgorithm];
142
- if (!defaultAlgorithm) {
143
- throw new Error("Default algorithm not found");
141
+ const allTracked = [];
142
+ for (const algorithm of Object.values(this.patches.algorithms)) {
143
+ if (!algorithm) continue;
144
+ const docs = await algorithm.listDocs(true);
145
+ allTracked.push(...docs);
146
+ for (const doc of docs) {
147
+ if (doc.algorithm) {
148
+ this.docAlgorithms.set(doc.docId, doc.algorithm);
149
+ }
150
+ }
144
151
  }
145
- const tracked = await defaultAlgorithm.listDocs(true);
146
- const activeDocs = tracked.filter((t) => !t.deleted);
147
- const deletedDocs = tracked.filter((t) => t.deleted);
152
+ const activeDocs = allTracked.filter((t) => !t.deleted);
153
+ const deletedDocs = allTracked.filter((t) => t.deleted);
148
154
  const activeDocIds = activeDocs.map((t) => t.docId);
149
155
  this.trackedDocs = new Set(activeDocIds);
150
156
  if (activeDocIds.length > 0) {
@@ -313,6 +319,17 @@ class PatchesSync {
313
319
  const newIds = docIds.filter((id) => !this.trackedDocs.has(id));
314
320
  if (!newIds.length) return;
315
321
  newIds.forEach((id) => this.trackedDocs.add(id));
322
+ for (const docId of newIds) {
323
+ for (const algorithm of Object.values(this.patches.algorithms)) {
324
+ if (!algorithm) continue;
325
+ const docs = await algorithm.listDocs(false);
326
+ const tracked = docs.find((d) => d.docId === docId);
327
+ if (tracked?.algorithm) {
328
+ this.docAlgorithms.set(docId, tracked.algorithm);
329
+ break;
330
+ }
331
+ }
332
+ }
316
333
  let subscribeIds = newIds;
317
334
  if (this.options?.subscribeFilter) {
318
335
  const alreadyTracked = this.options.subscribeFilter([...this.trackedDocs]);
@@ -87,7 +87,7 @@ class LWWServer {
87
87
  const change = changes[0];
88
88
  const serverNow = Date.now();
89
89
  const clientRev = change.rev;
90
- const newOps = change.ops.map((op) => op.ts ? op : { ...op, ts: serverNow });
90
+ const newOps = change.ops.map((op) => op.ts ? op : { ...op, ts: change.createdAt ?? serverNow });
91
91
  const existingOps = await this.store.listOps(docId);
92
92
  const { opsToSave, pathsToDelete, opsToReturn } = consolidateOps(existingOps, newOps);
93
93
  const opsToStore = convertDeltaOps(opsToSave);
@@ -38,9 +38,9 @@ interface PatchesProviderProps {
38
38
  * @example
39
39
  * ```tsx
40
40
  * import { PatchesProvider } from '@dabble/patches/solid';
41
- * import { Patches, InMemoryStore } from '@dabble/patches/client';
41
+ * import { Patches, OTInMemoryStore } from '@dabble/patches/client';
42
42
  *
43
- * const patches = new Patches({ store: new InMemoryStore() });
43
+ * const patches = new Patches({ store: new OTInMemoryStore() });
44
44
  *
45
45
  * <PatchesProvider patches={patches}>
46
46
  * <App />
@@ -40,11 +40,11 @@ interface PatchesContext {
40
40
  * @example
41
41
  * ```typescript
42
42
  * import { createApp } from 'vue'
43
- * import { Patches, InMemoryStore } from '@dabble/patches/client'
43
+ * import { Patches, OTInMemoryStore } from '@dabble/patches/client'
44
44
  * import { PatchesSync } from '@dabble/patches/net'
45
45
  * import { providePatchesContext } from '@dabble/patches/vue'
46
46
  *
47
- * const patches = new Patches({ store: new InMemoryStore() })
47
+ * const patches = new Patches({ store: new OTInMemoryStore() })
48
48
  * const sync = new PatchesSync(patches, 'wss://your-server.com')
49
49
  *
50
50
  * const app = createApp(App)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dabble/patches",
3
- "version": "0.7.4",
3
+ "version": "0.7.5",
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": {