@dabble/patches 0.6.0 → 0.7.0

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 (114) hide show
  1. package/README.md +221 -208
  2. package/dist/BaseDoc-DkP3tUhT.d.ts +206 -0
  3. package/dist/algorithms/client/applyCommittedChanges.d.ts +7 -0
  4. package/dist/algorithms/client/applyCommittedChanges.js +6 -3
  5. package/dist/algorithms/lww/consolidateOps.d.ts +40 -0
  6. package/dist/algorithms/lww/consolidateOps.js +103 -0
  7. package/dist/algorithms/lww/index.d.ts +2 -0
  8. package/dist/algorithms/lww/index.js +1 -0
  9. package/dist/algorithms/lww/mergeServerWithLocal.d.ts +22 -0
  10. package/dist/algorithms/lww/mergeServerWithLocal.js +32 -0
  11. package/dist/algorithms/server/commitChanges.d.ts +32 -8
  12. package/dist/algorithms/server/commitChanges.js +20 -5
  13. package/dist/algorithms/server/createVersion.d.ts +1 -1
  14. package/dist/algorithms/server/getSnapshotAtRevision.d.ts +1 -1
  15. package/dist/algorithms/server/getStateAtRevision.d.ts +1 -1
  16. package/dist/algorithms/server/handleOfflineSessionsAndBatches.d.ts +1 -1
  17. package/dist/client/BaseDoc.d.ts +6 -0
  18. package/dist/client/BaseDoc.js +70 -0
  19. package/dist/client/ClientAlgorithm.d.ts +101 -0
  20. package/dist/client/ClientAlgorithm.js +0 -0
  21. package/dist/client/InMemoryStore.d.ts +5 -7
  22. package/dist/client/InMemoryStore.js +6 -35
  23. package/dist/client/IndexedDBStore.d.ts +39 -73
  24. package/dist/client/IndexedDBStore.js +17 -220
  25. package/dist/client/LWWAlgorithm.d.ts +43 -0
  26. package/dist/client/LWWAlgorithm.js +87 -0
  27. package/dist/client/LWWClientStore.d.ts +73 -0
  28. package/dist/client/LWWClientStore.js +0 -0
  29. package/dist/client/LWWDoc.d.ts +56 -0
  30. package/dist/client/LWWDoc.js +84 -0
  31. package/dist/client/LWWInMemoryStore.d.ts +88 -0
  32. package/dist/client/LWWInMemoryStore.js +208 -0
  33. package/dist/client/LWWIndexedDBStore.d.ts +91 -0
  34. package/dist/client/LWWIndexedDBStore.js +275 -0
  35. package/dist/client/OTAlgorithm.d.ts +42 -0
  36. package/dist/client/OTAlgorithm.js +113 -0
  37. package/dist/client/OTClientStore.d.ts +50 -0
  38. package/dist/client/OTClientStore.js +0 -0
  39. package/dist/client/OTDoc.d.ts +6 -0
  40. package/dist/client/OTDoc.js +97 -0
  41. package/dist/client/OTIndexedDBStore.d.ts +84 -0
  42. package/dist/client/OTIndexedDBStore.js +163 -0
  43. package/dist/client/Patches.d.ts +36 -16
  44. package/dist/client/Patches.js +60 -27
  45. package/dist/client/PatchesDoc.d.ts +4 -113
  46. package/dist/client/PatchesDoc.js +3 -153
  47. package/dist/client/PatchesStore.d.ts +8 -105
  48. package/dist/client/factories.d.ts +72 -0
  49. package/dist/client/factories.js +80 -0
  50. package/dist/client/index.d.ts +14 -5
  51. package/dist/client/index.js +9 -0
  52. package/dist/compression/index.d.ts +1 -1
  53. package/dist/data/change.js +2 -0
  54. package/dist/fractionalIndex.d.ts +67 -0
  55. package/dist/fractionalIndex.js +241 -0
  56. package/dist/index.d.ts +13 -3
  57. package/dist/index.js +1 -0
  58. package/dist/json-patch/types.d.ts +2 -0
  59. package/dist/net/PatchesClient.js +15 -15
  60. package/dist/net/PatchesSync.d.ts +24 -12
  61. package/dist/net/PatchesSync.js +56 -64
  62. package/dist/net/index.d.ts +6 -10
  63. package/dist/net/index.js +6 -1
  64. package/dist/net/protocol/JSONRPCClient.d.ts +4 -4
  65. package/dist/net/protocol/JSONRPCClient.js +6 -4
  66. package/dist/net/protocol/JSONRPCServer.d.ts +45 -9
  67. package/dist/net/protocol/JSONRPCServer.js +63 -8
  68. package/dist/net/serverContext.d.ts +38 -0
  69. package/dist/net/serverContext.js +20 -0
  70. package/dist/net/webrtc/WebRTCTransport.js +1 -1
  71. package/dist/net/websocket/AuthorizationProvider.d.ts +3 -3
  72. package/dist/net/websocket/WebSocketServer.d.ts +29 -20
  73. package/dist/net/websocket/WebSocketServer.js +23 -12
  74. package/dist/server/BranchManager.d.ts +50 -0
  75. package/dist/server/BranchManager.js +0 -0
  76. package/dist/server/CompressedStoreBackend.d.ts +7 -5
  77. package/dist/server/CompressedStoreBackend.js +3 -9
  78. package/dist/server/LWWBranchManager.d.ts +82 -0
  79. package/dist/server/LWWBranchManager.js +99 -0
  80. package/dist/server/LWWMemoryStoreBackend.d.ts +78 -0
  81. package/dist/server/LWWMemoryStoreBackend.js +191 -0
  82. package/dist/server/LWWServer.d.ts +130 -0
  83. package/dist/server/LWWServer.js +207 -0
  84. package/dist/server/{PatchesBranchManager.d.ts → OTBranchManager.d.ts} +32 -12
  85. package/dist/server/{PatchesBranchManager.js → OTBranchManager.js} +25 -40
  86. package/dist/server/OTServer.d.ts +108 -0
  87. package/dist/server/OTServer.js +141 -0
  88. package/dist/server/PatchesHistoryManager.d.ts +20 -7
  89. package/dist/server/PatchesHistoryManager.js +26 -3
  90. package/dist/server/PatchesServer.d.ts +70 -81
  91. package/dist/server/PatchesServer.js +0 -175
  92. package/dist/server/branchUtils.d.ts +82 -0
  93. package/dist/server/branchUtils.js +66 -0
  94. package/dist/server/index.d.ts +17 -6
  95. package/dist/server/index.js +33 -4
  96. package/dist/server/tombstone.d.ts +29 -0
  97. package/dist/server/tombstone.js +32 -0
  98. package/dist/server/types.d.ts +128 -26
  99. package/dist/server/utils.d.ts +12 -0
  100. package/dist/server/utils.js +23 -0
  101. package/dist/solid/context.d.ts +5 -4
  102. package/dist/solid/doc-manager.d.ts +3 -3
  103. package/dist/solid/index.d.ts +5 -4
  104. package/dist/solid/primitives.d.ts +2 -3
  105. package/dist/types.d.ts +4 -2
  106. package/dist/vue/composables.d.ts +2 -3
  107. package/dist/vue/doc-manager.d.ts +3 -3
  108. package/dist/vue/index.d.ts +5 -4
  109. package/dist/vue/provider.d.ts +5 -4
  110. package/package.json +1 -1
  111. package/dist/algorithms/client/collapsePendingChanges.d.ts +0 -30
  112. package/dist/algorithms/client/collapsePendingChanges.js +0 -78
  113. package/dist/net/websocket/RPCServer.d.ts +0 -141
  114. package/dist/net/websocket/RPCServer.js +0 -204
@@ -8,7 +8,6 @@ import {
8
8
  var _openDoc_dec, _init;
9
9
  import { signal } from "../event-signal.js";
10
10
  import { singleInvocation } from "../utils/concurrency.js";
11
- import { PatchesDoc } from "./PatchesDoc.js";
12
11
  _openDoc_dec = [singleInvocation(true)];
13
12
  class Patches {
14
13
  constructor(opts) {
@@ -16,7 +15,8 @@ class Patches {
16
15
  __publicField(this, "options");
17
16
  __publicField(this, "docs", /* @__PURE__ */ new Map());
18
17
  __publicField(this, "docOptions");
19
- __publicField(this, "store");
18
+ __publicField(this, "algorithms");
19
+ __publicField(this, "defaultAlgorithm");
20
20
  __publicField(this, "trackedDocs", /* @__PURE__ */ new Set());
21
21
  // Public signals
22
22
  __publicField(this, "onError", signal());
@@ -24,27 +24,54 @@ class Patches {
24
24
  __publicField(this, "onTrackDocs", signal());
25
25
  __publicField(this, "onUntrackDocs", signal());
26
26
  __publicField(this, "onDeleteDoc", signal());
27
+ /** Emitted when a doc has pending changes ready to send */
27
28
  __publicField(this, "onChange", signal());
28
29
  this.options = opts;
29
- this.store = opts.store;
30
+ this.algorithms = opts.algorithms;
31
+ const algorithmNames = Object.keys(opts.algorithms);
32
+ if (algorithmNames.length === 0) {
33
+ throw new Error("At least one algorithm must be provided");
34
+ }
35
+ this.defaultAlgorithm = opts.defaultAlgorithm ?? algorithmNames[0];
36
+ if (!opts.algorithms[this.defaultAlgorithm]) {
37
+ throw new Error(`Default algorithm '${this.defaultAlgorithm}' not found in algorithms map`);
38
+ }
30
39
  this.docOptions = opts.docOptions ?? {};
31
- this.store.listDocs().then((docs) => {
40
+ this._getAlgorithm(this.defaultAlgorithm).listDocs().then((docs) => {
32
41
  this.trackDocs(docs.map(({ docId }) => docId));
33
42
  });
34
43
  }
44
+ /**
45
+ * Gets an algorithm by name, throwing if not found.
46
+ */
47
+ _getAlgorithm(name) {
48
+ const algorithm = this.algorithms[name];
49
+ if (!algorithm) {
50
+ throw new Error(`Algorithm '${name}' not found`);
51
+ }
52
+ return algorithm;
53
+ }
54
+ /**
55
+ * Gets the algorithm for an open document.
56
+ */
57
+ getDocAlgorithm(docId) {
58
+ return this.docs.get(docId)?.algorithm;
59
+ }
35
60
  // --- Public API Methods ---
36
61
  /**
37
62
  * Tracks the given document IDs, adding them to the set of tracked documents and notifying listeners.
38
63
  * Tracked docs are kept in sync with the server, even when not open locally.
39
64
  * This allows for background syncing and updates of unopened documents.
40
65
  * @param docIds - Array of document IDs to track.
66
+ * @param algorithmName - Algorithm to use for tracking (defaults to defaultAlgorithm).
41
67
  */
42
- async trackDocs(docIds) {
68
+ async trackDocs(docIds, algorithmName) {
43
69
  docIds = docIds.filter((id) => !this.trackedDocs.has(id));
44
70
  if (!docIds.length) return;
45
71
  docIds.forEach(this.trackedDocs.add, this.trackedDocs);
46
72
  this.onTrackDocs.emit(docIds);
47
- await this.store.trackDocs(docIds);
73
+ const algorithm = this._getAlgorithm(algorithmName ?? this.defaultAlgorithm);
74
+ await algorithm.trackDocs(docIds);
48
75
  }
49
76
  /**
50
77
  * Untracks the given document IDs, removing them from the set of tracked documents and notifying listeners.
@@ -59,23 +86,28 @@ class Patches {
59
86
  this.onUntrackDocs.emit(docIds);
60
87
  const closedPromises = docIds.filter((id) => this.docs.has(id)).map((id) => this.closeDoc(id));
61
88
  await Promise.all(closedPromises);
62
- await this.store.untrackDocs(docIds);
89
+ const byAlgorithm = /* @__PURE__ */ new Map();
90
+ for (const docId of docIds) {
91
+ const managed = this.docs.get(docId);
92
+ const algorithm = managed?.algorithm ?? this._getAlgorithm(this.defaultAlgorithm);
93
+ const list = byAlgorithm.get(algorithm) ?? [];
94
+ list.push(docId);
95
+ byAlgorithm.set(algorithm, list);
96
+ }
97
+ await Promise.all([...byAlgorithm.entries()].map(([algorithm, ids]) => algorithm.untrackDocs(ids)));
63
98
  }
64
99
  // ensure a second call to openDoc with the same docId returns the same promise while opening
65
100
  async openDoc(docId, opts = {}) {
66
101
  const existing = this.docs.get(docId);
67
102
  if (existing) return existing.doc;
68
- await this.trackDocs([docId]);
69
- const snapshot = await this.store.getDoc(docId);
70
- const initialState = snapshot?.state ?? {};
103
+ const algorithmName = opts.algorithm ?? this.defaultAlgorithm;
104
+ const algorithm = this._getAlgorithm(algorithmName);
105
+ await this.trackDocs([docId], algorithmName);
106
+ const snapshot = await algorithm.loadDoc(docId);
71
107
  const mergedMetadata = { ...this.options.metadata, ...opts.metadata };
72
- const doc = new PatchesDoc(initialState, mergedMetadata, this.docOptions);
73
- doc.setId(docId);
74
- if (snapshot) {
75
- doc.import(snapshot);
76
- }
77
- const unsubscribe = doc.onChange((changes) => this._savePendingChanges(docId, changes));
78
- this.docs.set(docId, { doc, unsubscribe });
108
+ const doc = algorithm.createDoc(docId, snapshot);
109
+ const unsubscribe = doc.onChange((ops) => this._handleDocChange(docId, ops, doc, algorithm, mergedMetadata));
110
+ this.docs.set(docId, { doc, algorithm, unsubscribe });
79
111
  return doc;
80
112
  }
81
113
  /**
@@ -99,13 +131,15 @@ class Patches {
99
131
  * @param docId - The document ID to delete.
100
132
  */
101
133
  async deleteDoc(docId) {
134
+ const managed = this.docs.get(docId);
135
+ const algorithm = managed?.algorithm ?? this._getAlgorithm(this.defaultAlgorithm);
102
136
  if (this.docs.has(docId)) {
103
137
  await this.closeDoc(docId);
104
138
  }
105
139
  if (this.trackedDocs.has(docId)) {
106
140
  await this.untrackDocs([docId]);
107
141
  }
108
- await this.store.deleteDoc(docId);
142
+ await algorithm.deleteDoc(docId);
109
143
  await this.onDeleteDoc.emit(docId);
110
144
  }
111
145
  /**
@@ -121,10 +155,10 @@ class Patches {
121
155
  * Closes all open documents and cleans up listeners and store connections.
122
156
  * Should be called when shutting down the client.
123
157
  */
124
- close() {
158
+ async close() {
125
159
  this.docs.forEach((managed) => managed.unsubscribe());
126
160
  this.docs.clear();
127
- this.store.close();
161
+ await Promise.all(Object.values(this.algorithms).map((s) => s?.close()));
128
162
  this.onChange.clear();
129
163
  this.onDeleteDoc.clear();
130
164
  this.onUntrackDocs.clear();
@@ -133,16 +167,15 @@ class Patches {
133
167
  this.onError.clear();
134
168
  }
135
169
  /**
136
- * Internal handler for saving pending changes to the store.
137
- * @param docId - The document ID to save the changes for.
138
- * @param changes - The changes to save.
170
+ * Internal handler for doc changes. Called when doc.onChange emits ops.
171
+ * Delegates to algorithm for packaging and persisting.
139
172
  */
140
- async _savePendingChanges(docId, changes) {
173
+ async _handleDocChange(docId, ops, doc, algorithm, metadata) {
141
174
  try {
142
- await this.store.savePendingChanges(docId, changes);
143
- this.onChange.emit(docId, changes);
175
+ await algorithm.handleDocChange(docId, ops, doc, metadata);
176
+ this.onChange.emit(docId);
144
177
  } catch (err) {
145
- console.error(`Error saving pending changes for doc ${docId}:`, err);
178
+ console.error(`Error handling doc change for ${docId}:`, err);
146
179
  this.onError.emit(err, { docId });
147
180
  }
148
181
  }
@@ -1,115 +1,6 @@
1
- import { Signal, Unsubscriber } from '../event-signal.js';
2
- import { SizeCalculator } from '../algorithms/shared/changeBatching.js';
3
- import { PatchesSnapshot, SyncingState, Change, ChangeMutator } from '../types.js';
1
+ import '../event-signal.js';
2
+ import '../json-patch/types.js';
3
+ import '../types.js';
4
+ export { O as OTDoc, a as PatchesDoc, O as PatchesDocClass, P as PatchesDocOptions } from '../BaseDoc-DkP3tUhT.js';
4
5
  import '../json-patch/JSONPatch.js';
5
6
  import '@dabble/delta';
6
- import '../json-patch/types.js';
7
-
8
- /**
9
- * Options for creating a PatchesDoc instance
10
- */
11
- interface PatchesDocOptions {
12
- /**
13
- * Maximum size in bytes for a single change's storage representation.
14
- * Changes exceeding this will be split. Used for backends with row size limits.
15
- */
16
- maxStorageBytes?: number;
17
- /**
18
- * Custom size calculator for storage limit checks.
19
- * Import from '@dabble/patches/compression' for actual compression measurement,
20
- * or provide your own function (e.g., ratio estimate).
21
- *
22
- * @example
23
- * import { compressedSizeBase64 } from '@dabble/patches/compression';
24
- * { sizeCalculator: compressedSizeBase64, maxStorageBytes: 1_000_000 }
25
- */
26
- sizeCalculator?: SizeCalculator;
27
- }
28
- /**
29
- * Represents a document synchronized using JSON patches.
30
- * Manages committed state, pending (local-only) changes, and
31
- * changes currently being sent to the server.
32
- */
33
- declare class PatchesDoc<T extends object = object> {
34
- protected _id: string | null;
35
- protected _state: T;
36
- protected _snapshot: PatchesSnapshot<T>;
37
- protected _changeMetadata: Record<string, any>;
38
- protected _syncing: SyncingState;
39
- protected readonly _maxStorageBytes?: number;
40
- protected readonly _sizeCalculator?: SizeCalculator;
41
- /** Subscribe to be notified before local state changes. */
42
- readonly onBeforeChange: Signal<(change: Change) => void>;
43
- /** Subscribe to be notified after local state changes are applied. */
44
- readonly onChange: Signal<(changes: Change[]) => void>;
45
- /** Subscribe to be notified whenever state changes from any source. */
46
- readonly onUpdate: Signal<(newState: T) => void>;
47
- /** Subscribe to be notified when syncing state changes. */
48
- readonly onSyncing: Signal<(newSyncing: SyncingState) => void>;
49
- /**
50
- * Creates an instance of PatchesDoc.
51
- * @param initialState Optional initial state.
52
- * @param initialMetadata Optional metadata to add to generated changes.
53
- * @param options Additional options for the document.
54
- */
55
- constructor(initialState?: T, initialMetadata?: Record<string, any>, options?: PatchesDocOptions);
56
- /** The unique identifier for this document, once assigned. */
57
- get id(): string | null;
58
- /** Current local state (committed + pending). */
59
- get state(): T;
60
- /** Are we currently syncing this document? */
61
- get syncing(): SyncingState;
62
- /** Last committed revision number from the server. */
63
- get committedRev(): number;
64
- /** Are there local changes that haven't been sent yet? */
65
- get hasPending(): boolean;
66
- /** Subscribe to be notified whenever the state changes. */
67
- subscribe(onUpdate: (newValue: T) => void): Unsubscriber;
68
- /**
69
- * Exports the document state for persistence.
70
- * NOTE: Any changes currently marked as `sending` are included in the
71
- * `changes` array alongside `pending` changes. On import, all changes
72
- * are treated as pending.
73
- */
74
- export(): PatchesSnapshot<T>;
75
- /**
76
- * Imports previously exported document state.
77
- * Resets sending state and treats all imported changes as pending.
78
- */
79
- import(snapshot: PatchesSnapshot<T>): void;
80
- /**
81
- * Sets metadata to be added to future changes.
82
- */
83
- setChangeMetadata(metadata: Record<string, any>): void;
84
- /**
85
- * Applies an update to the local state, generating a patch and adding it to pending changes.
86
- * @param mutator Function that uses JSONPatch methods with type-safe paths.
87
- * @returns The generated Change objects.
88
- */
89
- change(mutator: ChangeMutator<T>): Change[];
90
- /**
91
- * Returns the pending changes for this document.
92
- * @returns The pending changes.
93
- */
94
- getPendingChanges(): Change[];
95
- /**
96
- * Applies committed changes to the document. Should only be called from a sync provider.
97
- * @param serverChanges The changes to apply.
98
- * @param rebasedPendingChanges The rebased pending changes to apply.
99
- */
100
- applyCommittedChanges(serverChanges: Change[], rebasedPendingChanges: Change[]): void;
101
- /**
102
- * Assigns an identifier to this document. Can only be set once.
103
- * @param id The unique identifier for the document.
104
- * @throws Error if the ID has already been set.
105
- */
106
- setId(id: string): void;
107
- /**
108
- * Updates the syncing state of the document.
109
- * @param newSyncing The new syncing state.
110
- */
111
- updateSyncing(newSyncing: SyncingState): void;
112
- toJSON(): PatchesSnapshot<T>;
113
- }
114
-
115
- export { PatchesDoc, type PatchesDocOptions };
@@ -1,156 +1,6 @@
1
1
  import "../chunk-IZ2YBCUP.js";
2
- import { createStateFromSnapshot } from "../algorithms/client/createStateFromSnapshot.js";
3
- import { makeChange } from "../algorithms/client/makeChange.js";
4
- import { applyChanges } from "../algorithms/shared/applyChanges.js";
5
- import { signal } from "../event-signal.js";
6
- class PatchesDoc {
7
- _id = null;
8
- _state;
9
- _snapshot;
10
- _changeMetadata = {};
11
- _syncing = null;
12
- _maxStorageBytes;
13
- _sizeCalculator;
14
- /** Subscribe to be notified before local state changes. */
15
- onBeforeChange = signal();
16
- /** Subscribe to be notified after local state changes are applied. */
17
- onChange = signal();
18
- /** Subscribe to be notified whenever state changes from any source. */
19
- onUpdate = signal();
20
- /** Subscribe to be notified when syncing state changes. */
21
- onSyncing = signal();
22
- /**
23
- * Creates an instance of PatchesDoc.
24
- * @param initialState Optional initial state.
25
- * @param initialMetadata Optional metadata to add to generated changes.
26
- * @param options Additional options for the document.
27
- */
28
- constructor(initialState = {}, initialMetadata = {}, options = {}) {
29
- this._state = structuredClone(initialState);
30
- this._snapshot = { state: this._state, rev: 0, changes: [] };
31
- this._changeMetadata = initialMetadata;
32
- this._maxStorageBytes = options.maxStorageBytes;
33
- this._sizeCalculator = options.sizeCalculator;
34
- }
35
- /** The unique identifier for this document, once assigned. */
36
- get id() {
37
- return this._id;
38
- }
39
- /** Current local state (committed + pending). */
40
- get state() {
41
- return this._state;
42
- }
43
- /** Are we currently syncing this document? */
44
- get syncing() {
45
- return this._syncing;
46
- }
47
- /** Last committed revision number from the server. */
48
- get committedRev() {
49
- return this._snapshot.rev;
50
- }
51
- /** Are there local changes that haven't been sent yet? */
52
- get hasPending() {
53
- return this._snapshot.changes.length > 0;
54
- }
55
- /** Subscribe to be notified whenever the state changes. */
56
- subscribe(onUpdate) {
57
- const unsub = this.onUpdate(onUpdate);
58
- onUpdate(this._state);
59
- return unsub;
60
- }
61
- /**
62
- * Exports the document state for persistence.
63
- * NOTE: Any changes currently marked as `sending` are included in the
64
- * `changes` array alongside `pending` changes. On import, all changes
65
- * are treated as pending.
66
- */
67
- export() {
68
- return structuredClone(this._snapshot);
69
- }
70
- /**
71
- * Imports previously exported document state.
72
- * Resets sending state and treats all imported changes as pending.
73
- */
74
- import(snapshot) {
75
- this._snapshot = structuredClone(snapshot);
76
- this._state = createStateFromSnapshot(snapshot);
77
- this.onUpdate.emit(this._state);
78
- }
79
- /**
80
- * Sets metadata to be added to future changes.
81
- */
82
- setChangeMetadata(metadata) {
83
- this._changeMetadata = metadata;
84
- }
85
- /**
86
- * Applies an update to the local state, generating a patch and adding it to pending changes.
87
- * @param mutator Function that uses JSONPatch methods with type-safe paths.
88
- * @returns The generated Change objects.
89
- */
90
- change(mutator) {
91
- const changes = makeChange(
92
- this._snapshot,
93
- mutator,
94
- this._changeMetadata,
95
- this._maxStorageBytes,
96
- this._sizeCalculator
97
- );
98
- if (changes.length === 0) {
99
- return changes;
100
- }
101
- this._state = applyChanges(this._state, changes);
102
- this._snapshot.changes.push(...changes);
103
- this.onChange.emit(changes);
104
- this.onUpdate.emit(this._state);
105
- return changes;
106
- }
107
- /**
108
- * Returns the pending changes for this document.
109
- * @returns The pending changes.
110
- */
111
- getPendingChanges() {
112
- return this._snapshot.changes;
113
- }
114
- /**
115
- * Applies committed changes to the document. Should only be called from a sync provider.
116
- * @param serverChanges The changes to apply.
117
- * @param rebasedPendingChanges The rebased pending changes to apply.
118
- */
119
- applyCommittedChanges(serverChanges, rebasedPendingChanges) {
120
- if (this._snapshot.rev !== serverChanges[0].rev - 1) {
121
- throw new Error("Cannot apply committed changes to a doc that is not at the correct revision");
122
- }
123
- this._snapshot.state = applyChanges(this._snapshot.state, serverChanges);
124
- this._snapshot.rev = serverChanges[serverChanges.length - 1].rev;
125
- this._snapshot.changes = rebasedPendingChanges;
126
- this._state = createStateFromSnapshot(this._snapshot);
127
- this.onUpdate.emit(this._state);
128
- }
129
- /**
130
- * Assigns an identifier to this document. Can only be set once.
131
- * @param id The unique identifier for the document.
132
- * @throws Error if the ID has already been set.
133
- */
134
- setId(id) {
135
- if (this._id !== null && this._id !== id) {
136
- throw new Error(`Document ID cannot be changed once set. Current: ${this._id}, Attempted: ${id}`);
137
- }
138
- if (this._id === null) {
139
- this._id = id;
140
- }
141
- }
142
- /**
143
- * Updates the syncing state of the document.
144
- * @param newSyncing The new syncing state.
145
- */
146
- updateSyncing(newSyncing) {
147
- this._syncing = newSyncing;
148
- this.onSyncing.emit(newSyncing);
149
- }
150
- toJSON() {
151
- return this.export();
152
- }
153
- }
2
+ import { OTDoc, OTDoc as OTDoc2 } from "./OTDoc.js";
154
3
  export {
155
- PatchesDoc
4
+ OTDoc,
5
+ OTDoc2 as PatchesDocClass
156
6
  };
@@ -1,4 +1,4 @@
1
- import { PatchesSnapshot, Change, PatchesState } from '../types.js';
1
+ import { PatchesSnapshot, PatchesState } from '../types.js';
2
2
  import '../json-patch/JSONPatch.js';
3
3
  import '@dabble/delta';
4
4
  import '../json-patch/types.js';
@@ -10,8 +10,6 @@ interface TrackedDoc {
10
10
  committedRev: number;
11
11
  /** Optional flag indicating the document has been locally deleted. */
12
12
  deleted?: true;
13
- /** The last revision that was attempted to be submitted to the server. */
14
- lastAttemptedSubmissionRev?: number;
15
13
  }
16
14
  /**
17
15
  * Pluggable persistence layer contract used by Patches + PatchesSync.
@@ -81,36 +79,18 @@ interface PatchesStore {
81
79
  */
82
80
  getDoc(docId: string): Promise<PatchesSnapshot | undefined>;
83
81
  /**
84
- * Retrieves all pending (unconfirmed) changes for a document.
82
+ * Returns the last committed revision for a document.
85
83
  *
86
- * Pending changes are local edits that haven't been confirmed by the server yet.
87
- * Returns changes in chronological order as they were created locally.
88
- * Used during sync to resend unconfirmed operations.
84
+ * The committed revision is the last revision confirmed by the server.
85
+ * Used during sync to fetch changes since this revision.
89
86
  *
90
87
  * @param docId Document identifier
91
- * @returns Array of pending changes in chronological order
88
+ * @returns The last committed revision, or 0 if not found
92
89
  * @example
93
- * const pendingChanges = await store.getPendingChanges('my-document');
94
- * console.log(`${pendingChanges.length} changes waiting for server confirmation`);
90
+ * const committedRev = await store.getCommittedRev('my-document');
91
+ * const serverChanges = await api.getChangesSince(docId, committedRev);
95
92
  */
96
- getPendingChanges(docId: string): Promise<Change[]>;
97
- /**
98
- * Returns revision counters for tracking document sync state.
99
- *
100
- * committedRev: Last revision confirmed by the server
101
- * pendingRev: Next revision number for new local changes
102
- * The gap between these indicates how many changes are pending server confirmation.
103
- *
104
- * @param docId Document identifier
105
- * @returns Tuple of [committedRev, pendingRev]
106
- * @example
107
- * const [committed, pending] = await store.getLastRevs('my-document');
108
- * console.log(`Server confirmed through rev ${committed}, local changes at rev ${pending}`);
109
- * if (pending > committed) {
110
- * console.log(`${pending - committed} changes pending server confirmation`);
111
- * }
112
- */
113
- getLastRevs(docId: string): Promise<[committedRev: number, pendingRev: number]>;
93
+ getCommittedRev(docId: string): Promise<number>;
114
94
  /**
115
95
  * Saves the current document state to persistent storage.
116
96
  *
@@ -129,54 +109,6 @@ interface PatchesStore {
129
109
  * });
130
110
  */
131
111
  saveDoc(docId: string, docState: PatchesState): Promise<void>;
132
- /**
133
- * Appends new pending changes to the document's local change queue.
134
- *
135
- * Adds changes to the end of the pending changes list without replacing existing ones.
136
- * Called when the user makes local edits that haven't been sent to the server yet.
137
- * Changes should have sequential revision numbers starting after the last pending change.
138
- *
139
- * @param docId Document identifier
140
- * @param changes Array of new changes to append
141
- * @example
142
- * // User made a local edit
143
- * const newChange = { rev: 15, patches: [...], clientId: 'client-123' };
144
- * await store.savePendingChanges('my-document', [newChange]);
145
- */
146
- savePendingChanges(docId: string, changes: Change[]): Promise<void>;
147
- /**
148
- * Records changes confirmed by the server and optionally removes sent pending changes.
149
- *
150
- * Adds server-confirmed changes to the document's history and updates the committed revision.
151
- * If sentPendingRange is provided, removes the specified range of pending changes that
152
- * were confirmed by the server (they're no longer pending).
153
- *
154
- * @param docId Document identifier
155
- * @param changes Server-confirmed changes to record
156
- * @param sentPendingRange Optional range [startRev, endRev] of pending changes to remove
157
- * @example
158
- * // Server confirmed our changes
159
- * await store.saveCommittedChanges('my-document', serverChanges, [10, 12]);
160
- *
161
- * // Server sent changes from other clients
162
- * await store.saveCommittedChanges('my-document', serverChanges);
163
- */
164
- saveCommittedChanges(docId: string, changes: Change[], sentPendingRange?: [number, number]): Promise<void>;
165
- /**
166
- * Completely replaces the document's pending changes with a new set.
167
- *
168
- * Discards all existing pending changes and replaces them with the provided array.
169
- * Used when operational transformation rebases pending changes after receiving server updates.
170
- * The new changes should have sequential revision numbers.
171
- *
172
- * @param docId Document identifier
173
- * @param changes New complete set of pending changes
174
- * @example
175
- * // After rebasing pending changes due to server conflicts
176
- * const rebasedChanges = transformPendingChanges(serverChanges, currentPending);
177
- * await store.replacePendingChanges('my-document', rebasedChanges);
178
- */
179
- replacePendingChanges(docId: string, changes: Change[]): Promise<void>;
180
112
  /**
181
113
  * Marks a document for collaborative deletion.
182
114
  *
@@ -219,35 +151,6 @@ interface PatchesStore {
219
151
  * // Store is no longer usable
220
152
  */
221
153
  close(): Promise<void>;
222
- /**
223
- * Gets the last revision that was attempted to be submitted to the server.
224
- *
225
- * This bookmark is used by change collapsing to avoid modifying changes that
226
- * may have been partially committed by the server. Returns undefined if no
227
- * submission has been attempted yet.
228
- *
229
- * @param docId Document identifier
230
- * @returns The last attempted submission revision, or undefined if none
231
- * @example
232
- * const lastAttempted = await store.getLastAttemptedSubmissionRev('my-document');
233
- * // Use this to protect changes from collapsing
234
- */
235
- getLastAttemptedSubmissionRev?(docId: string): Promise<number | undefined>;
236
- /**
237
- * Sets the last revision that was attempted to be submitted to the server.
238
- *
239
- * Called before sending changes to the server to mark them as "in flight".
240
- * This prevents change collapsing from modifying these changes in case the
241
- * server commits them but the client doesn't receive confirmation.
242
- *
243
- * @param docId Document identifier
244
- * @param rev The revision being submitted
245
- * @example
246
- * // Before sending batch to server
247
- * await store.setLastAttemptedSubmissionRev('my-document', lastChange.rev);
248
- * await sendToServer(batch);
249
- */
250
- setLastAttemptedSubmissionRev?(docId: string, rev: number): Promise<void>;
251
154
  }
252
155
 
253
156
  export type { PatchesStore, TrackedDoc };
@@ -0,0 +1,72 @@
1
+ import { AlgorithmName } from './ClientAlgorithm.js';
2
+ import { Patches } from './Patches.js';
3
+ import { P as PatchesDocOptions } from '../BaseDoc-DkP3tUhT.js';
4
+ import '../json-patch/types.js';
5
+ import '../types.js';
6
+ import '../json-patch/JSONPatch.js';
7
+ import '@dabble/delta';
8
+ import './PatchesStore.js';
9
+ import '../event-signal.js';
10
+
11
+ /**
12
+ * Options for factory functions that create Patches instances.
13
+ */
14
+ interface PatchesFactoryOptions {
15
+ /** Initial metadata to attach to changes from this client. */
16
+ metadata?: Record<string, any>;
17
+ /** Document-level options to pass to each PatchesDoc instance. */
18
+ docOptions?: PatchesDocOptions;
19
+ }
20
+ /**
21
+ * Options for factory functions with multiple algorithms.
22
+ */
23
+ interface MultiAlgorithmFactoryOptions extends PatchesFactoryOptions {
24
+ /** Default algorithm to use when opening docs. */
25
+ defaultAlgorithm?: AlgorithmName;
26
+ }
27
+ /**
28
+ * Options for IndexedDB-based factory functions.
29
+ */
30
+ interface IndexedDBFactoryOptions extends PatchesFactoryOptions {
31
+ /** Database name for IndexedDB storage. */
32
+ dbName: string;
33
+ }
34
+ /**
35
+ * Options for IndexedDB-based factory functions with multiple algorithms.
36
+ */
37
+ interface MultiAlgorithmIndexedDBFactoryOptions extends MultiAlgorithmFactoryOptions {
38
+ /** Database name for IndexedDB storage. */
39
+ dbName: string;
40
+ }
41
+ /**
42
+ * Creates a Patches instance with OT algorithm and in-memory store.
43
+ * Useful for testing or when persistence isn't needed.
44
+ */
45
+ declare function createOTPatches(options?: PatchesFactoryOptions): Patches;
46
+ /**
47
+ * Creates a Patches instance with OT algorithm and IndexedDB store.
48
+ * For persistent storage in browser environments.
49
+ */
50
+ declare function createOTIndexedDBPatches(options: IndexedDBFactoryOptions): Patches;
51
+ /**
52
+ * Creates a Patches instance with LWW algorithm and in-memory store.
53
+ * Useful for testing or when persistence isn't needed.
54
+ */
55
+ declare function createLWWPatches(options?: PatchesFactoryOptions): Patches;
56
+ /**
57
+ * Creates a Patches instance with LWW algorithm and IndexedDB store.
58
+ * For persistent storage in browser environments.
59
+ */
60
+ declare function createLWWIndexedDBPatches(options: IndexedDBFactoryOptions): Patches;
61
+ /**
62
+ * Creates a Patches instance with both OT and LWW algorithms using in-memory stores.
63
+ * Useful for testing or applications that need both algorithms without persistence.
64
+ */
65
+ declare function createAllPatches(options?: MultiAlgorithmFactoryOptions): Patches;
66
+ /**
67
+ * Creates a Patches instance with both OT and LWW algorithms using IndexedDB stores.
68
+ * For persistent storage in browser environments with support for both algorithms.
69
+ */
70
+ declare function createAllIndexedDBPatches(options: MultiAlgorithmIndexedDBFactoryOptions): Patches;
71
+
72
+ export { type IndexedDBFactoryOptions, type MultiAlgorithmFactoryOptions, type MultiAlgorithmIndexedDBFactoryOptions, type PatchesFactoryOptions, createAllIndexedDBPatches, createAllPatches, createLWWIndexedDBPatches, createLWWPatches, createOTIndexedDBPatches, createOTPatches };