@dabble/patches 0.7.7 → 0.7.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/{BaseDoc-DkP3tUhT.d.ts → BaseDoc-_Rsau70J.d.ts} +9 -9
  2. package/dist/algorithms/lww/consolidateOps.d.ts +1 -1
  3. package/dist/algorithms/lww/consolidateOps.js +1 -0
  4. package/dist/client/BaseDoc.d.ts +1 -1
  5. package/dist/client/BaseDoc.js +2 -2
  6. package/dist/client/ClientAlgorithm.d.ts +1 -1
  7. package/dist/client/IndexedDBStore.d.ts +11 -11
  8. package/dist/client/IndexedDBStore.js +14 -14
  9. package/dist/client/LWWAlgorithm.d.ts +1 -1
  10. package/dist/client/LWWDoc.d.ts +3 -3
  11. package/dist/client/LWWIndexedDBStore.d.ts +1 -1
  12. package/dist/client/OTAlgorithm.d.ts +1 -1
  13. package/dist/client/OTDoc.d.ts +1 -1
  14. package/dist/client/OTIndexedDBStore.d.ts +1 -1
  15. package/dist/client/Patches.d.ts +1 -1
  16. package/dist/client/PatchesDoc.d.ts +1 -1
  17. package/dist/client/factories.d.ts +1 -1
  18. package/dist/client/index.d.ts +1 -1
  19. package/dist/index.d.ts +1 -1
  20. package/dist/net/PatchesClient.d.ts +1 -1
  21. package/dist/net/PatchesClient.js +2 -2
  22. package/dist/net/PatchesSync.d.ts +12 -2
  23. package/dist/net/PatchesSync.js +20 -7
  24. package/dist/net/index.d.ts +3 -3
  25. package/dist/net/protocol/JSONRPCClient.js +2 -0
  26. package/dist/net/protocol/JSONRPCServer.js +13 -1
  27. package/dist/net/protocol/types.d.ts +1 -1
  28. package/dist/net/websocket/WebSocketServer.d.ts +4 -10
  29. package/dist/net/websocket/WebSocketServer.js +5 -9
  30. package/dist/server/LWWBranchManager.d.ts +2 -2
  31. package/dist/server/LWWBranchManager.js +3 -3
  32. package/dist/server/OTBranchManager.d.ts +1 -1
  33. package/dist/server/OTBranchManager.js +2 -2
  34. package/dist/server/branchUtils.d.ts +1 -1
  35. package/dist/server/branchUtils.js +2 -2
  36. package/dist/shared/doc-manager.d.ts +1 -1
  37. package/dist/solid/context.d.ts +1 -1
  38. package/dist/solid/doc-manager.d.ts +1 -1
  39. package/dist/solid/index.d.ts +1 -1
  40. package/dist/solid/primitives.d.ts +1 -1
  41. package/dist/solid/primitives.js +18 -1
  42. package/dist/vue/composables.d.ts +1 -1
  43. package/dist/vue/doc-manager.d.ts +1 -1
  44. package/dist/vue/index.d.ts +1 -1
  45. package/dist/vue/managed-docs.d.ts +1 -1
  46. package/dist/vue/provider.d.ts +1 -1
  47. package/package.json +1 -1
@@ -8,7 +8,7 @@ import { Change, PatchesSnapshot, SyncingState, ChangeMutator } from './types.js
8
8
  * for handling concurrent edits.
9
9
  *
10
10
  * The `change()` method (inherited from BaseDoc) captures ops and emits them
11
- * via `onChange` - it does NOT apply locally. The OTStrategy handles packaging
11
+ * via `onChange` - it does NOT apply locally. The OTAlgorithm handles packaging
12
12
  * ops into Changes, persisting them, and calling `applyChanges()` to update state.
13
13
  *
14
14
  * ## State Model
@@ -90,7 +90,7 @@ interface PatchesDocOptions {
90
90
  * Interface for a document synchronized using JSON patches.
91
91
  *
92
92
  * This is the app-facing interface. The doc captures user changes as JSON Patch
93
- * ops and emits them via onChange. The strategy handles packaging ops into Changes,
93
+ * ops and emits them via onChange. The algorithm handles packaging ops into Changes,
94
94
  * persisting them, and updating the doc's state.
95
95
  *
96
96
  * This interface is implemented by both OTDoc (Operational Transformation)
@@ -112,7 +112,7 @@ interface PatchesDoc<T extends object = object> {
112
112
  /**
113
113
  * Subscribe to be notified when the user makes local changes.
114
114
  * Emits the JSON Patch ops captured from the change() call.
115
- * The strategy handles packaging these into Changes.
115
+ * The algorithm handles packaging these into Changes.
116
116
  */
117
117
  readonly onChange: Signal<(ops: JSONPatchOp[]) => void>;
118
118
  /** Subscribe to be notified whenever state changes from any source. */
@@ -123,7 +123,7 @@ interface PatchesDoc<T extends object = object> {
123
123
  subscribe(onUpdate: (newValue: T) => void): Unsubscriber;
124
124
  /**
125
125
  * Captures an update to the document, emitting JSON Patch ops via onChange.
126
- * Does NOT apply locally - the strategy handles state updates.
126
+ * Does NOT apply locally - the algorithm handles state updates.
127
127
  * @param mutator Function that uses JSONPatch methods with type-safe paths.
128
128
  */
129
129
  change(mutator: ChangeMutator<T>): void;
@@ -134,11 +134,11 @@ interface PatchesDoc<T extends object = object> {
134
134
  * Contains shared state and methods used by both OTDoc and LWWDoc.
135
135
  *
136
136
  * The `change()` method captures ops and emits them via `onChange` - it does NOT
137
- * apply locally. The strategy handles packaging ops, persisting them, and updating
137
+ * apply locally. The algorithm handles packaging ops, persisting them, and updating
138
138
  * the doc's state via `applyChanges()`.
139
139
  *
140
140
  * Internal methods (updateSyncing, applyChanges, import) are on this class but not
141
- * on the PatchesDoc interface, as they're only used by Strategy and PatchesSync.
141
+ * on the PatchesDoc interface, as they're only used by Algorithm and PatchesSync.
142
142
  */
143
143
  declare abstract class BaseDoc<T extends object = object> implements PatchesDoc<T> {
144
144
  protected _id: string;
@@ -147,7 +147,7 @@ declare abstract class BaseDoc<T extends object = object> implements PatchesDoc<
147
147
  /**
148
148
  * Subscribe to be notified when the user makes local changes.
149
149
  * Emits the JSON Patch ops captured from the change() call.
150
- * The strategy handles packaging these into Changes.
150
+ * The algorithm handles packaging these into Changes.
151
151
  */
152
152
  readonly onChange: Signal<(ops: JSONPatchOp[]) => void>;
153
153
  /** Subscribe to be notified whenever state changes from any source. */
@@ -174,7 +174,7 @@ declare abstract class BaseDoc<T extends object = object> implements PatchesDoc<
174
174
  subscribe(onUpdate: (newValue: T) => void): Unsubscriber;
175
175
  /**
176
176
  * Captures an update to the document, emitting JSON Patch ops via onChange.
177
- * Does NOT apply locally - the strategy handles state updates via applyChanges.
177
+ * Does NOT apply locally - the algorithm handles state updates via applyChanges.
178
178
  * @param mutator Function that uses JSONPatch methods with type-safe paths.
179
179
  */
180
180
  change(mutator: ChangeMutator<T>): void;
@@ -186,7 +186,7 @@ declare abstract class BaseDoc<T extends object = object> implements PatchesDoc<
186
186
  updateSyncing(newSyncing: SyncingState): void;
187
187
  /**
188
188
  * Applies changes to the document state.
189
- * Called by Strategy for local changes and broadcasts - not part of PatchesDoc interface.
189
+ * Called by Algorithm for local changes and broadcasts - not part of PatchesDoc interface.
190
190
  *
191
191
  * For OT: Distinguishes committed (committedAt > 0) vs pending (committedAt === 0) changes.
192
192
  * For LWW: Applies all ops from changes and updates metadata.
@@ -33,7 +33,7 @@ declare function consolidateOps(existingOps: JSONPatchOp[], newOps: JSONPatchOp[
33
33
  opsToReturn: JSONPatchOp[];
34
34
  };
35
35
  /**
36
- * Any delta ops that aren't combined in consolidateOps need to be converted to replace ops.
36
+ * Any delta ops that aren't combined in consolidateOps need to be converted to replace/remove ops.
37
37
  */
38
38
  declare function convertDeltaOps(ops: JSONPatchOp[]): JSONPatchOp[];
39
39
 
@@ -87,6 +87,7 @@ function consolidateOps(existingOps, newOps) {
87
87
  }
88
88
  function convertDeltaOps(ops) {
89
89
  return ops.map((op) => {
90
+ if (op.op === "remove") return op;
90
91
  const combiner = combinableOps[op.op];
91
92
  const value = typeof op.value === "string" ? "" : 0;
92
93
  if (combiner) return { ...op, op: "replace", value: combiner.apply(value, op.value) };
@@ -1,6 +1,6 @@
1
1
  import '../event-signal.js';
2
2
  import '../json-patch/types.js';
3
3
  import '../types.js';
4
- export { B as BaseDoc } from '../BaseDoc-DkP3tUhT.js';
4
+ export { B as BaseDoc } from '../BaseDoc-_Rsau70J.js';
5
5
  import '../json-patch/JSONPatch.js';
6
6
  import '@dabble/delta';
@@ -8,7 +8,7 @@ class BaseDoc {
8
8
  /**
9
9
  * Subscribe to be notified when the user makes local changes.
10
10
  * Emits the JSON Patch ops captured from the change() call.
11
- * The strategy handles packaging these into Changes.
11
+ * The algorithm handles packaging these into Changes.
12
12
  */
13
13
  onChange = signal();
14
14
  /** Subscribe to be notified whenever state changes from any source. */
@@ -44,7 +44,7 @@ class BaseDoc {
44
44
  }
45
45
  /**
46
46
  * Captures an update to the document, emitting JSON Patch ops via onChange.
47
- * Does NOT apply locally - the strategy handles state updates via applyChanges.
47
+ * Does NOT apply locally - the algorithm handles state updates via applyChanges.
48
48
  * @param mutator Function that uses JSONPatch methods with type-safe paths.
49
49
  */
50
50
  change(mutator) {
@@ -1,6 +1,6 @@
1
1
  import { JSONPatchOp } from '../json-patch/types.js';
2
2
  import { PatchesSnapshot, Change } from '../types.js';
3
- import { a as PatchesDoc } from '../BaseDoc-DkP3tUhT.js';
3
+ import { a as PatchesDoc } from '../BaseDoc-_Rsau70J.js';
4
4
  import { PatchesStore, TrackedDoc } from './PatchesStore.js';
5
5
  import '../json-patch/JSONPatch.js';
6
6
  import '@dabble/delta';
@@ -7,10 +7,10 @@ import '@dabble/delta';
7
7
  import '../json-patch/types.js';
8
8
 
9
9
  /**
10
- * IndexedDB store providing common database operations for all sync strategies.
10
+ * IndexedDB store providing common database operations for all sync algorithms.
11
11
  *
12
12
  * Can be used as a standalone store or as a shared database connection
13
- * for multiple strategy-specific stores (OT, LWW).
13
+ * for multiple algorithm-specific stores (OT, LWW).
14
14
  *
15
15
  * Provides:
16
16
  * - Database lifecycle management (open, close, delete)
@@ -18,7 +18,7 @@ import '../json-patch/types.js';
18
18
  * - Document tracking (listDocs, trackDocs, untrackDocs)
19
19
  * - Basic document operations (deleteDoc, confirmDeleteDoc)
20
20
  * - Revision tracking
21
- * - Extensibility via onUpgrade signal for strategy-specific stores
21
+ * - Extensibility via onUpgrade signal for algorithm-specific stores
22
22
  */
23
23
  declare class IndexedDBStore implements PatchesStore {
24
24
  private static readonly DB_VERSION;
@@ -26,13 +26,13 @@ declare class IndexedDBStore implements PatchesStore {
26
26
  protected dbName?: string;
27
27
  protected dbPromise: Deferred<IDBDatabase>;
28
28
  /**
29
- * Signal emitted during database upgrade, allowing strategy-specific stores
29
+ * Signal emitted during database upgrade, allowing algorithm-specific stores
30
30
  * to create their object stores.
31
31
  */
32
32
  readonly onUpgrade: Signal<(db: IDBDatabase, oldVersion: number, transaction: IDBTransaction) => void>;
33
33
  constructor(dbName?: string);
34
34
  /**
35
- * Creates shared object stores used by all sync strategies.
35
+ * Creates shared object stores used by all sync algorithms.
36
36
  */
37
37
  protected createSharedStores(db: IDBDatabase, _oldVersion: number, _transaction: IDBTransaction): void;
38
38
  protected initDB(): Promise<void>;
@@ -52,19 +52,19 @@ declare class IndexedDBStore implements PatchesStore {
52
52
  transaction(storeNames: string[], mode: IDBTransactionMode): Promise<[IDBTransactionWrapper, ...IDBStoreWrapper[]]>;
53
53
  /**
54
54
  * Retrieves the current document snapshot from storage.
55
- * Implementation varies by sync strategy (OT vs LWW).
56
- * This base implementation throws an error - override in strategy-specific stores.
55
+ * Implementation varies by sync algorithm (OT vs LWW).
56
+ * This base implementation throws an error - override in algorithm-specific stores.
57
57
  */
58
58
  getDoc(_docId: string): Promise<PatchesSnapshot | undefined>;
59
59
  /**
60
60
  * Saves the current document state to persistent storage.
61
- * Implementation varies by sync strategy.
62
- * This base implementation throws an error - override in strategy-specific stores.
61
+ * Implementation varies by sync algorithm.
62
+ * This base implementation throws an error - override in algorithm-specific stores.
63
63
  */
64
64
  saveDoc(_docId: string, _docState: PatchesState): Promise<void>;
65
65
  /**
66
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.
67
+ * This base implementation throws an error - override in algorithm-specific stores.
68
68
  */
69
69
  deleteDoc(_docId: string): Promise<void>;
70
70
  /**
@@ -88,7 +88,7 @@ declare class IndexedDBStore implements PatchesStore {
88
88
  /**
89
89
  * Untrack a document.
90
90
  * @param docIds - The IDs of the documents to untrack.
91
- * This base implementation throws an error - override in strategy-specific stores.
91
+ * This base implementation throws an error - override in algorithm-specific stores.
92
92
  */
93
93
  untrackDocs(_docIds: string[]): Promise<void>;
94
94
  /**
@@ -7,7 +7,7 @@ class IndexedDBStore {
7
7
  dbName;
8
8
  dbPromise;
9
9
  /**
10
- * Signal emitted during database upgrade, allowing strategy-specific stores
10
+ * Signal emitted during database upgrade, allowing algorithm-specific stores
11
11
  * to create their object stores.
12
12
  */
13
13
  onUpgrade = signal();
@@ -22,7 +22,7 @@ class IndexedDBStore {
22
22
  }
23
23
  }
24
24
  /**
25
- * Creates shared object stores used by all sync strategies.
25
+ * Creates shared object stores used by all sync algorithms.
26
26
  */
27
27
  createSharedStores(db, _oldVersion, _transaction) {
28
28
  if (!db.objectStoreNames.contains("docs")) {
@@ -94,30 +94,30 @@ class IndexedDBStore {
94
94
  const stores = storeNames.map((name) => tx.getStore(name));
95
95
  return [tx, ...stores];
96
96
  }
97
- // ─── Strategy-Specific Methods ───────────────────────────────────────────
98
- // These are implemented by strategy-specific stores (OT, LWW)
97
+ // ─── Algorithm-Specific Methods ──────────────────────────────────────────
98
+ // These are implemented by algorithm-specific stores (OT, LWW)
99
99
  /**
100
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.
101
+ * Implementation varies by sync algorithm (OT vs LWW).
102
+ * This base implementation throws an error - override in algorithm-specific stores.
103
103
  */
104
104
  async getDoc(_docId) {
105
- throw new Error("getDoc must be implemented by strategy-specific store");
105
+ throw new Error("getDoc must be implemented by algorithm-specific store");
106
106
  }
107
107
  /**
108
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.
109
+ * Implementation varies by sync algorithm.
110
+ * This base implementation throws an error - override in algorithm-specific stores.
111
111
  */
112
112
  async saveDoc(_docId, _docState) {
113
- throw new Error("saveDoc must be implemented by strategy-specific store");
113
+ throw new Error("saveDoc must be implemented by algorithm-specific store");
114
114
  }
115
115
  /**
116
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.
117
+ * This base implementation throws an error - override in algorithm-specific stores.
118
118
  */
119
119
  async deleteDoc(_docId) {
120
- throw new Error("deleteDoc must be implemented by strategy-specific store");
120
+ throw new Error("deleteDoc must be implemented by algorithm-specific store");
121
121
  }
122
122
  /**
123
123
  * Confirm the deletion of a document.
@@ -180,10 +180,10 @@ class IndexedDBStore {
180
180
  /**
181
181
  * Untrack a document.
182
182
  * @param docIds - The IDs of the documents to untrack.
183
- * This base implementation throws an error - override in strategy-specific stores.
183
+ * This base implementation throws an error - override in algorithm-specific stores.
184
184
  */
185
185
  async untrackDocs(_docIds) {
186
- throw new Error("untrackDocs must be implemented by strategy-specific store");
186
+ throw new Error("untrackDocs must be implemented by algorithm-specific store");
187
187
  }
188
188
  /**
189
189
  * Returns the last committed revision for a document.
@@ -2,7 +2,7 @@ import { JSONPatchOp } from '../json-patch/types.js';
2
2
  import { PatchesSnapshot, Change } from '../types.js';
3
3
  import { ClientAlgorithm } from './ClientAlgorithm.js';
4
4
  import { LWWClientStore } from './LWWClientStore.js';
5
- import { a as PatchesDoc } from '../BaseDoc-DkP3tUhT.js';
5
+ import { a as PatchesDoc } from '../BaseDoc-_Rsau70J.js';
6
6
  import { TrackedDoc } from './PatchesStore.js';
7
7
  import '../json-patch/JSONPatch.js';
8
8
  import '@dabble/delta';
@@ -1,5 +1,5 @@
1
1
  import { PatchesSnapshot, Change } from '../types.js';
2
- import { B as BaseDoc } from '../BaseDoc-DkP3tUhT.js';
2
+ import { B as BaseDoc } from '../BaseDoc-_Rsau70J.js';
3
3
  import '../json-patch/JSONPatch.js';
4
4
  import '@dabble/delta';
5
5
  import '../json-patch/types.js';
@@ -9,13 +9,13 @@ import '../event-signal.js';
9
9
  * LWW (Last-Write-Wins) document implementation.
10
10
  *
11
11
  * The `change()` method (inherited from BaseDoc) captures ops and emits them
12
- * via `onChange` - it does NOT apply locally. The LWWStrategy handles:
12
+ * via `onChange` - it does NOT apply locally. The LWWAlgorithm handles:
13
13
  * - Packaging ops with timestamps
14
14
  * - Merging with pending fields
15
15
  * - Updating the doc's state via `applyChanges()`
16
16
  *
17
17
  * Unlike OTDoc, LWWDoc doesn't need to track committed vs pending state
18
- * separately - the strategy handles all conflict resolution by timestamp.
18
+ * separately - the algorithm handles all conflict resolution by timestamp.
19
19
  *
20
20
  * ## Wire Efficiency
21
21
  * For Worker-Tab communication, `applyChanges()` sends only changes over the wire,
@@ -9,7 +9,7 @@ import '../event-signal.js';
9
9
  import '../utils/deferred.js';
10
10
 
11
11
  /**
12
- * IndexedDB store implementation for Last-Writer-Wins (LWW) sync strategy.
12
+ * IndexedDB store implementation for Last-Writer-Wins (LWW) sync algorithm.
13
13
  *
14
14
  * Creates stores:
15
15
  * - docs<{ docId: string; committedRev: number; deleted?: boolean }> (primary key: docId) [shared with OT]
@@ -2,7 +2,7 @@ import { JSONPatchOp } from '../json-patch/types.js';
2
2
  import { PatchesSnapshot, Change } from '../types.js';
3
3
  import { ClientAlgorithm } from './ClientAlgorithm.js';
4
4
  import { OTClientStore } from './OTClientStore.js';
5
- import { P as PatchesDocOptions, a as PatchesDoc } from '../BaseDoc-DkP3tUhT.js';
5
+ import { P as PatchesDocOptions, a as PatchesDoc } from '../BaseDoc-_Rsau70J.js';
6
6
  import { TrackedDoc } from './PatchesStore.js';
7
7
  import '../json-patch/JSONPatch.js';
8
8
  import '@dabble/delta';
@@ -1,5 +1,5 @@
1
1
  import '../types.js';
2
- export { O as OTDoc } from '../BaseDoc-DkP3tUhT.js';
2
+ export { O as OTDoc } from '../BaseDoc-_Rsau70J.js';
3
3
  import '../json-patch/JSONPatch.js';
4
4
  import '@dabble/delta';
5
5
  import '../json-patch/types.js';
@@ -9,7 +9,7 @@ import '../event-signal.js';
9
9
  import '../utils/deferred.js';
10
10
 
11
11
  /**
12
- * IndexedDB store implementation for Operational Transformation (OT) sync strategy.
12
+ * IndexedDB store implementation for Operational Transformation (OT) sync algorithm.
13
13
  *
14
14
  * Creates stores:
15
15
  * - snapshots<{ docId: string; rev: number; state: any }> (primary key: docId) [shared]
@@ -2,7 +2,7 @@ import { Unsubscriber, Signal } from '../event-signal.js';
2
2
  import { JSONPatchOp } from '../json-patch/types.js';
3
3
  import { Change } from '../types.js';
4
4
  import { ClientAlgorithm } from './ClientAlgorithm.js';
5
- import { P as PatchesDocOptions, a as PatchesDoc } from '../BaseDoc-DkP3tUhT.js';
5
+ import { P as PatchesDocOptions, a as PatchesDoc } from '../BaseDoc-_Rsau70J.js';
6
6
  import { AlgorithmName } from './PatchesStore.js';
7
7
  import '../json-patch/JSONPatch.js';
8
8
  import '@dabble/delta';
@@ -1,6 +1,6 @@
1
1
  import '../event-signal.js';
2
2
  import '../json-patch/types.js';
3
3
  import '../types.js';
4
- export { O as OTDoc, a as PatchesDoc, O as PatchesDocClass, P as PatchesDocOptions } from '../BaseDoc-DkP3tUhT.js';
4
+ export { O as OTDoc, a as PatchesDoc, O as PatchesDocClass, P as PatchesDocOptions } from '../BaseDoc-_Rsau70J.js';
5
5
  import '../json-patch/JSONPatch.js';
6
6
  import '@dabble/delta';
@@ -1,6 +1,6 @@
1
1
  import { AlgorithmName } from './PatchesStore.js';
2
2
  import { Patches } from './Patches.js';
3
- import { P as PatchesDocOptions } from '../BaseDoc-DkP3tUhT.js';
3
+ import { P as PatchesDocOptions } from '../BaseDoc-_Rsau70J.js';
4
4
  import '../types.js';
5
5
  import '../json-patch/JSONPatch.js';
6
6
  import '@dabble/delta';
@@ -1,4 +1,4 @@
1
- export { B as BaseDoc, O as OTDoc, a as PatchesDoc, O as PatchesDocClass, P as PatchesDocOptions } from '../BaseDoc-DkP3tUhT.js';
1
+ export { B as BaseDoc, O as OTDoc, a as PatchesDoc, O as PatchesDocClass, P as PatchesDocOptions } from '../BaseDoc-_Rsau70J.js';
2
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';
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { Delta } from '@dabble/delta';
2
- export { B as BaseDoc, O as OTDoc, a as PatchesDoc, O as PatchesDocClass, P as PatchesDocOptions } from './BaseDoc-DkP3tUhT.js';
2
+ export { B as BaseDoc, O as OTDoc, a as PatchesDoc, O as PatchesDocClass, P as PatchesDocOptions } from './BaseDoc-_Rsau70J.js';
3
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';
@@ -42,7 +42,7 @@ declare class PatchesClient implements PatchesAPI {
42
42
  * @param docId - The ID of the document.
43
43
  * @returns A promise resolving with the document snapshot.
44
44
  */
45
- getDoc<T = any>(docId: string, atRev?: number): Promise<PatchesState<T>>;
45
+ getDoc<T = any>(docId: string): Promise<PatchesState<T>>;
46
46
  /**
47
47
  * Gets changes that occurred for a document after a specific revision number.
48
48
  * @param docId - The ID of the document.
@@ -49,8 +49,8 @@ class PatchesClient {
49
49
  * @param docId - The ID of the document.
50
50
  * @returns A promise resolving with the document snapshot.
51
51
  */
52
- async getDoc(docId, atRev) {
53
- return this.rpc.call("getDoc", docId, atRev);
52
+ async getDoc(docId) {
53
+ return this.rpc.call("getDoc", docId);
54
54
  }
55
55
  /**
56
56
  * Gets changes that occurred for a document after a specific revision number.
@@ -5,15 +5,15 @@ import { JSONRPCClient } from './protocol/JSONRPCClient.js';
5
5
  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
+ import { ClientAlgorithm } from '../client/ClientAlgorithm.js';
8
9
  import { Patches } from '../client/Patches.js';
9
10
  import { AlgorithmName } from '../client/PatchesStore.js';
10
- import { ClientAlgorithm } from '../client/ClientAlgorithm.js';
11
11
  import '../json-patch/JSONPatch.js';
12
12
  import '@dabble/delta';
13
13
  import '../json-patch/types.js';
14
14
  import './PatchesClient.js';
15
15
  import '../utils/deferred.js';
16
- import '../BaseDoc-DkP3tUhT.js';
16
+ import '../BaseDoc-_Rsau70J.js';
17
17
 
18
18
  interface PatchesSyncState {
19
19
  online: boolean;
@@ -142,6 +142,16 @@ declare class PatchesSync {
142
142
  * Cleans up local state and notifies the application with any pending changes that were lost.
143
143
  */
144
144
  protected _handleRemoteDocDeleted(docId: string): Promise<void>;
145
+ /**
146
+ * Applies the subscribeFilter option to a list of doc IDs, returning the subset
147
+ * that should be sent to ws.subscribe/unsubscribe. Returns the full list if no filter is set.
148
+ */
149
+ protected _filterSubscribeIds(docIds: string[]): string[];
150
+ /**
151
+ * Returns the set of doc IDs currently subscribed on the server, derived by
152
+ * applying the subscribe filter to the full tracked set.
153
+ */
154
+ protected _getActiveSubscriptions(): Set<string>;
145
155
  /**
146
156
  * Helper to detect DOC_DELETED (410) errors from the server.
147
157
  */
@@ -155,7 +155,7 @@ class PatchesSync {
155
155
  this.trackedDocs = new Set(activeDocIds);
156
156
  if (activeDocIds.length > 0) {
157
157
  try {
158
- const subscribeIds = this.options?.subscribeFilter?.(activeDocIds) || activeDocIds;
158
+ const subscribeIds = this._filterSubscribeIds(activeDocIds);
159
159
  if (subscribeIds.length) {
160
160
  await this.ws.subscribe(subscribeIds);
161
161
  }
@@ -318,6 +318,7 @@ class PatchesSync {
318
318
  async _handleDocsTracked(docIds) {
319
319
  const newIds = docIds.filter((id) => !this.trackedDocs.has(id));
320
320
  if (!newIds.length) return;
321
+ const alreadySubscribed = this._getActiveSubscriptions();
321
322
  newIds.forEach((id) => this.trackedDocs.add(id));
322
323
  for (const docId of newIds) {
323
324
  for (const algorithm of Object.values(this.patches.algorithms)) {
@@ -330,13 +331,9 @@ class PatchesSync {
330
331
  }
331
332
  }
332
333
  }
333
- let subscribeIds = newIds;
334
- if (this.options?.subscribeFilter) {
335
- const alreadyTracked = this.options.subscribeFilter([...this.trackedDocs]);
336
- subscribeIds = subscribeIds.filter((id) => !alreadyTracked.includes(id));
337
- }
338
334
  if (this.state.connected) {
339
335
  try {
336
+ const subscribeIds = this._filterSubscribeIds(newIds).filter((id) => !alreadySubscribed.has(id));
340
337
  if (subscribeIds.length) {
341
338
  await this.ws.subscribe(subscribeIds);
342
339
  }
@@ -350,8 +347,10 @@ class PatchesSync {
350
347
  async _handleDocsUntracked(docIds) {
351
348
  const existingIds = docIds.filter((id) => this.trackedDocs.has(id));
352
349
  if (!existingIds.length) return;
350
+ const subscribedBefore = this._getActiveSubscriptions();
353
351
  existingIds.forEach((id) => this.trackedDocs.delete(id));
354
- const unsubscribeIds = this.options?.subscribeFilter?.(existingIds) || existingIds;
352
+ const subscribedAfter = this._getActiveSubscriptions();
353
+ const unsubscribeIds = [...subscribedBefore].filter((id) => !subscribedAfter.has(id));
355
354
  if (this.state.connected && unsubscribeIds.length) {
356
355
  try {
357
356
  await this.ws.unsubscribe(unsubscribeIds);
@@ -380,6 +379,20 @@ class PatchesSync {
380
379
  await algorithm.confirmDeleteDoc(docId);
381
380
  await this.onRemoteDocDeleted.emit(docId, pendingChanges);
382
381
  }
382
+ /**
383
+ * Applies the subscribeFilter option to a list of doc IDs, returning the subset
384
+ * that should be sent to ws.subscribe/unsubscribe. Returns the full list if no filter is set.
385
+ */
386
+ _filterSubscribeIds(docIds) {
387
+ return this.options?.subscribeFilter?.(docIds) || docIds;
388
+ }
389
+ /**
390
+ * Returns the set of doc IDs currently subscribed on the server, derived by
391
+ * applying the subscribe filter to the full tracked set.
392
+ */
393
+ _getActiveSubscriptions() {
394
+ return new Set(this._filterSubscribeIds([...this.trackedDocs]));
395
+ }
383
396
  /**
384
397
  * Helper to detect DOC_DELETED (410) errors from the server.
385
398
  */
@@ -15,11 +15,11 @@ export { WebSocketOptions, WebSocketTransport } from './websocket/WebSocketTrans
15
15
  export { CommitChangesOptions } from '../types.js';
16
16
  import '../event-signal.js';
17
17
  import '../algorithms/ot/shared/changeBatching.js';
18
- import '../client/Patches.js';
19
- import '../json-patch/types.js';
20
18
  import '../client/ClientAlgorithm.js';
21
- import '../BaseDoc-DkP3tUhT.js';
19
+ import '../json-patch/types.js';
20
+ import '../BaseDoc-_Rsau70J.js';
22
21
  import '../client/PatchesStore.js';
22
+ import '../client/Patches.js';
23
23
  import '../server/types.js';
24
24
  import '../utils/deferred.js';
25
25
  import '../json-patch/JSONPatch.js';
@@ -24,6 +24,7 @@ class JSONRPCClient {
24
24
  */
25
25
  async call(method, ...args) {
26
26
  const id = this.nextId++;
27
+ while (args.length > 0 && args[args.length - 1] === void 0) args.pop();
27
28
  const params = args.length > 0 ? args : void 0;
28
29
  const message = { jsonrpc: "2.0", id, method, params };
29
30
  return new Promise((resolve, reject) => {
@@ -38,6 +39,7 @@ class JSONRPCClient {
38
39
  * @param args - The arguments to pass to the remote procedure (sent as array)
39
40
  */
40
41
  notify(method, ...args) {
42
+ while (args.length > 0 && args[args.length - 1] === void 0) args.pop();
41
43
  const params = args.length > 0 ? args : void 0;
42
44
  const message = { jsonrpc: "2.0", method, params };
43
45
  this.transport.send(JSON.stringify(message));
@@ -52,6 +52,13 @@ class JSONRPCServer {
52
52
  throw new Error(`Method '${method}' not found on object`);
53
53
  }
54
54
  this.registerMethod(method, async (...args) => {
55
+ const docId = args[0];
56
+ if (typeof docId !== "string" || !docId) {
57
+ throw new StatusError(
58
+ 400,
59
+ `INVALID_REQUEST: docId is required (got ${docId === "" ? "empty string" : String(docId)})`
60
+ );
61
+ }
55
62
  const ctx = getAuthContext();
56
63
  await this.assertAccess(access, ctx, method, args);
57
64
  return obj[method](...args);
@@ -132,7 +139,12 @@ class JSONRPCServer {
132
139
  async assertAccess(access, ctx, method, args) {
133
140
  if (!this.auth) return;
134
141
  const docId = args?.[0];
135
- if (typeof docId !== "string") return;
142
+ if (typeof docId !== "string" || !docId) {
143
+ throw new StatusError(
144
+ 400,
145
+ `INVALID_REQUEST: docId is required (got ${docId === "" ? "empty string" : String(docId)})`
146
+ );
147
+ }
136
148
  const ok = await this.auth.canAccess(ctx, docId, access, method);
137
149
  if (!ok) {
138
150
  throw new StatusError(401, `${access.toUpperCase()}_FORBIDDEN:${docId}`);
@@ -119,7 +119,7 @@ interface PatchesAPI {
119
119
  */
120
120
  unsubscribe(ids: string | string[]): Promise<void>;
121
121
  /** Get the latest version of a document and changes since the last version. */
122
- getDoc(docId: string, atRev?: number): Promise<PatchesState>;
122
+ getDoc(docId: string): Promise<PatchesState>;
123
123
  /** Get changes that occurred after a specific revision. */
124
124
  getChangesSince(docId: string, rev: number): Promise<Change[]>;
125
125
  /** Apply a set of changes from the client to a document. Returns the committed changes. */
@@ -42,20 +42,14 @@ declare class WebSocketServer {
42
42
  /**
43
43
  * Subscribes the client to one or more documents to receive real-time updates.
44
44
  * If a document has been deleted (tombstone exists), sends immediate docDeleted notification.
45
- * @param params - The subscription parameters
46
- * @param params.ids - Document ID or IDs to subscribe to
45
+ * @param ids - Document ID or IDs to subscribe to
47
46
  */
48
- subscribe(params: {
49
- ids: string | string[];
50
- }): Promise<string[]>;
47
+ subscribe(ids: string | string[]): Promise<string[]>;
51
48
  /**
52
49
  * Unsubscribes the client from one or more documents.
53
- * @param params - The unsubscription parameters
54
- * @param params.ids - Document ID or IDs to unsubscribe from
50
+ * @param ids - Document ID or IDs to unsubscribe from
55
51
  */
56
- unsubscribe(params: {
57
- ids: string | string[];
58
- }): Promise<string[]>;
52
+ unsubscribe(ids: string | string[]): Promise<string[]>;
59
53
  }
60
54
 
61
55
  export { WebSocketServer, type WebSocketServerOptions };
@@ -39,19 +39,17 @@ class WebSocketServer {
39
39
  /**
40
40
  * Subscribes the client to one or more documents to receive real-time updates.
41
41
  * If a document has been deleted (tombstone exists), sends immediate docDeleted notification.
42
- * @param params - The subscription parameters
43
- * @param params.ids - Document ID or IDs to subscribe to
42
+ * @param ids - Document ID or IDs to subscribe to
44
43
  */
45
- async subscribe(params) {
44
+ async subscribe(ids) {
46
45
  const ctx = getAuthContext();
47
46
  if (!ctx?.clientId) return [];
48
- const { ids } = params;
49
47
  const allIds = Array.isArray(ids) ? ids : [ids];
50
48
  const allowed = [];
51
49
  await Promise.all(
52
50
  allIds.map(async (id) => {
53
51
  try {
54
- if (await this.auth.canAccess(ctx, id, "read", "subscribe", params)) {
52
+ if (await this.auth.canAccess(ctx, id, "read", "subscribe")) {
55
53
  allowed.push(id);
56
54
  }
57
55
  } catch (err) {
@@ -75,13 +73,11 @@ class WebSocketServer {
75
73
  }
76
74
  /**
77
75
  * Unsubscribes the client from one or more documents.
78
- * @param params - The unsubscription parameters
79
- * @param params.ids - Document ID or IDs to unsubscribe from
76
+ * @param ids - Document ID or IDs to unsubscribe from
80
77
  */
81
- async unsubscribe(params) {
78
+ async unsubscribe(ids) {
82
79
  const ctx = getAuthContext();
83
80
  if (!ctx?.clientId) return [];
84
- const { ids } = params;
85
81
  return this.transport.removeSubscription(ctx.clientId, Array.isArray(ids) ? ids : [ids]);
86
82
  }
87
83
  }
@@ -64,11 +64,11 @@ declare class LWWBranchManager implements BranchManager {
64
64
  * @param branchId - The branch document ID.
65
65
  * @param status - The status to set (defaults to 'closed').
66
66
  */
67
- closeBranch(branchId: string, status?: Exclude<BranchStatus, 'open'>): Promise<void>;
67
+ closeBranch(branchId: string, status?: Exclude<BranchStatus, 'open'> | null): Promise<void>;
68
68
  /**
69
69
  * Merges a branch back into its source document.
70
70
  *
71
- * LWW merge strategy:
71
+ * LWW merge algorithm:
72
72
  * 1. Get all ops changes made on the branch since it was created
73
73
  * 2. Apply those changes to the source document
74
74
  * 3. Timestamps automatically resolve any conflicts (later wins)
@@ -61,13 +61,13 @@ class LWWBranchManager {
61
61
  * @param branchId - The branch document ID.
62
62
  * @param status - The status to set (defaults to 'closed').
63
63
  */
64
- async closeBranch(branchId, status = "closed") {
65
- await this.store.updateBranch(branchId, { status });
64
+ async closeBranch(branchId, status) {
65
+ await this.store.updateBranch(branchId, { status: status ?? "closed" });
66
66
  }
67
67
  /**
68
68
  * Merges a branch back into its source document.
69
69
  *
70
- * LWW merge strategy:
70
+ * LWW merge algorithm:
71
71
  * 1. Get all ops changes made on the branch since it was created
72
72
  * 2. Apply those changes to the source document
73
73
  * 3. Timestamps automatically resolve any conflicts (later wins)
@@ -60,7 +60,7 @@ declare class OTBranchManager implements BranchManager {
60
60
  * @param branchId - The ID of the branch to close.
61
61
  * @param status - The status to set for the branch.
62
62
  */
63
- closeBranch(branchId: string, status?: Exclude<BranchStatus, 'open'>): Promise<void>;
63
+ closeBranch(branchId: string, status?: Exclude<BranchStatus, 'open'> | null): Promise<void>;
64
64
  /**
65
65
  * Merges changes from a branch back into its source document.
66
66
  * @param branchId - The ID of the branch document to merge.
@@ -70,8 +70,8 @@ class OTBranchManager {
70
70
  * @param branchId - The ID of the branch to close.
71
71
  * @param status - The status to set for the branch.
72
72
  */
73
- async closeBranch(branchId, status = "closed") {
74
- await this.store.updateBranch(branchId, { status });
73
+ async closeBranch(branchId, status) {
74
+ await this.store.updateBranch(branchId, { status: status ?? "closed" });
75
75
  }
76
76
  /**
77
77
  * Merges changes from a branch back into its source document.
@@ -77,6 +77,6 @@ declare function wrapMergeCommit<T>(branchId: string, sourceDocId: string, commi
77
77
  */
78
78
  declare function closeBranch(store: {
79
79
  updateBranch(branchId: string, updates: Partial<Pick<Branch, 'status' | 'name'>>): Promise<void>;
80
- }, branchId: string, status?: Exclude<BranchStatus, 'open'>): Promise<void>;
80
+ }, branchId: string, status?: Exclude<BranchStatus, 'open'> | null): Promise<void>;
81
81
 
82
82
  export { type BranchIdGenerator, type BranchLoader, assertBranchMetadata, assertBranchOpenForMerge, assertNotABranch, branchManagerApi, closeBranch, createBranchRecord, generateBranchId, wrapMergeCommit };
@@ -51,8 +51,8 @@ async function wrapMergeCommit(branchId, sourceDocId, commitFn) {
51
51
  throw new Error(`Merge failed: ${error instanceof Error ? error.message : String(error)}`);
52
52
  }
53
53
  }
54
- async function closeBranch(store, branchId, status = "closed") {
55
- await store.updateBranch(branchId, { status });
54
+ async function closeBranch(store, branchId, status) {
55
+ await store.updateBranch(branchId, { status: status ?? "closed" });
56
56
  }
57
57
  export {
58
58
  assertBranchMetadata,
@@ -1,5 +1,5 @@
1
1
  import { Patches, OpenDocOptions } from '../client/Patches.js';
2
- import { a as PatchesDoc } from '../BaseDoc-DkP3tUhT.js';
2
+ import { a as PatchesDoc } from '../BaseDoc-_Rsau70J.js';
3
3
  import '../event-signal.js';
4
4
  import '../json-patch/types.js';
5
5
  import '../types.js';
@@ -7,7 +7,7 @@ import '../types.js';
7
7
  import '../json-patch/JSONPatch.js';
8
8
  import '@dabble/delta';
9
9
  import '../client/ClientAlgorithm.js';
10
- import '../BaseDoc-DkP3tUhT.js';
10
+ import '../BaseDoc-_Rsau70J.js';
11
11
  import '../client/PatchesStore.js';
12
12
  import '../net/protocol/types.js';
13
13
  import '../net/protocol/JSONRPCClient.js';
@@ -6,5 +6,5 @@ import '../types.js';
6
6
  import '../json-patch/JSONPatch.js';
7
7
  import '@dabble/delta';
8
8
  import '../client/ClientAlgorithm.js';
9
- import '../BaseDoc-DkP3tUhT.js';
9
+ import '../BaseDoc-_Rsau70J.js';
10
10
  import '../client/PatchesStore.js';
@@ -11,7 +11,7 @@ import '../types.js';
11
11
  import '../json-patch/JSONPatch.js';
12
12
  import '@dabble/delta';
13
13
  import '../client/ClientAlgorithm.js';
14
- import '../BaseDoc-DkP3tUhT.js';
14
+ import '../BaseDoc-_Rsau70J.js';
15
15
  import '../client/PatchesStore.js';
16
16
  import '../net/PatchesSync.js';
17
17
  import '../net/protocol/types.js';
@@ -1,6 +1,6 @@
1
1
  import { Accessor } from 'solid-js';
2
2
  import { OpenDocOptions } from '../client/Patches.js';
3
- import { a as PatchesDoc } from '../BaseDoc-DkP3tUhT.js';
3
+ import { a as PatchesDoc } from '../BaseDoc-_Rsau70J.js';
4
4
  import { JSONPatch } from '../json-patch/JSONPatch.js';
5
5
  import { ChangeMutator } from '../types.js';
6
6
  import '../event-signal.js';
@@ -66,7 +66,24 @@ function createDocReactiveState(options) {
66
66
  change,
67
67
  doc
68
68
  };
69
- return { doc, setDoc, data, setData, loading, setLoading, error, setError, rev, setRev, hasPending, setHasPending, setupDoc, resetSignals, change, baseReturn };
69
+ return {
70
+ doc,
71
+ setDoc,
72
+ data,
73
+ setData,
74
+ loading,
75
+ setLoading,
76
+ error,
77
+ setError,
78
+ rev,
79
+ setRev,
80
+ hasPending,
81
+ setHasPending,
82
+ setupDoc,
83
+ resetSignals,
84
+ change,
85
+ baseReturn
86
+ };
70
87
  }
71
88
  function usePatchesDoc(docIdOrOptions, options) {
72
89
  if (typeof docIdOrOptions === "string" || typeof docIdOrOptions === "function") {
@@ -1,6 +1,6 @@
1
1
  import { ShallowRef, Ref, MaybeRef } from 'vue';
2
2
  import { OpenDocOptions } from '../client/Patches.js';
3
- import { a as PatchesDoc } from '../BaseDoc-DkP3tUhT.js';
3
+ import { a as PatchesDoc } from '../BaseDoc-_Rsau70J.js';
4
4
  import { JSONPatch } from '../json-patch/JSONPatch.js';
5
5
  import { ChangeMutator } from '../types.js';
6
6
  import '../event-signal.js';
@@ -6,5 +6,5 @@ import '../types.js';
6
6
  import '../json-patch/JSONPatch.js';
7
7
  import '@dabble/delta';
8
8
  import '../client/ClientAlgorithm.js';
9
- import '../BaseDoc-DkP3tUhT.js';
9
+ import '../BaseDoc-_Rsau70J.js';
10
10
  import '../client/PatchesStore.js';
@@ -11,7 +11,7 @@ import '../types.js';
11
11
  import '../json-patch/JSONPatch.js';
12
12
  import '@dabble/delta';
13
13
  import '../client/ClientAlgorithm.js';
14
- import '../BaseDoc-DkP3tUhT.js';
14
+ import '../BaseDoc-_Rsau70J.js';
15
15
  import '../client/PatchesStore.js';
16
16
  import '../net/PatchesSync.js';
17
17
  import '../net/protocol/types.js';
@@ -6,7 +6,7 @@ import '../types.js';
6
6
  import '../json-patch/JSONPatch.js';
7
7
  import '@dabble/delta';
8
8
  import '../client/ClientAlgorithm.js';
9
- import '../BaseDoc-DkP3tUhT.js';
9
+ import '../BaseDoc-_Rsau70J.js';
10
10
  import '../client/PatchesStore.js';
11
11
 
12
12
  /**
@@ -7,7 +7,7 @@ import '../types.js';
7
7
  import '../json-patch/JSONPatch.js';
8
8
  import '@dabble/delta';
9
9
  import '../client/ClientAlgorithm.js';
10
- import '../BaseDoc-DkP3tUhT.js';
10
+ import '../BaseDoc-_Rsau70J.js';
11
11
  import '../client/PatchesStore.js';
12
12
  import '../net/protocol/types.js';
13
13
  import '../net/protocol/JSONRPCClient.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dabble/patches",
3
- "version": "0.7.7",
3
+ "version": "0.7.9",
4
4
  "description": "Immutable JSON Patch implementation based on RFC 6902 supporting operational transformation and last-writer-wins",
5
5
  "author": "Jacob Wright <jacwright@gmail.com>",
6
6
  "bugs": {